From 2e7901521c6f0aaef791d913c15a5a7dde618769 Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Tue, 12 Nov 2024 17:23:54 +0000 Subject: [PATCH 01/13] Working... --- R/parse_expr.R | 2 +- R/parse_system.R | 49 +++++++++++++---- tests/testthat/test-generate.R | 96 ++++++++++++++++++++++++++++++++++ tests/testthat/test-parse.R | 28 ++++++++++ 4 files changed, 164 insertions(+), 11 deletions(-) diff --git a/R/parse_expr.R b/R/parse_expr.R index 8e6a791a..7859acf4 100644 --- a/R/parse_expr.R +++ b/R/parse_expr.R @@ -133,7 +133,7 @@ parse_expr_assignment_lhs <- function(lhs, src, call) { update = function(name) NULL, deriv = function(name) NULL, output = function(name) NULL, - dim = function(name) NULL, + dim = function(name, ...) NULL, config = function(name) NULL) args <- NULL diff --git a/R/parse_system.R b/R/parse_system.R index acd9c9c2..bdfe89c6 100644 --- a/R/parse_system.R +++ b/R/parse_system.R @@ -131,17 +131,35 @@ parse_system_overall <- function(exprs, call) { output <- NULL } - dims <- lapply(exprs[is_dim], function(x) x$rhs$value) - names <- vcapply(exprs[is_dim], function(x) x$lhs$name_data) - sizes <- lapply(dims, function(x) { - if (rlang::is_call(x, "dim")) { - 1 + dims <- list() + names <- list() + sizes <- list() + n <- 1 + for (i in which(is_dim)) { + expr <- exprs[[i]] + names_i <- c(expr$lhs$name_data, unlist(lapply(expr$lhs$args, deparse))) + dims_i <- expr$rhs$value + if (rlang::is_call(dims_i, "dim")) { + size_i <- 1 } else { - expr_prod(x) + size_i <- expr_prod(dims_i) + if (is.null(size_i)) { + size_i <- list(NULL) + } } - }) + + first_dim <- str2lang(sprintf("dim(%s)", expr$lhs$name_data)) + + for (j in seq_along(names_i)) { + dims[[n]] <- if (j == 1) dims_i else first_dim + names[[n]] <- names_i[j] + sizes[[n]] <- size_i + n <- n + 1 + } + } + arrays <- resolve_array_references(data_frame( - name = names, + name = unlist(names), rank = lengths(dims), dims = I(dims), size = I(sizes))) @@ -561,11 +579,14 @@ parse_system_arrays <- function(exprs, call) { is_dim <- vlapply(exprs, function(x) identical(x$special, "dim")) dim_nms <- vcapply(exprs[is_dim], function(x) x$lhs$name_data) + more_dim_nms <- unlist(lapply(exprs[is_dim], function(x) + unlist(lapply(x$lhs$args, deparse)))) ## First, look for any array calls that do not have a corresponding ## dim() is_array <- !vlapply(exprs, function(x) is.null(x$lhs$array)) - err <- !vlapply(exprs[is_array], function(x) x$lhs$name %in% dim_nms) + err <- !vlapply(exprs[is_array], + function(x) x$lhs$name %in% c(dim_nms, more_dim_nms)) if (any(err)) { src <- exprs[is_array][err] err_nms <- unique(vcapply(src, function(x) x$lhs$name)) @@ -578,7 +599,7 @@ parse_system_arrays <- function(exprs, call) { ## Next, we collect up any subexpressions, in order, for all arrays, ## and make sure that we are always assigned as an array. nms <- vcapply(exprs, function(x) x$lhs$name %||% "") # empty for compare... - for (nm in dim_nms) { + for (nm in c(dim_nms, more_dim_nms)) { i <- nms == nm & !is_dim err <- vlapply(exprs[i], function(x) { is.null(x$lhs$array) && !identical(x$special, "parameter") @@ -600,6 +621,14 @@ parse_system_arrays <- function(exprs, call) { vcapply(exprs[is_dim], function(eq) eq$lhs$name), dim_nms) + for (i in which(is_dim)) { + x <- exprs[[i]] + extra_dims <- unlist(lapply(x$lhs$args, deparse)) + for (d in extra_dims) { + name_dim_equation[[d]] <- odin_dim_name(d) + } + } + is_array_assignment <- is_array | (nms %in% dim_nms) for (i in which(is_array_assignment)) { eq <- exprs[[i]] diff --git a/tests/testthat/test-generate.R b/tests/testthat/test-generate.R index 71a09337..f2e6017a 100644 --- a/tests/testthat/test-generate.R +++ b/tests/testthat/test-generate.R @@ -987,6 +987,102 @@ test_that("can generate system with aliased array", { "}")) }) +test_that("can generate system with aliased arrays dimmed together", { + dat <- odin_parse({ + initial(x) <- 0 + update(x) <- x + a[1] + b[1] + dim(a, b) <- 1 + a[] <- 1 + b[] <- 2 + }) + dat <- generate_prepare(dat) + + expect_equal( + generate_dust_system_shared_state(dat), + c("struct shared_state {", + " struct dim_type {", + " dust2::array::dimensions<1> a;", + " } dim;", + " struct offset_type {", + " struct {", + " size_t x;", + " } state;", + " } offset;", + " std::vector b;", + " std::vector a;", + "};")) + + expect_equal( + generate_dust_system_build_shared(dat), + c(method_args$build_shared, + " shared_state::dim_type dim;", + " dim.a.set({static_cast(1)});", + " std::vector b(dim.a.size);", + " for (size_t i = 1; i <= dim.a.size; ++i) {", + " b[i - 1] = 2;", + " }", + " std::vector a(dim.a.size);", + " for (size_t i = 1; i <= dim.a.size; ++i) {", + " a[i - 1] = 1;", + " }", + " shared_state::offset_type offset;", + " offset.state.x = 0;", + " return shared_state{dim, offset, b, a};", + "}")) +}) + + +test_that("can generate system with 2 aliased arrays dimmed together", { + dat <- odin_parse({ + initial(x) <- 0 + update(x) <- x + a[1] + b[1] + c[1] + dim(c, a, b) <- 1 + a[] <- 1 + b[] <- 2 + c[] <- 3 + }) + dat <- generate_prepare(dat) + + expect_equal( + generate_dust_system_shared_state(dat), + c("struct shared_state {", + " struct dim_type {", + " dust2::array::dimensions<1> c;", + " } dim;", + " struct offset_type {", + " struct {", + " size_t x;", + " } state;", + " } offset;", + " std::vector a;", + " std::vector b;", + " std::vector c;", + "};")) + + expect_equal( + generate_dust_system_build_shared(dat), + c(method_args$build_shared, + " shared_state::dim_type dim;", + " dim.c.set({static_cast(1)});", + " std::vector a(dim.c.size);", + " for (size_t i = 1; i <= dim.c.size; ++i) {", + " a[i - 1] = 1;", + " }", + " std::vector b(dim.c.size);", + " for (size_t i = 1; i <= dim.c.size; ++i) {", + " b[i - 1] = 2;", + " }", + " std::vector c(dim.c.size);", + " for (size_t i = 1; i <= dim.c.size; ++i) {", + " c[i - 1] = 3;", + " }", + " shared_state::offset_type offset;", + " offset.state.x = 0;", + " return shared_state{dim, offset, a, b, c};", + "}")) +}) + + test_that("can generate system with length and sum of aliased array", { dat <- odin_parse({ diff --git a/tests/testthat/test-parse.R b/tests/testthat/test-parse.R index 21d631fb..4d0da6b1 100644 --- a/tests/testthat/test-parse.R +++ b/tests/testthat/test-parse.R @@ -561,6 +561,7 @@ test_that("alias dim with ranked parameter", { }) + test_that("don't confuse compare statements for arrays (mrc-5866)", { dat <- odin_parse({ a ~ Normal(0, 1) @@ -574,6 +575,33 @@ test_that("don't confuse compare statements for arrays (mrc-5866)", { }) +test_that("Multiple dims on a line", { + arrays <- odin_parse({ + update(x) <- sum(a) + sum(b) + initial(x) <- 0 + dim(a, b) <- 1 + a[] <- 1 + b[] <- 2 + })$storage$arrays + + expect_equal(arrays$alias[arrays$name == "b"], "a") + expect_equal(arrays$alias[arrays$name == "a"], "a") + + arrays <- odin_parse({ + update(x) <- sum(a) + sum(b) + sum(c) + initial(x) <- 0 + dim(c, a, b) <- 1 + a[] <- 1 + b[] <- 2 + c[] <- 3 + })$storage$arrays + + expect_equal(arrays$alias[arrays$name == "b"], "c") + expect_equal(arrays$alias[arrays$name == "a"], "c") + expect_equal(arrays$alias[arrays$name == "c"], "c") +}) + + test_that("multline array equations must be contiguous", { expect_error( odin_parse({ From 2b09249e5bd6084ad0da094fdccbaf6d5731df80 Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Tue, 12 Nov 2024 17:29:49 +0000 Subject: [PATCH 02/13] Version --- DESCRIPTION | 2 +- tests/testthat/test-parse.R | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index ae8119dd..b7b436f0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: odin2 Title: Next generation odin -Version: 0.2.5 +Version: 0.2.6 Authors@R: c(person("Rich", "FitzJohn", role = c("aut", "cre"), email = "rich.fitzjohn@gmail.com"), person("Wes", "Hinsley", role = "aut"), diff --git a/tests/testthat/test-parse.R b/tests/testthat/test-parse.R index 4d0da6b1..6e1c7d3c 100644 --- a/tests/testthat/test-parse.R +++ b/tests/testthat/test-parse.R @@ -561,7 +561,6 @@ test_that("alias dim with ranked parameter", { }) - test_that("don't confuse compare statements for arrays (mrc-5866)", { dat <- odin_parse({ a ~ Normal(0, 1) From 34e0da7c7c707e6bcefef62b3d5fe462dab441f4 Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Tue, 12 Nov 2024 17:51:42 +0000 Subject: [PATCH 03/13] Fix mixed dependency issue --- R/parse_system.R | 15 ++++++++++ tests/testthat/test-generate.R | 52 ++++++++++++++++++++++++++++++++++ tests/testthat/test-parse.R | 17 +++++++++++ 3 files changed, 84 insertions(+) diff --git a/R/parse_system.R b/R/parse_system.R index bdfe89c6..33a67334 100644 --- a/R/parse_system.R +++ b/R/parse_system.R @@ -279,6 +279,21 @@ resolve_array_references <- function(arrays) { arrays$alias[i] <- res$alias } } + + # Resolve case where + # dim(a) <- 1 + # dim(b, c) <- dim(a) + # - here, c will be aliased to b, not a. + + not_aliased <- arrays$name[arrays$name == arrays$alias] + while (TRUE) { + wrong <- arrays$name != arrays$alias & !(arrays$alias %in% not_aliased) + if (!any(wrong)) { + break + } + arrays$alias[wrong] <- arrays$alias[arrays$name == arrays$alias[wrong]] + } + arrays } diff --git a/tests/testthat/test-generate.R b/tests/testthat/test-generate.R index f2e6017a..93fca65f 100644 --- a/tests/testthat/test-generate.R +++ b/tests/testthat/test-generate.R @@ -1083,6 +1083,58 @@ test_that("can generate system with 2 aliased arrays dimmed together", { }) +test_that("can generate system with dependent-arrays dimmed together", { + dat <- odin_parse({ + update(x) <- sum(a) + sum(b) + sum(c) + initial(x) <- 0 + dim(a) <- 1 + dim(b, c) <- dim(a) + a[] <- 1 + b[] <- 2 + c[] <- 3 + }) + dat <- generate_prepare(dat) + + expect_equal( + generate_dust_system_shared_state(dat), + c("struct shared_state {", + " struct dim_type {", + " dust2::array::dimensions<1> a;", + " } dim;", + " struct offset_type {", + " struct {", + " size_t x;", + " } state;", + " } offset;", + " std::vector c;", + " std::vector a;", + " std::vector b;", + "};")) + + expect_equal( + generate_dust_system_build_shared(dat), + c(method_args$build_shared, + " shared_state::dim_type dim;", + " dim.a.set({static_cast(1)});", + " std::vector c(dim.a.size);", + " for (size_t i = 1; i <= dim.a.size; ++i) {", + " c[i - 1] = 3;", + " }", + " std::vector a(dim.a.size);", + " for (size_t i = 1; i <= dim.a.size; ++i) {", + " a[i - 1] = 1;", + " }", + " std::vector b(dim.a.size);", + " for (size_t i = 1; i <= dim.a.size; ++i) {", + " b[i - 1] = 2;", + " }", + " shared_state::offset_type offset;", + " offset.state.x = 0;", + " return shared_state{dim, offset, c, a, b};", + "}")) +}) + + test_that("can generate system with length and sum of aliased array", { dat <- odin_parse({ diff --git a/tests/testthat/test-parse.R b/tests/testthat/test-parse.R index 6e1c7d3c..417776ec 100644 --- a/tests/testthat/test-parse.R +++ b/tests/testthat/test-parse.R @@ -601,6 +601,23 @@ test_that("Multiple dims on a line", { }) +test_that("Multiple dims on a line with alias...", { + arrays <- odin_parse({ + update(x) <- sum(a) + sum(b) + sum(c) + initial(x) <- 0 + dim(a) <- 1 + dim(b, c) <- dim(a) + a[] <- 1 + b[] <- 2 + c[] <- 3 + })$storage$arrays + + expect_equal(arrays$alias[arrays$name == "a"], "a") + expect_equal(arrays$alias[arrays$name == "b"], "a") + expect_equal(arrays$alias[arrays$name == "c"], "a") +}) + + test_that("multline array equations must be contiguous", { expect_error( odin_parse({ From 07fa4dc58de6f0149f85a12cfc5f2bb9ecb29a91 Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Tue, 12 Nov 2024 18:57:35 +0000 Subject: [PATCH 04/13] Update dims docs --- vignettes/functions.Rmd | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/vignettes/functions.Rmd b/vignettes/functions.Rmd index cb2f6dec..e6a0960a 100644 --- a/vignettes/functions.Rmd +++ b/vignettes/functions.Rmd @@ -174,7 +174,7 @@ dim(ay) <- c(nr, nc) where `y` is defined to be a 1-dimensional array of length 10 and `ay` is a matrix (2-dimensional array) with `nr` rows and `nc` columns. The extents of arrays must be determined at the **first** system initialisation, and this is checked during parse. -If you have different arrays with the same dimensons, you can also use `dim()` on the right-hand side, to copy from an array you have set the dimensions of elsewhere. For example:- +If you have different arrays with the same dimensions, you can also use `dim()` on the right-hand side, to copy from an array you have set the dimensions of elsewhere. For example:- ```r dim(x1) <- c(5, 3, 2) @@ -182,6 +182,15 @@ dim(x2) <- dim(x1) dim(x3) <- dim(x1) ``` +You can also combine arrays on the left-hand side to group arrays with +the same dimensions together. These 5 arrays will all have the same dimensions:- + +```r +dim(x, y) <- c(5, 3, 2) +dim(a, b, c) <- dim(x) +``` + + ## Special functions for arrays We provide several functions for retrieving dimensions from an array From 416929cd759e8a17ff0d1895f457d6e50f5c1939 Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Tue, 12 Nov 2024 18:59:56 +0000 Subject: [PATCH 05/13] Better comment --- R/parse_system.R | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/R/parse_system.R b/R/parse_system.R index 33a67334..d40c38fe 100644 --- a/R/parse_system.R +++ b/R/parse_system.R @@ -281,9 +281,11 @@ resolve_array_references <- function(arrays) { } # Resolve case where - # dim(a) <- 1 - # dim(b, c) <- dim(a) - # - here, c will be aliased to b, not a. + # dim(a) <- 1 + # dim(b, c) <- dim(a) + # At this point, dim(c) will be aliased to dim(b), not dim(a), + # so find aliases that actually point to other aliases, and + # resolve them to something that is not an alias. not_aliased <- arrays$name[arrays$name == arrays$alias] while (TRUE) { From 5c4e76a6d8ab072ff80325c48b2b3ed300ce37e8 Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Tue, 12 Nov 2024 21:21:22 +0000 Subject: [PATCH 06/13] Spot dup dims with multi-lhs --- R/parse_system.R | 20 +++++++++++++++++++- R/sysdata.rda | Bin 17574 -> 17641 bytes tests/testthat/test-parse.R | 13 +++++++++++++ vignettes/errors.Rmd | 9 +++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/R/parse_system.R b/R/parse_system.R index d40c38fe..8f7280bf 100644 --- a/R/parse_system.R +++ b/R/parse_system.R @@ -67,6 +67,12 @@ parse_system_overall <- function(exprs, call) { "E2014", src, call) } + throw_duplicate_dim <- function(name, places) { + odin_parse_error( + paste("The variable {name} was given dimensions multiple times."), + "E2021", places, call) + } + special <- vcapply(exprs, function(x) x$special %||% "") is_update <- special == "update" is_deriv <- special == "deriv" @@ -164,6 +170,18 @@ parse_system_overall <- function(exprs, call) { dims = I(dims), size = I(sizes))) + if (any(duplicated(arrays$name))) { + dup_dim <- unique(arrays$name[duplicated(arrays$name)])[1] + lines <- vlapply(exprs, function(x) { + isTRUE(x$special == "dim" & + dup_dim %in% c(x$lhs$name_data, + unlist(lapply(x$lhs$args, deparse)))) + }) + srcs <- lapply(exprs[lines], "[[", "src") + #eqs <- vcapply(which(lines), function(i) deparse(exprs[[i]]$src$value)) + throw_duplicate_dim(dup_dim, srcs) + } + parameters <- parse_system_overall_parameters(exprs, arrays) data <- data_frame( name = vcapply(exprs[is_data], function(x) x$lhs$name)) @@ -284,7 +302,7 @@ resolve_array_references <- function(arrays) { # dim(a) <- 1 # dim(b, c) <- dim(a) # At this point, dim(c) will be aliased to dim(b), not dim(a), - # so find aliases that actually point to other aliases, and + # so find aliases that actually point to other aliases, and # resolve them to something that is not an alias. not_aliased <- arrays$name[arrays$name == arrays$alias] diff --git a/R/sysdata.rda b/R/sysdata.rda index 7d266d53bf3928f71efccd117cba737bcf76ba8f..2a3eb271a01fc773e8ae77630b6aae0d5b75fb00 100644 GIT binary patch delta 17130 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 Ww58O~`MLK$|Di3vWx6*pG5`P{BvqLJ delta 17078 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 diff --git a/tests/testthat/test-parse.R b/tests/testthat/test-parse.R index 417776ec..56197eaf 100644 --- a/tests/testthat/test-parse.R +++ b/tests/testthat/test-parse.R @@ -600,6 +600,19 @@ test_that("Multiple dims on a line", { expect_equal(arrays$alias[arrays$name == "c"], "c") }) +test_that("Spot duplicate dims in multi-lhs dim", { + expect_error( + odin_parse({ + update(x) <- sum(a) + sum(b) + sum(c) + initial(x) <- 0 + dim(a, b, c) <- 1 + dim(c) <- 2 + a[] <- 1 + b[] <- 2 + c[] <- 3 + }), "The variable c was given dimensions multiple") + }) + test_that("Multiple dims on a line with alias...", { arrays <- odin_parse({ diff --git a/vignettes/errors.Rmd b/vignettes/errors.Rmd index 9fe14539..4982af35 100644 --- a/vignettes/errors.Rmd +++ b/vignettes/errors.Rmd @@ -1088,3 +1088,12 @@ are both invalid. `output()` was used (on lhs) within a discrete-time system. You can only use `output()` within systems that involve `deriv()` as it provides a way of computing variables for which you do not have derivatives. If you are writing a discrete-time system, you should use `update()` with a corresponding `initial()` statement. + +# `E2021` + +You have tried to set the dimensions of the same variable more than once. For example: + +``` +dim(a, b) <- 2 +dim(b, c) <- 3 +``` From 6bdfb74a061828239b9088683d11cdd7bb49b9b4 Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Tue, 12 Nov 2024 21:22:21 +0000 Subject: [PATCH 07/13] Remove comment --- R/parse_system.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/parse_system.R b/R/parse_system.R index 8f7280bf..b5ff6329 100644 --- a/R/parse_system.R +++ b/R/parse_system.R @@ -178,7 +178,6 @@ parse_system_overall <- function(exprs, call) { unlist(lapply(x$lhs$args, deparse)))) }) srcs <- lapply(exprs[lines], "[[", "src") - #eqs <- vcapply(which(lines), function(i) deparse(exprs[[i]]$src$value)) throw_duplicate_dim(dup_dim, srcs) } From 501ad891cf455a64a2ad94bc55cd7c8c26b6dd83 Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Tue, 12 Nov 2024 22:12:31 +0000 Subject: [PATCH 08/13] Do dup check a bit earlier --- R/parse_system.R | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/R/parse_system.R b/R/parse_system.R index b5ff6329..48845467 100644 --- a/R/parse_system.R +++ b/R/parse_system.R @@ -164,14 +164,9 @@ parse_system_overall <- function(exprs, call) { } } - arrays <- resolve_array_references(data_frame( - name = unlist(names), - rank = lengths(dims), - dims = I(dims), - size = I(sizes))) - - if (any(duplicated(arrays$name))) { - dup_dim <- unique(arrays$name[duplicated(arrays$name)])[1] + names <- unlist(names) + if (any(duplicated(names))) { + dup_dim <- unique(names[duplicated(names)])[1] lines <- vlapply(exprs, function(x) { isTRUE(x$special == "dim" & dup_dim %in% c(x$lhs$name_data, @@ -181,6 +176,12 @@ parse_system_overall <- function(exprs, call) { throw_duplicate_dim(dup_dim, srcs) } + arrays <- resolve_array_references(data_frame( + name = names, + rank = lengths(dims), + dims = I(dims), + size = I(sizes))) + parameters <- parse_system_overall_parameters(exprs, arrays) data <- data_frame( name = vcapply(exprs[is_data], function(x) x$lhs$name)) From e716f4433eaadfb04007c51f18d0e3f4e527c030 Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Wed, 13 Nov 2024 10:01:48 +0000 Subject: [PATCH 09/13] Update R/parse_system.R Co-authored-by: Rich FitzJohn --- R/parse_system.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/parse_system.R b/R/parse_system.R index 48845467..570cbfe3 100644 --- a/R/parse_system.R +++ b/R/parse_system.R @@ -67,10 +67,10 @@ parse_system_overall <- function(exprs, call) { "E2014", src, call) } - throw_duplicate_dim <- function(name, places) { + throw_duplicate_dim <- function(name, src) { odin_parse_error( paste("The variable {name} was given dimensions multiple times."), - "E2021", places, call) + "E2021", src, call) } special <- vcapply(exprs, function(x) x$special %||% "") From 9bc5b409fea5a84e33ad04e40bf9001e75174c36 Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Wed, 13 Nov 2024 21:01:31 +0000 Subject: [PATCH 10/13] Use lhs$names --- R/parse_expr.R | 21 ++++++++++++++++++++- R/parse_system.R | 27 ++++++++------------------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/R/parse_expr.R b/R/parse_expr.R index 7859acf4..c216dc30 100644 --- a/R/parse_expr.R +++ b/R/parse_expr.R @@ -133,7 +133,7 @@ parse_expr_assignment_lhs <- function(lhs, src, call) { update = function(name) NULL, deriv = function(name) NULL, output = function(name) NULL, - dim = function(name, ...) NULL, + dim = function(...) NULL, config = function(name) NULL) args <- NULL @@ -153,6 +153,24 @@ parse_expr_assignment_lhs <- function(lhs, src, call) { "E1003", src, call) } + if (special == "dim") { + if (length(lhs) < 2) { + odin_parse_error(c("Invalid call to dim function; no variables given"), + "E1003", src, call) + } + lhs <- vcapply(lhs, function(x) { + if (!is.symbol(x)) { + odin_parse_error("Invalid target '{x}' in dim declaration", + "E1005", src, call) + } + deparse(x)})[-1] + + return(list( + name = lhs[1], + names = lhs, + special = special)) + } + lhs <- lhs[[2]] if (length(m$value) > 2) { args <- as.list(m$value[-(1:2)]) @@ -164,6 +182,7 @@ parse_expr_assignment_lhs <- function(lhs, src, call) { } } + is_array <- rlang::is_call(lhs, "[") if (is_array) { name <- parse_expr_check_lhs_name(lhs[[2]], special, is_array, src, call) diff --git a/R/parse_system.R b/R/parse_system.R index 570cbfe3..e6216299 100644 --- a/R/parse_system.R +++ b/R/parse_system.R @@ -143,7 +143,7 @@ parse_system_overall <- function(exprs, call) { n <- 1 for (i in which(is_dim)) { expr <- exprs[[i]] - names_i <- c(expr$lhs$name_data, unlist(lapply(expr$lhs$args, deparse))) + names_i <- expr$lhs$names dims_i <- expr$rhs$value if (rlang::is_call(dims_i, "dim")) { size_i <- 1 @@ -154,7 +154,7 @@ parse_system_overall <- function(exprs, call) { } } - first_dim <- str2lang(sprintf("dim(%s)", expr$lhs$name_data)) + first_dim <- call("dim", as.symbol(expr$lhs$names[1])) for (j in seq_along(names_i)) { dims[[n]] <- if (j == 1) dims_i else first_dim @@ -175,7 +175,6 @@ parse_system_overall <- function(exprs, call) { srcs <- lapply(exprs[lines], "[[", "src") throw_duplicate_dim(dup_dim, srcs) } - arrays <- resolve_array_references(data_frame( name = names, rank = lengths(dims), @@ -611,17 +610,16 @@ parse_zero_every <- function(time, phases, equations, variables, call) { parse_system_arrays <- function(exprs, call) { + is_dim <- vlapply(exprs, function(x) identical(x$special, "dim")) - dim_nms <- vcapply(exprs[is_dim], function(x) x$lhs$name_data) - more_dim_nms <- unlist(lapply(exprs[is_dim], function(x) - unlist(lapply(x$lhs$args, deparse)))) + dim_nms <- unlist(lapply(exprs[is_dim], function(x) x$lhs$names)) ## First, look for any array calls that do not have a corresponding ## dim() is_array <- !vlapply(exprs, function(x) is.null(x$lhs$array)) err <- !vlapply(exprs[is_array], - function(x) x$lhs$name %in% c(dim_nms, more_dim_nms)) + function(x) x$lhs$name %in% dim_nms) if (any(err)) { src <- exprs[is_array][err] err_nms <- unique(vcapply(src, function(x) x$lhs$name)) @@ -634,7 +632,7 @@ parse_system_arrays <- function(exprs, call) { ## Next, we collect up any subexpressions, in order, for all arrays, ## and make sure that we are always assigned as an array. nms <- vcapply(exprs, function(x) x$lhs$name %||% "") # empty for compare... - for (nm in c(dim_nms, more_dim_nms)) { + for (nm in dim_nms) { i <- nms == nm & !is_dim err <- vlapply(exprs[i], function(x) { is.null(x$lhs$array) && !identical(x$special, "parameter") @@ -652,17 +650,8 @@ parse_system_arrays <- function(exprs, call) { } - name_dim_equation <- set_names( - vcapply(exprs[is_dim], function(eq) eq$lhs$name), - dim_nms) - - for (i in which(is_dim)) { - x <- exprs[[i]] - extra_dims <- unlist(lapply(x$lhs$args, deparse)) - for (d in extra_dims) { - name_dim_equation[[d]] <- odin_dim_name(d) - } - } + name_dim_equation <- set_names(unlist(lapply(dim_nms, odin_dim_name)), + dim_nms) is_array_assignment <- is_array | (nms %in% dim_nms) for (i in which(is_array_assignment)) { From e8578891b0bfd46d3f88a61500f2460e442c7bac Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Wed, 13 Nov 2024 21:04:34 +0000 Subject: [PATCH 11/13] Cleanup diff --- R/parse_expr.R | 1 - R/parse_system.R | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/R/parse_expr.R b/R/parse_expr.R index c216dc30..f02a13dc 100644 --- a/R/parse_expr.R +++ b/R/parse_expr.R @@ -182,7 +182,6 @@ parse_expr_assignment_lhs <- function(lhs, src, call) { } } - is_array <- rlang::is_call(lhs, "[") if (is_array) { name <- parse_expr_check_lhs_name(lhs[[2]], special, is_array, src, call) diff --git a/R/parse_system.R b/R/parse_system.R index e6216299..9c697b23 100644 --- a/R/parse_system.R +++ b/R/parse_system.R @@ -618,8 +618,7 @@ parse_system_arrays <- function(exprs, call) { ## First, look for any array calls that do not have a corresponding ## dim() is_array <- !vlapply(exprs, function(x) is.null(x$lhs$array)) - err <- !vlapply(exprs[is_array], - function(x) x$lhs$name %in% dim_nms) + err <- !vlapply(exprs[is_array], function(x) x$lhs$name %in% dim_nms) if (any(err)) { src <- exprs[is_array][err] err_nms <- unique(vcapply(src, function(x) x$lhs$name)) From da9c7b494f71f44ec5fe30ae9f4108980e3ec4a1 Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Wed, 13 Nov 2024 21:51:25 +0000 Subject: [PATCH 12/13] Refactor array checks into separate funcs/file --- DESCRIPTION | 2 +- R/parse_system.R | 108 +++--------------------------------- R/parse_system_arrays.R | 118 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 102 deletions(-) create mode 100644 R/parse_system_arrays.R diff --git a/DESCRIPTION b/DESCRIPTION index b7b436f0..8b65bb33 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: odin2 Title: Next generation odin -Version: 0.2.6 +Version: 0.2.10 Authors@R: c(person("Rich", "FitzJohn", role = c("aut", "cre"), email = "rich.fitzjohn@gmail.com"), person("Wes", "Hinsley", role = "aut"), diff --git a/R/parse_system.R b/R/parse_system.R index 9c697b23..1a07a282 100644 --- a/R/parse_system.R +++ b/R/parse_system.R @@ -67,12 +67,6 @@ parse_system_overall <- function(exprs, call) { "E2014", src, call) } - throw_duplicate_dim <- function(name, src) { - odin_parse_error( - paste("The variable {name} was given dimensions multiple times."), - "E2021", src, call) - } - special <- vcapply(exprs, function(x) x$special %||% "") is_update <- special == "update" is_deriv <- special == "deriv" @@ -137,49 +131,10 @@ parse_system_overall <- function(exprs, call) { output <- NULL } - dims <- list() - names <- list() - sizes <- list() - n <- 1 - for (i in which(is_dim)) { - expr <- exprs[[i]] - names_i <- expr$lhs$names - dims_i <- expr$rhs$value - if (rlang::is_call(dims_i, "dim")) { - size_i <- 1 - } else { - size_i <- expr_prod(dims_i) - if (is.null(size_i)) { - size_i <- list(NULL) - } - } - - first_dim <- call("dim", as.symbol(expr$lhs$names[1])) - - for (j in seq_along(names_i)) { - dims[[n]] <- if (j == 1) dims_i else first_dim - names[[n]] <- names_i[j] - sizes[[n]] <- size_i - n <- n + 1 - } - } - - names <- unlist(names) - if (any(duplicated(names))) { - dup_dim <- unique(names[duplicated(names)])[1] - lines <- vlapply(exprs, function(x) { - isTRUE(x$special == "dim" & - dup_dim %in% c(x$lhs$name_data, - unlist(lapply(x$lhs$args, deparse)))) - }) - srcs <- lapply(exprs[lines], "[[", "src") - throw_duplicate_dim(dup_dim, srcs) - } - arrays <- resolve_array_references(data_frame( - name = names, - rank = lengths(dims), - dims = I(dims), - size = I(sizes))) + arrays <- build_array_table(exprs[is_dim], call) + check_duplicate_dims(arrays, exprs, call) + arrays <- resolve_array_references(arrays) + arrays <- resolve_split_dependencies(arrays, call) parameters <- parse_system_overall_parameters(exprs, arrays) data <- data_frame( @@ -267,55 +222,6 @@ parse_system_overall <- function(exprs, call) { exprs = exprs) } - -resolve_array_references <- function(arrays) { - lookup_array <- function(name, copy_from, d) { - i <- which(d$name == copy_from) - if (length(i) == 0) { - return(NULL) - } - dim_i <- d$dims[i] - if (rlang::is_call(dim_i[[1]], "dim")) { - rhs_dim_var <- deparse(dim_i[[1]][[2]]) - return(lookup_array(name, rhs_dim_var, d[-i, ])) - } - return(list(rank = d$rank[i], alias = d$name[i])) - } - - arrays$alias <- arrays$name - is_ref <- vlapply(arrays$dims, rlang::is_call, "dim") - - for (i in which(is_ref)) { - lhs_dim_var <- arrays$name[i] - rhs_dim_var <- deparse(arrays$dims[i][[1]][[2]]) - res <- lookup_array(lhs_dim_var, rhs_dim_var, arrays[-i, ]) - if (!is.null(res)) { - arrays$dims[i] <- list(NULL) - arrays$size[i] <- NA_integer_ - arrays$rank[i] <- res$rank - arrays$alias[i] <- res$alias - } - } - - # Resolve case where - # dim(a) <- 1 - # dim(b, c) <- dim(a) - # At this point, dim(c) will be aliased to dim(b), not dim(a), - # so find aliases that actually point to other aliases, and - # resolve them to something that is not an alias. - - not_aliased <- arrays$name[arrays$name == arrays$alias] - while (TRUE) { - wrong <- arrays$name != arrays$alias & !(arrays$alias %in% not_aliased) - if (!any(wrong)) { - break - } - arrays$alias[wrong] <- arrays$alias[arrays$name == arrays$alias[wrong]] - } - - arrays -} - parse_system_depends <- function(equations, variables, call) { automatic <- c("time", "dt") implicit <- c(variables, automatic) @@ -610,7 +516,6 @@ parse_zero_every <- function(time, phases, equations, variables, call) { parse_system_arrays <- function(exprs, call) { - is_dim <- vlapply(exprs, function(x) identical(x$special, "dim")) dim_nms <- unlist(lapply(exprs[is_dim], function(x) x$lhs$names)) @@ -649,8 +554,9 @@ parse_system_arrays <- function(exprs, call) { } - name_dim_equation <- set_names(unlist(lapply(dim_nms, odin_dim_name)), - dim_nms) + name_dim_equation <- set_names( + unlist(lapply(dim_nms, odin_dim_name)), + dim_nms) is_array_assignment <- is_array | (nms %in% dim_nms) for (i in which(is_array_assignment)) { diff --git a/R/parse_system_arrays.R b/R/parse_system_arrays.R new file mode 100644 index 00000000..14793d5d --- /dev/null +++ b/R/parse_system_arrays.R @@ -0,0 +1,118 @@ +build_array_table <- function(exprs, call) { + dims <- list() + names <- list() + sizes <- list() + n <- 1 + for (expr in exprs) { + names_i <- expr$lhs$names + dims_i <- expr$rhs$value + if (rlang::is_call(dims_i, "dim")) { + size_i <- 1 + } else { + size_i <- expr_prod(dims_i) + if (is.null(size_i)) { + size_i <- list(NULL) + } + } + + first_dim <- call("dim", as.symbol(expr$lhs$names[1])) + + for (j in seq_along(names_i)) { + dims[[n]] <- if (j == 1) dims_i else first_dim + names[[n]] <- names_i[j] + sizes[[n]] <- size_i + n <- n + 1 + } + } + + data_frame( + name = unlist(names), + rank = lengths(dims), + dims = I(dims), + size = I(sizes)) +} + +check_duplicate_dims <- function(arrays, exprs, call) { + throw_duplicate_dim <- function(name, src) { + odin_parse_error( + paste("The variable {name} was given dimensions multiple times."), + "E2021", src, call) + } + + names <- unlist(arrays$name) + if (any(duplicated(names))) { + dup_dim <- unique(names[duplicated(names)])[1] + lines <- vlapply(exprs, function(x) { + isTRUE(x$special == "dim" & + dup_dim %in% c(x$lhs$names)) + }) + srcs <- lapply(exprs[lines], "[[", "src") + throw_duplicate_dim(dup_dim, srcs) + } +} + + +resolve_array_references <- function(arrays) { + lookup_array <- function(name, copy_from, d) { + i <- which(d$name == copy_from) + if (length(i) == 0) { + return(NULL) + } + dim_i <- d$dims[i] + if (rlang::is_call(dim_i[[1]], "dim")) { + rhs_dim_var <- deparse(dim_i[[1]][[2]]) + return(lookup_array(name, rhs_dim_var, d[-i, ])) + } + return(list(rank = d$rank[i], alias = d$name[i])) + } + + arrays$alias <- arrays$name + is_ref <- vlapply(arrays$dims, rlang::is_call, "dim") + + for (i in which(is_ref)) { + lhs_dim_var <- arrays$name[i] + rhs_dim_var <- deparse(arrays$dims[i][[1]][[2]]) + res <- lookup_array(lhs_dim_var, rhs_dim_var, arrays[-i, ]) + if (!is.null(res)) { + arrays$dims[i] <- list(NULL) + arrays$size[i] <- NA_integer_ + arrays$rank[i] <- res$rank + arrays$alias[i] <- res$alias + } + } + + arrays +} + +resolve_split_dependencies <- function(arrays, call) { + # Resolve case where + # dim(a) <- 1 + # dim(b, c) <- dim(a) + # At this point, dim(c) will be aliased to dim(b), not dim(a), + # so find aliases that actually point to other aliases, and + # resolve them to something that is not an alias. + + find_non_alias <- function(current, original = current, visited = NULL) { + # I don't think below is actually possible to get to + + if (any(duplicated(visited))) { + odin_parse_error( + c("Cyclic dependency detected for array {original}."), + "E2005", NULL, call) + } + + array <- arrays[arrays$name == current, ] + if (array$alias == array$name) { + return(array$alias) + } + find_non_alias(array$alias, c(visited, original, array$name)) + } + + not_aliased <- arrays$name[arrays$name == arrays$alias] + wrong <- arrays$name != arrays$alias & !(arrays$alias %in% not_aliased) + for (i in which(wrong)) { + arrays$alias[i] <- find_non_alias(arrays$alias[i], arrays$name[i]) + } + + arrays +} From 1b229d2fa2a0105ab2a40aaca4035c8d2512457c Mon Sep 17 00:00:00 2001 From: Wes Hinsley Date: Thu, 14 Nov 2024 08:08:47 +0000 Subject: [PATCH 13/13] Apply suggestions from code review Co-authored-by: Rich FitzJohn --- R/parse_expr.R | 5 +++-- R/parse_system_arrays.R | 8 +------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/R/parse_expr.R b/R/parse_expr.R index f02a13dc..47eb2ef7 100644 --- a/R/parse_expr.R +++ b/R/parse_expr.R @@ -158,12 +158,13 @@ parse_expr_assignment_lhs <- function(lhs, src, call) { odin_parse_error(c("Invalid call to dim function; no variables given"), "E1003", src, call) } - lhs <- vcapply(lhs, function(x) { + lhs <- vcapply(lhs[-1], function(x) { if (!is.symbol(x)) { odin_parse_error("Invalid target '{x}' in dim declaration", "E1005", src, call) } - deparse(x)})[-1] + deparse(x) + }) return(list( name = lhs[1], diff --git a/R/parse_system_arrays.R b/R/parse_system_arrays.R index 14793d5d..3036036e 100644 --- a/R/parse_system_arrays.R +++ b/R/parse_system_arrays.R @@ -93,13 +93,7 @@ resolve_split_dependencies <- function(arrays, call) { # resolve them to something that is not an alias. find_non_alias <- function(current, original = current, visited = NULL) { - # I don't think below is actually possible to get to - - if (any(duplicated(visited))) { - odin_parse_error( - c("Cyclic dependency detected for array {original}."), - "E2005", NULL, call) - } + stopifnot(!any(duplicated(visited))) array <- arrays[arrays$name == current, ] if (array$alias == array$name) {