Skip to content

Commit 2da9d92

Browse files
authored
Merge pull request #91 from t-kalinowski/fix-clamp
Fix nested scalar min/max reductions
2 parents cdc1967 + a1d5134 commit 2da9d92

3 files changed

Lines changed: 49 additions & 1 deletion

File tree

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@
8888
to Fortran scalars so subsetted scalars in reductions (e.g., `min(m[1, 1], m[2, 1])`)
8989
no longer emit `minval` on scalars (#64).
9090
91+
- Fixed nested scalar `min()`/`max()` in reductions, so clamp-style expressions
92+
like `min(max(x[i], lo), hi)` work reliably.
93+
9194
- Fixed an issue where subsetting logical arrays could fail when compiling quick
9295
functions, e.g. `(x > 0)[2, 3]` (#68).
9396

R/r2f-reductions.R

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,17 @@ register_r2f_handler(
2020

2121
reduce_arg <- function(arg) {
2222
mask_hoist <- create_mask_hoist()
23-
x <- r2f(arg, scope, ..., hoist_mask = mask_hoist$try_set)
23+
# Nested reductions (e.g., min(max(...), ...)) can thread an existing
24+
# hoist_mask through `...`. We always want a single mask hoister per
25+
# reduction context, so we ignore any inherited one and install ours.
26+
dots <- list(...)
27+
x <- r2f(
28+
arg,
29+
scope,
30+
calls = dots$calls,
31+
hoist = dots$hoist,
32+
hoist_mask = mask_hoist$try_set
33+
)
2434
if (mask_hoist$has_conflict()) {
2535
stop(
2636
"reduction expressions only support a single logical mask",

tests/testthat/test-reduction-scalars.R

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,38 @@ test_that("reductions over vectors still use intrinsics", {
5555
expect_identical(fn(x2), 1L)
5656
expect_quick_identical(fn, x1, x2)
5757
})
58+
59+
60+
test_that("nested scalar min/max compiles and runs", {
61+
fn <- function(m) {
62+
declare(type(m = integer(2, 2)))
63+
lo <- 1L
64+
hi <- 2L
65+
min(max(m[1, 1], lo), hi)
66+
}
67+
68+
m1 <- matrix(c(0L, 3L, 1L, 2L), nrow = 2L, byrow = TRUE)
69+
m2 <- matrix(c(10L, 3L, 1L, 2L), nrow = 2L, byrow = TRUE)
70+
expect_identical(fn(m1), 1L)
71+
expect_identical(fn(m2), 2L)
72+
expect_quick_identical(fn, m1, m2)
73+
})
74+
75+
76+
test_that("clamp on a 1d array works with nested scalar min/max", {
77+
clamp <- function(x, lo, hi) {
78+
declare(type(x = double(n)), type(lo = double(1)), type(hi = double(1)))
79+
out <- double(length(x))
80+
for (i in seq_along(x)) {
81+
out[i] <- min(max(x[i], lo), hi)
82+
}
83+
out
84+
}
85+
86+
x <- c(-2.0, -0.5, 0.25, 1.25, 10.0)
87+
lo <- -0.25
88+
hi <- 1.0
89+
90+
expect_identical(clamp(x, lo, hi), pmin(pmax(x, lo), hi))
91+
expect_quick_identical(clamp, list(x, lo, hi))
92+
})

0 commit comments

Comments
 (0)