!> @file biometeorology_ipt_mod.f90 !--------------------------------------------------------------------------------! ! This file is part of PALM-4U. ! ! PALM-4U is free software: you can redistribute it and/or modify it under the ! terms of the GNU General Public License as published by the Free Software ! Foundation, either version 3 of the License, or (at your option) any later ! version. ! ! PALM-4U is distributed in the hope that it will be useful, but WITHOUT ANY ! WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR ! A PARTICULAR PURPOSE. See the GNU General Public License for more details. ! ! You should have received a copy of the GNU General Public License along with ! PALM. If not, see . ! ! Copyright 1997-2019, Deutscher Wetterdienst (DWD) / ! German Meteorological Service (DWD) and ! Leibniz Universitaet Hannover !--------------------------------------------------------------------------------! ! ! Current revisions: 001 ! ----------------- ! ! ! Former revisions: 001 ! ----------------- ! $Id$ ! Initial revision 001 ! ! ! ! Authors: ! -------- ! @author Dominik Froehlich ! ! ! Description: ! ------------ !> Module for the calculation of the dynamic thermal index iPT !> !> @todo !> !> @note nothing now !> !> @bug no known bugs by now !------------------------------------------------------------------------------! MODULE biometeorology_ipt_mod ! !-- Load required variables from existing modules USE kinds !< to set precision of INTEGER and REAL arrays according to PALM USE biometeorology_pt_mod, & ONLY: saturation_vapor_pressure, deltapmv, dpmv_adj, dpmv_cold, & calc_sultr, pt_regression, ireq_neutral, iso_ridder IMPLICIT NONE PRIVATE !-- No private interfaces --! PUBLIC ipt_cycle, ipt_init !-- PALM interfaces: INTERFACE ipt_cycle MODULE PROCEDURE ipt_cycle END INTERFACE ipt_cycle INTERFACE ipt_init MODULE PROCEDURE ipt_init END INTERFACE ipt_init CONTAINS !------------------------------------------------------------------------------! ! Description: ! ------------ !> The SUBROUTINE surface area calculates the surface area of the individual !> according to its height (m), weight (kg), and age (y) !------------------------------------------------------------------------------! SUBROUTINE surface_area ( height_cm, weight, age, surf ) IMPLICIT NONE REAL(wp) , INTENT(in) :: weight REAL(wp) , INTENT(in) :: height_cm INTEGER(iwp), INTENT(in) :: age REAL(wp) , INTENT(out) :: surf REAL(wp) :: height height = height_cm * 100._wp !-- According to Gehan-George, for children IF ( age < 19_iwp ) THEN IF ( age < 5_iwp ) THEN surf = 0.02667_wp * height**0.42246_wp * weight**0.51456_wp RETURN END IF surf = 0.03050_wp * height**0.35129_wp * weight**0.54375_wp RETURN END IF !-- DuBois D, DuBois EF: A formula to estimate the approximate surface area if ! height and weight be known. In: Arch. Int. Med.. 17, 1916, S. 863?871. surf = 0.007184_wp * height**0.725_wp * weight**0.425_wp RETURN END SUBROUTINE surface_area !------------------------------------------------------------------------------! ! Description: ! ------------ !> The SUBROUTINE persdat calculates !> - the total internal energy production = metabolic + workload, !> - the total internal energy production for a standardized surface (actlev) !> - the DuBois - area (a_surf [m2]) !> from !> - the persons age (years), !> - weight (kg), !> - height (m), !> - sex (1 = male, 2 = female), !> - work load (W) !> for a sample human. !------------------------------------------------------------------------------! SUBROUTINE persdat ( age, weight, height, sex, work, a_surf, actlev ) ! IMPLICIT NONE REAL(wp), INTENT(in) :: age REAL(wp), INTENT(in) :: weight REAL(wp), INTENT(in) :: height REAL(wp), INTENT(in) :: work INTEGER(iwp), INTENT(in) :: sex REAL(wp), INTENT(out) :: actlev REAL(wp) :: a_surf REAL(wp) :: energieumsatz REAL(wp) :: s REAL(wp) :: factor REAL(wp) :: heightundumsatz CALL surface_area ( height, weight, INT( age ), a_surf ) s = height * 100._wp / ( weight ** ( 1._wp / 3._wp ) ) factor = 1._wp + .004_wp * ( 30._wp - age ) heightundumsatz = 0. IF ( sex == 1_iwp ) THEN heightundumsatz = 3.45_wp * weight ** ( 3._wp / 4._wp ) * ( factor + & .01_wp * ( s - 43.4_wp ) ) ELSE IF ( sex == 2_iwp ) THEN heightundumsatz = 3.19_wp * weight ** ( 3._wp / 4._wp ) * ( factor + & .018_wp * ( s - 42.1_wp ) ) END IF energieumsatz = work + heightundumsatz actlev = energieumsatz / a_surf END SUBROUTINE persdat !------------------------------------------------------------------------------! ! Description: ! ------------ !> SUBROUTINE ipt_init !> initializes the instationary perceived temperature !------------------------------------------------------------------------------! SUBROUTINE ipt_init (age, weight, height, sex, work, actlev, clo, & ta, vp, vau1m, tmrt, pb, dt, storage, t_clothing, & ipt ) IMPLICIT NONE !-- Input parameters REAL(wp), INTENT(in) :: age !< Persons age (years) REAL(wp), INTENT(in) :: weight !< Persons weight (kg) REAL(wp), INTENT(in) :: height !< Persons height (m) REAL(wp), INTENT(in) :: work !< Current workload (W) REAL(wp), INTENT(in) :: ta !< Air Temperature (°C) REAL(wp), INTENT(in) :: vp !< Vapor pressure (hPa) REAL(wp), INTENT(in) :: vau1m !< Wind speed in approx. 1.1m (m/s) REAL(wp), INTENT(in) :: tmrt !< Mean radiant temperature (°C) REAL(wp), INTENT(in) :: pb !< Air pressure (hPa) REAL(wp), INTENT(in) :: dt !< Timestep (s) INTEGER(iwp), INTENT(in) :: sex !< Persons sex (1 = male, 2 = female) !-- Output parameters REAL(wp), INTENT(out) :: actlev REAL(wp), INTENT(out) :: clo REAL(wp), INTENT(out) :: storage REAL(wp), INTENT(out) :: t_clothing REAL(wp), INTENT(out) :: ipt !-- Internal variables REAL(wp), PARAMETER :: eps = 0.0005_wp REAL(wp), PARAMETER :: eta = 0._wp REAL(wp) :: uclo REAL(wp) :: oclo REAL(wp) :: d_pmv REAL(wp) :: svp_tt REAL(wp) :: sult_lim REAL(wp) :: dgtcm REAL(wp) :: dgtcstd REAL(wp) :: clon REAL(wp) :: ireq_minimal REAL(wp) :: clo_fanger REAL(wp) :: pmv_o REAL(wp) :: pmv_u REAL(wp) :: pmva REAL(wp) :: ptc REAL(wp) :: d_std REAL(wp) :: pmvs REAL(wp) :: top REAL(wp) :: a_surf REAL(wp) :: acti INTEGER(iwp) :: nzaehl INTEGER(iwp) :: nerr_kalt INTEGER(iwp) :: nerr LOGICAL :: sultrieness storage = 0._wp CALL persdat ( age, weight, height, sex, work, a_surf, actlev ) !-- Initialise t_clothing = -999.0_wp ipt = -999.0_wp nerr = 0_wp nzaehl = 0_wp sultrieness = .FALSE. !-- Tresholds: clothing insulation (account for model inaccuracies) !-- Summer clothing uclo = 0.44453_wp !-- Winter clothing oclo = 1.76267_wp !-- Decision: firstly calculate for winter or summer clothing IF ( ta <= 10._wp ) THEN !-- First guess: winter clothing insulation: cold stress clo = oclo t_clothing = -999.0_wp ! force initial run CALL fanger_s_acti ( ta, tmrt, vp, vau1m, pb, clo, actlev, work, & t_clothing, storage, dt, pmva ) pmv_o = pmva IF ( pmva > 0._wp ) THEN !-- Case summer clothing insulation: heat load ? clo = uclo t_clothing = -999.0_wp ! force initial run CALL fanger_s_acti ( ta, tmrt, vp, vau1m, pb, clo, actlev, work, & t_clothing, storage, dt, pmva ) pmv_u = pmva IF ( pmva <= 0._wp ) THEN !-- Case: comfort achievable by varying clothing insulation ! between winter and summer set values CALL iso_ridder ( ta, tmrt, vp, vau1m, pb, actlev, eta , uclo, & pmv_u, oclo, pmv_o, eps, pmva, top, nzaehl, clo ) IF ( nzaehl < 0_iwp ) THEN nerr = -1_iwp RETURN ENDIF ELSE IF ( pmva > 0.06_wp ) THEN clo = 0.5_wp t_clothing = -999.0_wp CALL fanger_s_acti ( ta, tmrt, vp, vau1m, pb, clo, actlev, work, & t_clothing, storage, dt, pmva ) ENDIF ELSE IF ( pmva < -0.11_wp ) THEN clo = 1.75_wp t_clothing = -999.0_wp CALL fanger_s_acti ( ta, tmrt, vp, vau1m, pb, clo, actlev, work, & t_clothing, storage, dt, pmva ) ENDIF ELSE !-- First guess: summer clothing insulation: heat load clo = uclo t_clothing = -999.0_wp CALL fanger_s_acti ( ta, tmrt, vp, vau1m, pb, clo, actlev, work, & t_clothing, storage, dt, pmva ) pmv_u = pmva IF ( pmva < 0._wp ) THEN !-- Case winter clothing insulation: cold stress ? clo = oclo t_clothing = -999.0_wp CALL fanger_s_acti ( ta, tmrt, vp, vau1m, pb, clo, actlev, work, & t_clothing, storage, dt, pmva ) pmv_o = pmva IF ( pmva >= 0._wp ) THEN !-- Case: comfort achievable by varying clothing insulation ! between winter and summer set values CALL iso_ridder ( ta, tmrt, vp, vau1m, pb, actlev, eta, uclo, & pmv_u, oclo, pmv_o, eps, pmva, top, nzaehl, clo ) IF ( nzaehl < 0_wp ) THEN nerr = -1_iwp RETURN ENDIF ELSE IF ( pmva < -0.11_wp ) THEN clo = 1.75_wp t_clothing = -999.0_wp CALL fanger_s_acti ( ta, tmrt, vp, vau1m, pb, clo, actlev, work, & t_clothing, storage, dt, pmva ) ENDIF ELSE IF ( pmva > 0.06_wp ) THEN clo = 0.5_wp t_clothing = -999.0_wp CALL fanger_s_acti ( ta, tmrt, vp, vau1m, pb, clo, actlev, work, & t_clothing, storage, dt, pmva ) ENDIF ENDIF !-- Determine perceived temperature by regression equation + adjustments pmvs = pmva CALL pt_regression ( pmva, clo, ipt ) ptc = ipt IF ( clo >= 1.75_wp .AND. pmva <= -0.11_wp ) THEN !-- Adjust for cold conditions according to Gagge 1986 CALL dpmv_cold ( pmva, ta, vau1m, tmrt, nerr_kalt, d_pmv ) IF ( nerr_kalt > 0_iwp ) nerr = -5_iwp pmvs = pmva - d_pmv IF ( pmvs > -0.11_wp ) THEN d_pmv = 0._wp pmvs = -0.11_wp ENDIF CALL pt_regression ( pmvs, clo, ipt ) ENDIF clo_fanger = clo clon = clo IF ( clo > 0.5_wp .AND. ipt <= 8.73_wp ) THEN !-- Required clothing insulation (ireq) is exclusively defined for ! operative temperatures (top) less 10 (C) for a ! reference wind of 0.2 m/s according to 8.73 (C) for 0.1 m/s clon = ireq_neutral ( ipt, ireq_minimal, nerr ) clo = clon ENDIF CALL calc_sultr ( ptc, dgtcm, dgtcstd, sult_lim ) sultrieness = .FALSE. d_std = -99._wp IF ( pmva > 0.06_wp .AND. clo <= 0.5_wp ) THEN !-- Adjust for warm/humid conditions according to Gagge 1986 CALL saturation_vapor_pressure ( ta, svp_tt ) d_pmv = deltapmv ( pmva, ta, vp, svp_tt, tmrt, vau1m, nerr ) pmvs = pmva + d_pmv CALL pt_regression ( pmvs, clo, ipt ) IF ( sult_lim < 99._wp ) THEN IF ( (ipt - ptc) > sult_lim ) sultrieness = .TRUE. ENDIF ENDIF END SUBROUTINE ipt_init !------------------------------------------------------------------------------! ! Description: ! ------------ !> SUBROUTINE ipt_cycle !> Calculates one timestep for the instationary version of perceived !> temperature (iPT, °C) for !> - standard measured/predicted meteorological values and TMRT !> as input; !> - regressions for determination of PT; !> - adjustment to Gagge's PMV* (2-node-model, 1986) as base of PT !> under warm/humid conditions (Icl= 0.50 clo) and under cold !> conditions (Icl= 1.75 clo) !> !------------------------------------------------------------------------------! SUBROUTINE ipt_cycle( ta, vp, vau1m, tmrt, pb, dt, storage, t_clothing, clo, & actlev, work, ipt ) IMPLICIT NONE !-- Type of input of the argument list REAL(wp), INTENT ( IN ) :: ta !< Air temperature (°C) REAL(wp), INTENT ( IN ) :: vp !< Vapor pressure (hPa) REAL(wp), INTENT ( IN ) :: tmrt !< Mean radiant temperature (°C) REAL(wp), INTENT ( IN ) :: vau1m !< Wind speed (m/s) REAL(wp), INTENT ( IN ) :: pb !< Air pressure (hPa) REAL(wp), INTENT ( IN ) :: dt !< Timestep (s) REAL(wp), INTENT ( IN ) :: clo !< Clothing index (no dim) REAL(wp), INTENT ( IN ) :: actlev !< Internal heat production (W) REAL(wp), INTENT ( IN ) :: work !< Mechanical work load (W) !-- In and output parameters REAL(wp), INTENT (INOUT) :: storage !< Heat storage (W) REAL(wp), INTENT (INOUT) :: t_clothing !< Clothig temperature (°C) !-- Type of output of the argument list REAL(wp), INTENT ( OUT ) :: ipt !< Instationary perceived temperature (°C) !-- Type of program variables REAL(wp), PARAMETER :: eps = 0.0005_wp REAL(wp) :: d_pmv REAL(wp) :: pmv_o REAL(wp) :: pmv_s REAL(wp) :: storage_rate REAL(wp) :: ener_in REAL(wp) :: svp_tt REAL(wp) :: sult_lim REAL(wp) :: dgtcm REAL(wp) :: dgtcstd REAL(wp) :: clon REAL(wp) :: ireq_minimal REAL(wp) :: clo_fanger REAL(wp) :: pmv_u REAL(wp) :: half_hour_frac REAL(wp) :: storage_delta REAL(wp) :: storage_delta_max REAL(wp) :: pmva REAL(wp) :: ptc REAL(wp) :: d_std REAL(wp) :: pmvs REAL(wp) :: top INTEGER(iwp) :: nzaehl INTEGER(iwp) :: nerr_kalt INTEGER(iwp) :: nerr LOGICAL :: sultrieness !-- Initialise ipt = -999.0_wp nerr = 0_iwp nzaehl = 0_iwp sultrieness = .FALSE. !-- Determine pmv_adjusted for current conditions CALL fanger_s_acti ( ta, tmrt, vp, vau1m, pb, clo, actlev, work, & t_clothing, storage, dt, pmva ) pmv_s = pmva !< PMV considering storage !-- Determine perceived temperature by regression equation + adjustments CALL pt_regression ( pmva, clo, ipt ) IF ( clo >= 1.75_wp .AND. pmva <= -0.11_wp ) THEN !-- Adjust for cold conditions according to Gagge 1986 CALL dpmv_cold ( pmva, ta, vau1m, tmrt, nerr_kalt, d_pmv ) IF ( nerr_kalt > 0_iwp ) nerr = -5_iwp pmvs = pmva - d_pmv IF ( pmvs > -0.11_wp ) THEN d_pmv = 0._wp pmvs = -0.11_wp ENDIF CALL pt_regression ( pmvs, clo, ipt ) ENDIF !-- Consider sultriness if appropriate ptc = ipt CALL calc_sultr ( ptc, dgtcm, dgtcstd, sult_lim ) sultrieness = .FALSE. d_std = -99._wp IF ( pmva > 0.06_wp .AND. clo <= 0.5_wp ) THEN !-- Adjust for warm/humid conditions according to Gagge 1986 CALL saturation_vapor_pressure ( ta, svp_tt ) d_pmv = deltapmv ( pmva, ta, vp, svp_tt, tmrt, vau1m, nerr ) pmvs = pmva + d_pmv CALL pt_regression ( pmvs, clo, ipt ) IF ( sult_lim < 99._wp ) THEN IF ( (ipt - ptc) > sult_lim ) sultrieness = .TRUE. ENDIF ENDIF END SUBROUTINE ipt_cycle !------------------------------------------------------------------------------! ! Description: ! ------------ !> SUBROUTINE fanger_s calculates the !> actual Predicted Mean Vote (dimensionless) according !> to Fanger corresponding to meteorological (tt,tmrt,pa,vau,pb) !> and individual variables (clo, actlev, eta) considering a storage !> and clothing temperature for a given timestep. !------------------------------------------------------------------------------! SUBROUTINE fanger_s_acti( tt, tmrt, pa, in_vau, pb, in_clo, actlev, & activity, t_cloth, s, dt, pmva ) IMPLICIT NONE !-- Input argument types REAL(wp), INTENT ( IN ) :: tt !< Air temperature (°C) REAL(wp), INTENT ( IN ) :: tmrt !< Mean radiant temperature (°C) REAL(wp), INTENT ( IN ) :: pa !< Vapour pressure (hPa) REAL(wp), INTENT ( IN ) :: pb !< Air pressure (hPa) REAL(wp), INTENT ( IN ) :: in_vau !< Wind speed (m/s) REAL(wp), INTENT ( IN ) :: actlev !< Metabolic + work energy (W/m²) REAL(wp), INTENT ( IN ) :: dt !< Timestep (s) REAL(wp), INTENT ( IN ) :: activity !< Work load (W/m²) REAL(wp), INTENT ( IN ) :: in_clo !< Clothing index (clo) (no dim) !-- Output argument types REAL(wp), INTENT ( OUT ) :: pmva !< actual Predicted Mean Vote (no dim) REAL(wp), INTENT (INOUT) :: s !< storage var. of energy balance (W/m2) REAL(wp), INTENT (INOUT) :: t_cloth !< clothing temperature (°C) !-- Internal variables REAL(wp) :: f_cl REAL(wp) :: heat_convection REAL(wp) :: t_skin_aver REAL(wp) :: bc REAL(wp) :: cc REAL(wp) :: dc REAL(wp) :: ec REAL(wp) :: gc REAL(wp) :: hr REAL(wp) :: clo REAL(wp) :: vau REAL(wp) :: z1 REAL(wp) :: z2 REAL(wp) :: z3 REAL(wp) :: z4 REAL(wp) :: z5 REAL(wp) :: z6 REAL(wp) :: en REAL(wp) :: d_s REAL(wp) :: t_clothing !< Clothing index value (dimensionless) REAL(wp) :: adjustrate REAL(wp) :: adjustrate_cloth REAL(wp), PARAMETER :: time_equil = 7200._wp INTEGER(iwp) :: i INTEGER(iwp) :: niter !< Running index !-- Clo must be > 0. to avoid div. by 0! clo = in_clo IF ( clo < 001._wp ) clo = .001_wp !-- Increase in surface due to clothing f_cl = 1._wp + .15_wp * clo !-- Case of free convection (vau < 0.1 m/s ) not considered vau = in_vau IF ( vau < .1_wp ) THEN vau = .1_wp ENDIF !-- Heat_convection = forced convection heat_convection = 12.1_wp * SQRT ( vau * pb / 1013.25_wp ) !-- Average skin temperature t_skin_aver = 35.7_wp - .0275_wp * activity !-- Calculation of constants for evaluation below bc = .155_wp * clo * 3.96_wp * 10._wp**( -8._wp ) * f_cl cc = f_cl * heat_convection ec = .155_wp * clo dc = ( 1._wp + ec * cc ) / bc gc = ( t_skin_aver + bc * ( tmrt + 273.2_wp )**4._wp + ec * cc * tt ) / bc !-- Calculation of clothing surface temperature (t_clothing) based on ! newton-approximation with air temperature as initial guess niter = dt adjustrate = 1._wp - EXP ( -1._wp * ( 10._wp / time_equil ) * dt ) IF ( adjustrate >= 1._wp ) adjustrate = 1._wp adjustrate_cloth = adjustrate * 30._wp t_clothing = t_cloth IF ( t_cloth <= -998.0_wp ) THEN ! If initial run niter = 3_wp adjustrate = 1._wp adjustrate_cloth = 1._wp t_clothing = tt ENDIF DO i = 1, niter t_clothing = t_clothing - adjustrate_cloth * ( ( t_clothing + & 273.2_wp )**4._wp + t_clothing * & dc - gc ) / ( 4._wp * ( t_clothing + 273.2_wp )**3._wp + dc ) ENDDO !-- Empiric factor for the adaption of the heat ballance equation ! to the psycho-physical scale (Equ. 40 in FANGER) z1 = ( .303_wp * EXP ( -.036_wp * actlev ) + .0275_wp ) !-- Water vapour diffution through the skin z2 = .31_wp * ( 57.3_wp - .07_wp * activity-pa ) !-- Sweat evaporation from the skin surface z3 = .42_wp * ( activity - 58._wp ) !-- Loss of latent heat through respiration z4 = .0017_wp * actlev * ( 58.7_wp - pa ) + .0014_wp * actlev * & ( 34._wp - tt ) !-- Loss of radiational heat z5 = 3.96e-8_wp * f_cl * ( ( t_clothing + 273.2_wp )**4 - ( tmrt + & 273.2_wp )**4 ) !-- Heat loss through forced convection z6 = cc * ( t_clothing - tt ) !-- Write together as energy ballance en = activity - z2 - z3 - z4 - z5 - z6 !-- Manage storage d_s = adjustrate * en + ( 1._wp - adjustrate ) * s !-- Predicted Mean Vote pmva = z1 * d_s !-- Update storage s = d_s t_cloth = t_clothing END SUBROUTINE fanger_s_acti END MODULE biometeorology_ipt_mod