Skip to content

Commit 43a634b

Browse files
committed
feat: Re-enable AnOHM without requiring future forcing data
- Added rolling 24-hour forcing buffers to OHM_STATE and SUEWS_cal_Qs so StorageHeatMethod 3 now diagnoses coefficients from the most recently completed day - Refactored module_phys_anohm to consume buffered shortwave, meteorology, and anthropogenic heat series directly (removed the legacy MetForcingData_grid dependency) - Updated StorageHeatMethod documentation to clarify the trailing-day workflow and avoid confusion about forcing requirements
1 parent d6f25e5 commit 43a634b

7 files changed

Lines changed: 195 additions & 207 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@
170170
- Same functionality, lighter dependencies
171171
- Consolidates all parallel processing to one library
172172

173+
### 16 Nov 2025
174+
- [feature] Re-enabled AnOHM without requiring future forcing data
175+
- Added rolling 24-hour forcing buffers to `OHM_STATE` and `SUEWS_cal_Qs` so StorageHeatMethod 3 now diagnoses coefficients from the most recently completed day
176+
- Refactored `module_phys_anohm` to consume buffered shortwave, meteorology, and anthropogenic heat series directly (removed the legacy `MetForcingData_grid` dependency)
177+
- Updated StorageHeatMethod documentation to clarify the trailing-day workflow and avoid confusion about forcing requirements
178+
173179
### 14 Nov 2025
174180
- [feature] Added `SUEWSSimulation.from_sample_data()` factory method and comprehensive OOP enhancements (#779)
175181
- New factory method for cleaner OOP workflow: `sim = SUEWSSimulation.from_sample_data()`
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"Value","Comments"
22
0,"Uses observed values of ΔQS supplied in meteorological forcing file."
33
1,"ΔQS modelled using the objective hysteresis model (OHM) :cite:`G91` using parameters specified for each surface type."
4-
3,"ΔQS modelled using AnOHM :cite:`S17`. |NotRecmd|"
4+
3,"ΔQS modelled using AnOHM :cite:`S17` with coefficients diagnosed from the most recent completed day of forcing (no future data required). |NotRecmd|"
55
4,"ΔQS modelled using the Element Surface Temperature Method (ESTM) :cite:`O05`. |NotRecmd|"

docs/source/parameterisations-and-sub-models.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ Storage heat flux, ΔQ\ :sub:`S`
9999

100100
- **OHM** (Objective Hysteresis Model) :cite:`G91,GO99,GO02`. Storage heat heat flux is calculated using empirically-fitted relations with net all-wave radiation and the rate of change in net all-wave radiation.
101101
- **AnOHM** (Analytical Objective Hysteresis Model) :cite:`S17`. OHM approach using analytically-derived coefficients. |NotRecmd|
102+
- Coefficients are now updated using the most recently completed 24 h of observed forcing, so coupling no longer requires access to future meteorological data.
102103
- **ESTM** (Element Surface Temperature Method) :cite:`O05`. Heat transfer through urban facets (roof, wall, road, interior) is calculated from surface temperature measurements and knowledge of material properties. |NotRecmd|
103104

104105
#. Alternatively, 'observed' storage heat flux can be supplied with the meteorological forcing data.

src/suews/src/suews_ctrl_driver.f95

Lines changed: 118 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@ MODULE SUEWS_Driver
2424
ROUGHNESS_STATE, solar_State, atm_state, flag_STATE, &
2525
SUEWS_STATE, SUEWS_DEBUG, STEBBS_STATE, BUILDING_ARCHETYPE_PRM, STEBBS_PRM, STEBBS_BLDG, &
2626
SUEWS_STATE_BLOCK, &
27-
output_line, output_block
27+
output_line, output_block, &
28+
ANOHM_MAX_SAMPLES, ANOHM_MIN_VALID_SAMPLES
2829
USE meteo, ONLY: qsatf, RH2qa, qa2RH
2930
USE module_phys_atmmoiststab, ONLY: cal_AtmMoist, cal_Stab, stab_psi_heat, stab_psi_mom, SUEWS_update_atmState
3031
USE module_phys_narp, ONLY: NARP_cal_SunPosition, NARP_cal_SunPosition_DTS
3132
USE module_phys_atmmoiststab, ONLY: cal_AtmMoist, cal_Stab, stab_psi_heat, stab_psi_mom
3233
USE module_phys_narp, ONLY: NARP_cal_SunPosition
3334
USE module_phys_spartacus, ONLY: SPARTACUS
34-
! USE AnOHM_module, ONLY: AnOHM
35+
USE module_phys_anohm, ONLY: AnOHM
3536
USE module_phys_resist, ONLY: AerodynamicResistance, BoundaryLayerResistance, SurfaceResistance, &
3637
SUEWS_cal_RoughnessParameters
3738
USE module_phys_ohm, ONLY: OHM
@@ -1496,13 +1497,15 @@ SUBROUTINE SUEWS_cal_Qs( &
14961497
REAL(KIND(1D0)), DIMENSION(nsurf) :: cpAnOHM ! heat capacity [J m-3 K-1]
14971498
REAL(KIND(1D0)), DIMENSION(nsurf) :: kkAnOHM ! thermal conductivity [W m-1 K-1]
14981499
REAL(KIND(1D0)), DIMENSION(nsurf) :: chAnOHM ! bulk transfer coef [J m-3 K-1]
1500+
REAL(KIND(1D0)), DIMENSION(nsurf) :: moist_surf ! normalised soil moisture state [-]
14991501

15001502
REAL(KIND(1D0)), DIMENSION(27), INTENT(out) :: dataOutLineESTM !data output from ESTM
15011503
REAL(KIND(1D0)), DIMENSION(ncolumnsDataOutSTEBBS - 5), INTENT(out) :: dataOutLineSTEBBS !data output from STEBBS
15021504

15031505
! internal use arrays
15041506
REAL(KIND(1D0)) :: Tair_mav_5d ! Tair_mav_5d=HDD(id-1,4) HDD at the begining of today (id-1)
15051507
REAL(KIND(1D0)) :: qn_use ! qn used in OHM calculations [W m-2]
1508+
INTEGER :: is
15061509

15071510
ASSOCIATE ( &
15081511
atmState => modState%atmState, &
@@ -1819,7 +1822,8 @@ SUBROUTINE SUEWS_cal_Qs( &
18191822
IF (StorageHeatMethod == 0) THEN !Use observed QS
18201823
qs = qs_obs
18211824

1822-
ELSEIF (StorageHeatMethod == 1 .OR. StorageHeatMethod == 6 .OR. StorageHeatMethod == 7) THEN !Use OHM to calculate QS
1825+
ELSEIF (StorageHeatMethod == 1 .OR. StorageHeatMethod == 6 .OR. StorageHeatMethod == 7 .OR. &
1826+
(StorageHeatMethod == 3 .AND. .NOT. ohmState%anohm_coeff_ready)) THEN !Use OHM to calculate QS or fallback for ANOHM spin-up
18231827
Tair_mav_5d = HDD_id(10)
18241828
IF (Diagnose == 1) WRITE (*, *) 'Calling OHM...'
18251829
CALL OHM(qn_use, ohmState%qn_av, ohmState%dqndt, &
@@ -1853,27 +1857,34 @@ SUBROUTINE SUEWS_cal_Qs( &
18531857
QS_roof = qs
18541858
QS_wall = qs
18551859

1856-
! use AnOHM to calculate QS, TS 14 Mar 2016
1857-
! disable AnOHM, TS 20 Jul 2023
1858-
! ELSEIF (StorageHeatMethod == 3) THEN !
1859-
! IF (Diagnose == 1) WRITE (*, *) 'Calling AnOHM...'
1860-
! ! CALL AnOHM(qn1_use,qn1_store_grid,qn1_av_store_grid,qf,&
1861-
! ! MetForcingData_grid,state_id/StoreDrainPrm(6,:),&
1862-
! ! alb, emis, cpAnOHM, kkAnOHM, chAnOHM,&
1863-
! ! sfr_surf,nsurf,nsh,EmissionsMethod,id,Gridiv,&
1864-
! ! a1,a2,a3,qs,deltaQi)
1865-
! moist_surf = state_id/StoreDrainPrm(6, :)
1866-
! ! CALL AnOHM( &
1867-
! ! tstep, dt_since_start, &
1868-
! ! qn_use, qn_av_prev, dqndt_prev, qf, &
1869-
! ! MetForcingData_grid, moist_surf, &
1870-
! ! alb, emis, cpAnOHM, kkAnOHM, chAnOHM, & ! input
1871-
! ! sfr_surf, nsurf, EmissionsMethod, id, Gridiv, &
1872-
! ! qn_av_next, dqndt_next, &
1873-
! ! a1, a2, a3, qs, deltaQi) ! output
1874-
! QS_surf = qs
1875-
! QS_roof = qs
1876-
! QS_wall = qs
1860+
ELSEIF (StorageHeatMethod == 3) THEN
1861+
DO is = 1, nsurf
1862+
IF (StoreDrainPrm(6, is) > 0.0D0) THEN
1863+
moist_surf(is) = MAX(0.0D0, MIN(1.0D0, state_id(is)/StoreDrainPrm(6, is)))
1864+
ELSE
1865+
moist_surf(is) = 0.0D0
1866+
END IF
1867+
END DO
1868+
1869+
IF (Diagnose == 1) WRITE (*, *) 'Calling AnOHM...'
1870+
CALL AnOHM( &
1871+
tstep, dt_since_start, &
1872+
qn_use, ohmState%qn_av, ohmState%dqndt, &
1873+
ohmState%anohm_coeff_sd(1:ohmState%anohm_coeff_count), &
1874+
ohmState%anohm_coeff_ta(1:ohmState%anohm_coeff_count), &
1875+
ohmState%anohm_coeff_rh(1:ohmState%anohm_coeff_count), &
1876+
ohmState%anohm_coeff_pres(1:ohmState%anohm_coeff_count), &
1877+
ohmState%anohm_coeff_ws(1:ohmState%anohm_coeff_count), &
1878+
ohmState%anohm_coeff_ah(1:ohmState%anohm_coeff_count), &
1879+
ohmState%anohm_coeff_tHr(1:ohmState%anohm_coeff_count), &
1880+
moist_surf, &
1881+
alb, emis, cpAnOHM, kkAnOHM, chAnOHM, &
1882+
sfr_surf, nsurf, ohmState%anohm_coeff_day, Gridiv, &
1883+
ohmState%qn_av, ohmState%dqndt, &
1884+
a1, a2, a3, qs, deltaQi)
1885+
QS_surf = qs
1886+
QS_roof = qs
1887+
QS_wall = qs
18771888

18781889
! !Calculate QS using ESTM
18791890
ELSEIF (StorageHeatMethod == 4 .OR. StorageHeatMethod == 14) THEN
@@ -1942,6 +1953,85 @@ SUBROUTINE SUEWS_cal_Qs( &
19421953
END SUBROUTINE SUEWS_cal_Qs
19431954
!=======================================================================
19441955

1956+
SUBROUTINE anohm_rollover_if_needed(timer, ohmState)
1957+
TYPE(SUEWS_TIMER), INTENT(IN) :: timer
1958+
TYPE(OHM_STATE), INTENT(INOUT) :: ohmState
1959+
1960+
IF (ohmState%anohm_working_day == -999) THEN
1961+
ohmState%anohm_working_day = timer%id
1962+
END IF
1963+
1964+
IF (timer%id /= ohmState%anohm_working_day) THEN
1965+
IF (ohmState%anohm_working_count > 0) THEN
1966+
ohmState%anohm_coeff_tHr = ohmState%anohm_working_tHr
1967+
ohmState%anohm_coeff_sd = ohmState%anohm_working_sd
1968+
ohmState%anohm_coeff_ta = ohmState%anohm_working_ta
1969+
ohmState%anohm_coeff_rh = ohmState%anohm_working_rh
1970+
ohmState%anohm_coeff_pres = ohmState%anohm_working_pres
1971+
ohmState%anohm_coeff_ws = ohmState%anohm_working_ws
1972+
ohmState%anohm_coeff_ah = ohmState%anohm_working_ah
1973+
ohmState%anohm_coeff_day = ohmState%anohm_working_day
1974+
ohmState%anohm_coeff_count = MIN(ohmState%anohm_working_count, ANOHM_MAX_SAMPLES)
1975+
ohmState%anohm_coeff_ready = (ohmState%anohm_coeff_count >= ANOHM_MIN_VALID_SAMPLES)
1976+
ELSE
1977+
ohmState%anohm_coeff_ready = .FALSE.
1978+
ohmState%anohm_coeff_count = 0
1979+
ohmState%anohm_coeff_day = ohmState%anohm_working_day
1980+
ohmState%anohm_coeff_tHr = -999.0D0
1981+
ohmState%anohm_coeff_sd = -999.0D0
1982+
ohmState%anohm_coeff_ta = -999.0D0
1983+
ohmState%anohm_coeff_rh = -999.0D0
1984+
ohmState%anohm_coeff_pres = -999.0D0
1985+
ohmState%anohm_coeff_ws = -999.0D0
1986+
ohmState%anohm_coeff_ah = -999.0D0
1987+
END IF
1988+
1989+
ohmState%anohm_working_day = timer%id
1990+
ohmState%anohm_working_count = 0
1991+
ohmState%anohm_working_tHr = -999.0D0
1992+
ohmState%anohm_working_sd = -999.0D0
1993+
ohmState%anohm_working_ta = -999.0D0
1994+
ohmState%anohm_working_rh = -999.0D0
1995+
ohmState%anohm_working_pres = -999.0D0
1996+
ohmState%anohm_working_ws = -999.0D0
1997+
ohmState%anohm_working_ah = -999.0D0
1998+
END IF
1999+
2000+
END SUBROUTINE anohm_rollover_if_needed
2001+
2002+
SUBROUTINE anohm_append_sample(timer, forcing, qf, ohmState)
2003+
TYPE(SUEWS_TIMER), INTENT(IN) :: timer
2004+
TYPE(SUEWS_FORCING), INTENT(IN) :: forcing
2005+
REAL(KIND(1D0)), INTENT(IN) :: qf
2006+
TYPE(OHM_STATE), INTENT(INOUT) :: ohmState
2007+
2008+
INTEGER :: idx
2009+
2010+
IF (ohmState%anohm_working_day == -999) THEN
2011+
ohmState%anohm_working_day = timer%id
2012+
END IF
2013+
2014+
IF (timer%id /= ohmState%anohm_working_day) THEN
2015+
CALL anohm_rollover_if_needed(timer, ohmState)
2016+
END IF
2017+
2018+
IF (timer%imin /= 0 .OR. timer%isec /= 0) RETURN
2019+
2020+
idx = MIN(timer%it + 1, ANOHM_MAX_SAMPLES)
2021+
ohmState%anohm_working_tHr(idx) = REAL(timer%it, KIND(1D0))
2022+
ohmState%anohm_working_sd(idx) = forcing%kdown
2023+
ohmState%anohm_working_ta(idx) = forcing%temp_c
2024+
ohmState%anohm_working_rh(idx) = forcing%RH
2025+
ohmState%anohm_working_pres(idx) = forcing%pres
2026+
ohmState%anohm_working_ws(idx) = forcing%U
2027+
ohmState%anohm_working_ah(idx) = qf
2028+
2029+
IF (idx > ohmState%anohm_working_count) THEN
2030+
ohmState%anohm_working_count = idx
2031+
END IF
2032+
2033+
END SUBROUTINE anohm_append_sample
2034+
19452035
!==========================drainage and runoff================================
19462036
SUBROUTINE SUEWS_cal_Water( &
19472037
timer, config, forcing, siteInfo, & ! input
@@ -2005,6 +2095,10 @@ SUBROUTINE SUEWS_cal_Water( &
20052095

20062096
state_id = hydroState%state_surf
20072097
StoreDrainPrm = phenState%StoreDrainPrm
2098+
IF (config%StorageHeatMethod == 3) THEN
2099+
CALL anohm_rollover_if_needed(timer, modState%ohmState)
2100+
CALL anohm_append_sample(timer, forcing, modState%heatState%qf, modState%ohmState)
2101+
END IF
20082102

20092103
! sfr_surf = [pavedPrm%sfr, bldgPrm%sfr, evetrPrm%sfr, dectrPrm%sfr, grassPrm%sfr, bsoilPrm%sfr, waterPrm%sfr]
20102104
WaterDist(1, 1) = pavedPrm%waterdist%to_paved

src/suews/src/suews_ctrl_type.f95

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ MODULE module_ctrl_type
5151
solar_State, atm_state
5252

5353
IMPLICIT NONE
54+
INTEGER, PARAMETER, PUBLIC :: ANOHM_MAX_SAMPLES = 48
55+
INTEGER, PARAMETER, PUBLIC :: ANOHM_MIN_VALID_SAMPLES = 6
5456
! in the following, the type definitions starting with `SUEWS_` are used in the main program
5557

5658
! ********** SUEWS_parameters schema (basic) **********

0 commit comments

Comments
 (0)