diff --git a/bld/CLMBuildNamelist.pm b/bld/CLMBuildNamelist.pm
index 049054fcb9..2cab312661 100755
--- a/bld/CLMBuildNamelist.pm
+++ b/bld/CLMBuildNamelist.pm
@@ -2375,9 +2375,26 @@ sub setup_logic_urban {
add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'building_temp_method');
add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'urban_hac');
+ add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'building_humidity_mode');
add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'urban_explicit_ac');
add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'urban_traffic');
add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'init_interp_fill_missing_urban_with_HD');
+ my $building_humidity_mode = $nl->get_value('building_humidity_mode');
+ if ($building_humidity_mode < 0 || $building_humidity_mode > 2) {
+ $log->fatal_error("building_humidity_mode must be 0, 1, or 2");
+ }
+ if ($building_humidity_mode >= 1 && $nl->get_value('building_temp_method') != 1) {
+ $log->fatal_error("building_humidity_mode=1 or 2 requires building_temp_method=1");
+ }
+ if ($building_humidity_mode == 2) {
+ my $urban_hac = remove_leading_and_trailing_quotes($nl->get_value('urban_hac'));
+ if ($urban_hac ne 'ON' && $urban_hac ne 'ON_WASTEHEAT') {
+ $log->fatal_error("building_humidity_mode=2 requires urban_hac to be ON or ON_WASTEHEAT");
+ }
+ if (! value_is_true($nl->get_value('urban_explicit_ac'))) {
+ $log->fatal_error("building_humidity_mode=2 requires urban_explicit_ac=.true.");
+ }
+ }
}
#-------------------------------------------------------------------------------
diff --git a/bld/namelist_files/namelist_defaults_ctsm.xml b/bld/namelist_files/namelist_defaults_ctsm.xml
index e011258f41..e0c9ee2635 100644
--- a/bld/namelist_files/namelist_defaults_ctsm.xml
+++ b/bld/namelist_files/namelist_defaults_ctsm.xml
@@ -235,6 +235,9 @@ attributes from the config_cache.xml file (with keys converted to upper-case).
FAST
NONE
+
+0
+
.false.
.true.
diff --git a/bld/namelist_files/namelist_definition_ctsm.xml b/bld/namelist_files/namelist_definition_ctsm.xml
index 75a96a6c5f..d70b464496 100644
--- a/bld/namelist_files/namelist_definition_ctsm.xml
+++ b/bld/namelist_files/namelist_definition_ctsm.xml
@@ -1247,6 +1247,18 @@ Turn urban air conditioning/heating ON or OFF and add wasteheat:
If TRUE, use explicit, time-varying AC adoption rate for air-conditioning flux and interior building temperature calculations.
+See Li et al. (2024), doi:10.1029/2023MS004107
+
+
+
+Select treatment of indoor building humidity:
+ 0 = Existing CLMU formulation without indoor humidity or air-conditioning dehumidification
+ 1 = Indoor humidity and latent heat exchange without air-conditioning dehumidification
+ 2 = Indoor humidity and latent heat exchange with air-conditioning dehumidification and condensate production
+Modes 1 and 2 require building_temp_method=1 (prognostic calculation of interior building temp (clm5_0)).
+Mode 2 also requires urban_hac to be ON or ON_WASTEHEAT and urban_explicit_ac=.true.
+See Li et al. (2026), doi:10.1038/s44284-026-00474-4
{ options=>"-envxml_dir .",
+ namelst=>"building_humidity_mode=-1",
+ phys=>"clm6_0",
+ },
+ "building humidity mode above range"
+ =>{ options=>"-envxml_dir .",
+ namelst=>"building_humidity_mode=3",
+ phys=>"clm6_0",
+ },
+ "humidity mode 1 with simple temperature"
+ =>{ options=>"-envxml_dir .",
+ namelst=>"building_humidity_mode=1,building_temp_method=0",
+ phys=>"clm6_0",
+ },
+ "humidity mode 2 with simple temperature"
+ =>{ options=>"-envxml_dir .",
+ namelst=>"building_humidity_mode=2,building_temp_method=0,urban_hac='ON',urban_explicit_ac=.true.",
+ phys=>"clm6_0",
+ },
+ "humidity mode 2 with AC off"
+ =>{ options=>"-envxml_dir .",
+ namelst=>"building_humidity_mode=2,building_temp_method=1,urban_hac='OFF',urban_explicit_ac=.true.",
+ phys=>"clm6_0",
+ },
+ "humidity mode 2 without explicit AC"
+ =>{ options=>"-envxml_dir .",
+ namelst=>"building_humidity_mode=2,building_temp_method=1,urban_hac='ON',urban_explicit_ac=.false.",
+ phys=>"clm6_0",
+ },
"cmip7_w_issp" =>{ options=>"-envxml_dir . -use_case 1850-2100_SSP2-4.5_transient",
namelst=>"",
CLM_CMIP_ERA=>"cmip7",
diff --git a/src/biogeophys/BalanceCheckMod.F90 b/src/biogeophys/BalanceCheckMod.F90
index b79fcee46e..95efd0e632 100644
--- a/src/biogeophys/BalanceCheckMod.F90
+++ b/src/biogeophys/BalanceCheckMod.F90
@@ -36,6 +36,7 @@ module BalanceCheckMod
use column_varcon , only : icol_roof, icol_sunwall, icol_shadewall
use column_varcon , only : icol_road_perv, icol_road_imperv
use clm_varctl , only : use_hillslope_routing
+ use UrbanParamsType , only : IsACDehumidificationEnabled
!
! !PUBLIC TYPES:
implicit none
@@ -499,6 +500,7 @@ subroutine BalanceCheck( bounds, &
real(r8) :: qflx_glcice_dyn_water_flux_grc(bounds%begg:bounds%endg) ! grid cell-level water flux needed for balance check due to glc_dyn_runoff_routing [mm H2O/s] (positive means addition of water to the system)
real(r8) :: qflx_snwcp_discarded_liq_grc(bounds%begg:bounds%endg) ! grid cell-level excess liquid h2o due to snow capping, which we simply discard in order to reset the snow pack [mm H2O /s]
real(r8) :: qflx_snwcp_discarded_ice_grc(bounds%begg:bounds%endg) ! grid cell-level excess solid h2o due to snow capping, which we simply discard in order to reset the snow pack [mm H2O /s]
+ real(r8) :: qflx_condensate_from_ac_grc(bounds%begg:bounds%endg) ! grid cell-level condensate water flux from air-conditioning [mm H2O /s]
real(r8) :: errh2o_max_val ! Maximum value of error in water conservation error over all columns [mm H2O]
real(r8) :: errh2osno_max_val ! Maximum value of error in h2osno conservation error over all columns [kg m-2]
@@ -558,7 +560,8 @@ subroutine BalanceCheck( bounds, &
qflx_sfc_irrig_col => waterflux_inst%qflx_sfc_irrig_col , & ! Input: [real(r8) (:) ] column level irrigation flux (mm H2O /s)
qflx_sfc_irrig_grc => waterlnd2atm_inst%qirrig_grc , & ! Input: [real(r8) (:) ] grid cell-level irrigation flux (mm H20 /s)
- qflx_glcice_dyn_water_flux_col => waterflux_inst%qflx_glcice_dyn_water_flux_col & ! Input: [real(r8) (:)] column level water flux needed for balance check due to glc_dyn_runoff_routing (mm H2O/s) (positive means addition of water to the system)
+ qflx_glcice_dyn_water_flux_col => waterflux_inst%qflx_glcice_dyn_water_flux_col, & ! Input: [real(r8) (:)] column level water flux needed for balance check due to glc_dyn_runoff_routing (mm H2O/s) (positive means addition of water to the system)
+ qflx_condensate_from_ac_col => waterflux_inst%qflx_condensate_from_ac_col & ! Input: [real(r8) (:)] column level condensate water flux from air-conditioning (mm H2O /s)
)
! Get step size and time step
@@ -604,6 +607,10 @@ subroutine BalanceCheck( bounds, &
- qflx_snwcp_discarded_liq_col(c) &
- qflx_snwcp_discarded_ice_col(c)) * dtime
+ if (IsACDehumidificationEnabled()) then
+ errh2o_col(c) = errh2o_col(c) - qflx_condensate_from_ac_col(c) * dtime
+ end if
+
else
errh2o_col(c) = 0.0_r8
@@ -638,6 +645,9 @@ subroutine BalanceCheck( bounds, &
write(iulog,*)'qflx_surf = ',qflx_surf_col(indexc)*dtime
write(iulog,*)'qflx_qrgwl = ',qflx_qrgwl_col(indexc)*dtime
write(iulog,*)'qflx_drain = ',qflx_drain_col(indexc)*dtime
+ if (IsACDehumidificationEnabled()) then
+ write(iulog,*)'qflx_condensate_from_ac = ',qflx_condensate_from_ac_col(indexc)*dtime
+ end if
write(iulog,*)'qflx_ice_runoff = ',qflx_ice_runoff_col(indexc)*dtime
@@ -675,6 +685,12 @@ subroutine BalanceCheck( bounds, &
qflx_snwcp_discarded_ice_col(bounds%begc:bounds%endc), &
qflx_snwcp_discarded_ice_grc(bounds%begg:bounds%endg), &
c2l_scale_type= 'urbanf', l2g_scale_type='unity' )
+ if (IsACDehumidificationEnabled()) then
+ call c2g( bounds, &
+ qflx_condensate_from_ac_col(bounds%begc:bounds%endc), &
+ qflx_condensate_from_ac_grc(bounds%begg:bounds%endg), &
+ c2l_scale_type= 'urbanf', l2g_scale_type='unity' )
+ end if
do g = bounds%begg, bounds%endg
errh2o_grc(g) = endwb_grc(g) - begwb_grc(g) &
@@ -691,6 +707,10 @@ subroutine BalanceCheck( bounds, &
- qflx_ice_runoff_grc(g) &
- qflx_snwcp_discarded_liq_grc(g) &
- qflx_snwcp_discarded_ice_grc(g)) * dtime
+
+ if (IsACDehumidificationEnabled()) then
+ errh2o_grc(g) = errh2o_grc(g) - qflx_condensate_from_ac_grc(g) * dtime
+ end if
end do
! add landunit level flux variable, convert from (m3/s) to (kg m-2 s-1)
@@ -738,6 +758,9 @@ subroutine BalanceCheck( bounds, &
write(iulog,*)'qflx_drain_perched = ',qflx_drain_perched_grc(indexg)*dtime
write(iulog,*)'forc_flood = ',forc_flood_grc(indexg)*dtime
write(iulog,*)'qflx_glcice_dyn_water_flux = ',qflx_glcice_dyn_water_flux_grc(indexg)*dtime
+ if (IsACDehumidificationEnabled()) then
+ write(iulog,*)'qflx_condensate_from_ac = ',qflx_condensate_from_ac_grc(indexg)*dtime
+ end if
write(iulog,*)'CTSM is stopping'
call endrun(subgrid_index=indexg, subgrid_level=subgrid_level_gridcell, msg=errmsg(sourcefile, __LINE__))
diff --git a/src/biogeophys/EnergyFluxType.F90 b/src/biogeophys/EnergyFluxType.F90
index df8abb3f01..164f7fbfa5 100644
--- a/src/biogeophys/EnergyFluxType.F90
+++ b/src/biogeophys/EnergyFluxType.F90
@@ -14,6 +14,7 @@ module EnergyFluxType
use ColumnType , only : col
use PatchType , only : patch
use AnnualFluxDribbler, only : annual_flux_dribbler_type, annual_flux_dribbler_gridcell
+ use UrbanParamsType, only : IsACDehumidificationEnabled
!
implicit none
save
@@ -63,14 +64,15 @@ module EnergyFluxType
real(r8), pointer :: eflx_anthro_patch (:) ! patch total anthropogenic heat flux (W/m**2)
real(r8), pointer :: eflx_traffic_patch (:) ! patch traffic sensible heat flux (W/m**2)
real(r8), pointer :: eflx_wasteheat_patch (:) ! patch sensible heat flux from domestic heating/cooling sources of waste heat (W/m**2)
- real(r8), pointer :: eflx_ventilation_patch (:) ! patch sensible heat flux from building ventilation (W/m**2)
+ real(r8), pointer :: eflx_ventilation_patch (:) ! patch sensible and latent heat flux from building ventilation (W/m**2)
real(r8), pointer :: eflx_heat_from_ac_patch (:) ! patch sensible heat flux put back into canyon due to removal by AC (W/m**2)
real(r8), pointer :: eflx_traffic_lun (:) ! lun traffic sensible heat flux (W/m**2)
real(r8), pointer :: eflx_wasteheat_lun (:) ! lun sensible heat flux from domestic heating/cooling sources of waste heat (W/m**2)
- real(r8), pointer :: eflx_ventilation_lun (:) ! lun sensible heat flux from building ventilation (W/m**2)
+ real(r8), pointer :: eflx_ventilation_lun (:) ! lun sensible and latent heat flux from building ventilation (W/m**2)
real(r8), pointer :: eflx_heat_from_ac_lun (:) ! lun sensible heat flux to be put back into canyon due to removal by AC (W/m**2)
- real(r8), pointer :: eflx_building_lun (:) ! lun building heat flux from change in interior building air temperature (W/m**2)
+ real(r8), pointer :: eflx_building_lun (:) ! lun building heat flux from change in interior building air temperature (and humidity, if prognosed indoor humidity) (W/m**2)
real(r8), pointer :: eflx_urban_ac_lun (:) ! lun urban air conditioning flux (W/m**2)
+ real(r8), pointer :: eflx_urban_ac_sen_lun (:) ! lun sensible heat component of air conditioning flux (W/m**2)
real(r8), pointer :: eflx_urban_heat_lun (:) ! lun urban heating flux (W/m**2)
! Derivatives of energy fluxes
@@ -234,6 +236,7 @@ subroutine InitAllocate(this, bounds)
allocate( this%eflx_heat_from_ac_lun (begl:endl)) ; this%eflx_heat_from_ac_lun (:) = nan
allocate( this%eflx_building_lun (begl:endl)) ; this%eflx_building_lun (:) = nan
allocate( this%eflx_urban_ac_lun (begl:endl)) ; this%eflx_urban_ac_lun (:) = nan
+ allocate( this%eflx_urban_ac_sen_lun (begl:endl)) ; this%eflx_urban_ac_sen_lun (:) = nan
allocate( this%eflx_urban_heat_lun (begl:endl)) ; this%eflx_urban_heat_lun (:) = nan
allocate( this%eflx_traffic_lun (begl:endl)) ; this%eflx_traffic_lun (:) = nan
allocate( this%eflx_wasteheat_lun (begl:endl)) ; this%eflx_wasteheat_lun (:) = nan
@@ -581,7 +584,7 @@ subroutine InitHistory(this, bounds, is_simple_buildtemp, is_prog_buildtemp)
else
this%eflx_building_lun(begl:endl) = spval
call hist_addfld1d (fname='EFLXBUILD', units='W/m^2', &
- avgflag='A', long_name='building heat flux from change in interior building air temperature', &
+ avgflag='A', long_name='building heat flux from change in interior building air temperature (and humidity, if prognosed indoor humidity)', &
ptr_lunit=this%eflx_building_lun, set_nourb=0._r8, l2g_scale_type='unity')
this%eflx_urban_ac_lun(begl:endl) = spval
@@ -589,6 +592,13 @@ subroutine InitHistory(this, bounds, is_simple_buildtemp, is_prog_buildtemp)
avgflag='A', long_name='urban air conditioning flux', &
ptr_lunit=this%eflx_urban_ac_lun, set_nourb=0._r8, l2g_scale_type='unity')
+ if (IsACDehumidificationEnabled()) then
+ this%eflx_urban_ac_sen_lun(begl:endl) = spval
+ call hist_addfld1d (fname='URBAN_AC_SEN', units='W/m^2', &
+ avgflag='A', long_name='sensible heat component of urban air conditioning flux', &
+ ptr_lunit=this%eflx_urban_ac_sen_lun, set_nourb=0._r8, l2g_scale_type='unity')
+ end if
+
this%eflx_urban_heat_lun(begl:endl) = spval
call hist_addfld1d (fname='URBAN_HEAT', units='W/m^2', &
avgflag='A', long_name='urban heating flux', &
@@ -625,7 +635,7 @@ subroutine InitHistory(this, bounds, is_simple_buildtemp, is_prog_buildtemp)
if ( is_prog_buildtemp )then
this%eflx_ventilation_patch(begp:endp) = spval
call hist_addfld1d (fname='VENTILATION', units='W/m^2', &
- avgflag='A', long_name='sensible heat flux from building ventilation', &
+ avgflag='A', long_name='sensible (and latent, if prognosed indoor humidity) heat flux from building ventilation', &
ptr_patch=this%eflx_ventilation_patch, set_nourb=0._r8, c2l_scale_type='urbanf')
end if
@@ -763,6 +773,7 @@ subroutine InitCold(this, bounds, t_grnd_col, is_simple_buildtemp, is_prog_build
if ( is_prog_buildtemp )then
this%eflx_building_lun(l) = 0._r8
this%eflx_urban_ac_lun(l) = 0._r8
+ this%eflx_urban_ac_sen_lun(l)= 0._r8
this%eflx_urban_heat_lun(l) = 0._r8
end if
@@ -776,6 +787,7 @@ subroutine InitCold(this, bounds, t_grnd_col, is_simple_buildtemp, is_prog_build
if ( is_prog_buildtemp )then
this%eflx_building_lun(l) = 0._r8
this%eflx_urban_ac_lun(l) = 0._r8
+ this%eflx_urban_ac_sen_lun(l)= 0._r8
this%eflx_urban_heat_lun(l) = 0._r8
this%eflx_ventilation_lun(l)= 0._r8
end if
@@ -874,7 +886,7 @@ subroutine Restart(this, bounds, ncid, flag, is_simple_buildtemp, is_prog_buildt
end if
call restartvar(ncid=ncid, flag=flag, varname='EFLX_VENTILATION', xtype=ncd_double, &
dim1name='landunit', &
- long_name='sensible heat flux from building ventilation', units='watt/m^2', &
+ long_name='sensible (and latent, if prognosed indoor humidity) heat flux from building ventilation', units='watt/m^2', &
interpinic_flag='interp', readvar=readvar, data=this%eflx_ventilation_lun)
if (flag=='read' .and. .not. readvar) then
if (masterproc) write(iulog,*) "can't find EFLX_VENTILATION in initial file..."
diff --git a/src/biogeophys/HumanIndexMod.F90 b/src/biogeophys/HumanIndexMod.F90
index c5fa5e5385..5be821bcbf 100644
--- a/src/biogeophys/HumanIndexMod.F90
+++ b/src/biogeophys/HumanIndexMod.F90
@@ -1387,4 +1387,3 @@ end subroutine QSat_2
!-----------------------------------------------------------------------
end module HumanIndexMod
-
diff --git a/src/biogeophys/SoilFluxesMod.F90 b/src/biogeophys/SoilFluxesMod.F90
index 44e6d0e1cd..2e8b443596 100644
--- a/src/biogeophys/SoilFluxesMod.F90
+++ b/src/biogeophys/SoilFluxesMod.F90
@@ -115,7 +115,7 @@ subroutine SoilFluxes (bounds, num_urbanl, filter_urbanl, &
htvp => energyflux_inst%htvp_col , & ! Input: [real(r8) (:) ] latent heat of vapor of water (or sublimation) [j/kg]
eflx_building_heat_errsoi=> energyflux_inst%eflx_building_heat_errsoi_col , & ! Input: [real(r8) (:)] heat flux to interior surface of walls and roof for errsoi check (W m-2)
eflx_wasteheat_patch => energyflux_inst%eflx_wasteheat_patch , & ! Input: [real(r8) (:) ] sensible heat flux from urban heating/cooling sources of waste heat (W/m**2)
- eflx_ventilation_patch => energyflux_inst%eflx_ventilation_patch , & ! Input: [real(r8) (:) ] sensible heat flux from building ventilation (W/m**2)
+ eflx_ventilation_patch => energyflux_inst%eflx_ventilation_patch , & ! Input: [real(r8) (:) ] sensible and latent heat flux from building ventilation (W/m**2)
eflx_heat_from_ac_patch => energyflux_inst%eflx_heat_from_ac_patch , & ! Input: [real(r8) (:) ] sensible heat flux put back into canyon due to removal by AC (W/m**2)
eflx_traffic_patch => energyflux_inst%eflx_traffic_patch , & ! Input: [real(r8) (:) ] traffic sensible heat flux (W/m**2)
dlrad => energyflux_inst%dlrad_patch , & ! Input: [real(r8) (:) ] downward longwave radiation below the canopy [W/m2]
diff --git a/src/biogeophys/SoilHydrologyMod.F90 b/src/biogeophys/SoilHydrologyMod.F90
index 753ddc59fc..f1e38d92e2 100644
--- a/src/biogeophys/SoilHydrologyMod.F90
+++ b/src/biogeophys/SoilHydrologyMod.F90
@@ -33,6 +33,7 @@ module SoilHydrologyMod
use LandunitType , only : lun
use ColumnType , only : column_type, col
use PatchType , only : patch
+ use UrbanParamsType , only : IsACDehumidificationEnabled
!
! !PUBLIC TYPES:
@@ -497,6 +498,7 @@ subroutine TotalSurfaceRunoff(bounds, num_hydrologyc, filter_hydrologyc, &
qflx_liqevap_from_top_layer => waterfluxbulk_inst%qflx_liqevap_from_top_layer_col, & ! Input: [real(r8) (:) ] rate of liquid water evaporated from top soil or snow layer (mm H2O/s) [+]
qflx_floodc => waterfluxbulk_inst%qflx_floodc_col , & ! Input: [real(r8) (:) ] column flux of flood water from RTM
qflx_sat_excess_surf => waterfluxbulk_inst%qflx_sat_excess_surf_col , & ! Input: [real(r8) (:) ] surface runoff due to saturated surface (mm H2O /s)
+ qflx_condensate_from_ac => waterfluxbulk_inst%qflx_condensate_from_ac_col, & ! Input: [real(r8) (:) ] condensate from air-conditioning (mm H2O /s)
xs_urban => soilhydrology_inst%xs_urban_col , & ! Output: [real(r8) (:) ] excess soil water above urban ponding limit
@@ -538,8 +540,14 @@ subroutine TotalSurfaceRunoff(bounds, num_hydrologyc, filter_hydrologyc, &
pondmx_urban/dtime)
qflx_surf(c) = xs_urban(c)
end if
- ! send flood water flux to runoff for all urban columns
- qflx_surf(c) = qflx_surf(c) + qflx_floodc(c)
+ ! Send flood water flux and air-conditioning condensate (the latter exists only
+ ! when prognosed indoor humidity and AC dehumidification are enabled)
+ ! to runoff for all urban columns
+ if (IsACDehumidificationEnabled()) then
+ qflx_surf(c) = qflx_surf(c) + qflx_floodc(c) + qflx_condensate_from_ac(c)
+ else
+ qflx_surf(c) = qflx_surf(c) + qflx_floodc(c)
+ end if
else if (col%itype(c) == icol_sunwall .or. col%itype(c) == icol_shadewall) then
qflx_surf(c) = 0._r8
end if
diff --git a/src/biogeophys/SoilTemperatureMod.F90 b/src/biogeophys/SoilTemperatureMod.F90
index 367da626e6..19c30b72a4 100644
--- a/src/biogeophys/SoilTemperatureMod.F90
+++ b/src/biogeophys/SoilTemperatureMod.F90
@@ -541,8 +541,10 @@ subroutine SoilTemperature(bounds, num_urbanl, filter_urbanl, num_urbanc, filter
if ( IsProgBuildTemp() )then
call BuildingTemperature(bounds, num_urbanl, filter_urbanl, num_nolakec, filter_nolakec, &
+ num_urbanc, filter_urbanc, &
tk(bounds%begc:bounds%endc, :), urbanparams_inst, &
- temperature_inst, energyflux_inst, urbantv_inst, atm2lnd_inst)
+ temperature_inst, energyflux_inst, urbantv_inst, &
+ waterdiagnosticbulk_inst, waterfluxbulk_inst, atm2lnd_inst)
end if
do fc = 1,num_nolakec
@@ -1632,7 +1634,7 @@ subroutine ComputeGroundHeatFluxAndDeriv(bounds, &
dlrad => energyflux_inst%dlrad_patch , & ! Input: [real(r8) (:) ] downward longwave radiation blow the canopy [W/m2]
eflx_traffic => energyflux_inst%eflx_traffic_lun , & ! Input: [real(r8) (:) ] traffic sensible heat flux (W/m**2)
eflx_wasteheat => energyflux_inst%eflx_wasteheat_lun , & ! Input: [real(r8) (:) ] sensible heat flux from urban heating/cooling sources of waste heat (W/m**2)
- eflx_ventilation => energyflux_inst%eflx_ventilation_lun , & ! Input: [real(r8) (:) ] sensible heat flux from building ventilation (W/m**2)
+ eflx_ventilation => energyflux_inst%eflx_ventilation_lun , & ! Input: [real(r8) (:) ] sensible and latent heat flux from building ventilation (W/m**2)
eflx_heat_from_ac => energyflux_inst%eflx_heat_from_ac_lun , & ! Input: [real(r8) (:) ] sensible heat flux put back into canyon due to removal by AC (W/m**2)
eflx_sh_snow => energyflux_inst%eflx_sh_snow_patch , & ! Input: [real(r8) (:) ] sensible heat flux from snow (W/m**2) [+ to atm]
eflx_sh_soil => energyflux_inst%eflx_sh_soil_patch , & ! Input: [real(r8) (:) ] sensible heat flux from soil (W/m**2) [+ to atm]
@@ -1640,7 +1642,7 @@ subroutine ComputeGroundHeatFluxAndDeriv(bounds, &
eflx_sh_grnd => energyflux_inst%eflx_sh_grnd_patch , & ! Input: [real(r8) (:) ] sensible heat flux from ground (W/m**2) [+ to atm]
eflx_lwrad_net => energyflux_inst%eflx_lwrad_net_patch , & ! Input: [real(r8) (:) ] net infrared (longwave) rad (W/m**2) [+ = to atm]
eflx_wasteheat_patch => energyflux_inst%eflx_wasteheat_patch , & ! Input: [real(r8) (:) ] sensible heat flux from urban heating/cooling sources of waste heat (W/m**2)
- eflx_ventilation_patch => energyflux_inst%eflx_ventilation_patch , & ! Input: [real(r8) (:) ] sensible heat flux from building ventilation (W/m**2)
+ eflx_ventilation_patch => energyflux_inst%eflx_ventilation_patch , & ! Input: [real(r8) (:) ] sensible and latent heat flux from building ventilation (W/m**2)
eflx_heat_from_ac_patch => energyflux_inst%eflx_heat_from_ac_patch , & ! Input: [real(r8) (:) ] sensible heat flux put back into canyon due to removal by AC (W/m**2)
eflx_traffic_patch => energyflux_inst%eflx_traffic_patch , & ! Input: [real(r8) (:) ] traffic sensible heat flux (W/m**2)
eflx_anthro => energyflux_inst%eflx_anthro_patch , & ! Input: [real(r8) (:) ] total anthropogenic heat flux (W/m**2)
diff --git a/src/biogeophys/UrbBuildTempOleson2015Mod.F90 b/src/biogeophys/UrbBuildTempOleson2015Mod.F90
index 3081948f6b..d9836e453d 100644
--- a/src/biogeophys/UrbBuildTempOleson2015Mod.F90
+++ b/src/biogeophys/UrbBuildTempOleson2015Mod.F90
@@ -6,6 +6,10 @@ module UrbBuildTempOleson2015Mod
! !DESCRIPTION:
! Calculates internal building air temperature
!
+ ! Explicit air-conditioning adoption is described by Li et al. (2024),
+ ! doi:10.1029/2023MS004107. Indoor humidity and air-conditioning dehumidification
+ ! are described by Li et al. (2026), doi:10.1038/s44284-026-00474-4.
+ !
! !USES:
use shr_kind_mod , only : r8 => shr_kind_r8
use decompMod , only : bounds_type, subgrid_level_landunit
@@ -16,9 +20,11 @@ module UrbBuildTempOleson2015Mod
use UrbanTimeVarType , only : urbantv_type
use EnergyFluxType , only : energyflux_type
use TemperatureType , only : temperature_type
- use atm2lndType , only : atm2lnd_type
+ use WaterDiagnosticBulkType, only : waterdiagnosticbulk_type
+ use WaterFluxBulkType , only : waterfluxbulk_type
use LandunitType , only : lun
- use ColumnType , only : col
+ use ColumnType , only : col
+ use atm2lndType , only : atm2lnd_type
!
! !PUBLIC TYPES:
implicit none
@@ -42,8 +48,10 @@ module UrbBuildTempOleson2015Mod
!
! !INTERFACE:
subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec, &
- filter_nolakec, tk, urbanparams_inst, temperature_inst, &
- energyflux_inst, urbantv_inst, atm2lnd_inst)
+ filter_nolakec, num_urbanc, filter_urbanc, &
+ tk, urbanparams_inst, temperature_inst, &
+ energyflux_inst, urbantv_inst, waterdiagnosticbulk_inst, &
+ waterfluxbulk_inst, atm2lnd_inst)
!
! !DESCRIPTION:
! Solve for t_building, inner surface temperatures of roof, sunw, shdw, and floor temperature
@@ -54,11 +62,11 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
! qrd_sunw + qcd_sunw + qcv_sunw = 0
! qrd_shdw + qcd_shdw + qcv_shdw = 0
! qrd_floor + qcd_floor + qcv_floor = 0
-! Vbld*rho_dair*cpair*(dt_building/dt) = sum(Asfc*hcv_sfc*(t_sfc - t_building)
-! + Vvent*rho_dair*cpair*(taf - t_building)
+! Vbld*rho_air*cp_air*(dt_building/dt) = sum(Asfc*hcv_sfc*(t_sfc - t_building)
+! + Vvent*rho_air*cp_air*(taf - t_building)
! where Vlbd is volume of building air,
-! rho_dair is density of dry air at t_building (kg m-3),
-! cpair is specific heat of dry air (J kg-1 K-1),
+! rho_air is density of indoor air at outdoor pressure and t_building (kg m-3),
+! cp_air is specific heat capacity of indoor air (J kg-1 K-1),
! dt_building is change in interior building temperature (K),
! dt is timestep (s),
! Asfc is surface area of roof, sunw, shdw, floor (m2)
@@ -206,18 +214,24 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
use clm_varcon , only : rair, cpair, sb, hcv_roof, hcv_roof_enhanced, &
hcv_floor, hcv_floor_enhanced, hcv_sunw, hcv_shdw, &
em_roof_int, em_floor_int, em_sunw_int, em_shdw_int, &
- dz_floor, dens_floor, cp_floor, vent_ach
+ dz_floor, dens_floor, cp_floor, vent_ach, &
+ rh_building_max, hvap, rwat, cpwvap
use column_varcon , only : icol_roof, icol_sunwall, icol_shadewall
use clm_varctl , only : iulog
use abortutils , only : endrun
use clm_varpar , only : nlevurb, nlevsno, nlevmaxurbgrnd
- use UrbanParamsType , only : urban_hac, urban_hac_off, urban_hac_on, urban_wasteheat_on, urban_explicit_ac
+ use UrbanParamsType , only : urban_hac, urban_hac_off, urban_hac_on, urban_wasteheat_on, &
+ urban_explicit_ac, IsBuildingHumidityEnabled, &
+ IsACDehumidificationEnabled
+ use QSatMod , only : QSat
!
! !ARGUMENTS:
implicit none
type(bounds_type), intent(in) :: bounds ! bounds
integer , intent(in) :: num_nolakec ! number of column non-lake points in column filter
integer , intent(in) :: filter_nolakec(:) ! column filter for non-lake points
+ integer , intent(in) :: num_urbanc ! number of column urban points in column filter
+ integer , intent(in) :: filter_urbanc(:) ! column filter for urban points
integer , intent(in) :: num_urbanl ! number of urban landunits in clump
integer , intent(in) :: filter_urbanl(:) ! urban landunit filter
real(r8), intent(in) :: tk(bounds%begc: , -nlevsno+1: ) ! thermal conductivity (W m-1 K-1) [col, j]
@@ -225,6 +239,8 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
type(temperature_type), intent(inout) :: temperature_inst ! temperature variables
type(energyflux_type) , intent(inout) :: energyflux_inst ! energy flux variables
type(urbantv_type) , intent(in) :: urbantv_inst ! urban time varying variables
+ type(waterdiagnosticbulk_type), intent(inout) :: waterdiagnosticbulk_inst ! water diagnostic variables
+ type(waterfluxbulk_type), intent(inout) :: waterfluxbulk_inst ! water flux variables
type(atm2lnd_type) , intent(in) :: atm2lnd_inst ! forcing variables from atmosphere
!
! !LOCAL VARIABLES:
@@ -238,7 +254,10 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
real(r8) :: t_floor_bef(bounds%begl:bounds%endl) ! floor temperature at previous time step (K)
real(r8) :: t_building_bef(bounds%begl:bounds%endl) ! internal building air temperature at previous time step [K]
real(r8) :: t_building_bef_hac(bounds%begl:bounds%endl)! internal building air temperature before applying HAC [K]
+ real(r8) :: q_building_bef(bounds%begl:bounds%endl) ! internal building air specific humidity at previous time step (kg/kg)
+ real(r8) :: q_building_bef_hac(bounds%begl:bounds%endl)! internal building air specific humidity before applying HAC (kg/kg)
real(r8) :: eflx_urban_ac_sat(bounds%begl:bounds%endl) ! urban air conditioning flux under AC adoption saturation (W/m**2)
+ real(r8) :: eflx_urban_ac_sat_lat(bounds%begl:bounds%endl) ! latent heat component of urban air conditioning flux under AC adoption saturation (W/m**2)
real(r8) :: hcv_roofi(bounds%begl:bounds%endl) ! roof convective heat transfer coefficient (W m-2 K-1)
real(r8) :: hcv_sunwi(bounds%begl:bounds%endl) ! sunwall convective heat transfer coefficient (W m-2 K-1)
real(r8) :: hcv_shdwi(bounds%begl:bounds%endl) ! shadewall convective heat transfer coefficient (W m-2 K-1)
@@ -250,7 +269,8 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
real(r8) :: dz_floori(bounds%begl:bounds%endl) ! concrete floor thickness (m)
real(r8) :: cp_floori(bounds%begl:bounds%endl) ! concrete floor volumetric heat capacity (J m-3 K-1)
real(r8) :: cv_floori(bounds%begl:bounds%endl) ! intermediate calculation for concrete floor (W m-2 K-1)
- real(r8) :: rho_dair(bounds%begl:bounds%endl) ! density of dry air at standard pressure and t_building (kg m-3)
+ real(r8) :: rho_air(bounds%begl:bounds%endl) ! density of indoor air at outdoor pressure and t_building (kg m-3)
+ real(r8) :: cp_air(bounds%begl:bounds%endl) ! specific heat capacity of indoor air (J kg-1 K-1)
real(r8) :: vf_rf(bounds%begl:bounds%endl) ! view factor of roof for floor (-)
real(r8) :: vf_fr(bounds%begl:bounds%endl) ! view factor of floor for roof (-)
real(r8) :: vf_wf(bounds%begl:bounds%endl) ! view factor of wall for floor (-)
@@ -301,6 +321,14 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
! on exit, if info = 0, the n-by-nrhs solution matrix x
integer :: info ! exit information for LAPACK routine dgesv
integer :: ipiv(neq) ! the pivot indices that define the permutation matrix P
+ real(r8) :: q_building_max ! maximum internal building air specific humidity determined from rh_building_max (kg/kg)
+ real(r8) :: qsat_building_max ! maximum specific humidity/mixing ratio of saturated internal building air at indoor temperature before HAC, used to determine q_building_max from rh_building_max (kg/kg)
+ real(r8) :: qsat_building ! specific humidity/mixing ratio of saturated internal building air at indoor temperature, used to calculate rho_air, cp_air, and rh_building (kg/kg)
+ real(r8) :: esat_building ! internal building air saturated vapor pressure used to calculate p_vapor (Pa)
+ real(r8) :: p_vapor ! internal building air partial pressure of water vapor (Pa)
+ real(r8) :: qtot_condensate(bounds%begl:bounds%endl) ! total condensed water due to dehumidification per building area (kg m-2)
+ real(r8) :: eflx_urban_ac_lat_derived(bounds%begl:bounds%endl) ! urban air conditioning latent heat flux derived from condensate output, for error check (W m-2)
+ real(r8) :: err_eflx_urban_ac_lat(bounds%begl:bounds%endl) ! Difference between the dehumidification energy flux calculated from condensate and the urban AC latent heat flux for error check (W m-2)
!EOP
!-----------------------------------------------------------------------
@@ -317,6 +345,7 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
ht_roof => lun%ht_roof , & ! Input: [real(r8) (:)] height of urban roof (m)
canyon_hwr => lun%canyon_hwr , & ! Input: [real(r8) (:)] ratio of building height to street hwidth (-)
wtlunit_roof => lun%wtlunit_roof , & ! Input: [real(r8) (:)] weight of roof with respect to landunit
+ wtroad_perv => lun%wtroad_perv , & ! Input: [real(r8) (:)] weight of pervious road column to total road (-)
urbpoi => lun%urbpoi , & ! Input: [logical (:)] true => landunit is an urban point
taf => temperature_inst%taf_lun , & ! Input: [real(r8) (:)] urban canopy air temperature (K)
@@ -332,10 +361,18 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
t_building_max => urbantv_inst%t_building_max , & ! Input: [real(r8) (:)] maximum internal building air temperature (K)
t_building_min => urbanparams_inst%t_building_min , & ! Input: [real(r8) (:)] minimum internal building air temperature (K)
- eflx_building => energyflux_inst%eflx_building_lun , & ! Output: [real(r8) (:)] building heat flux from change in interior building air temperature (W/m**2)
+ qaf => waterdiagnosticbulk_inst%qaf_lun , & ! Input: [real(r8) (:)] urban canopy air specific humidity (kg/kg)
+ q_building => waterdiagnosticbulk_inst%q_building_lun,& ! InOut: [real(r8) (:)] internal building air specific humidity (kg/kg)
+ rh_building => waterdiagnosticbulk_inst%rh_building_lun,& ! InOut: [real(r8) (:)] internal building air relative humidity (%)
+
+ eflx_building => energyflux_inst%eflx_building_lun , & ! Output: [real(r8) (:)] building heat flux from change in interior building air temperature (and humidity, if prognosed indoor humidity) (W/m**2)
eflx_urban_ac => energyflux_inst%eflx_urban_ac_lun , & ! Output: [real(r8) (:)] urban air conditioning flux (W/m**2)
+ eflx_urban_ac_sen => energyflux_inst%eflx_urban_ac_sen_lun,& ! Output: [real(r8) (:)] sensible heat component of urban air conditioning flux (W/m**2)
eflx_urban_heat => energyflux_inst%eflx_urban_heat_lun,& ! Output: [real(r8) (:)] urban heating flux (W/m**2)
- eflx_ventilation => energyflux_inst%eflx_ventilation_lun & ! Output: [real(r8) (:)] sensible heat flux from building ventilation (W/m**2)
+ eflx_ventilation => energyflux_inst%eflx_ventilation_lun, & ! Output: [real(r8) (:)] sensible and latent heat flux from building ventilation (W/m**2)
+
+ qflx_condensate_from_ac => waterfluxbulk_inst%qflx_condensate_from_ac_col, & ! Output: [real(r8) (:)] condensed water flux due to dehumidification for roof (building footprint) area (mm/s)
+ qflx_condensate_from_ac_lu => waterfluxbulk_inst%qflx_condensate_from_ac_lun & ! Output: [real(r8) (:)] condensed water flux due to dehumidification for urban area (mm/s)
)
! Get step size
@@ -358,6 +395,9 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
t_shdw_inner_bef(l) = t_shdw_inner(l)
t_floor_bef(l) = t_floor(l)
t_building_bef(l) = t_building(l)
+ if (IsBuildingHumidityEnabled()) then
+ q_building_bef(l) = q_building(l)
+ end if
if (t_roof_inner_bef(l) .le. t_building_bef(l)) then
hcv_roofi(l) = hcv_roof_enhanced
else
@@ -380,8 +420,21 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
cp_floori(l) = cp_floor
! Intermediate calculation for concrete floor (W m-2 K-1)
cv_floori(l) = (dz_floori(l) * cp_floori(l)) / dtime
- ! Density of dry air at surface pressure and t_building (kg m-3)
- rho_dair(l) = forc_pbot(g) / (rair*t_building_bef(l))
+ if (IsBuildingHumidityEnabled()) then
+ ! Specific humidity (kg/kg) and vapor pressure (Pa) at saturation at current temperature and pressure
+ call QSat(t_building_bef(l), forc_pbot(g), qsat_building, es = esat_building)
+ ! Indoor vapor pressure (Pa) from saturation vapor pressure. The saturation ratio is capped at 1 (100% saturation)
+ ! so indoor vapor pressure does not exceed saturation vapor pressure
+ p_vapor = min(1._r8, q_building_bef(l) / qsat_building) * esat_building
+ ! Density (kg m-3) and specific heat capacity (J kg-1 K-1) of moist air at current temperature and pressure
+ rho_air(l) = (forc_pbot(g) - p_vapor) / (rair * t_building_bef(l)) &
+ + p_vapor / (rwat * t_building_bef(l))
+ cp_air(l) = cpair + cpwvap * q_building_bef(l)
+ else
+ ! Density (kg m-3) and specific heat capacity (J kg-1 K-1) of dry air at current temperature and pressure
+ rho_air(l) = forc_pbot(g) / (rair*t_building_bef(l))
+ cp_air(l) = cpair
+ end if
! Building height to building width ratio
building_hwr(l) = canyon_hwr(l)*(1._r8-wtlunit_roof(l))/wtlunit_roof(l)
end if
@@ -645,15 +698,15 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
a(5,4) = - 0.5_r8*hcv_floori(l)
- a(5,5) = ((ht_roof(l)*rho_dair(l)*cpair)/dtime) + &
- ((ht_roof(l)*vent_ach)/3600._r8)*rho_dair(l)*cpair + &
+ a(5,5) = ((ht_roof(l)*rho_air(l)*cp_air(l))/dtime) + &
+ ((ht_roof(l)*vent_ach)/3600._r8)*rho_air(l)*cp_air(l) + &
0.5_r8*hcv_roofi(l) + &
0.5_r8*hcv_sunwi(l)*building_hwr(l) + &
0.5_r8*hcv_shdwi(l)*building_hwr(l) + &
0.5_r8*hcv_floori(l)
- result(5) = (ht_roof(l)*rho_dair(l)*cpair/dtime)*t_building_bef(l) &
- + ((ht_roof(l)*vent_ach)/3600._r8)*rho_dair(l)*cpair*taf(l) &
+ result(5) = (ht_roof(l)*rho_air(l)*cp_air(l)/dtime)*t_building_bef(l) &
+ + ((ht_roof(l)*vent_ach)/3600._r8)*rho_air(l)*cp_air(l)*taf(l) &
+ 0.5_r8*hcv_roofi(l)*(t_roof_inner_bef(l) - t_building_bef(l)) &
+ 0.5_r8*hcv_sunwi(l)*(t_sunw_inner_bef(l) - t_building_bef(l))*building_hwr(l) &
+ 0.5_r8*hcv_shdwi(l)*(t_shdw_inner_bef(l) - t_building_bef(l))*building_hwr(l) &
@@ -680,6 +733,17 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
end if
end do
+ ! Update indoor specific humidity assuming well-mixed indoor air
+ if (IsBuildingHumidityEnabled()) then
+ do fl = 1,num_urbanl
+ l = filter_urbanl(fl)
+ if (urbpoi(l)) then
+ q_building(l) = qaf(l) * (vent_ach/3600._r8 * dtime) &
+ + q_building_bef(l) * (1 - vent_ach/3600._r8 * dtime)
+ end if
+ end do
+ end if
+
! Energy balance checks
do fl = 1,num_urbanl
l = filter_urbanl(fl)
@@ -891,8 +955,8 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
call endrun(subgrid_index=l, subgrid_level=subgrid_level_landunit)
end if
- enrgy_bal_buildair(l) = (ht_roof(l)*rho_dair(l)*cpair/dtime)*(t_building(l) - t_building_bef(l)) &
- - ht_roof(l)*(vent_ach/3600._r8)*rho_dair(l)*cpair*(taf(l) - t_building(l)) &
+ enrgy_bal_buildair(l) = (ht_roof(l)*rho_air(l)*cp_air(l)/dtime)*(t_building(l) - t_building_bef(l)) &
+ - ht_roof(l)*(vent_ach/3600._r8)*rho_air(l)*cp_air(l)*(taf(l) - t_building(l)) &
- 0.5_r8*hcv_roofi(l)*(t_roof_inner(l) - t_building(l)) &
- 0.5_r8*hcv_roofi(l)*(t_roof_inner_bef(l) - t_building_bef(l)) &
- 0.5_r8*hcv_sunwi(l)*(t_sunw_inner(l) - t_building(l))*building_hwr(l) &
@@ -907,57 +971,157 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec,
call endrun(subgrid_index=l, subgrid_level=subgrid_level_landunit)
end if
- ! Sensible heat flux from ventilation. It is added as a flux to the canyon floor in SoilTemperatureMod.
+ ! Sensible and latent heat flux (if indoor humidity is prognosed) from ventilation. It is added as a flux to the canyon floor in SoilTemperatureMod.
! Note that we multiply it here by wtlunit_roof which converts it from W/m2 of building area to W/m2
! of urban area. eflx_urban_ac and eflx_urban_heat are treated similarly below. This flux is balanced
! by an equal and opposite flux into/out of the building and so has a net effect of zero on the energy balance
! of the urban landunit.
- eflx_ventilation(l) = wtlunit_roof(l) * ( - ht_roof(l)*(vent_ach/3600._r8) &
- * rho_dair(l) * cpair * (taf(l) - t_building(l)) )
+ if (IsBuildingHumidityEnabled()) then
+ eflx_ventilation(l) = wtlunit_roof(l) * ( &
+ - ht_roof(l) * (vent_ach/3600._r8) * rho_air(l) * cp_air(l) * (taf(l) - t_building(l)) &
+ - ht_roof(l) * (vent_ach/3600._r8) * rho_air(l) * hvap * (qaf(l) - q_building(l)) )
+ else
+ eflx_ventilation(l) = wtlunit_roof(l) * ( - ht_roof(l)*(vent_ach/3600._r8) &
+ * rho_air(l) * cpair * (taf(l) - t_building(l)) )
+ end if
end if
end do
! Restrict internal building air temperature to between min and max
+ ! and restrict internal building air specific humidity to below max, when AC dehumidification is enabled.
! Calculate heating or air conditioning flux from energy required to change
- ! internal building air temperature to t_building_min or t_building_max.
+ ! internal building air temperature to t_building_min or t_building_max
+ ! and internal building specific humidity to below q_building_max, when AC dehumidification is enabled.
do fl = 1,num_urbanl
l = filter_urbanl(fl)
+ g = lun%gridcell(l)
if (urbpoi(l)) then
if (trim(urban_hac) == urban_hac_on .or. trim(urban_hac) == urban_wasteheat_on) then
t_building_bef_hac(l) = t_building(l)
! rho_dair(l) = pstd / (rair*t_building(l))
+ if (IsACDehumidificationEnabled()) then
+ q_building_bef_hac(l) = q_building(l)
+ ! Convert maximum indoor relative humidity to maximum specific humidity
+ ! at the current indoor temperature and pressure.
+ call QSat(t_building_bef_hac(l), forc_pbot(g), qsat_building_max)
+ q_building_max = rh_building_max / 100._r8 * qsat_building_max
+ end if
if (t_building_bef_hac(l) > t_building_max(l)) then
if (urban_explicit_ac) then ! use explicit ac adoption rate parameterization scheme:
+
+ ! Sensible heat component of AC
! Here, t_building_max is the AC saturation setpoint
- eflx_urban_ac_sat(l) = wtlunit_roof(l) * abs( (ht_roof(l) * rho_dair(l) * cpair / dtime) * t_building_max(l) &
- - (ht_roof(l) * rho_dair(l) * cpair / dtime) * t_building_bef_hac(l) )
+ eflx_urban_ac_sat(l) = wtlunit_roof(l) * abs( (ht_roof(l) * rho_air(l) * cp_air(l) / dtime) * t_building_max(l) &
+ - (ht_roof(l) * rho_air(l) * cp_air(l) / dtime) * t_building_bef_hac(l) )
t_building(l) = t_building_max(l) + ( 1._r8 - p_ac(l) ) * eflx_urban_ac_sat(l) &
- * dtime / (ht_roof(l) * rho_dair(l) * cpair * wtlunit_roof(l))
+ * dtime / (ht_roof(l) * rho_air(l) * cp_air(l) * wtlunit_roof(l))
+ if (IsACDehumidificationEnabled()) then
+ ! Apply the AC adoption rate and assign to the AC sensible heat flux in preparation for later calculations
+ eflx_urban_ac_sen(l) = p_ac(l) * eflx_urban_ac_sat(l)
+ end if
+
+ ! Latent heat component of AC
+ if (IsACDehumidificationEnabled()) then
+ if (q_building_bef_hac(l) > q_building_max) then
+ ! Calculate latent heat flux needed to lower indoor specific humidity to q_building_max at saturated AC adoption
+ eflx_urban_ac_sat_lat(l) = wtlunit_roof(l) * abs( &
+ (ht_roof(l) * rho_air(l) * hvap / dtime) * q_building_max &
+ - (ht_roof(l) * rho_air(l) * hvap / dtime) * q_building_bef_hac(l) )
+ ! Add to the sensible heat component calculated previously
+ eflx_urban_ac_sat(l) = eflx_urban_ac_sat(l) + eflx_urban_ac_sat_lat(l)
+ ! Reset q_building based on actual moisture removed at the actual AC adoption rate
+ q_building(l) = q_building_max + (1._r8 - p_ac(l)) * eflx_urban_ac_sat_lat(l) &
+ * dtime / (ht_roof(l) * rho_air(l) * hvap * wtlunit_roof(l))
+ end if
+ end if
eflx_urban_ac(l) = p_ac(l) * eflx_urban_ac_sat(l)
else
t_building(l) = t_building_max(l)
- eflx_urban_ac(l) = wtlunit_roof(l) * abs( (ht_roof(l) * rho_dair(l) * cpair / dtime) * t_building(l) &
- - (ht_roof(l) * rho_dair(l) * cpair / dtime) * t_building_bef_hac(l) )
+ eflx_urban_ac(l) = wtlunit_roof(l) * abs( &
+ (ht_roof(l) * rho_air(l) * cp_air(l) / dtime) * t_building(l) &
+ - (ht_roof(l) * rho_air(l) * cp_air(l) / dtime) * t_building_bef_hac(l) )
+ if (IsACDehumidificationEnabled()) then
+ eflx_urban_ac_sen(l) = eflx_urban_ac(l)
+ end if
end if
else if (t_building_bef_hac(l) < t_building_min(l)) then
+ ! Humidification during urban heating is not implemented.
t_building(l) = t_building_min(l)
- eflx_urban_heat(l) = wtlunit_roof(l) * abs( (ht_roof(l) * rho_dair(l) * cpair / dtime) * t_building(l) &
- - (ht_roof(l) * rho_dair(l) * cpair / dtime) * t_building_bef_hac(l) )
+ eflx_urban_heat(l) = wtlunit_roof(l) * abs( (ht_roof(l) * rho_air(l) * cp_air(l) / dtime) * t_building(l) &
+ - (ht_roof(l) * rho_air(l) * cp_air(l) / dtime) * t_building_bef_hac(l) )
else
+ if (IsACDehumidificationEnabled()) eflx_urban_ac_sen(l) = 0._r8
eflx_urban_ac(l) = 0._r8
eflx_urban_heat(l) = 0._r8
end if
+
else
+ if (IsACDehumidificationEnabled()) eflx_urban_ac_sen(l) = 0._r8
eflx_urban_ac(l) = 0._r8
eflx_urban_heat(l) = 0._r8
end if
- eflx_building(l) = wtlunit_roof(l) * (ht_roof(l) * rho_dair(l)*cpair/dtime) * (t_building(l) - t_building_bef(l))
+ if (IsBuildingHumidityEnabled()) then
+ ! Calculate sensible and latent heat flux from the change in indoor air state.
+ eflx_building(l) = wtlunit_roof(l) * ( &
+ (ht_roof(l) * rho_air(l)*cp_air(l)/dtime) * (t_building(l) - t_building_bef(l)) &
+ + (ht_roof(l) * rho_air(l)*hvap/dtime) * (q_building(l) - q_building_bef(l)) )
+ if (IsACDehumidificationEnabled()) then
+ ! Convert the decrease in indoor water vapor to condensate per building footprint area.
+ qtot_condensate(l) = max(0._r8, (-q_building(l)+q_building_bef_hac(l))) * ht_roof(l) * rho_air(l)
+ qflx_condensate_from_ac_lu(l) = wtlunit_roof(l) * qtot_condensate(l) / dtime
+ end if
+ ! Diagnose indoor relative humidity from the updated temperature and humidity.
+ call QSat(t_building(l), forc_pbot(g), qsat_building)
+ rh_building(l) = min(100._r8, q_building(l) / qsat_building * 100._r8)
+ else
+ eflx_building(l) = wtlunit_roof(l) * (ht_roof(l) * rho_air(l)*cpair/dtime) &
+ * (t_building(l) - t_building_bef(l))
+ end if
end if
end do
+ ! AC dehumidification consistency check
+ if (IsACDehumidificationEnabled()) then
+ do fl = 1,num_urbanl
+ l = filter_urbanl(fl)
+ ! Dehumidification energy flux calculated from condensate
+ eflx_urban_ac_lat_derived(l) = qflx_condensate_from_ac_lu(l) * hvap
+ ! Error between the above and the Latent heat component of AC energy flux
+ err_eflx_urban_ac_lat(l) = eflx_urban_ac_lat_derived(l) - (eflx_urban_ac(l) - eflx_urban_ac_sen(l))
+ if (abs(err_eflx_urban_ac_lat(l)) > 1.e-5_r8 ) then
+ write (iulog,*) 'dehumidification energy flux derived from condensate does not match condensate output'
+ write (iulog,*) 'dehumidification energy flux derived from condensate [W/m2 urban]: ',eflx_urban_ac_lat_derived(l)
+ write (iulog,*) 'total AC energy flux [W/m2 urban]: ',eflx_urban_ac(l)
+ write (iulog,*) 'sensible heat component of AC energy flux [W/m2 urban]: ',eflx_urban_ac_sen(l)
+ write (iulog,*) 'latent heat component of AC energy flux (total minus sensible) [W/m2 urban]: ', &
+ eflx_urban_ac(l) - eflx_urban_ac_sen(l)
+ write (iulog,*) 'error in dehumidification energy flux [W/m2 urban]: ',err_eflx_urban_ac_lat(l)
+ write (iulog,*) 'error tolerance [W/m2 urban]: ',1.e-5_r8
+ write (iulog,*) 'clm model is stopping'
+ call endrun(subgrid_index=l, subgrid_level=subgrid_level_landunit)
+ end if
+ end do
+
+ ! Assign condensate water flux to roof column, which then goes directly to surface runoff.
+ ! It is assigned to roof column rather than other columns as it is calculated per building footprint,
+ ! which is equivalent to roof area.
+ ! Set condensate water flux to zero for all other columns.
+ do fc = 1,num_urbanc
+ c = filter_urbanc(fc)
+ l = clandunit(c)
+ if (urbpoi(l)) then
+ if (ctype(c) == icol_roof) then
+ qflx_condensate_from_ac(c) = qtot_condensate(l)/dtime
+ else
+ qflx_condensate_from_ac(c) = 0._r8
+ end if
+ end if
+ end do
+ end if
+
end associate
end subroutine BuildingTemperature
diff --git a/src/biogeophys/UrbanParamsType.F90 b/src/biogeophys/UrbanParamsType.F90
index c6443897fe..060dccb8c2 100644
--- a/src/biogeophys/UrbanParamsType.F90
+++ b/src/biogeophys/UrbanParamsType.F90
@@ -23,6 +23,8 @@ module UrbanParamsType
public :: CheckUrban ! Check validity of urban points
public :: IsSimpleBuildTemp ! If using the simple building temperature method
public :: IsProgBuildTemp ! If using the prognostic building temperature method
+ public :: IsBuildingHumidityEnabled ! If indoor building humidity is prognosed
+ public :: IsACDehumidificationEnabled ! If air-conditioning dehumidification is enabled
!
! !PRIVATE TYPE
type urbinp_type
@@ -93,6 +95,7 @@ module UrbanParamsType
contains
procedure, public :: Init
+ procedure, public :: InitForTesting
end type urbanparams_type
!
@@ -101,6 +104,10 @@ module UrbanParamsType
character(len= *), parameter, public :: urban_hac_on = 'ON'
character(len= *), parameter, public :: urban_wasteheat_on = 'ON_WASTEHEAT'
character(len= 16), public :: urban_hac = urban_hac_off
+ integer, parameter, public :: BUILDING_HUMIDITY_MODE_OFF = 0
+ integer, parameter, public :: BUILDING_HUMIDITY_MODE_ON = 1
+ integer, parameter, public :: BUILDING_HUMIDITY_MODE_DEHUMIDIFY = 2
+ integer, public :: building_humidity_mode = BUILDING_HUMIDITY_MODE_OFF
logical, public :: urban_explicit_ac = .true. ! whether to use explicit, time-varying AC adoption rate
logical, public :: urban_traffic = .false. ! urban traffic fluxes
@@ -363,6 +370,34 @@ subroutine Init(this, bounds)
end subroutine Init
+ !-----------------------------------------------------------------------
+ subroutine InitForTesting(this, bounds)
+ !
+ ! !DESCRIPTION:
+ ! Version of Init routine just for unit tests
+ !
+ ! This version sets building_humidity_mode and then calls Init
+ !
+ ! !ARGUMENTS:
+ class(urbanparams_type) , intent(inout) :: this
+ type(bounds_type) , intent(in) :: bounds
+ !
+ ! !LOCAL VARIABLES:
+
+ character(len=*), parameter :: subname = 'InitForTesting'
+ !-----------------------------------------------------------------------
+
+ ! Default setting (prognostic indoor humidity and dehumidification off)
+ building_humidity_mode = 0
+
+ ! We set this to .true. to enable the IsBuildingHumidityEnabled and
+ ! IsACDehumidificationEnabled functions to be used for unit testing
+ ReadNamelist = .true.
+
+ call Init(this, bounds = bounds)
+
+ end subroutine InitForTesting
+
!-----------------------------------------------------------------------
subroutine UrbanInput(begg, endg, mode)
!
@@ -848,7 +883,7 @@ subroutine UrbanReadNML ( NLFilename )
integer :: unitn ! unit for namelist file
character(len=32) :: subname = 'UrbanReadNML' ! subroutine name
- namelist / clmu_inparm / urban_hac, urban_explicit_ac, urban_traffic, building_temp_method
+ namelist / clmu_inparm / urban_hac, building_humidity_mode, urban_explicit_ac, urban_traffic, building_temp_method
!EOP
!-----------------------------------------------------------------------
@@ -876,6 +911,7 @@ subroutine UrbanReadNML ( NLFilename )
! Broadcast namelist variables read in
call shr_mpi_bcast(urban_hac, mpicom)
+ call shr_mpi_bcast(building_humidity_mode, mpicom)
call shr_mpi_bcast(urban_explicit_ac, mpicom)
call shr_mpi_bcast(urban_traffic, mpicom)
call shr_mpi_bcast(building_temp_method, mpicom)
@@ -885,9 +921,28 @@ subroutine UrbanReadNML ( NLFilename )
write(iulog,*)'Urban traffic fluxes are not implemented currently'
call endrun(msg=errMsg(sourcefile, __LINE__))
end if
+ if (building_humidity_mode < BUILDING_HUMIDITY_MODE_OFF .or. &
+ building_humidity_mode > BUILDING_HUMIDITY_MODE_DEHUMIDIFY) then
+ call endrun(msg='building_humidity_mode must be 0, 1, or 2'//errmsg(sourcefile, __LINE__))
+ end if
+ if (building_humidity_mode >= BUILDING_HUMIDITY_MODE_ON .and. &
+ building_temp_method /= BUILDING_TEMP_METHOD_PROG) then
+ call endrun(msg='building_humidity_mode=1 or 2 requires the prognostic building temperature method'// &
+ errmsg(sourcefile, __LINE__))
+ end if
+ if (building_humidity_mode == BUILDING_HUMIDITY_MODE_DEHUMIDIFY) then
+ if (trim(urban_hac) /= urban_hac_on .and. trim(urban_hac) /= urban_wasteheat_on) then
+ call endrun(msg='building_humidity_mode=2 requires urban_hac to be ON or ON_WASTEHEAT'// &
+ errmsg(sourcefile, __LINE__))
+ end if
+ if (.not. urban_explicit_ac) then
+ call endrun(msg='building_humidity_mode=2 requires urban_explicit_ac=.true.'//errmsg(sourcefile, __LINE__))
+ end if
+ end if
!
if ( masterproc )then
write(iulog,*) ' urban air conditioning/heating and wasteheat = ', urban_hac
+ write(iulog,*) ' urban building humidity mode = ', building_humidity_mode
write(iulog,*) ' urban explicit air-conditioning adoption rate = ', urban_explicit_ac
write(iulog,*) ' urban traffic flux = ', urban_traffic
end if
@@ -954,4 +1009,60 @@ end function IsProgBuildTemp
!-----------------------------------------------------------------------
+ !-----------------------------------------------------------------------
+ !BOP
+ !
+ ! !IROUTINE: IsBuildingHumidityEnabled
+ !
+ ! !INTERFACE:
+ !
+ logical function IsBuildingHumidityEnabled( )
+ !
+ ! !DESCRIPTION:
+ !
+ ! If indoor building humidity is being simulated
+ !
+ ! !USES:
+ implicit none
+ !EOP
+ !-----------------------------------------------------------------------
+
+ if ( .not. ReadNamelist )then
+ write(iulog,*)'Testing on building_humidity_mode before urban namelist was read in'
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ end if
+ IsBuildingHumidityEnabled = building_humidity_mode >= BUILDING_HUMIDITY_MODE_ON
+
+ end function IsBuildingHumidityEnabled
+
+ !-----------------------------------------------------------------------
+
+ !-----------------------------------------------------------------------
+ !BOP
+ !
+ ! !IROUTINE: IsACDehumidificationEnabled
+ !
+ ! !INTERFACE:
+ !
+ logical function IsACDehumidificationEnabled( )
+ !
+ ! !DESCRIPTION:
+ !
+ ! If air-conditioning dehumidification is enabled
+ !
+ ! !USES:
+ implicit none
+ !EOP
+ !-----------------------------------------------------------------------
+
+ if ( .not. ReadNamelist )then
+ write(iulog,*)'Testing on building_humidity_mode before urban namelist was read in'
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ end if
+ IsACDehumidificationEnabled = building_humidity_mode == BUILDING_HUMIDITY_MODE_DEHUMIDIFY
+
+ end function IsACDehumidificationEnabled
+
+ !-----------------------------------------------------------------------
+
end module UrbanParamsType
diff --git a/src/biogeophys/WaterDiagnosticBulkType.F90 b/src/biogeophys/WaterDiagnosticBulkType.F90
index 2daef42b77..429536a5cb 100644
--- a/src/biogeophys/WaterDiagnosticBulkType.F90
+++ b/src/biogeophys/WaterDiagnosticBulkType.F90
@@ -28,6 +28,7 @@ module WaterDiagnosticBulkType
use WaterStateType, only : waterstate_type
use WaterStateBulkType, only : waterstatebulk_type
use WaterFluxType, only : waterflux_type
+ use UrbanParamsType, only : IsBuildingHumidityEnabled
!
implicit none
save
@@ -63,6 +64,7 @@ module WaterDiagnosticBulkType
real(r8), pointer :: rh_ref2m_patch (:) ! patch 2 m height surface relative humidity (%)
real(r8), pointer :: rh_ref2m_r_patch (:) ! patch 2 m height surface relative humidity - rural (%)
real(r8), pointer :: rh_ref2m_u_patch (:) ! patch 2 m height surface relative humidity - urban (%)
+ real(r8), pointer :: rh_building_lun (:) ! lun internal building air relative humidity (%)
real(r8), pointer :: rh_af_patch (:) ! patch fractional humidity of canopy air (dimensionless) ! private
real(r8), pointer :: rh10_af_patch (:) ! 10-day mean patch fractional humidity of canopy air (dimensionless)
real(r8), pointer :: dqgdT_col (:) ! col d(qg)/dT
@@ -154,7 +156,7 @@ subroutine InitBulk(this, bounds, info, vars, &
real(r8) , intent(in) :: h2osno_input_col(bounds%begc:) ! Initial total snow water (mm H2O)
- call this%Init(bounds, info, vars)
+ call this%Init(bounds, info, vars, IsBuildingHumidityEnabled())
call this%InitBulkAllocate(bounds)
@@ -219,6 +221,7 @@ subroutine InitBulkAllocate(this, bounds)
allocate(this%rh_ref2m_patch (begp:endp)) ; this%rh_ref2m_patch (:) = nan
allocate(this%rh_ref2m_u_patch (begp:endp)) ; this%rh_ref2m_u_patch (:) = nan
allocate(this%rh_ref2m_r_patch (begp:endp)) ; this%rh_ref2m_r_patch (:) = nan
+ allocate(this%rh_building_lun (begl:endl)) ; this%rh_building_lun (:) = nan
allocate(this%rh_af_patch (begp:endp)) ; this%rh_af_patch (:) = nan
allocate(this%rh10_af_patch (begp:endp)) ; this%rh10_af_patch (:) = spval
@@ -357,6 +360,16 @@ subroutine InitBulkHistory(this, bounds)
long_name=this%info%lname('Urban 2m relative humidity'), &
ptr_patch=this%rh_ref2m_u_patch, set_nourb=spval)
+ if (IsBuildingHumidityEnabled()) then
+ this%rh_building_lun(begl:endl) = spval
+ call hist_addfld1d ( &
+ fname=this%info%fname('RHBUILD'), &
+ units='%', &
+ avgflag='A', &
+ long_name=this%info%lname('Internal urban building air relative humidity'), &
+ ptr_lunit=this%rh_building_lun, set_nourb=spval)
+ end if
+
this%rh_af_patch(begp:endp) = spval
! Commented out failing fields (see https://github.com/ESCOMP/CTSM/issues/3661) to allow all_outputs test to catch new problems as they arise
! call hist_addfld1d ( &
@@ -815,7 +828,7 @@ subroutine RestartBulk(this, bounds, ncid, flag, writing_finidat_interp_dest_fil
!------------------------------------------------------------------------
- call this%Restart(bounds, ncid, flag=flag)
+ call this%Restart(bounds, ncid, flag=flag, is_prog_buildhumidity=IsBuildingHumidityEnabled())
if(use_luna)then
call restartvar(ncid=ncid, flag=flag, &
diff --git a/src/biogeophys/WaterDiagnosticType.F90 b/src/biogeophys/WaterDiagnosticType.F90
index 57be0e62af..fc4b609a84 100644
--- a/src/biogeophys/WaterDiagnosticType.F90
+++ b/src/biogeophys/WaterDiagnosticType.F90
@@ -49,6 +49,7 @@ module WaterDiagnosticType
real(r8), pointer :: qg_h2osfc_col (:) ! col ground specific humidity [kg/kg]
real(r8), pointer :: qg_col (:) ! col ground specific humidity [kg/kg]
real(r8), pointer :: qaf_lun (:) ! lun urban canopy air specific humidity (kg/kg)
+ real(r8), pointer :: q_building_lun (:) ! lun internal building air specific humidity (kg/kg)
contains
@@ -69,20 +70,21 @@ module WaterDiagnosticType
contains
!------------------------------------------------------------------------
- subroutine Init(this, bounds, info, tracer_vars)
+ subroutine Init(this, bounds, info, tracer_vars, is_prog_buildhumidity)
class(waterdiagnostic_type), intent(inout) :: this
type(bounds_type) , intent(in) :: bounds
class(water_info_base_type), intent(in), target :: info
type(water_tracer_container_type), intent(inout) :: tracer_vars
+ logical, intent(in) :: is_prog_buildhumidity ! Prognostic building humidity is being used
this%info => info
call this%InitAllocate(bounds, tracer_vars)
- call this%InitHistory(bounds)
+ call this%InitHistory(bounds, is_prog_buildhumidity)
- call this%InitCold(bounds)
+ call this%InitCold(bounds, is_prog_buildhumidity)
end subroutine Init
@@ -138,11 +140,14 @@ subroutine InitAllocate(this, bounds, tracer_vars)
call AllocateVar1d(var = this%q_ref2m_patch, name = 'q_ref2m_patch', &
container = tracer_vars, &
bounds = bounds, subgrid_level = subgrid_level_patch)
+ call AllocateVar1d(var = this%q_building_lun, name = 'q_building_lun', &
+ container = tracer_vars, &
+ bounds = bounds, subgrid_level = subgrid_level_landunit)
end subroutine InitAllocate
!------------------------------------------------------------------------
- subroutine InitHistory(this, bounds)
+ subroutine InitHistory(this, bounds, is_prog_buildhumidity)
!
! !DESCRIPTION:
! Initialize module data structure
@@ -153,15 +158,18 @@ subroutine InitHistory(this, bounds)
! !ARGUMENTS:
class(waterdiagnostic_type), intent(in) :: this
type(bounds_type), intent(in) :: bounds
+ logical, intent(in) :: is_prog_buildhumidity ! Prognostic building humidity is being used
!
! !LOCAL VARIABLES:
integer :: begp, endp
integer :: begc, endc
+ integer :: begl, endl
integer :: begg, endg
!------------------------------------------------------------------------
begp = bounds%begp; endp= bounds%endp
begc = bounds%begc; endc= bounds%endc
+ begl = bounds%begl; endl= bounds%endl
begg = bounds%begg; endg= bounds%endg
@@ -241,12 +249,21 @@ subroutine InitHistory(this, bounds)
long_name=this%info%lname('snow ice (ice landunits only)'), &
ptr_col=this%snowice_col, c2l_scale_type='urbanf', l2g_scale_type='ice', &
default='inactive')
-
+
+ if ( is_prog_buildhumidity ) then
+ this%q_building_lun(begl:endl) = spval
+ call hist_addfld1d ( &
+ fname=this%info%fname('QBUILD'), &
+ units='kg/kg', &
+ avgflag='A', &
+ long_name=this%info%lname('internal urban building air specific humidity'), &
+ ptr_lunit=this%q_building_lun, l2g_scale_type='unity', set_nourb=spval)
+ end if
end subroutine InitHistory
!-----------------------------------------------------------------------
- subroutine InitCold(this, bounds)
+ subroutine InitCold(this, bounds, is_prog_buildhumidity)
!
! !DESCRIPTION:
! Initialize time constant variables and cold start conditions
@@ -257,6 +274,7 @@ subroutine InitCold(this, bounds)
! !ARGUMENTS:
class(waterdiagnostic_type), intent(in) :: this
type(bounds_type) , intent(in) :: bounds
+ logical , intent(in) :: is_prog_buildhumidity ! Prognostic building humidity is being used
!
! !LOCAL VARIABLES:
integer :: l
@@ -286,10 +304,19 @@ subroutine InitCold(this, bounds)
end if
end do
+ ! Initialize internal building specific humidity (following example above and t_building_max in TemperatureType.F90)
+ if ( is_prog_buildhumidity ) then
+ do l = bounds%begl, bounds%endl
+ if (lun%urbpoi(l)) then
+ this%q_building_lun(l) = this%qaf_lun(l) ! set to urban canopy specific humidity
+ end if
+ end do
+ end if
+
end subroutine InitCold
!------------------------------------------------------------------------
- subroutine Restart(this, bounds, ncid, flag)
+ subroutine Restart(this, bounds, ncid, flag, is_prog_buildhumidity)
!
! !DESCRIPTION:
! Read/Write module information to/from restart file.
@@ -297,14 +324,16 @@ subroutine Restart(this, bounds, ncid, flag)
! !USES:
use clm_varcon , only : nameg, namec
use ncdio_pio , only : file_desc_t, ncd_double
- use clm_varctl , only : use_fates_planthydro
+ use clm_varctl , only : use_fates_planthydro, iulog
use restUtilMod
+ use spmdMod , only : masterproc
!
! !ARGUMENTS:
class(waterdiagnostic_type), intent(in) :: this
type(bounds_type), intent(in) :: bounds
type(file_desc_t), intent(inout) :: ncid ! netcdf id
character(len=*) , intent(in) :: flag ! 'read' or 'write'
+ logical , intent(in) :: is_prog_buildhumidity ! Prognostic building humidity is being used
!
! !LOCAL VARIABLES:
integer :: c,l,j
@@ -340,6 +369,21 @@ subroutine Restart(this, bounds, ncid, flag)
interpinic_flag='interp', readvar=readvar, data=this%total_plant_stored_h2o_col)
end if
+ if ( is_prog_buildhumidity ) then
+ ! landunit type physical state variable - q_building
+ call restartvar(ncid=ncid, flag=flag, &
+ varname=this%info%fname('q_building'), &
+ xtype=ncd_double, dim1name='landunit', &
+ long_name=this%info%lname('internal building air specific humidity'), &
+ units='kg/kg', &
+ interpinic_flag='interp', readvar=readvar, data=this%q_building_lun)
+ if (flag=='read' .and. .not. readvar) then
+ if (masterproc) write(iulog,*) "can't find q_building in initial file..."
+ if (masterproc) write(iulog,*) "Initialize q_building to qaf"
+ this%q_building_lun(bounds%begl:bounds%endl) = this%qaf_lun(bounds%begl:bounds%endl)
+ end if
+ end if
+
end subroutine Restart
!-----------------------------------------------------------------------
diff --git a/src/biogeophys/WaterFluxType.F90 b/src/biogeophys/WaterFluxType.F90
index 88b1b3e5cf..ecbed09b19 100644
--- a/src/biogeophys/WaterFluxType.F90
+++ b/src/biogeophys/WaterFluxType.F90
@@ -17,6 +17,7 @@ module WaterFluxType
use WaterInfoBaseType, only : water_info_base_type
use WaterTracerContainerType, only : water_tracer_container_type
use WaterTracerUtils, only : AllocateVar1d, AllocateVar2d
+ use UrbanParamsType, only : IsACDehumidificationEnabled
!
implicit none
private
@@ -50,7 +51,9 @@ module WaterFluxType
real(r8), pointer :: qflx_evap_tot_col (:) ! col col_qflx_evap_soi + col_qflx_evap_veg + qflx_tran_veg
real(r8), pointer :: qflx_liqevap_from_top_layer_patch(:) ! patch rate of liquid water evaporated from top soil or snow layer (mm H2O/s) [+]
real(r8), pointer :: qflx_liqevap_from_top_layer_col(:) ! col rate of liquid water evaporated from top soil or snow layer (mm H2O/s) [+]
-
+ real(r8), pointer :: qflx_condensate_from_ac_col(:) ! col condensate due to dehumidification from air-conditioning (mm H2O/S) [+]
+ real(r8), pointer :: qflx_condensate_from_ac_lun(:) ! lun condensate due to dehumidification from air-conditioning (mm H2O/S) [+]
+
! In the snow capping parametrization excess mass above h2osno_max is removed. A breakdown of mass into liquid
! and solid fluxes is done, these are represented by qflx_snwcp_liq_col and qflx_snwcp_ice_col.
real(r8), pointer :: qflx_snwcp_liq_col (:) ! col excess liquid h2o due to snow capping (outgoing) (mm H2O /s)
@@ -269,6 +272,12 @@ subroutine InitAllocate(this, bounds, tracer_vars)
call AllocateVar1d(var = this%qflx_liqevap_from_top_layer_patch, name = 'qflx_liqevap_from_top_layer_patch', &
container = tracer_vars, &
bounds = bounds, subgrid_level = subgrid_level_patch)
+ call AllocateVar1d(var = this%qflx_condensate_from_ac_col, name = 'qflx_condensate_from_ac_col', &
+ container = tracer_vars, &
+ bounds = bounds, subgrid_level = subgrid_level_column, ival = 0.0_r8)
+ call AllocateVar1d(var = this%qflx_condensate_from_ac_lun, name = 'qflx_condensate_from_ac_lun', &
+ container = tracer_vars, &
+ bounds = bounds, subgrid_level = subgrid_level_landunit, ival = 0.0_r8)
call AllocateVar1d(var = this%qflx_infl_col, name = 'qflx_infl_col', &
container = tracer_vars, &
@@ -588,6 +597,17 @@ subroutine InitHistory(this, bounds)
long_name=this%info%lname('Rural total runoff'), &
ptr_col=this%qflx_runoff_r_col, set_spec=spval, default='inactive')
+ if (IsACDehumidificationEnabled()) then
+ this%qflx_condensate_from_ac_col(begc:endc) = 0.0_r8
+ call hist_addfld1d ( &
+ fname=this%info%fname('QCOND_FROM_AC'), &
+ units='mm/s', &
+ avgflag='A', &
+ long_name=this%info%lname('Condensed water flux from AC dehumidification'), &
+ ptr_col=this%qflx_condensate_from_ac_col, set_nourb=0.0_r8, c2l_scale_type='urbanf')
+ this%qflx_condensate_from_ac_lun(begl:endl) = 0.0_r8
+ end if
+
this%qflx_snomelt_col(begc:endc) = spval
call hist_addfld1d ( &
fname=this%info%fname('QSNOMELT'), &
diff --git a/src/biogeophys/WaterType.F90 b/src/biogeophys/WaterType.F90
index 9a4884ce8c..f0745acc9d 100644
--- a/src/biogeophys/WaterType.F90
+++ b/src/biogeophys/WaterType.F90
@@ -78,6 +78,7 @@ module WaterType
use Wateratm2lndBulkType , only : wateratm2lndbulk_type
use WaterTracerContainerType , only : water_tracer_container_type
use WaterTracerUtils , only : CompareBulkToTracer, SetTracerToBulkTimesRatio
+ use UrbanParamsType , only : IsBuildingHumidityEnabled
implicit none
private
@@ -394,7 +395,8 @@ subroutine DoInit(this, bounds, &
call this%bulk_and_tracers(i)%waterdiagnostic_inst%Init(bounds, &
this%bulk_and_tracers(i)%info, &
- this%bulk_and_tracers(i)%vars)
+ this%bulk_and_tracers(i)%vars, &
+ IsBuildingHumidityEnabled())
call this%bulk_and_tracers(i)%waterbalance_inst%Init(bounds, &
this%bulk_and_tracers(i)%info, &
@@ -733,7 +735,7 @@ end subroutine UpdateAccVars
!-----------------------------------------------------------------------
subroutine Restart(this, bounds, ncid, flag, writing_finidat_interp_dest_file, &
- watsat_col, t_soisno_col, altmax_lastyear_indx)
+ watsat_col, t_soisno_col, altmax_lastyear_indx, is_prog_buildhumidity)
!
! !DESCRIPTION:
! Read/write information to/from restart file for all water variables
@@ -747,6 +749,7 @@ subroutine Restart(this, bounds, ncid, flag, writing_finidat_interp_dest_file, &
real(r8) , intent(in) :: watsat_col (bounds%begc:, 1:) ! volumetric soil water at saturation (porosity)
real(r8) , intent(in) :: t_soisno_col(bounds%begc:, -nlevsno+1:) ! col soil temperature (Kelvin)
integer , intent(in) :: altmax_lastyear_indx(bounds%begc:) !col active layer index last year
+ logical , intent(in) :: is_prog_buildhumidity ! Prognostic building humidity is being used
!
! !LOCAL VARIABLES:
integer :: i
@@ -776,7 +779,8 @@ subroutine Restart(this, bounds, ncid, flag, writing_finidat_interp_dest_file, &
t_soisno_col=t_soisno_col(bounds%begc:, -nlevsno+1:), &
altmax_lastyear_indx=altmax_lastyear_indx(bounds%begc:))
- call this%bulk_and_tracers(i)%waterdiagnostic_inst%Restart(bounds, ncid, flag=flag)
+ call this%bulk_and_tracers(i)%waterdiagnostic_inst%Restart(bounds, ncid, flag=flag, &
+ is_prog_buildhumidity=is_prog_buildhumidity)
end do
diff --git a/src/biogeophys/test/Irrigation_test/test_irrigation.pf b/src/biogeophys/test/Irrigation_test/test_irrigation.pf
index d83fc94329..8257180af2 100644
--- a/src/biogeophys/test/Irrigation_test/test_irrigation.pf
+++ b/src/biogeophys/test/Irrigation_test/test_irrigation.pf
@@ -22,6 +22,7 @@ module test_irrigation
use ColumnType , only : col
use GridcellType , only : grc
use pftconMod , only : pftcon
+ use UrbanParamsType, only : urbanparams_type
use unittestWaterTypeFactory, only : unittest_water_type_factory_type
use unittestSimpleSubgridSetupsMod, only : setup_single_veg_patch, setup_n_veg_patches
use unittestFilterBuilderMod, only : filter_from_range
@@ -37,6 +38,7 @@ module test_irrigation
type, extends(irrigation_type) :: irrigation_test_type
real(r8) :: gw_frac_from_con ! Fraction of groundwater irrigation taken from confined aquifer (same for all columns)
real(r8), allocatable :: gw_frac_from_uncon(:) ! Fraction of groundwater irrigation taken from unconfined aquifer in various layers (same for all columns)
+ type(urbanparams_type) :: urbanparams_inst
contains
procedure, public :: InitForTesting ! Call the main irrigation InitForTesting, and also initialize IrrigationTestType's test-specific data to reasonable values
procedure, public :: SetGwFractions ! Set fraction of groundwater from various sources
@@ -417,6 +419,8 @@ contains
!
! !ARGUMENTS:
class(TestIrrigation), intent(in) :: this
+ type(urbanparams_type) :: urbanparams_inst
+
integer, intent(in) :: maxpft ! max pft type that needs to be supported
! Irrigation method (drip by default); just set for the module-level pft_type parameter
@@ -437,6 +441,10 @@ contains
allocate(pftcon%irrigated(0:maxpft), source=1.0_r8)
+ ! Reguired so that the IsBuildingHumidityEnabled and IsACDehumidificationEnabled
+ ! namelist functions can be used
+ call urbanparams_inst%InitForTesting(bounds)
+
! In the production code, irrig_method goes cft_lb:cft_ub; but it's safe to allocate
! more space than we really need here.
allocate(irrig_method(bounds%begg:bounds%endg, 0:maxpft))
diff --git a/src/biogeophys/test/WaterType_test/test_water_type.pf b/src/biogeophys/test/WaterType_test/test_water_type.pf
index f3edc731a3..3367047bf2 100644
--- a/src/biogeophys/test/WaterType_test/test_water_type.pf
+++ b/src/biogeophys/test/WaterType_test/test_water_type.pf
@@ -5,6 +5,7 @@ module test_water_type
use funit
use WaterType
use shr_kind_mod , only : r8 => shr_kind_r8
+ use UrbanParamsType, only : urbanparams_type
use unittestSubgridMod, only : bounds, unittest_subgrid_teardown
use unittestSimpleSubgridSetupsMod, only : setup_single_veg_patch
use unittestUtils, only : endrun_msg
@@ -15,6 +16,7 @@ module test_water_type
@TestCase
type, extends(TestCase) :: TestWaterType
type(water_type) :: water_inst
+ type(urbanparams_type) :: urbanparams_inst
type(unittest_water_type_factory_type) :: factory
contains
procedure :: setUp
@@ -49,6 +51,11 @@ contains
class(TestWaterType), intent(inout) :: this
call setup_single_veg_patch(pft_type=1)
+
+ ! Reguired so that the IsBuildingHumidityEnabled and IsACDehumidificationEnabled
+ ! namelist functions can be used
+ call this%urbanparams_inst%InitForTesting(bounds)
+
call this%factory%setup_after_subgrid(snl = 0, dz = 1._r8)
call this%factory%create_water_type(this%water_inst, &
enable_consistency_checks = .true., &
diff --git a/src/dyn_subgrid/test/dynConsBiogeophys_test/test_dyn_cons_biogeophys.pf b/src/dyn_subgrid/test/dynConsBiogeophys_test/test_dyn_cons_biogeophys.pf
index d991f0cc54..f77780a424 100644
--- a/src/dyn_subgrid/test/dynConsBiogeophys_test/test_dyn_cons_biogeophys.pf
+++ b/src/dyn_subgrid/test/dynConsBiogeophys_test/test_dyn_cons_biogeophys.pf
@@ -30,9 +30,9 @@ module test_dyn_cons_biogeophys
type(unittest_water_type_factory_type) :: water_type_factory
type(soilstate_type) :: soilstate_inst
type(temperature_type) :: temperature_inst
- type(urbanparams_type) :: urbanparams_inst
type(lakestate_type) :: lakestate_inst
type(water_type) :: water_inst
+ type(urbanparams_type) :: urbanparams_inst
contains
procedure :: setUp
procedure :: tearDown
@@ -195,6 +195,10 @@ contains
! Arbitrary value, not important here
this%urbanparams_inst%nlev_improad(bounds%begl:bounds%endl) = 3
+ ! Reguired so that the IsBuildingHumidityEnabled and IsACDehumidificationEnabled
+ ! namelist functions can be used
+ call this%urbanparams_inst%InitForTesting(bounds)
+
! Initialize water_inst
! (snl and dz are totally arbitrary here: these are unimportant for this test)
call this%water_type_factory%setup_after_subgrid(snl = -2, dz = 0.05_r8)
diff --git a/src/dyn_subgrid/test/dynInitColumns_test/test_init_columns.pf b/src/dyn_subgrid/test/dynInitColumns_test/test_init_columns.pf
index 79ec506f90..6b474ed261 100644
--- a/src/dyn_subgrid/test/dynInitColumns_test/test_init_columns.pf
+++ b/src/dyn_subgrid/test/dynInitColumns_test/test_init_columns.pf
@@ -15,6 +15,7 @@ module test_init_columns
use shr_kind_mod , only : r8 => shr_kind_r8
use TemperatureType , only : temperature_type
use WaterType , only : water_type
+ use UrbanParamsType, only : urbanparams_type
use unittestWaterTypeFactory, only : unittest_water_type_factory_type
use WaterstateType , only : waterstate_type
use dynColumnTemplateMod, only : TEMPLATE_NONE_FOUND
@@ -29,6 +30,7 @@ module test_init_columns
integer :: l2 ! index of the landunit with landunit type 2
type(temperature_type) :: temperature_inst
type(water_type) :: water_inst
+ type(urbanparams_type) :: urbanparams_inst
type(unittest_water_type_factory_type) :: water_factory
contains
procedure :: setUp
@@ -87,6 +89,10 @@ contains
call unittest_subgrid_setup_end()
+ ! Reguired so that the IsBuildingHumidityEnabled and IsACDehumidificationEnabled
+ ! namelist functions can be used
+ call this%urbanparams_inst%InitForTesting(bounds)
+
call this%water_factory%setup_after_subgrid(snl = 0, dz = 1._r8)
call this%water_factory%create_water_type(this%water_inst, &
enable_isotopes = .true.)
diff --git a/src/main/clm_instMod.F90 b/src/main/clm_instMod.F90
index 7d9a0f6ad2..f6d0617367 100644
--- a/src/main/clm_instMod.F90
+++ b/src/main/clm_instMod.F90
@@ -516,7 +516,7 @@ subroutine clm_instRest(bounds, ncid, flag, writing_finidat_interp_dest_file)
!
! !USES:
use ncdio_pio , only : file_desc_t
- use UrbanParamsType , only : IsSimpleBuildTemp, IsProgBuildTemp
+ use UrbanParamsType , only : IsSimpleBuildTemp, IsProgBuildTemp, IsBuildingHumidityEnabled
use decompMod , only : get_proc_bounds, get_proc_clumps, get_clump_bounds
use clm_varpar , only : nlevsno
@@ -566,8 +566,9 @@ subroutine clm_instRest(bounds, ncid, flag, writing_finidat_interp_dest_file)
call water_inst%restart(bounds, ncid, flag=flag, &
writing_finidat_interp_dest_file = writing_finidat_interp_dest_file, &
watsat_col = soilstate_inst%watsat_col(bounds%begc:bounds%endc,:), &
- t_soisno_col=temperature_inst%t_soisno_col(bounds%begc:bounds%endc, -nlevsno+1:), &
- altmax_lastyear_indx=active_layer_inst%altmax_lastyear_indx_col(bounds%begc:bounds%endc))
+ t_soisno_col=temperature_inst%t_soisno_col(bounds%begc:bounds%endc, -nlevsno+1:), &
+ altmax_lastyear_indx=active_layer_inst%altmax_lastyear_indx_col(bounds%begc:bounds%endc), &
+ is_prog_buildhumidity = IsBuildingHumidityEnabled())
call irrigation_inst%restart (bounds, ncid, flag=flag)
diff --git a/src/main/clm_varcon.F90 b/src/main/clm_varcon.F90
index 234b89c797..751145ce97 100644
--- a/src/main/clm_varcon.F90
+++ b/src/main/clm_varcon.F90
@@ -13,7 +13,8 @@ module clm_varcon
SHR_CONST_RHOICE,SHR_CONST_TKFRZ,SHR_CONST_REARTH, &
SHR_CONST_PDB, SHR_CONST_PI, SHR_CONST_CDAY, &
SHR_CONST_RGAS, SHR_CONST_PSTD, &
- SHR_CONST_MWDAIR, SHR_CONST_MWWV, SHR_CONST_CPFW
+ SHR_CONST_MWDAIR, SHR_CONST_MWWV, SHR_CONST_CPFW, &
+ SHR_CONST_CPWV
use clm_varpar , only: numrad, nlevgrnd, nlevlak, nlevdecomp_full
use clm_varpar , only: ngases
use clm_varpar , only: nlayer
@@ -56,6 +57,7 @@ module clm_varcon
real(r8), public :: cpliq = SHR_CONST_CPFW ! Specific heat of water [J/kg-K]
real(r8), public :: cpice = SHR_CONST_CPICE ! Specific heat of ice [J/kg-K]
real(r8), public :: cpair = SHR_CONST_CPDAIR ! specific heat of dry air [J/kg/K]
+ real(r8), public :: cpwvap = SHR_CONST_CPWV ! specific heat of water vapor [J/kg/K]
real(r8), public :: hvap = SHR_CONST_LATVAP ! Latent heat of evap for water [J/kg]
real(r8), public :: hsub = SHR_CONST_LATSUB ! Latent heat of sublimation [J/kg]
real(r8), public :: hfus = SHR_CONST_LATICE ! Latent heat of fusion for ice [J/kg]
@@ -188,6 +190,7 @@ module clm_varcon
real(r8), public, parameter :: sh_floor = 880._r8 ! specific heat of floor - concrete (Salmanca et al. 2010, TAC) (J kg-1 K-1)
real(r8), public :: cp_floor = dens_floor*sh_floor ! volumetric heat capacity of floor - concrete (Salmanca et al. 2010, TAC) (J m-3 K-1)
real(r8), public :: vent_ach = 0.3_r8 ! ventilation rate (air exchanges per hour)
+ real(r8), public :: rh_building_max = 65._r8 ! maximum internal building air relative humidity (Li et al. 2026, Nat Cities, which follows ASHRAE Standards 62.1-2022 and 62.1-2013) (%)
real(r8), public :: wasteheat_limit = 100._r8 ! limit on wasteheat (W/m2)
diff --git a/src/unit_test_shr/unittestDustEmisInputs.F90 b/src/unit_test_shr/unittestDustEmisInputs.F90
index 771eb410f7..60d6504931 100644
--- a/src/unit_test_shr/unittestDustEmisInputs.F90
+++ b/src/unit_test_shr/unittestDustEmisInputs.F90
@@ -9,6 +9,7 @@ module unittestDustEmisInputs
use shr_kind_mod , only : r8 => shr_kind_r8
use unittestFilterBuilderMod, only : filter_from_range
use atm2lndType, only : atm2lnd_type, atm2lnd_params_type
+ use UrbanParamsType, only : urbanparams_type
use SoilStateType, only : soilstate_type
use CanopyStateType, only : canopystate_type
use TemperatureType, only : temperature_type
@@ -32,6 +33,7 @@ module unittestDustEmisInputs
type(unittest_water_type_factory_type), private :: water_factory
type(water_type) :: water_inst
type(frictionvel_type) :: frictionvel_inst
+ type(urbanparams_type) :: urbanparams_inst
contains
procedure, public :: setUp
procedure, public :: tearDown
@@ -86,6 +88,11 @@ subroutine setUp(this)
)
call this%atm2lnd_inst%InitForTesting(bounds, atm2lnd_params)
+
+ ! Reguired so that the IsBuildingHumidityEnabled and IsACDehumidificationEnabled
+ ! namelist functions can be used
+ call this%urbanparams_inst%InitForTesting(bounds)
+
! Water and soil state -- after the subgrid setup
call this%water_factory%setup_after_subgrid(snl = snl)
call this%setupSoilState( ) ! This needs to happen before the water_type object creation
@@ -271,4 +278,4 @@ end subroutine print_values
!-----------------------------------------------------------------------
-end module unittestDustEmisInputs
\ No newline at end of file
+end module unittestDustEmisInputs