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