Skip to content

Commit 3c6da16

Browse files
committed
Fix error in distribution of measures
Keep original behaviour of run_rabimo_with_measures() by setting new arg "old_version" to TRUE
1 parent d48884b commit 3c6da16

7 files changed

Lines changed: 449 additions & 124 deletions

R/apply_measures_to_blocks.R

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# @param measures list with elements green_roof, unpaved, to_swale representing
2+
# the target percentages of the total areas corresponding to each measure.
3+
# A value of NA means that the corresponding measure column is not touched.
4+
apply_measures_to_blocks <- function(blocks, measures, dbg = FALSE, check = FALSE)
5+
{
6+
#dbg = FALSE; check = FALSE
7+
8+
# Define helper functions
9+
{
10+
report_problem <- function(...) {
11+
problem <- paste(unlist(list(...)), collapse = "")
12+
warning(problem, call. = FALSE)
13+
}
14+
15+
debug <- function(...) {
16+
if (dbg) {
17+
writeLines(...)
18+
}
19+
}
20+
21+
share_of_sum <- function(x) {
22+
# Do not divide by zero, return vector of zeros instead.
23+
if ((s <- sum(x)) == 0) {
24+
rep(0, length(x))
25+
} else {
26+
x/s
27+
}
28+
}
29+
30+
check_if_target_was_reached <- function(blocks, measure) {
31+
obtained <- kwb.rabimo::get_measure_stats(blocks)[[measure]]$mean
32+
target <- measures[[measure]]
33+
if (!isTRUE(all.equal(obtained, target))) {
34+
warning(
35+
sprintf("Target value %0.2f for '%s' ", target, measure),
36+
sprintf("could not be achieved. Actual value: %0.2f", obtained),
37+
call. = FALSE
38+
)
39+
}
40+
}
41+
42+
check_for_negative_values <- function(blocks, measure) {
43+
is_negative <- blocks[[measure]] < 0
44+
if (any(is_negative)) {
45+
warning(call. = FALSE, sprintf(
46+
"There are %d negative values in column '%s'",
47+
sum(is_negative), measure
48+
))
49+
}
50+
}
51+
}
52+
53+
# The prefix "a_" refers to absolute area (in square metres)
54+
55+
# Provide the total areas and roof areas in advance. They are not changed by
56+
# the measures.
57+
a_total <- blocks$total_area
58+
a_roof <- blocks$total_area * blocks$roof
59+
60+
# 1. Handle measure "green roof"
61+
if (!is.na(measures$green_roof)) {
62+
63+
a_green_roof <- a_roof * blocks$green_roof
64+
65+
# Total green roof area to add (if value >= 0) or to remove (if value < 0)
66+
a_green_roof_change <- sum(measures$green_roof * a_total) - sum(a_green_roof)
67+
68+
# Roof area that can be converted to green roof area
69+
if (a_green_roof_change >= 0) {
70+
71+
# increase green roof area
72+
a_green_roof_potential <- a_roof - a_green_roof
73+
74+
if (a_green_roof_change > sum(a_green_roof_potential)) {
75+
report_problem(sprintf(
76+
"Not enough (non-green) roof area available (%0.2f m2 missing)",
77+
a_green_roof_change - sum(a_green_roof_potential)
78+
))
79+
}
80+
81+
} else {
82+
83+
# decrease green roof area
84+
a_green_roof_potential <- a_green_roof
85+
86+
if (- a_green_roof_change > sum(a_green_roof_potential)) {
87+
report_problem(sprintf(
88+
"Not enough green roof area available (%0.2f m2 missing)",
89+
- a_green_roof_change - sum(a_green_roof_potential)
90+
))
91+
}
92+
}
93+
94+
# Distribute change in green roof area to the different blocks
95+
a_green_roof_new <- a_green_roof + share_of_sum(a_green_roof_potential) * a_green_roof_change
96+
97+
# Update column "green_roof" (as fraction of roof area)
98+
blocks$green_roof <- ifelse(a_roof == 0, 0, a_green_roof_new / a_roof)
99+
}
100+
101+
# 2. Handle measure "Unsealing"
102+
if (!is.na(measures$unpaved)) {
103+
104+
# current paved/unpaved areas
105+
a_paved <- a_total * blocks$pvd
106+
a_unpaved <- a_total - a_roof - a_paved
107+
108+
# Required increase/decrease in unpaved area
109+
a_unpaved_change <- measures$unpaved * sum(a_total) - sum(a_unpaved)
110+
unpave <- a_unpaved_change >= 0
111+
112+
debug(sprintf(
113+
"%s area to be %s: %0.2f m2",
114+
ifelse(unpave, "Paved", "Unpaved"),
115+
ifelse(unpave, "unpaved", "paved"),
116+
abs(a_unpaved_change)
117+
))
118+
119+
if (unpave) {
120+
121+
a_potential <- a_paved
122+
123+
if (a_unpaved_change > sum(a_potential)) {
124+
report_problem(sprintf(
125+
"Not enough paved area available to be unpaved (%0.2f m2 missing)",
126+
a_unpaved_change - sum(a_potential)
127+
))
128+
}
129+
130+
} else {
131+
132+
# actually pave instead of unpave!
133+
a_potential <- a_unpaved
134+
135+
# a_unpaved_change is negative here
136+
if (- a_unpaved_change > sum(a_potential)) {
137+
report_problem(sprintf(
138+
"Not enough unpaved area available to be paved (%0.2f m2 missing)",
139+
- a_unpaved_change - sum(a_potential)
140+
))
141+
}
142+
}
143+
144+
# Distribute change in paved/unpaved area to the different blocks
145+
a_paved_new <- a_paved - share_of_sum(a_potential) * a_unpaved_change
146+
147+
blocks$pvd <- ifelse(a_total == 0, 0, a_paved_new / a_total)
148+
}
149+
150+
# 3. Handle measure "Connection to swales"
151+
if (!is.na(measures$to_swale)) {
152+
a_sealed <- a_roof + a_total * blocks$pvd
153+
a_to_swale <- blocks$to_swale * a_sealed
154+
a_to_swale_change <- sum(measures$to_swale * a_total) - sum(a_to_swale)
155+
156+
increase <- a_to_swale_change >= 0
157+
158+
debug(sprintf(
159+
"Sealed area to be %s swales: %0.2f m2",
160+
ifelse(increase, "connected to", "disconnected from"),
161+
abs(a_to_swale_change)
162+
))
163+
164+
if (increase) {
165+
166+
a_to_swale_potential <- a_sealed - a_to_swale
167+
168+
if (a_to_swale_change > sum(a_to_swale_potential)) {
169+
report_problem(sprintf(
170+
"Not enough sealed area available to be connected to swales (%0.2f m2 missing)",
171+
a_to_swale_change - sum(a_to_swale_potential)
172+
))
173+
}
174+
175+
} else {
176+
177+
# a_to_swale_change is negative here
178+
a_to_swale_potential <- a_to_swale
179+
180+
if (- a_to_swale_change > sum(a_to_swale_potential)) {
181+
report_problem(sprintf(
182+
"Not enough swale-connected sealed area available to be disconnected (%0.2f m2 missing)",
183+
abs(a_to_swale_change) - sum(a_to_swale_potential)
184+
))
185+
}
186+
}
187+
188+
# distribute
189+
a_to_swale_new <- a_to_swale + share_of_sum(a_to_swale_potential) * a_to_swale_change
190+
191+
# Update column "to_swale"
192+
blocks$to_swale <- ifelse(a_sealed == 0, 0, a_to_swale_new / a_sealed)
193+
}
194+
195+
# Targets reached?
196+
if (check) {
197+
for (measure in names(measures)[!is.na(measures)]) {
198+
check_if_target_was_reached(blocks, measure)
199+
check_for_negative_values(blocks, measure)
200+
}
201+
}
202+
203+
blocks
204+
}

R/run_rabimo_with_measures.R

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,24 @@
77
#' corresponding to each measure
88
#' @param config configuration object, default:
99
#' \code{\link{rabimo_inputs_2020}$config}
10+
#' @param old_version if \code{TRUE} the old, erroneous version of this function
11+
#' is used (not correctly considering the updated pvd value before calculating
12+
#' the new to_swale values). The default is \code{FALSE}.
1013
#' @export
1114
run_rabimo_with_measures <- function(
1215
blocks,
1316
measures,
14-
config = kwb.rabimo::rabimo_inputs_2020$config
17+
config = kwb.rabimo::rabimo_inputs_2020$config,
18+
old_version = FALSE
1519
)
1620
{
1721
#kwb.utils::assignPackageObjects("kwb.rabimo")
18-
rescaled_targets <- rescale_target_values(
19-
new_targets = measures,
20-
blocks = blocks
21-
)
22-
23-
run_rabimo(
24-
distribute_measures(blocks = blocks, targets = rescaled_targets),
25-
config = config
26-
)
22+
23+
new_blocks <- if (old_version) {
24+
distribute_measures(blocks, rescale_target_values(measures, blocks))
25+
} else {
26+
apply_measures_to_blocks(blocks, measures)
27+
}
28+
29+
run_rabimo(new_blocks, config = config)
2730
}

man/run_rabimo_with_measures.Rd

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#library(testthat)
2+
test_that("apply_measures_to_blocks() works", {
3+
4+
apply_measures_to_blocks <- kwb.rabimo:::apply_measures_to_blocks
5+
6+
test_me <- function(data) {
7+
8+
blocks <- data[sample(seq_len(nrow(data)), 10L), ]
9+
stats <- kwb.rabimo:::get_measure_stats(blocks)
10+
safety_factor <- 0.999
11+
12+
measures_max <- list(
13+
green_roof = safety_factor * stats$green_roof$max,
14+
unpaved_max = safety_factor * stats$unpaved$max,
15+
to_swale = NA
16+
)
17+
18+
measures_too_big_green_roof <- list(
19+
green_roof = measures_max$green_roof + 0.01,
20+
unpaved = measures_max$unpaved,
21+
to_swale = NA
22+
)
23+
24+
measures_too_big_unpaved <- list(
25+
green_roof = measures_max$green_roof,
26+
unpaved = measures_max$unpaved + 0.01,
27+
to_swale = NA
28+
)
29+
30+
expect_silent(b1 <- apply_measures_to_blocks(
31+
blocks,
32+
measures = measures_max
33+
))
34+
35+
expect_warning(b2 <- apply_measures_to_blocks(
36+
blocks,
37+
measures = measures_too_big_green_roof
38+
))
39+
40+
expect_warning(b3 <- apply_measures_to_blocks(
41+
blocks,
42+
measures = measures_too_big_unpaved
43+
))
44+
}
45+
46+
test_me(data = kwb.rabimo::rabimo_inputs_2020$data)
47+
test_me(data = kwb.rabimo::rabimo_inputs_2025$data)
48+
49+
})
50+
51+
if (FALSE) {
52+
53+
blocks <- rbind(
54+
data.frame(total_area = 100, roof = 0.1, green_roof = 0, pvd = 0.7, to_swale = 1)
55+
, data.frame(total_area = 200, roof = 0.2, green_roof = 0.8, pvd = 0.5, to_swale = 1)
56+
, data.frame(total_area = 50, roof = 0.9, green_roof = 0.2, pvd = 0.1, to_swale = 1)
57+
)
58+
59+
stats <- kwb.rabimo::get_measure_stats(blocks)
60+
str(stats)
61+
62+
n <- 10L
63+
64+
m <- as.data.frame(matrix(
65+
runif(3*n),
66+
ncol = 3L,
67+
dimnames = list(NULL, c("green_roof", "unpaved", "to_swale"))
68+
))
69+
70+
combinations <- split(m, seq_len(n))
71+
72+
result <- lapply(combinations, function(measures) {
73+
apply_measures_to_blocks(blocks, measures, dbg = TRUE, check = TRUE)
74+
})
75+
76+
measures <- list(
77+
green_roof = 0,
78+
unpaved = 0,
79+
to_swale = 0.1
80+
)
81+
82+
new_blocks <- apply_measures_to_blocks(blocks, measures)
83+
84+
new_stats <- kwb.rabimo::get_measure_stats(new_blocks)
85+
new_stats$green_roof$mean
86+
new_stats$unpaved$mean
87+
new_stats$to_swale$mean
88+
}

0 commit comments

Comments
 (0)