Legacy Structure:
- Effect files are currently organized by model (e.g., REM, DyNAM-choice).
- Functions follow the naming convention:
update_DyNAM_choice_effect()
- Some models reuse effects defined for other models via wrappers.
Example: indeg and outdeg are defined in REM and reused in DyNAM-rate, -choice, and -coordination models.
Describe the solution you'd like:
- Restructure effect files by type, not by model:
effects_attribute.R: ego, alter, same, ...,
effects_dyadic_stats.R: tie, inertia, recip.
effects_nodal_stats.R: degree, indegree, outdegree.
effects_closure.R: trans, cycle, common_sender, common_receiver, four, node_trans.
effects_mixed_closure.R: mixed_trans, mixed_cycle, ...
effects_attr_struc.R: tertius, tertius_diff
effects_groups_nodal_stats.R
effects_groups_dyad_stats.R
effects_default.R: default method to initialize the effect when one is not provided.
- Avoid duplication: centralize definitions where possible.
- Add robust input validation:
- Informative error messages when an effect is not suitable for a given input:
- Undirected: only a few effects are valid (e.g., degree (new), trans (renamed?), tie, inertia).
- Directed: most of the dyadic effects assume direction.
- Two-mode: prohibit recip and closure effects use.
- Restrict effect usage by model:
Example: rate model should only allow nodal statistics.
Example using a central registry:
.effect_registry <- new.env(parent = parent.env(environment()))
register_effect <- function(name, init, update, meta = list()) {
.effect_registry[[name]] <- list(
init = init(),
update = update(),
meta = meta,
args_validation = fn_validate()
)
}
get_effect_def <- function(name) {
if (!exists(name, envir = .effect_registry)) {
stop("Unknown effect: ", name)
}
.effect_registry[[name]]
}
make_effect <- function(name, args = list()) {
def <- get_effect_def(name)
list(
init = effect_factory(def$init, args),
update = effect_factory(def$update, args)
)
}
## example
register_effect(
"indeg",
init = function(network, weighted, window, transformer_fn, n2) {
if (!is.infinite(windowed)) return(numeric(n2))
.colSums(if (weighted) network else network > 0, n1, n2, na.rm = TRUE)
},
update = function(network, sender, receiver, replace, cache, weighted, transformer_fn) {
cache[receiver] <- cache[receiver] + replace - oldValue
...
list(changes, cache)
},
validate = c(not_two_mode, not_undirected), # either functions or a character vector or checks to perform
meta = list(
label = "Indegree",
description = "Incoming degree of an actor in a network",
family = "degree, nodal statistics"
)
)
Wishlist:
-
Generate a single JSON/YAML table with all effect metadata:
- Useful for documentation (pictorical representation of the effects)
- Enables tooling like searchERGMterm()
- Example fields: name, allowed_models, input_type, family, description, pictogram
Possible drawbacks: file size, it makes complex to add news effects
-
Create help pages by type of effect that can be opened with ?effect_name
-
Enable tooling like searchERGMterm() given a type of input
Describe alternatives you've considered
Define effects as an R6:
EffectBase <- R6::R6Class("EffectBase",
public = list(
name = NULL,
args = NULL,
metadata = NULL,
initialize = function(name, args = list(), metadata = list()) {
self$name <- name
self$args <- args
self$metadata <- metadata
self$validate_once()
},
validate_once = function() {
# Shared checks done only once per effect object.
stopifnot(is.character(self$name))
# e.g. check args match allowed types, windows, weighted etc
if (!self$metadata$suitable) {
warning(sprintf("Effect %s not recommended: %s",
self$name, self$metadata$reason))
}
},
update = function(network, sender, ...) { ... }
)
)
# example
IndegreeEffect <- R6::R6Class("IndegreeEffect",
public = list(
name = "indegree",
initialize = function(args) { ... },
update = function(network, sender, ...) { ... }
)
)
It seems that using R6 classes can lead to an overhead in method distpatch. It's expected that S3 method dispatch is generally faster, especially when a calling method millions of times.
Legacy Structure:
update_DyNAM_choice_effect()Example: indeg and outdeg are defined in REM and reused in DyNAM-rate, -choice, and -coordination models.
Describe the solution you'd like:
effects_attribute.R: ego, alter, same, ...,effects_dyadic_stats.R: tie, inertia, recip.effects_nodal_stats.R: degree, indegree, outdegree.effects_closure.R: trans, cycle, common_sender, common_receiver, four, node_trans.effects_mixed_closure.R: mixed_trans, mixed_cycle, ...effects_attr_struc.R: tertius, tertius_diffeffects_groups_nodal_stats.Reffects_groups_dyad_stats.Reffects_default.R: default method to initialize the effect when one is not provided.Example: rate model should only allow nodal statistics.
Example using a central registry:
Wishlist:
Generate a single JSON/YAML table with all effect metadata:
Possible drawbacks: file size, it makes complex to add news effects
Create help pages by type of effect that can be opened with
?effect_nameEnable tooling like
searchERGMterm()given a type of inputDescribe alternatives you've considered
Define effects as an
R6:It seems that using
R6classes can lead to an overhead in method distpatch. It's expected that S3 method dispatch is generally faster, especially when a calling method millions of times.