From 7321b4c168fea10479622c98d83fdd33725e3f15 Mon Sep 17 00:00:00 2001 From: Rich FitzJohn Date: Tue, 10 Sep 2024 08:18:05 +0100 Subject: [PATCH 1/8] Support for basic complete sums --- R/generate_dust.R | 4 +-- R/generate_dust_sexp.R | 26 ++++++++++++++++++++ R/package.R | 4 +-- R/parse_expr.R | 45 +++++++++++++++++++++++++++++++++- inst/include/odin2.hpp | 18 ++++++++++++++ tests/testthat/helper-odin2.R | 2 +- tests/testthat/test-generate.R | 15 +++++++++++- 7 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 inst/include/odin2.hpp diff --git a/R/generate_dust.R b/R/generate_dust.R index a1ebddde..30963ea4 100644 --- a/R/generate_dust.R +++ b/R/generate_dust.R @@ -2,8 +2,8 @@ generate_dust_system <- function(dat) { dat <- generate_prepare(dat) body <- collector() - body$add("#include ") - body$add("#include ") + body$add("// [[dust2::linking_to(odin2)]]") + body$add("#include ") body$add(generate_dust_system_attributes(dat)) body$add(sprintf("class %s {", dat$class)) body$add("public:") diff --git a/R/generate_dust_sexp.R b/R/generate_dust_sexp.R index ea05bc07..b24286bf 100644 --- a/R/generate_dust_sexp.R +++ b/R/generate_dust_sexp.R @@ -7,6 +7,9 @@ generate_dust_sexp <- function(expr, dat, options = list()) { fn <- as.character(expr[[1]]) } + ## There's a group here where we don't want to evaluate the + ## arguments, because the interpretation of some values will be + ## different to odin's normal rewrite semantics. if (fn == "[") { return(generate_dust_array_access(expr, dat, options)) } else if (fn == "OdinDim") { @@ -41,8 +44,11 @@ generate_dust_sexp <- function(expr, dat, options = list()) { return(generate_dust_sexp( call("OdinDim", as.character(expr[[2]]), if (fn == "nrow") 1 else 2), dat, options)) + } else if (fn == "OdinReduce") { + return(generate_dust_sexp_reduce(expr, dat, options)) } + ## Below here is much simpler, really. args <- vcapply(expr[-1], generate_dust_sexp, dat, options) n <- length(args) @@ -152,3 +158,23 @@ flatten_index <- function(idx, name) { expr_sum(idx) } } + + +generate_dust_sexp_reduce <- function(expr, dat, options) { + fn <- expr[[2]] + target <- expr[[3]] + target_str <- generate_dust_sexp(expr[[3]], dat, options) + if (isTRUE(expr$complete)) { + if (dat$location[[target]] == "state") { + begin <- target_str + len <- generate_dust_sexp(call("OdinLength", target), dat, options) + end <- sprintf("%s + %s", target_str, len) + } else { + begin <- sprintf("%s.begin()", target_str) + end <- sprintf("%s.end()", target_str) + } + sprintf("odin2::reduce_%s(%s, %s)", fn, begin, end) + } else { + browser() + } +} diff --git a/R/package.R b/R/package.R index a7ae627c..adafefcb 100644 --- a/R/package.R +++ b/R/package.R @@ -10,7 +10,7 @@ ##' For your `DESCRIPTION` file: ##' ##' * `dust2` must be in `Imports` -##' * `cpp11`, `dust2` and `monty` must be in `LinkingTo` +##' * `cpp11`, `dust2`, `odin2` and `monty` must be in `LinkingTo` ##' ##' For your `NAMESPACE` file: ##' @@ -25,7 +25,7 @@ ##' `DESCRIPTION`: ##' ##' ``` -##' Remotes: mrc-ide/dust2, mrc-ide/monty +##' Remotes: mrc-ide/dust2, mrc-ide/monty, mrc-ide/odin2 ##' ``` ##' ##' Note that you do not need to include odin2 itself as a dependency. diff --git a/R/parse_expr.R b/R/parse_expr.R index 48598f11..86477eab 100644 --- a/R/parse_expr.R +++ b/R/parse_expr.R @@ -363,7 +363,9 @@ parse_expr_usage <- function(expr, src, call) { fn <- expr[[1]] fn_str <- as.character(fn) ignore <- "[" - if (fn_str %in% monty::monty_dsl_distributions()$name) { + if (fn_str == "sum") { + expr <- parse_expr_usage_rewrite_reduce(expr, src, call) + } else if (fn_str %in% monty::monty_dsl_distributions()$name) { expr <- parse_expr_usage_rewrite_stochastic(expr, src, call) } else if (fn_str %in% names(FUNCTIONS)) { usage <- FUNCTIONS[[fn_str]] @@ -431,6 +433,47 @@ parse_expr_usage_rewrite_stochastic <- function(expr, src, call) { } +parse_expr_usage_rewrite_reduce <- function(expr, src, call) { + fn <- as.character(expr)[[1]] + n_args <- length(expr) - 1 + if (n_args != 1) { + odin_parse_error( + paste("Invalid call to '{fn}': incorrect number of arguments", + "(expected 1 but received {n_args}"), + "E1030", src, call) + } + + arg <- expr[[2]] + if (rlang::is_symbol(arg)) { + ## We might prevent this in future, it's a slightly odd bit of + ## syntax that only really makes sense for a 1d array + name <- as.character(arg) + return(call("OdinReduce", fn, name, complete = TRUE)) + } else if (!rlang::is_call(arg, "[")) { + odin_parse_error( + paste("Expected argument to '{fn}' to be an array"), + "E1099", src, call) + } + + name <- as.character(arg[[2]]) + index <- as.list(arg[-(1:2)]) + + is_empty <- vlapply(index, rlang::is_missing) + if (all(is_empty)) { + return(call("OdinReduce", fn, name, complete = TRUE)) + } + index[is_empty] <- vector("list", sum(is_empty)) + if (any(!is_empty)) { + ## This is basically the same check as parse_expr_check_lhs_index, + ## work this out shortly.... + browser() + stop("writeme") + } + + call("OdinReduce", fn, name, index = index) +} + + rewrite_stochastic_to_expectation <- function(expr) { if (is.recursive(expr)) { if (rlang::is_call(expr[[1]], "OdinStochasticCall")) { diff --git a/inst/include/odin2.hpp b/inst/include/odin2.hpp new file mode 100644 index 00000000..5aa91659 --- /dev/null +++ b/inst/include/odin2.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace odin2 { + +template +T reduce_sum(T start, T end) { + return std::accumulate(start, end, static_cast(0)); +} + +template +T reduce_prod(T start, T end) { + return std::accumulate(start, end, static_cast(1), std::multiplies()); +} + +} diff --git a/tests/testthat/helper-odin2.R b/tests/testthat/helper-odin2.R index 38c2b515..a874b7f1 100644 --- a/tests/testthat/helper-odin2.R +++ b/tests/testthat/helper-odin2.R @@ -31,7 +31,7 @@ test_pkg_setup <- function(path, name = "pkg") { dir.create(file.path(path, "inst/odin"), FALSE, TRUE) writeLines(c( paste("Package:", name), - "LinkingTo: cpp11, dust2, monty", + "LinkingTo: cpp11, dust2, monty, odin2", "Imports: dust2", "Version: 0.0.1", "Authors@R: c(person('A', 'Person', role = c('aut', 'cre'),", diff --git a/tests/testthat/test-generate.R b/tests/testthat/test-generate.R index 8dc7258b..e74b7e02 100644 --- a/tests/testthat/test-generate.R +++ b/tests/testthat/test-generate.R @@ -1250,7 +1250,6 @@ test_that("can use length() on the rhs", { }) - test_that("can use nrow() and ncol() on the rhs", { dat <- odin_parse({ update(x[, ]) <- x[i, j] + nrow(x) / ncol(x) @@ -1269,3 +1268,17 @@ test_that("can use nrow() and ncol() on the rhs", { " }", "}")) }) + + +test_that("can generate sums over arrays", { + + dat <- odin_parse({ + update(x) <- sum(y) + initial(x) <- 0 + y[] <- Normal(0, 1) + dim(y) <- 3 + }) + dat <- generate_prepare(dat) + generate_dust_system_update(dat) + +}) From 71df7d5d19a3ee54fca565f2647001dddd670175 Mon Sep 17 00:00:00 2001 From: Rich FitzJohn Date: Tue, 10 Sep 2024 17:48:13 +0100 Subject: [PATCH 2/8] Most of sums working, needs C++ support though --- R/generate_dust_sexp.R | 5 +++- R/parse_expr.R | 49 ++++++++++++++++++++++++++-------- inst/include/odin2.hpp | 3 +++ tests/testthat/test-generate.R | 9 +++++++ 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/R/generate_dust_sexp.R b/R/generate_dust_sexp.R index b24286bf..60b5b8cb 100644 --- a/R/generate_dust_sexp.R +++ b/R/generate_dust_sexp.R @@ -164,7 +164,8 @@ generate_dust_sexp_reduce <- function(expr, dat, options) { fn <- expr[[2]] target <- expr[[3]] target_str <- generate_dust_sexp(expr[[3]], dat, options) - if (isTRUE(expr$complete)) { + index <- expr$index + if (is.null(index)) { if (dat$location[[target]] == "state") { begin <- target_str len <- generate_dust_sexp(call("OdinLength", target), dat, options) @@ -175,6 +176,8 @@ generate_dust_sexp_reduce <- function(expr, dat, options) { } sprintf("odin2::reduce_%s(%s, %s)", fn, begin, end) } else { + ## OK, here we probably need to get a sensible bit of C++ written; + ## we hold all the pieces at this point at least. browser() } } diff --git a/R/parse_expr.R b/R/parse_expr.R index 86477eab..9608a27b 100644 --- a/R/parse_expr.R +++ b/R/parse_expr.R @@ -445,8 +445,6 @@ parse_expr_usage_rewrite_reduce <- function(expr, src, call) { arg <- expr[[2]] if (rlang::is_symbol(arg)) { - ## We might prevent this in future, it's a slightly odd bit of - ## syntax that only really makes sense for a 1d array name <- as.character(arg) return(call("OdinReduce", fn, name, complete = TRUE)) } else if (!rlang::is_call(arg, "[")) { @@ -458,16 +456,27 @@ parse_expr_usage_rewrite_reduce <- function(expr, src, call) { name <- as.character(arg[[2]]) index <- as.list(arg[-(1:2)]) - is_empty <- vlapply(index, rlang::is_missing) - if (all(is_empty)) { - return(call("OdinReduce", fn, name, complete = TRUE)) + ## Handle special case efficiently: + if (all(vlapply(index, rlang::is_missing))) { + return(call("OdinReduce", fn, name, index = NULL)) } - index[is_empty] <- vector("list", sum(is_empty)) - if (any(!is_empty)) { - ## This is basically the same check as parse_expr_check_lhs_index, - ## work this out shortly.... - browser() - stop("writeme") + + for (i in seq_along(index)) { + v <- parse_index(name, i, index[[i]]) + deps <- v$depends + if (!is.null(deps)) { + if (":" %in% deps$functions) { + odin_parse_error( + c("Invalid use of range operator ':' within '{fn}' call", + paste("If you use ':' as a range operator within an index,", + "then it must be the outermost call, for e.g,", + "{.code (a + 1):(b + 1)}, not {.code 1 + (a:b)}")), + "E1099", src, call) + } + ## And see parse_expr_check_lhs_index for more + } + v$depends <- NULL + index[[i]] <- v } call("OdinReduce", fn, name, index = index) @@ -567,3 +576,21 @@ parse_index <- function(name_data, dim, value) { NULL } } + + +parse_index <- function(name_data, dim, value) { + name <- INDEX[[dim]] + if (rlang::is_missing(value)) { + to <- call("OdinDim", name_data, dim) + list(name = name, is_range = TRUE, from = 1, to = to, depends = NULL) + } else if (rlang::is_call(value, ":")) { + from <- value[[2]] + to <- value[[3]] + depends <- join_dependencies(list(find_dependencies(from), + find_dependencies(to))) + list(name = name, is_range = TRUE, from = from, to = to, depends = depends) + } else { + depends <- find_dependencies(value) + list(name = name, is_range = FALSE, at = value, depends = depends) + } +} diff --git a/inst/include/odin2.hpp b/inst/include/odin2.hpp index 5aa91659..3dacdb22 100644 --- a/inst/include/odin2.hpp +++ b/inst/include/odin2.hpp @@ -1,5 +1,8 @@ #pragma once +// Better than including this in the package, I think we might copy it +// into the model, essentially vendoring at the point of generation? + #include #include diff --git a/tests/testthat/test-generate.R b/tests/testthat/test-generate.R index e74b7e02..277a7365 100644 --- a/tests/testthat/test-generate.R +++ b/tests/testthat/test-generate.R @@ -1281,4 +1281,13 @@ test_that("can generate sums over arrays", { dat <- generate_prepare(dat) generate_dust_system_update(dat) + dat <- odin_parse({ + update(x[]) <- sum(y[i, ]) + initial(x[]) <- 0 + y[, ] <- Normal(0, 1) + dim(y) <- c(3, 4) + dim(x) <- 3 + }) + dat <- generate_prepare(dat) + generate_dust_system_update(dat) }) From 68385f9a3abbdfab5622539519d3f58793e2ff11 Mon Sep 17 00:00:00 2001 From: Rich FitzJohn Date: Thu, 12 Sep 2024 21:19:01 +0100 Subject: [PATCH 3/8] Simplify implementation --- R/generate_dust.R | 3 +-- R/generate_dust_sexp.R | 30 ++++++++++++++++++------------ R/package.R | 4 ++-- R/parse_expr.R | 18 ------------------ inst/include/odin2.hpp | 21 --------------------- 5 files changed, 21 insertions(+), 55 deletions(-) delete mode 100644 inst/include/odin2.hpp diff --git a/R/generate_dust.R b/R/generate_dust.R index 30963ea4..9abf5b53 100644 --- a/R/generate_dust.R +++ b/R/generate_dust.R @@ -2,8 +2,7 @@ generate_dust_system <- function(dat) { dat <- generate_prepare(dat) body <- collector() - body$add("// [[dust2::linking_to(odin2)]]") - body$add("#include ") + body$add("#include ") body$add(generate_dust_system_attributes(dat)) body$add(sprintf("class %s {", dat$class)) body$add("public:") diff --git a/R/generate_dust_sexp.R b/R/generate_dust_sexp.R index 60b5b8cb..e0de69ad 100644 --- a/R/generate_dust_sexp.R +++ b/R/generate_dust_sexp.R @@ -165,19 +165,25 @@ generate_dust_sexp_reduce <- function(expr, dat, options) { target <- expr[[3]] target_str <- generate_dust_sexp(expr[[3]], dat, options) index <- expr$index + dim <- paste0( + if (isFALSE(options$shared_exists)) "dim_" else "shared.dim.", + target) + stopifnot(fn == "sum") if (is.null(index)) { - if (dat$location[[target]] == "state") { - begin <- target_str - len <- generate_dust_sexp(call("OdinLength", target), dat, options) - end <- sprintf("%s + %s", target_str, len) - } else { - begin <- sprintf("%s.begin()", target_str) - end <- sprintf("%s.end()", target_str) - } - sprintf("odin2::reduce_%s(%s, %s)", fn, begin, end) + sprintf("dust2::array::sum(%s, %s)", target_str, dim) } else { - ## OK, here we probably need to get a sensible bit of C++ written; - ## we hold all the pieces at this point at least. - browser() + index_str <- paste(vcapply(index, function(el) { + if (el$type == "single") { + from <- expr_minus(el$at, 1) + to <- from + } else { + from <- expr_minus(el$from, 1) + to <- expr_minus(el$to, 1) + } + sprintf("{%s, %s}", + generate_dust_sexp(from, dat, options), + generate_dust_sexp(to, dat, options)) + }), collapse = ", ") + sprintf("dust2::array::sum(%s, %s, %s)", target_str, dim, index_str) } } diff --git a/R/package.R b/R/package.R index adafefcb..a7ae627c 100644 --- a/R/package.R +++ b/R/package.R @@ -10,7 +10,7 @@ ##' For your `DESCRIPTION` file: ##' ##' * `dust2` must be in `Imports` -##' * `cpp11`, `dust2`, `odin2` and `monty` must be in `LinkingTo` +##' * `cpp11`, `dust2` and `monty` must be in `LinkingTo` ##' ##' For your `NAMESPACE` file: ##' @@ -25,7 +25,7 @@ ##' `DESCRIPTION`: ##' ##' ``` -##' Remotes: mrc-ide/dust2, mrc-ide/monty, mrc-ide/odin2 +##' Remotes: mrc-ide/dust2, mrc-ide/monty ##' ``` ##' ##' Note that you do not need to include odin2 itself as a dependency. diff --git a/R/parse_expr.R b/R/parse_expr.R index 9608a27b..6befcd9e 100644 --- a/R/parse_expr.R +++ b/R/parse_expr.R @@ -576,21 +576,3 @@ parse_index <- function(name_data, dim, value) { NULL } } - - -parse_index <- function(name_data, dim, value) { - name <- INDEX[[dim]] - if (rlang::is_missing(value)) { - to <- call("OdinDim", name_data, dim) - list(name = name, is_range = TRUE, from = 1, to = to, depends = NULL) - } else if (rlang::is_call(value, ":")) { - from <- value[[2]] - to <- value[[3]] - depends <- join_dependencies(list(find_dependencies(from), - find_dependencies(to))) - list(name = name, is_range = TRUE, from = from, to = to, depends = depends) - } else { - depends <- find_dependencies(value) - list(name = name, is_range = FALSE, at = value, depends = depends) - } -} diff --git a/inst/include/odin2.hpp b/inst/include/odin2.hpp deleted file mode 100644 index 3dacdb22..00000000 --- a/inst/include/odin2.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -// Better than including this in the package, I think we might copy it -// into the model, essentially vendoring at the point of generation? - -#include -#include - -namespace odin2 { - -template -T reduce_sum(T start, T end) { - return std::accumulate(start, end, static_cast(0)); -} - -template -T reduce_prod(T start, T end) { - return std::accumulate(start, end, static_cast(1), std::multiplies()); -} - -} From 34d2eb6a680a10329f7f289a687f0667ca9ff247 Mon Sep 17 00:00:00 2001 From: Rich FitzJohn Date: Fri, 13 Sep 2024 09:32:45 +0100 Subject: [PATCH 4/8] Add more tests --- R/constants.R | 1 + R/parse_expr.R | 91 +++++++++++++------------ R/sysdata.rda | Bin 8375 -> 8848 bytes tests/testthat/test-parse-expr-array.R | 62 +++++++++++++++++ vignettes/errors.Rmd | 52 ++++++++++++++ 5 files changed, 163 insertions(+), 43 deletions(-) diff --git a/R/constants.R b/R/constants.R index 543f113e..6636ba2e 100644 --- a/R/constants.R +++ b/R/constants.R @@ -58,6 +58,7 @@ FUNCTIONS <- list( length = 1, nrow = 1, ncol = 1, + sum = 1, ceiling = ceiling, sign = sign, floor = floor, diff --git a/R/parse_expr.R b/R/parse_expr.R index 6befcd9e..6faf1d28 100644 --- a/R/parse_expr.R +++ b/R/parse_expr.R @@ -368,37 +368,7 @@ parse_expr_usage <- function(expr, src, call) { } else if (fn_str %in% monty::monty_dsl_distributions()$name) { expr <- parse_expr_usage_rewrite_stochastic(expr, src, call) } else if (fn_str %in% names(FUNCTIONS)) { - usage <- FUNCTIONS[[fn_str]] - if (is.function(usage)) { - res <- match_call(expr, usage) - if (!res$success) { - err <- conditionMessage(res$error) - odin_parse_error("Invalid call to '{fn_str}': {err}", - "E1028", src, call) - } - } else { - n_args <- length(expr) - 1 - if (!is.null(names(expr))) { - odin_parse_error( - "Calls to '{fn_str}' may not have any named arguments", - "E1029", src, call) - } - if (length(usage) == 1) { - if (n_args != usage) { - odin_parse_error( - paste("Invalid call to '{fn_str}': incorrect number of arguments", - "(expected {usage} but received {n_args})"), - "E1030", src, call) - } - } else if (n_args < usage[[1]] || n_args > usage[[2]]) { - collapse <- if (diff(usage) == 1) " or " else " to " - usage_str <- paste(usage, collapse = collapse) - odin_parse_error( - paste("Invalid call to '{fn_str}': incorrect number of arguments", - "(expected {usage_str} but received {n_args})"), - "E1030", src, call) - } - } + parse_expr_check_call(expr, src, call) args <- lapply(expr[-1], parse_expr_usage, src, call) expr <- as.call(c(list(fn), args)) } else if (!(fn_str %in% ignore)) { @@ -411,6 +381,43 @@ parse_expr_usage <- function(expr, src, call) { } +parse_expr_check_call <- function(expr, usage, src, call) { + fn <- as.character(expr[[1]]) + usage <- FUNCTIONS[[fn]] + if (is.function(usage)) { + res <- match_call(expr, usage) + if (!res$success) { + err <- conditionMessage(res$error) + odin_parse_error("Invalid call to '{fn}': {err}", + "E1028", src, call) + } + } else { + n_args <- length(expr) - 1 + if (!is.null(names(expr))) { + odin_parse_error( + "Calls to '{fn}' may not have any named arguments", + "E1029", src, call) + } + if (length(usage) == 1) { + if (n_args != usage) { + odin_parse_error( + paste("Invalid call to '{fn}': incorrect number of arguments", + "(expected {usage} but received {n_args})"), + "E1030", src, call) + } + } else if (n_args < usage[[1]] || n_args > usage[[2]]) { + collapse <- if (diff(usage) == 1) " or " else " to " + usage_str <- paste(usage, collapse = collapse) + odin_parse_error( + paste("Invalid call to '{fn}': incorrect number of arguments", + "(expected {usage_str} but received {n_args})"), + "E1030", src, call) + } + } + expr +} + + parse_expr_usage_rewrite_stochastic <- function(expr, src, call) { res <- monty::monty_dsl_parse_distribution(expr) if (!res$success) { @@ -434,23 +441,21 @@ parse_expr_usage_rewrite_stochastic <- function(expr, src, call) { parse_expr_usage_rewrite_reduce <- function(expr, src, call) { - fn <- as.character(expr)[[1]] - n_args <- length(expr) - 1 - if (n_args != 1) { - odin_parse_error( - paste("Invalid call to '{fn}': incorrect number of arguments", - "(expected 1 but received {n_args}"), - "E1030", src, call) - } + parse_expr_check_call(expr, src, call) + fn <- as.character(expr[[1]]) arg <- expr[[2]] if (rlang::is_symbol(arg)) { name <- as.character(arg) - return(call("OdinReduce", fn, name, complete = TRUE)) + return(call("OdinReduce", fn, name, index = NULL)) } else if (!rlang::is_call(arg, "[")) { odin_parse_error( - paste("Expected argument to '{fn}' to be an array"), - "E1099", src, call) + c("Expected argument to '{fn}' to be an array", + i = paste("The argument to '{fn}' should be name of an array (as", + "a symbol) to sum over all elements of the array, or", + "an array access (using '[]') to sum over part of", + "an array")), + "E1033", src, call) } name <- as.character(arg[[2]]) @@ -471,7 +476,7 @@ parse_expr_usage_rewrite_reduce <- function(expr, src, call) { paste("If you use ':' as a range operator within an index,", "then it must be the outermost call, for e.g,", "{.code (a + 1):(b + 1)}, not {.code 1 + (a:b)}")), - "E1099", src, call) + "E1034", src, call) } ## And see parse_expr_check_lhs_index for more } diff --git a/R/sysdata.rda b/R/sysdata.rda index c1ac0f15617fd47a4c5c4aeca7691599e5b6e196..1150336f5757f84cae4c92a427a64e6f0fb272d2 100644 GIT binary patch literal 8848 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

Ln8JS zn6Ok3hO(s^Xho8llIiKDlXO%G_R1UF_fNRQ=~Bwl7rJpWsb?>4|Hd1Ub9fG}kPr?U zjBG%ZMZXO8j18nwP*EXaQMs}@%|3CfnTtn|xl%*b!!>)D;jU`d#Z(!>}(hhLduY;_U4Zr#6efFy50{dsS@2Ie$tEu zCVat5s#CcpbjqMqf;|N6GpbivB9L5DF`Z5-4{>P-IYsm-DZK~utiFhY@yeNY-*=E` z&a)&yyQ-H4_hcD_PV-0sS{E4eK1Z=y4k3E74n%KU5M0i^ullu2ft{Z6RF!w$H@y7| z*Mh*2gcLglNY4@`ihPiI(3(y9_+l6cvy_=EVY7f zfgkFEY*mXlYgUr*@Wy~Q?`A|+rd5nNU4Tp?^mw9t*A(Cs{b3X;pmj-V3Z0YSb8p>R zF@C=U#!5Uk=}s~F7ZF3pl0reRgLM+@m`J2<&B+%nrq-opVqP^&C=|TewQU%% z#)Zmz8bVcqN@Y8ONxB83DUUnCsp5&+a%qxV(PZJ2YG{rdp@wc)4TOEwXjYm07y}aW zV-GOSkE%HchhC4)WY@L(Q+W^v%Adz@KJ<3%Q5;ZVP>VIk*sy3xur~34I(6kE4VgU@ zrxmW9uIHKy0RLCfYep_tNmkSk$!lU}6h1)Mf~VN&Ux_WG=hWvh$Go z>UBaxHe1VBegry#KDtTh1unv4p^BUwmyOP9 zYH3siA-|FT?Jm1!EqIk_cZgHFkewLM%?e&j4p!Jad^~zt83~9`vp6ca!CxvvDKL4f z#dcbL%lxWS#dd7D+?Vn>y-u$pnIg@Rj44Nu+?{GA-`Qn1;xVNHMh+(wS}mArEW#r{ zj-<>*Kd20|5KJ8##vW!Qy`?m9Qr7iRKiC}-pktzsHEiGr`|C={r;lq>L^g%hs?sNv zaUrjAw#!UaLX1~{Q7T7s!rT0YIaoqcgLdP1M;@y8EO;Z-W#uccyjr!gQ?rF?-80?9fa1$n zJ(G)>;`w|S*3=x5%dR(^-h z167Au;>M`rYB<0gRqvxXUwQWJ-k#PK3v{<%)E&3%Zl&6JVC$omjSGtBx3b3eWV%jSlA0D_B$b&fdg*-X2$}5jKc^ZuqW(y# zx{7)R$245}5SNW9GGni3vVD6!n8-+jLVTz@Zr+;A!pePH zq<7|cA}MigCnd8og{7s`jmw~eeRWZv2YKsd%8RoGBq;S9cOczB#0Hm1Kcj;`(SMd`Cz1fv51aNm=gd_)Uz7FI%C6W0?|=DSwf(^sy9wGKWQZ@-!h`5ty~T6pHlMFlAd5XDTzqcARkQb38|JSoR^{dd*Y^9iF3+q z6()I4Q`KZS4xGx?na79sXa~wXs-@1uukK?tjQ(+THd?hNYg4C?L->h28c z?hNYg4C?L-F1s_h?9Sk_ok88jLfyqe-NgcBm^xpsxL>ZiU!HZpJm-FS-uXhB$dB>= N{|Ej?`5 Date: Fri, 13 Sep 2024 11:01:38 +0100 Subject: [PATCH 5/8] Add dep --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7bc3ac70..820b8119 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -28,7 +28,7 @@ Suggests: withr Config/testthat/edition: 3 Remotes: - mrc-ide/dust2, + mrc-ide/dust2@mrc-5758, mrc-ide/monty VignetteBuilder: knitr Language: en-GB From 9288d6b77ef260a3fc4d20f15a07e1a6c19d35f4 Mon Sep 17 00:00:00 2001 From: Rich FitzJohn Date: Fri, 13 Sep 2024 11:16:47 +0100 Subject: [PATCH 6/8] Update generation test and docs --- R/generate_dust_sexp.R | 5 +++-- tests/testthat/test-generate.R | 28 ++++++++++++++++++++++++---- vignettes/functions.Rmd | 26 ++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/R/generate_dust_sexp.R b/R/generate_dust_sexp.R index e0de69ad..aa552352 100644 --- a/R/generate_dust_sexp.R +++ b/R/generate_dust_sexp.R @@ -170,7 +170,7 @@ generate_dust_sexp_reduce <- function(expr, dat, options) { target) stopifnot(fn == "sum") if (is.null(index)) { - sprintf("dust2::array::sum(%s, %s)", target_str, dim) + sprintf("dust2::array::sum(%s, %s)", target_str, dim) } else { index_str <- paste(vcapply(index, function(el) { if (el$type == "single") { @@ -184,6 +184,7 @@ generate_dust_sexp_reduce <- function(expr, dat, options) { generate_dust_sexp(from, dat, options), generate_dust_sexp(to, dat, options)) }), collapse = ", ") - sprintf("dust2::array::sum(%s, %s, %s)", target_str, dim, index_str) + sprintf("dust2::array::sum(%s, %s, %s)", + target_str, dim, index_str) } } diff --git a/tests/testthat/test-generate.R b/tests/testthat/test-generate.R index 277a7365..8aa34d5e 100644 --- a/tests/testthat/test-generate.R +++ b/tests/testthat/test-generate.R @@ -1270,8 +1270,7 @@ test_that("can use nrow() and ncol() on the rhs", { }) -test_that("can generate sums over arrays", { - +test_that("can generate complete sums over arrays", { dat <- odin_parse({ update(x) <- sum(y) initial(x) <- 0 @@ -1279,8 +1278,18 @@ test_that("can generate sums over arrays", { dim(y) <- 3 }) dat <- generate_prepare(dat) - generate_dust_system_update(dat) + expect_equal( + generate_dust_system_update(dat), + c(method_args$update, + " for (size_t i = 1; i <= shared.dim.y.size; ++i) {", + " internal.y[i - 1] = monty::random::normal(rng_state, 0, 1);", + " }", + " state_next[0] = dust2::array::sum(internal.y, shared.dim.y);", + "}")) +}) + +test_that("can generate partial sums over arrays", { dat <- odin_parse({ update(x[]) <- sum(y[i, ]) initial(x[]) <- 0 @@ -1289,5 +1298,16 @@ test_that("can generate sums over arrays", { dim(x) <- 3 }) dat <- generate_prepare(dat) - generate_dust_system_update(dat) + expect_equal( + generate_dust_system_update(dat), + c(method_args$update, + " for (size_t i = 1; i <= shared.dim.y.dim[0]; ++i) {", + " for (size_t j = 1; j <= shared.dim.y.dim[1]; ++j) {", + " internal.y[i - 1 + (j - 1) * (shared.dim.y.mult[1])] = monty::random::normal(rng_state, 0, 1);", + " }", + " }", + " for (size_t i = 1; i <= shared.dim.x.size; ++i) {", + " state_next[i - 1 + 0] = dust2::array::sum(internal.y, shared.dim.y, {i - 1, i - 1}, {0, shared.dim.y.dim[1] - 1});", + " }", + "}")) }) diff --git a/vignettes/functions.Rmd b/vignettes/functions.Rmd index f5ad6539..0753c39e 100644 --- a/vignettes/functions.Rmd +++ b/vignettes/functions.Rmd @@ -164,6 +164,32 @@ We provide several functions for retrieving dimensions from an array We do not currently offer a function for accessing the size of higher dimensions, please let us know if this is an issue (see `vignette("migrating")`) +Frequently, you will want to take a sum over an array, or part of an array, using `sum`. To sum over all elements of an array, use `sum()` with the name of the array you would like to sum over: + +```r +dim(x) <- 10 +x_tot <- sum(x) +``` + +If `m` is a matrix you can compute the sums over the second column by writing: + +```r +m1_tot <- sum(m[, 2]) +``` + +This partial sum approach is frequently used within implicit loops: + +``` +m_col_totals[] <- sum([, i]) +``` + +You can use this approach to compute a matrix-vector product $\mathbf(Ax)$: + +``` +ax_tmp[, ] <- a[i, j] * x[j] +ax[] <- sum(a[, i]) +``` + # Distribution functions We support distribution functions in two places: From cccf5270546d50edf3316afdd1d6f462327b1c7a Mon Sep 17 00:00:00 2001 From: Rich FitzJohn Date: Mon, 16 Sep 2024 08:48:10 +0100 Subject: [PATCH 7/8] Unpin --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 820b8119..7bc3ac70 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -28,7 +28,7 @@ Suggests: withr Config/testthat/edition: 3 Remotes: - mrc-ide/dust2@mrc-5758, + mrc-ide/dust2, mrc-ide/monty VignetteBuilder: knitr Language: en-GB From 43b83b119e71c31459d9ecbc837eec5b9df554a4 Mon Sep 17 00:00:00 2001 From: Rich FitzJohn Date: Tue, 17 Sep 2024 17:20:23 +0100 Subject: [PATCH 8/8] Fix incomplete sentence in help --- R/sysdata.rda | Bin 8848 -> 8939 bytes vignettes/errors.Rmd | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/R/sysdata.rda b/R/sysdata.rda index 1150336f5757f84cae4c92a427a64e6f0fb272d2..5ef4bc12e1e9f8d09682cac1a529c4b56c758619 100644 GIT binary patch literal 8939 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 F004DVA`$=q delta 8809 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 diff --git a/vignettes/errors.Rmd b/vignettes/errors.Rmd index 88fe1feb..c7da0fea 100644 --- a/vignettes/errors.Rmd +++ b/vignettes/errors.Rmd @@ -401,13 +401,15 @@ Here, you must decide if `a` should be differentiable (in which case remove the # `E1033` -The argument to sum must be an array. This can either be a complete array (in which case the argument will be a symbol). So this is fine: +The argument to sum must be an array. This can either be a complete array (in which case the argument will be a symbol), or an indexed array. So these are both fine: ```r a <- sum(x) b[] <- sum(x[, i]) ``` +with the first summing over the whole array and the second summing over rows (each element of `b` will contain a sum over the corresponding row of `x`. + But these are errors: ```r