Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions nimbleModel/R/modelBaseClass.R
Original file line number Diff line number Diff line change
Expand Up @@ -370,29 +370,30 @@ modelBase_nClass <- nClass(
},
# `self` arg masks the reference to the object.
# TODO: perhaps we should rename the arg `includeSelf`, but that is not back compatible.
getDependencies = function(nodes, self = TRUE, determOnly = FALSE, stochOnly = FALSE,
getDependencies = function(nodes, omit = NULL, self = TRUE,
determOnly = FALSE, stochOnly = FALSE,
includeData = TRUE, dataOnly = FALSE,
includePredictive = nimble::getNimbleOption('getDependenciesIncludesPredictiveNodes'),
predictiveOnly = FALSE, includeRHSonly = FALSE,
downstream = FALSE, immediateOnly = FALSE,
nodesAsChars = getNimbleModelOption("nodesAsChars"),
returnScalarComponents = FALSE, .sort = FALSE) {
nimbleModel::getDependencies(
thisModel, nodes, self,
thisModel, nodes, omit, self,
determOnly, stochOnly, includeData, dataOnly,
includePredictive, predictiveOnly, includeRHSonly,
downstream, immediateOnly,
nodesAsChars, returnScalarComponents, .sort
)
},
getParents = function(nodes, self = FALSE,
getParents = function(nodes, omit = NULL, self = FALSE,
determOnly = FALSE, stochOnly = FALSE,
includeData = TRUE, dataOnly = FALSE, includeRHSonly = FALSE,
upstream = FALSE, immediateOnly = FALSE,
nodesAsChars = getNimbleModelOption("nodesAsChars"),
returnScalarComponents = FALSE, .sort = FALSE) {
nimbleModel::getParents(
thisModel, nodes, self,
thisModel, nodes, omit, self,
determOnly, stochOnly, includeData, dataOnly, includeRHSonly,
upstream, immediateOnly,
nodesAsChars, returnScalarComponents, .sort
Expand Down Expand Up @@ -446,13 +447,13 @@ modelBase_nClass <- nClass(
topologicallySortNodes = function(nodes) {
nimbleModel::expandNodeNames(self, nodes, sort = TRUE, unique = TRUE)
},
getConditionallyIndependentSets = function(nodes, givenNodes,
getConditionallyIndependentSets = function(nodes, givenNodes, omit = NULL,
explore = c("both", "down", "up"),
unknownAsGiven = TRUE, returnScalarComponents = FALSE,
endAsGiven = FALSE,
nodesAsChars = getNimbleModelOption("nodesAsChars")) {
nimbleModel::getConditionallyIndependentSets(
self, nodes, givenNodes, explore, unknownAsGiven,
self, nodes, givenNodes, omit, explore, unknownAsGiven,
returnScalarComponents, endAsGiven, nodesAsChars
)
},
Expand Down
27 changes: 20 additions & 7 deletions nimbleModel/R/modelFunctions.R
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ taggedClass <- R6Class(
)
)

getDependencies <- function(model, nodes,
getDependencies <- function(model, nodes, omit = NULL,
self = TRUE, determOnly = FALSE, stochOnly = FALSE,
includeData = TRUE, dataOnly = FALSE,
includePredictive = nimble::getNimbleOption('getDependenciesIncludesPredictiveNodes'),
Expand All @@ -225,7 +225,7 @@ getDependencies <- function(model, nodes,
nodesAsChars = getNimbleModelOption("nodesAsChars"),
returnScalarComponents = FALSE, .sort = FALSE) {
traverseGraph(model$modelDef$downstreamRules, model$modelDef$declRules,
nodes = nodes,
nodes = nodes, omit = omit,
down = TRUE, self = self,
determOnly = determOnly, stochOnly = stochOnly,
includeData = includeData, dataOnly = dataOnly, includePredictive = includePredictive,
Expand All @@ -236,14 +236,14 @@ getDependencies <- function(model, nodes,
)
}

getParents <- function(model, nodes,
getParents <- function(model, nodes, omit = NULL,
self = FALSE, determOnly = FALSE, stochOnly = FALSE,
includeData = TRUE, dataOnly = FALSE, includeRHSonly = FALSE,
upstream = FALSE, immediateOnly = FALSE,
nodesAsChars = getNimbleModelOption("nodesAsChars"),
returnScalarComponents = FALSE, .sort = FALSE) {
traverseGraph(model$modelDef$upstreamRules, model$modelDef$declRules,
nodes = nodes,
nodes = nodes, omit = omit,
down = FALSE, self = self,
determOnly = determOnly, stochOnly = stochOnly,
includeData = includeData, dataOnly = dataOnly, includePredictive = TRUE,
Expand Down Expand Up @@ -356,7 +356,7 @@ setdiff_nodes <- function(nodeSet1, nodeSet2) {
}

#' @export
getConditionallyIndependentSets <- function(model, nodes, givenNodes,
getConditionallyIndependentSets <- function(model, nodes, givenNodes, omit = NULL,
explore = c("both", "down", "up"),
unknownAsGiven = TRUE, returnScalarComponents = FALSE,
endAsGiven = FALSE,
Expand Down Expand Up @@ -428,10 +428,19 @@ getConditionallyIndependentSets <- function(model, nodes, givenNodes,
if (unknownAsGiven) {
givenNodes <- c(givenNodes, unknownNodes)
} # else nodes <- c(nodes, unknownNodes)

stochDecl <- sapply(model$modelDef$declInfo, \(declInfo) declInfo$stoch)
touched <- lapply(model$modelDef$declInfo[stochDecl], \(declInfo) taggedClass$new(declInfo$declRule))
names(touched) <- sapply(model$modelDef$declInfo[stochDecl], \(declInfo) declInfo$declRule$ID)

if(!is.null(omit)) {
omit <- model$getNodes(omit, nodesAsChars = FALSE)
givenNodes <- setdiff_nodes(givenNodes, omit)
nodes <- setdiff_nodes(nodes, omit)
tmp <- sapply(omit, \(node) touched[[node$decl$declRule$ID]]$tag(node$getIDs()))
}


given <- lapply(model$modelDef$declInfo[stochDecl], \(declInfo) taggedClass$new(declInfo$declRule))
names(given) <- names(touched)
tmp <- sapply(givenNodes, \(node) given[[node$decl$declRule$ID]]$tag(node$getIDs()))
Expand Down Expand Up @@ -932,7 +941,11 @@ setupMargNodes <- function(model, paramNodes, randomEffectsNodes, calcNodes,
nodes = randomEffectsNodes, givenNodes = givenNodes,
unknownAsGiven = TRUE
)
} else if (is.numeric(split)) { # TODO: check this makes sense with inputs being nodeRanges.
} else if (is.numeric(split)) {
# TODO: reconsider this - it relies on user knowing what the `randomEffectsNodes` will be
# even if the user provides them as character strings instead of a list of nodeRanges.
if(length(randomEffectsNodes) != length(split))
stop("when providing numeric vector for `split`, its length must match that of the `randomEffectsNodes`. This will usually mean that `randomEffectsNodes` should be provided by the user as a list of nodeRanges.")
reSets <- split(randomEffectsNodes, split)
} else {
stop("setupMargNodes: Invalid value for `split`")
Expand Down
20 changes: 14 additions & 6 deletions nimbleModel/R/processModelGraph.R
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ setSortIDs <- function(calcRules) {
# (`follow = TRUE`) or to stop at immediate parent or child
# (`immediateOnly = TRUE`).
traverseGraph <- function(streamRules, declRules,
nodes, down, self = TRUE,
nodes, omit = NULL, down, self = TRUE,
determOnly = FALSE, stochOnly = FALSE,
includeData = TRUE, dataOnly = FALSE,
includePredictive = TRUE, predictiveOnly = FALSE, includeRHSonly = FALSE,
Expand All @@ -253,7 +253,10 @@ traverseGraph <- function(streamRules, declRules,

if (inherits(nodes, "varRangeClass")) nodes <- list(nodes) # We use `lapply` on 'nodes' later.

results <- traverseGraphRecurse(streamRules, nodes, down, follow, immediateOnly)
if(!is.null(omit))
omit <- model$getNodes(omit, nodesAsChars = FALSE)

results <- traverseGraphRecurse(streamRules, nodes, omit, down, follow, immediateOnly, model)

results <- model$getNodes(results, determOnly = determOnly, stochOnly = stochOnly,
includeData = includeData, dataOnly = dataOnly,
Expand Down Expand Up @@ -315,8 +318,13 @@ traverseGraph <- function(streamRules, declRules,
return(results)
}

traverseGraphRecurse <- function(rules, nodes, down, follow = FALSE, immediateOnly = FALSE, firstPass = TRUE) {
traverseGraphRecurse <- function(rules, nodes, omit = NULL, down, follow = FALSE, immediateOnly = FALSE, model = NULL, firstPass = TRUE) {
results <- flatten(lapply(nodes, function(node) applyRules(rules, node)))
if(!is.null(omit)) {
results <- model$getNodes(results, nodesAsChars = FALSE)
results <- setdiff_nodes(results, omit)
## We presumably don't need to convert back to varRange(s) since the nodeRange has all that information.
}
if (immediateOnly) {
return(results)
}
Expand All @@ -326,18 +334,18 @@ traverseGraphRecurse <- function(rules, nodes, down, follow = FALSE, immediateOn
if (!down && !firstPass && !follow) {
# For upward traversal, check current rule to see if continue upwards, but always go up on first pass.
# (Because we need to determine stochasticity of the next rule up, not stochasticity of starting rule.
stoch <- sapply(results, function(varRange) varRange$fromStochRule)
stoch <- sapply(results, function(x) if(inherits(x, 'nodeRangeClass')) x$decl$stoch else x$fromStochRule)
results <- results[!stoch] # Stop here if upwards involves stochastic rule, excluding the upwards result.
}
propagators <- results
if (!follow && down) {
# For downward traversal, stop propagating at stochastic cases, but results included.
stoch <- sapply(propagators, function(varRange) varRange$fromStochRule)
stoch <- sapply(propagators, function(x) if(inherits(x, 'nodeRangeClass')) x$decl$stoch else x$fromStochRule)
propagators <- propagators[!stoch]
}
# Continue traversing.
if (length(propagators)) {
results <- c(results, traverseGraphRecurse(rules, propagators, down, follow, firstPass = FALSE))
results <- c(results, traverseGraphRecurse(rules, propagators, omit, down, follow, immediateOnly, model, firstPass = FALSE))
} else {
return(results)
}
Expand Down
53 changes: 53 additions & 0 deletions nimbleModel/tests/testthat/test-modelGraph.R
Original file line number Diff line number Diff line change
Expand Up @@ -2605,3 +2605,56 @@ test_that("handling RHSonly with getParents", {
c("mu[2]","y[2]"))

})

test_that("using omit with getDependencies and getParents", {
setNimbleModelOption('nodesAsChars', TRUE)

code <- nimbleCode({
y ~ dnorm(mu,1)
mu <- mu0+1
mu0 ~ dnorm(0,1)
})
m <- nimbleModel(code)
expect_identical(m$getDependencies('mu0',omit='mu'), "mu0")
expect_identical(m$getParents('y',omit='mu',self=TRUE), "y")

code <- nimbleCode({
z ~ dnorm(y,1)
y ~ dnorm(mu,1)
mu <- mu0+1
mu0~dnorm(0,1)
})
m <- nimbleModel(code)
expect_identical(m$getDependencies('mu0',omit='y'), c("mu0", "mu"))
expect_identical(m$getParents('y',omit='mu0'), "mu")


code <- nimbleCode({
w ~ dnorm(y,1)
z ~ dnorm(y+mu,1)
y ~ dnorm(mu+b,1)
mu <- mu0+1
b ~ dnorm(0,1)
mu0~dnorm(0,1)
})
m <- nimbleModel(code)
expect_identical(m$getDependencies('mu0',omit='y',downstream=TRUE),
c("mu0","mu","lifted_y_plus_mu","lifted_mu_plus_b","z"))
expect_identical(m$getParents('z',omit='y',upstream=TRUE),
c("lifted_y_plus_mu","mu","mu0"))

setNimbleModelOption('nodesAsChars', FALSE)
})

test_that("getDependencies handles non-node-based query", {
code <- nimbleCode({
for(i in 1:4)
y[i] ~ dnorm(mu[i],1)
mu[1:4]~dmnorm(z[1:4],pr[1:4,1:4])
})
m=nimbleModel(code)
expect_identical(m$getDependencies('mu[2]', self = FALSE, nodesAsChars =TRUE),
"y[2]") # Not all of y is included.
expect_identical(m$getDependencies('mu[2]', nodesAsChars =TRUE),
c("mu[1:4]", "y[2]")) # All of mu is included.
})
9 changes: 4 additions & 5 deletions nimbleModel/tests/testthat/test-setupMargNodes.R
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ test_that("getConditionallyIndependentSets works in model with a couple of sets"
expect_identical(getConditionallyIndependentSets(m, 'y[2]', explore = "down"), list('y[2]'))
expect_identical(getConditionallyIndependentSets(m, 'x[1:2]', explore = "up"), list(c('x[1]'), c('x[2]')))
expect_true(nimble:::testConditionallyIndependentSets(m, getConditionallyIndependentSets(m)))
# expect_identical(getConditionallyIndependentSets(m, omit = 'y[2]'), list(c('x[1]', 'y[1]'), c('x[2]')))
# expect_identical(getConditionallyIndependentSets(m, omit = 5), list(c('x[1]', 'y[1]'), c('x[2]')))
expect_identical(getConditionallyIndependentSets(m, omit = 'y[2]'), list(c('x[1]', 'y[1]'), c('x[2]')))
expect_identical(getConditionallyIndependentSets(m, 'x[1]'), list(c('x[1]')))
expect_identical(getConditionallyIndependentSets(m, 'x[1]', unknownAsGiven=FALSE), list(c('x[1]', 'y[1]')))

Expand Down Expand Up @@ -240,7 +239,7 @@ test_that("getConditionallyIndependentSets works in model with diamond shape", {
})

test_that("getConditionallyIndependentSets works in double-state state-space model", {
# two stae-space chains of latent states with one data set that depends on both
# two state-space chains of latent states with one data set that depends on both
mc <- nimbleCode({
x[1] ~ dnorm(0, 1)
w[1] ~ dnorm(0, 1)
Expand All @@ -254,8 +253,8 @@ test_that("getConditionallyIndependentSets works in double-state state-space mod

expect_identical(getConditionallyIndependentSets(m),
list(c(paste0("x[", 2:4, "]"), paste0("w[", 2:4, "]"))))
# expect_identical(getConditionallyIndependentSets(m, omit = "w[2]"),
# list(c("x[2]", "x[3]", "w[3]", "x[4]", "w[4]")))
expect_identical(getConditionallyIndependentSets(m, omit = "w[2]"),
list(c("x[2]", "x[3]", "x[4]", "w[3]", "w[4]")))
expect_identical(getConditionallyIndependentSets(m, givenNodes = c("y", "w[3]"), unknownAsGiven=FALSE),
list(c("x[2]", "x[3]", "x[4]", "x[1]", "w[2]", "w[4]", "w[1]")))
expect_identical(getConditionallyIndependentSets(m, givenNodes = c("y", "x[3]", "w[3]"), unknownAsGiven=FALSE),
Expand Down