Skip to content

Commit 7ea97e7

Browse files
authored
feat: add ground temperature conversion (#20)
* feat: add ground temperature conversion * docs: record ground temperature pr number
1 parent 7ff383a commit 7ea97e7

9 files changed

Lines changed: 393 additions & 5 deletions

File tree

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# destep 0.0.0.9000
22

3+
- Added `GROUND_DATA` conversion to
4+
`Site:GroundTemperature:BuildingSurface` using monthly averages of the
5+
selected hourly ground-temperature series (#20).
36
- Added occupant outdoor-air conversion from `OCCUPANT_GAINS.MIN_REQUIRE_FRESH_AIR`
47
to `DesignSpecification:OutdoorAir`, with IdealLoads systems referencing the
58
converted outdoor-air objects (#19).

R/conv-ground-temperature.R

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# GROUND_DATA -> Site:GroundTemperature:BuildingSurface.
2+
# EnergyPlus uses this object for building surfaces whose outside boundary
3+
# condition is Ground, so DeST's hourly user-defined ground temperatures are
4+
# reduced to the 12 monthly values required by the IDD.
5+
destep_conv_ground_temperature <- function(dest, ep) {
6+
if (!destep_has_rows(dest, "GROUND_DATA")) return(NULL)
7+
8+
ground <- destep_ground_temperature_table(dest)
9+
monthly <- destep_monthly_ground_temperature(ground)
10+
11+
out <- destep_add(
12+
dest, ep,
13+
"Site:GroundTemperature:BuildingSurface" :=
14+
destep_ground_temperature_value(monthly)
15+
)
16+
attr(out, "table") <- monthly
17+
18+
out
19+
}
20+
21+
# Select one GROUND_DATA series and return the validated hourly table used by
22+
# the converter. The selection mirrors the DeST library path when available
23+
# and keeps the unique-ID fallback explicit for models like the current fixture.
24+
destep_ground_temperature_table <- function(dest) {
25+
ground_id <- destep_select_ground_data_id(dest)
26+
if (is.null(ground_id)) return(data.table::data.table())
27+
28+
ground <- DBI::dbGetQuery(
29+
dest,
30+
sprintf(
31+
"
32+
SELECT
33+
ID,
34+
HOUR,
35+
T
36+
FROM GROUND_DATA
37+
WHERE ID = %s
38+
ORDER BY HOUR
39+
",
40+
DBI::dbQuoteLiteral(dest, ground_id)
41+
)
42+
)
43+
data.table::setDT(ground)
44+
destep_force_numeric(ground, c("ID", "HOUR", "T"))
45+
destep_validate_ground_temperature_table(ground, ground_id)
46+
47+
ground
48+
}
49+
50+
# Resolve the ground-temperature data set from ENVIRONMENT/SYS_CITY first.
51+
# If that bridge is absent or points outside GROUND_DATA, a single available
52+
# GROUND_DATA.ID is safe; multiple IDs need a deliberate selection rule.
53+
destep_select_ground_data_id <- function(dest) {
54+
resolved <- destep_resolve_city_ground_data_ids(dest)
55+
if (length(resolved) == 1L) return(resolved[[1L]])
56+
if (length(resolved) > 1L) {
57+
stop(sprintf(
58+
"Multiple GROUND_DATA IDs are referenced by ENVIRONMENT/SYS_CITY: %s",
59+
paste(resolved, collapse = ", ")
60+
), call. = FALSE)
61+
}
62+
63+
ids <- DBI::dbGetQuery(
64+
dest,
65+
"SELECT DISTINCT ID FROM GROUND_DATA ORDER BY ID"
66+
)$ID
67+
ids <- ids[!is.na(ids)]
68+
if (length(ids) == 0L) return(NULL)
69+
if (length(ids) == 1L) return(ids[[1L]])
70+
71+
stop(sprintf(
72+
paste(
73+
"Cannot choose GROUND_DATA ID;",
74+
"multiple IDs are present and ENVIRONMENT/SYS_CITY did not select one: %s"
75+
),
76+
paste(ids, collapse = ", ")
77+
), call. = FALSE)
78+
}
79+
80+
# Keep the ENVIRONMENT/SYS_CITY bridge optional because ad hoc fixtures and some
81+
# DeST exports may carry GROUND_DATA without a resolvable city-library row.
82+
destep_resolve_city_ground_data_ids <- function(dest) {
83+
if (!all(c("ENVIRONMENT", "SYS_CITY", "GROUND_DATA") %in% DBI::dbListTables(dest))) {
84+
return(numeric())
85+
}
86+
if (!destep_table_has_fields(dest, "ENVIRONMENT", "CITY_ID") ||
87+
!destep_table_has_fields(dest, "SYS_CITY", c("CITY_ID", "GROUND_ID"))) {
88+
return(numeric())
89+
}
90+
91+
ids <- DBI::dbGetQuery(
92+
dest,
93+
"
94+
SELECT DISTINCT C.GROUND_ID AS ID
95+
FROM ENVIRONMENT E
96+
INNER JOIN SYS_CITY C
97+
ON E.CITY_ID = C.CITY_ID
98+
INNER JOIN GROUND_DATA G
99+
ON C.GROUND_ID = G.ID
100+
WHERE C.GROUND_ID IS NOT NULL
101+
ORDER BY C.GROUND_ID
102+
"
103+
)$ID
104+
105+
ids[!is.na(ids)]
106+
}
107+
108+
# Check a table's columns before running optional bridge SQL. This avoids
109+
# turning small unit-test fixtures into schema-completeness tests.
110+
destep_table_has_fields <- function(dest, table, fields) {
111+
all(fields %in% DBI::dbListFields(dest, table))
112+
}
113+
114+
# A BuildingSurface ground-temperature object has no room for gaps or duplicate
115+
# hours, so the selected DeST series must be exactly one non-leap 8760-hour year.
116+
destep_validate_ground_temperature_table <- function(ground, ground_id) {
117+
issues <- character()
118+
hour <- ground$HOUR
119+
120+
if (nrow(ground) != 8760L) {
121+
issues <- c(issues, sprintf(
122+
"expected 8760 rows but found %i",
123+
nrow(ground)
124+
))
125+
}
126+
if (anyNA(hour)) {
127+
issues <- c(issues, "HOUR contains missing values")
128+
} else {
129+
missing_hours <- setdiff(0:8759, hour)
130+
duplicate_hours <- unique(hour[duplicated(hour)])
131+
unexpected_hours <- setdiff(hour, 0:8759)
132+
133+
if (length(missing_hours)) {
134+
issues <- c(issues, sprintf(
135+
"missing HOUR value(s): %s",
136+
destep_format_integer_sample(missing_hours)
137+
))
138+
}
139+
if (length(duplicate_hours)) {
140+
issues <- c(issues, sprintf(
141+
"duplicate HOUR value(s): %s",
142+
destep_format_integer_sample(duplicate_hours)
143+
))
144+
}
145+
if (length(unexpected_hours)) {
146+
issues <- c(issues, sprintf(
147+
"unexpected HOUR value(s): %s",
148+
destep_format_integer_sample(unexpected_hours)
149+
))
150+
}
151+
}
152+
if (anyNA(ground$T)) {
153+
issues <- c(issues, "T contains missing values")
154+
}
155+
156+
if (length(issues)) {
157+
stop(sprintf(
158+
"Invalid GROUND_DATA series for ID %s: %s",
159+
ground_id,
160+
paste(issues, collapse = "; ")
161+
), call. = FALSE)
162+
}
163+
164+
invisible(ground)
165+
}
166+
167+
# Show enough hour IDs for a useful error while keeping long validation messages
168+
# readable.
169+
destep_format_integer_sample <- function(x, n = 10L) {
170+
x <- sort(unique(as.integer(x)))
171+
out <- paste(utils::head(x, n), collapse = ", ")
172+
if (length(x) > n) out <- paste0(out, ", ...")
173+
out
174+
}
175+
176+
# Aggregate the validated hourly series using the standard non-leap calendar
177+
# implied by DeST's HOUR = 0:8759 convention.
178+
destep_monthly_ground_temperature <- function(ground) {
179+
month_hours <- c(31L, 28L, 31L, 30L, 31L, 30L, 31L, 31L, 30L, 31L, 30L, 31L) * 24L
180+
month <- rep(seq_along(month_hours), month_hours)
181+
182+
monthly <- data.table::data.table(
183+
GROUND_DATA_ID = unique(ground$ID),
184+
MONTH = seq_along(month_hours),
185+
GROUND_TEMPERATURE = as.numeric(tapply(ground$T, month, mean))
186+
)
187+
monthly
188+
}
189+
190+
# Build the exact EnergyPlus field list for Site:GroundTemperature:BuildingSurface.
191+
destep_ground_temperature_value <- function(monthly) {
192+
values <- as.list(monthly$GROUND_TEMPERATURE)
193+
names(values) <- paste0(
194+
tolower(month.name),
195+
"_ground_temperature"
196+
)
197+
values
198+
}

R/conv.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ to_eplus <- function(dest, ver = "latest", copy = TRUE, verbose = FALSE) {
160160
# TODO: is it possible to have multiple locations in tmpdb?
161161
conv <- list(
162162
location = destep_conv_location(tmpdb, ep),
163+
ground_temperature = destep_conv_ground_temperature(tmpdb, ep),
163164
building = destep_conv_building(tmpdb, ep),
164165
zone = destep_conv_zone(tmpdb, ep),
165166
surface = destep_conv_surface(tmpdb, ep),

README.Rmd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ This package is still under heavy development and is not ready for use. Currentl
4747
* [x] Ideal loads zone equipment from `ROOM_GROUP`
4848
* [x] Internal gains from `OCCUPANT_GAINS`, `LIGHT_GAINS`, and `EQUIPMENT_GAINS`
4949
* [x] Outdoor air requirements from `OCCUPANT_GAINS`
50+
* [x] Ground temperatures from `GROUND_DATA`
5051
* [ ] Shading
5152
* [ ] HVAC
5253

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Currently, the following components are supported:
3838
- [x] Internal gains from `OCCUPANT_GAINS`, `LIGHT_GAINS`, and
3939
`EQUIPMENT_GAINS`
4040
- [x] Outdoor air requirements from `OCCUPANT_GAINS`
41+
- [x] Ground temperatures from `GROUND_DATA`
4142
- [ ] Shading
4243
- [ ] HVAC
4344

@@ -74,7 +75,8 @@ read_dest(path) |> to_eplus(23.1)
7475
#> └─ [001<O>] Class: <Building>
7576
#>
7677
#> Group: <Location and Climate>
77-
#> └─ [001<O>] Class: <Site:Location>
78+
#> ├─ [001<O>] Class: <Site:Location>
79+
#> └─ [001<O>] Class: <Site:GroundTemperature:BuildingSurface>
7880
#>
7981
#> Group: <Schedules>
8082
#> ├─ [003<O>] Class: <ScheduleTypeLimits>

inst/schema/fields.tsv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -523,8 +523,8 @@ CLIMATE_DATA WS 9 "" Wind speed in m/s. ""
523523
CLIMATE_DATA WD 10 "" Wind direction code: 0 C, 1 N, 2 NNE, 3 NE, 4 ENE, 5 E, ..., 9 S, ..., 13 W, ..., 16 NNW. ""
524524
CLIMATE_DATA B 11 "" Atmospheric pressure in Pa. ""
525525
GROUND_DATA ID 1 primary_key ID ""
526-
GROUND_DATA HOUR 2 "" Hour index from 0 to 8759. ""
527-
GROUND_DATA T 3 "" User-defined hourly ground temperature in degrees C. ""
526+
GROUND_DATA HOUR 2 "" Hour index from 0 to 8759. Used with a non-leap year calendar to aggregate the selected 8760-hour series to 12 monthly BuildingSurface ground temperatures.
527+
GROUND_DATA T 3 "" User-defined hourly ground temperature in degrees C. Converted by monthly averaging to Site:GroundTemperature:BuildingSurface fields.
528528
AC_SYS AC_SYS_ID 1 primary_key ID ""
529529
AC_SYS NAME 2 "" Name. ""
530530
AC_SYS OF_STOREY 3 "" Owning storey. ""

inst/schema/tables.tsv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ OCCUPANT_GAINS loads converted GAIN_ID destep_conv_people; destep_conv_design_sp
4545
LIGHT_GAINS loads converted GAIN_ID destep_conv_lights "" Lights "" "" Lighting gain record with room, schedule, maximum/minimum nameplate power, heat/electric ratio, calculation basis, distribution mode, storey, and drawing-position fields. ""
4646
EQUIPMENT_GAINS loads converted GAIN_ID destep_conv_electric_equipment "" ElectricEquipment "" "" Equipment gain record with room, schedule, maximum/minimum heat-gain power, maximum/minimum moisture generation, calculation basis, distribution mode, storey, and drawing-position fields. ""
4747
CLIMATE_DATA weather deferred ID "" "" "" "" "" Hourly climate data record with hour, dry-bulb temperature, humidity ratio, horizontal radiation, ground temperature, sky temperature, wind speed, wind direction, and coefficient fields. Likely future source for EPW generation or weather validation.
48-
GROUND_DATA weather deferred ID "" "" "" "" "" Hourly ground-temperature data record with hour and temperature fields. Likely future source for Site:GroundTemperature objects or EPW support.
48+
GROUND_DATA weather converted ID destep_conv_ground_temperature "" Site:GroundTemperature:BuildingSurface "" "" Hourly ground-temperature data record with hour and temperature fields. Selected through SYS_CITY.GROUND_ID when available, with a unique GROUND_DATA.ID fallback; T is aggregated to monthly BuildingSurface ground temperatures.
4949
AC_SYS hvac deferred AC_SYS_ID "" "" "" "" "" Air-conditioning system record with building and storey ownership, supply-temperature schedules, water and system type codes, outdoor-air controls, user-defined supply state, result-view, drawing-position, and extended property fields. ""
5050
AHU hvac deferred AHU_ID "" "" "" "" "" Air-handling unit record with owning system, type, coil, sprayer, heater, humidifier, reheat, heat recovery, exchange coefficient, secondary return air, fan, resistance model, and extended property fields. ""
5151
VRV_SOURCE hvac deferred ID "" "" "" "" "" VRV outdoor/source equipment record with name, library type reference, drawing-position, and extended property fields. ""

0 commit comments

Comments
 (0)