From 76500b01c2482126edf9dfcc19aee790448db0e8 Mon Sep 17 00:00:00 2001 From: "Pavel N. Krivitsky" Date: Sun, 22 Dec 2024 22:25:54 +1100 Subject: [PATCH 1/7] Modernized the Sum() operator, removing code left over from before the move to .Call() and using the iinputs API. --- DESCRIPTION | 2 +- R/InitErgmTerm.operator.R | 4 +-- src/changestats_operator.c | 34 +++++-------------- .../testthat/{test-Sum.R => test-term-Sum.R} | 0 4 files changed, 10 insertions(+), 30 deletions(-) rename tests/testthat/{test-Sum.R => test-term-Sum.R} (100%) diff --git a/DESCRIPTION b/DESCRIPTION index 3767c9e7a..aea9d0ce3 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: ergm -Version: 4.8.0-7022 +Version: 4.8.0-7504 Date: 2024-12-22 Title: Fit, Simulate and Diagnose Exponential-Family Models for Networks Authors@R: c( diff --git a/R/InitErgmTerm.operator.R b/R/InitErgmTerm.operator.R index aab9d9498..126668234 100644 --- a/R/InitErgmTerm.operator.R +++ b/R/InitErgmTerm.operator.R @@ -715,8 +715,6 @@ InitErgmTerm.Sum <- function(nw, arglist,...){ nparam <- nparams[1] inputs <- unlist(wl%>%map(t)) - inputs <- c(nf, length(inputs), inputs) - if(is.function(a$label)){ cns <- lapply(ms, param_names, canonical=TRUE) @@ -770,7 +768,7 @@ InitErgmTerm.Sum <- function(nw, arglist,...){ } }else offset <- FALSE - c(list(name="Sum", coef.names = coef.names, inputs=inputs, submodels=ms, emptynwstats=gs, + c(list(name="Sum", coef.names = coef.names, inputs=inputs, iinputs=nf, submodels=ms, emptynwstats=gs, dependence=dependence, offset=offset, ext.encode = if(ms %>% map("terms") %>% unlist(FALSE) %>% map("ext.encode") %>% compact %>% length) function(el, nw0) diff --git a/src/changestats_operator.c b/src/changestats_operator.c index 36503f4da..bb02a7a4e 100644 --- a/src/changestats_operator.c +++ b/src/changestats_operator.c @@ -177,18 +177,7 @@ F_CHANGESTAT_FN(f__submodel_and_summary_term){ // Sum: Take a weighted sum of the models' statistics. I_CHANGESTAT_FN(i_Sum){ - /* - inputs expected: - 1: number of models (nms) - 1: total length of all weight matrices (tml) - tml: a list of mapping matrices in row-major order - */ - - double *inputs = INPUT_PARAM; - unsigned int nms = *(inputs++); - unsigned int tml = *(inputs++); - inputs+=tml; // Jump to start of model specifications. - + unsigned int nms = *IINPUT_PARAM; ALLOC_STORAGE(nms, Model*, ms); SEXP submodels = getListElement(mtp->R, "submodels"); @@ -200,11 +189,9 @@ I_CHANGESTAT_FN(i_Sum){ } C_CHANGESTAT_FN(c_Sum){ - double *inputs = INPUT_PARAM; GET_STORAGE(Model*, ms); - unsigned int nms = *(inputs++); - inputs++; // Skip total length of weight matrices. - double *wts = inputs; + unsigned int nms = *IINPUT_PARAM; + double *wts = INPUT_PARAM; for(unsigned int i=0; i Date: Sun, 22 Dec 2024 22:55:56 +1100 Subject: [PATCH 2/7] The MH proposal C API apparently did not make available the lengths of the input vectors as the change statistics API did. --- inst/include/ergm_MHproposal.h | 9 +++++++-- inst/include/ergm_wtMHproposal.h | 9 +++++++-- src/MHproposal.c | 6 ++++-- src/wtMHproposal.c | 6 ++++-- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/inst/include/ergm_MHproposal.h b/inst/include/ergm_MHproposal.h index 20bdba633..db3bd54db 100644 --- a/inst/include/ergm_MHproposal.h +++ b/inst/include/ergm_MHproposal.h @@ -65,8 +65,10 @@ typedef struct MHProposalstruct { Vertex *togglehead; double logratio; int status; - double *inputs; /* may be used if needed, ignored if not. */ - int *iinputs; /* may be used if needed, ignored if not. */ + int ninputs; + double *inputs; + int niinputs; + int *iinputs; void *storage; void **aux_storage; unsigned int n_aux; @@ -79,8 +81,11 @@ MHProposal *MHProposalInitialize(SEXP pR, Network *nwp, void **aux_storage); void MHProposalDestroy(MHProposal *MHp, Network *nwp); /* Helper macros */ +#define MH_N_DINPUTS MHp->ninputs #define MH_DINPUTS MHp->inputs +#define MH_N_INPUTS MH_N_DINPUTS #define MH_INPUTS MH_DINPUTS +#define MH_N_IINPUTS MHp->niinputs #define MH_IINPUTS MHp->iinputs #define Mtail (MHp->toggletail) diff --git a/inst/include/ergm_wtMHproposal.h b/inst/include/ergm_wtMHproposal.h index 604e83809..d5f4e750a 100644 --- a/inst/include/ergm_wtMHproposal.h +++ b/inst/include/ergm_wtMHproposal.h @@ -66,8 +66,10 @@ typedef struct WtMHProposalstruct { double *toggleweight; double logratio; int status; - double *inputs; /* may be used if needed, ignored if not. */ - int *iinputs; /* may be used if needed, ignored if not. */ + int ninputs; + double *inputs; + int niinputs; + int *iinputs; void *storage; void **aux_storage; unsigned int n_aux; @@ -79,8 +81,11 @@ WtMHProposal *WtMHProposalInitialize(SEXP pR, WtNetwork *nwp, void **aux_storage void WtMHProposalDestroy(WtMHProposal *MH, WtNetwork *nwp); /* Helper macros */ +#define MH_N_DINPUTS MHp->ninputs #define MH_DINPUTS MHp->inputs +#define MH_N_INPUTS MH_N_DINPUTS #define MH_INPUTS MH_DINPUTS +#define MH_N_IINPUTS MHp->niinputs #define MH_IINPUTS MHp->iinputs #define Mtail (MHp->toggletail) diff --git a/src/MHproposal.c b/src/MHproposal.c index a81506d64..e576638d6 100644 --- a/src/MHproposal.c +++ b/src/MHproposal.c @@ -62,9 +62,11 @@ MHProposal *MHProposalInitialize(SEXP pR, Network *nwp, void **aux_storage){ MHp->x_func=(void (*)(unsigned int, void *, MHProposal*, Network*)) R_FindSymbol(fn,sn,NULL); SEXP tmp = getListElement(pR, "inputs"); - MHp->inputs=length(tmp) ? REAL(tmp) : NULL; + MHp->ninputs = length(tmp); + MHp->inputs = MHp->ninputs ? REAL(tmp) : NULL; tmp = getListElement(pR, "iinputs"); - MHp->iinputs=length(tmp) ? INTEGER(tmp) : NULL; + MHp->niinputs = length(tmp); + MHp->iinputs = MHp->niinputs ? INTEGER(tmp) : NULL; /*Clean up by freeing sn and fn*/ R_Free(fn); diff --git a/src/wtMHproposal.c b/src/wtMHproposal.c index b2d95f8d3..9dcbe5aac 100644 --- a/src/wtMHproposal.c +++ b/src/wtMHproposal.c @@ -61,9 +61,11 @@ WtMHProposal *WtMHProposalInitialize(SEXP pR, WtNetwork *nwp, void **aux_storage MHp->x_func=(void (*)(unsigned int, void *, WtMHProposal*, WtNetwork*)) R_FindSymbol(fn,sn,NULL); SEXP tmp = getListElement(pR, "inputs"); - MHp->inputs=length(tmp) ? REAL(tmp) : NULL; + MHp->ninputs = length(tmp); + MHp->inputs = MHp->ninputs ? REAL(tmp) : NULL; tmp = getListElement(pR, "iinputs"); - MHp->iinputs=length(tmp) ? INTEGER(tmp) : NULL; + MHp->niinputs = length(tmp); + MHp->iinputs = MHp->niinputs ? INTEGER(tmp) : NULL; /*Clean up by freeing sn and fn*/ R_Free(fn); From e71e4fc88972d080372457e50778ed8cbc641077 Mon Sep 17 00:00:00 2001 From: "Pavel N. Krivitsky" Date: Sun, 22 Dec 2024 23:27:50 +1100 Subject: [PATCH 3/7] fixallbut() constraint now also accepts an rlebdm as input. --- DESCRIPTION | 2 +- R/InitErgmConstraint.R | 25 ++++++++++++------------ man/fixallbut-ergmConstraint-ea96b2e0.Rd | 2 +- tests/testthat/test-constraints.R | 25 +++++++++++++++--------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index aea9d0ce3..b0ce889d4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: ergm -Version: 4.8.0-7504 +Version: 4.8.0-7505 Date: 2024-12-22 Title: Fit, Simulate and Diagnose Exponential-Family Models for Networks Authors@R: c( diff --git a/R/InitErgmConstraint.R b/R/InitErgmConstraint.R index d081be948..a93ef63e3 100644 --- a/R/InitErgmConstraint.R +++ b/R/InitErgmConstraint.R @@ -415,7 +415,7 @@ InitErgmConstraint.observed <- function(nw, arglist, ...){ } warn_netsize <- function(.n, ...){ - mismatch <- vapply(list(...), function(x) is.network(x) && network.size(x) != .n, logical(1)) + mismatch <- map_lgl(list(...), function(x) (is.network(x) && network.size(x) != .n) || (is(x, "rlebdm") && nrow(x) != .n)) if(any(mismatch)) ergm_Init_warning("Network size of argument(s) ", paste.and(sQuote(...names()[mismatch])), " differs from that of the response network.") } @@ -477,7 +477,7 @@ InitErgmConstraint.fixedas<-function(nw, arglist,...){ #' #' @usage #' # fixallbut(free.dyads) -#' @param free.dyads edgelist or network. Networks will be converted to the corresponding edgelist. +#' @param free.dyads a two-column edge list, a [`network`], or an [`rlebdm`]. Networks will be converted to the corresponding edgelist. #' #' @template ergmConstraint-general #' @@ -487,7 +487,7 @@ InitErgmConstraint.fixedas<-function(nw, arglist,...){ InitErgmConstraint.fixallbut<-function(nw, arglist,...){ a <- check.ErgmTerm(nw, arglist, varnames = c("free.dyads"), - vartypes = c("network,matrix"), + vartypes = c("network,matrix,rlebdm"), defaultvalues = list(NULL), required = c(TRUE)) free.dyads <- a$free.dyads @@ -495,15 +495,16 @@ InitErgmConstraint.fixallbut<-function(nw, arglist,...){ warn_netsize(network.size(nw), free.dyads = free.dyads) list( - free_dyads = function(){ - if(is.network(free.dyads)) free.dyads <- as.edgelist(free.dyads) - else free.dyads <- as.edgelist(free.dyads, - n=nw%n%"n", - directed=nw%n%"directed", - bipartite=nw%n%"bipartite", - loops=nw%n%"loops") - as.rlebdm(free.dyads) - }, + free_dyads = + if(is(free.dyads, "rlebdm")) free.dyads + else function() + as.rlebdm(if(is.network(free.dyads)) as.edgelist(free.dyads) + else as.edgelist(free.dyads, + n=nw%n%"n", + directed=nw%n%"directed", + bipartite=nw%n%"bipartite", + loops=nw%n%"loops") + ), dependence = FALSE) } diff --git a/man/fixallbut-ergmConstraint-ea96b2e0.Rd b/man/fixallbut-ergmConstraint-ea96b2e0.Rd index 860ee2b86..cb0782a38 100644 --- a/man/fixallbut-ergmConstraint-ea96b2e0.Rd +++ b/man/fixallbut-ergmConstraint-ea96b2e0.Rd @@ -8,7 +8,7 @@ # fixallbut(free.dyads) } \arguments{ -\item{free.dyads}{edgelist or network. Networks will be converted to the corresponding edgelist.} +\item{free.dyads}{a two-column edge list, a \code{\link[network:network]{network}}, or an \code{\link{rlebdm}}. Networks will be converted to the corresponding edgelist.} } \description{ Preserve the dyad status in all but \code{free.dyads}. diff --git a/tests/testthat/test-constraints.R b/tests/testthat/test-constraints.R index 42b199e61..aad2348b0 100644 --- a/tests/testthat/test-constraints.R +++ b/tests/testthat/test-constraints.R @@ -74,18 +74,25 @@ test_that("fixedas with network input", { expect_true(all(!sapply(s1,function(x)as.data.frame(t(as.edgelist(absent))) %in% as.data.frame(t(as.edgelist(x)))))) }) -test_that("fixallbut with network input", { - net1 <- network(10,directed=FALSE,density=0.5) - free.dyads <- matrix(sample(2:9,8,replace=FALSE),4,2) +net1 <- network(10,directed=FALSE,density=0.5) +fdel <- matrix(sample(2:9,8,replace=FALSE),4,2) - t1 <- ergm(net1~edges, constraint = ~fixallbut(free.dyads = free.dyads)) - s1 <- simulate(t1, 100) +for(free.dyads in list( + fdel, + fdnw <- as.network(structure(fdel, n = 10), directed = FALSE), + fd <- as.rlebdm(fdnw) + )){ + test_that(sprintf("fixallbut with %s input", class(free.dyads)[1]), { + t1 <- ergm(net1~edges, constraint = ~fixallbut(free.dyads = free.dyads)) + s1 <- simulate(t1, 100) - fixed.dyads <- as.edgelist(!update(net1,free.dyads,matrix.type="edgelist")) - fixed.dyads.state <- net1[fixed.dyads] + fixed.dyads <- as.edgelist(!update(net1,fdel,matrix.type="edgelist")) + fixed.dyads.state <- net1[fixed.dyads] + + expect_true(all(sapply(s1,function(x) all.equal(x[fixed.dyads],fixed.dyads.state)))) + }) +} - expect_true(all(sapply(s1,function(x) all.equal(x[fixed.dyads],fixed.dyads.state)))) -}) test_that("constraint conflict is detected", { data(florentine) From e04e0563bd3bc951c3223079068eaea8c52c75ce Mon Sep 17 00:00:00 2001 From: "Pavel N. Krivitsky" Date: Mon, 23 Dec 2024 00:08:11 +1100 Subject: [PATCH 4/7] Fixed a typo exposed by a fix in statnet.common::trim_env() that affected initialization of constrained MH proposal when it requested auxiliaries. --- R/ergm.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/ergm.R b/R/ergm.R index 148a9782d..76b9bb0af 100644 --- a/R/ergm.R +++ b/R/ergm.R @@ -491,7 +491,7 @@ ergm <- function(formula, response=NULL, model.obs <- NULL if(identical(proposal$auxiliaries, proposal.obs$auxiliaries)){ ## Reuse auxiliaries from the unconstrained proposal if identical. - proposal.obs$slots.extra.aux <- model$slots.extra.aux$proposal + proposal.obs$aux.slots <- model$slots.extra.aux$proposal }else if(!is.null(proposal.obs$auxiliaries)){ if (verbose) message("Constrained proposal requires different auxiliaries: reinitializing model...") model.obs <- ergm_model(formula, nw, extra.aux = NVL3(proposal.obs$auxiliaries,list(proposal=.)), term.options=control$term.options) From 6f91e7f91bc73fe46722fc88579b067512ef4a04 Mon Sep 17 00:00:00 2001 From: "Pavel N. Krivitsky" Date: Mon, 23 Dec 2024 01:20:28 +1100 Subject: [PATCH 5/7] Bumped the C API version number in ergm_constants.h . --- inst/include/ergm_constants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/include/ergm_constants.h b/inst/include/ergm_constants.h index ff2fcace0..7bc4e9618 100644 --- a/inst/include/ergm_constants.h +++ b/inst/include/ergm_constants.h @@ -12,7 +12,7 @@ // Macros indicating the version of the C API. #define ERGM_API_MAJOR 4 -#define ERGM_API_MINOR 7 +#define ERGM_API_MINOR 8 typedef enum MCMCStatus_enum { MCMC_OK = 0, From 8bd0d94c30c1f9b0cd6d580fea072f75a26f4742 Mon Sep 17 00:00:00 2001 From: "Pavel N. Krivitsky" Date: Mon, 23 Dec 2024 10:59:09 +1100 Subject: [PATCH 6/7] Updated NEWS. --- inst/NEWS.Rd | 110 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 98 insertions(+), 12 deletions(-) diff --git a/inst/NEWS.Rd b/inst/NEWS.Rd index cec235c79..e43bb64fe 100644 --- a/inst/NEWS.Rd +++ b/inst/NEWS.Rd @@ -11,6 +11,15 @@ \title{NEWS file for the \code{\link[=ergm-package]{ergm}} package} \encoding{UTF-8} + +%% Note: This needs to be kept in sync with man/macros/ergmTerm.Rd +\newcommand{\ergmTerm}{\code{\link[#1:#2-ergmTerm]{#2#3}}} +\newcommand{\ergmConstraint}{\code{\link[#1:#2-ergmConstraint]{#2#3}}} +\newcommand{\ergmReference}{\code{\link[#1:#2-ergmReference]{#2#3}}} +\newcommand{\ergmHint}{\code{\link[#1:#2-ergmHint]{#2#3}}} +\newcommand{\ergmProposal}{\code{\link[#1:#2-ergmProposal]{#2#3}}} + + % When a release is forked, copy the sections to a new release and % delete unused sections. @@ -65,6 +74,83 @@ %% } +\section{Changes in version 4.8.0}{ + + \subsection{NEW FEATURES}{ + \itemize{ + \item{ + A new bipartite term operator \ergmTerm{ergm}{Project}{()}, with aliases \ergmTerm{ergm}{Proj1}{()} and \ergmTerm{ergm}{Proj2}{()}, evaluating the statistics on an undirected valued network of counts constructed by projecting the bipartite network onto one of its modes. + } + \item{ + A new family of terms, \ergmTerm{ergm}{nodecovrange}{()}, \ergmTerm{ergm}{nodeocovrange}{()}, \ergmTerm{ergm}{nodeicovrange}{()}, \ergmTerm{ergm}{b1covrange}{()}, and \ergmTerm{ergm}{b2covrange}{()}, to evaluate the sum over the nodes of the range of the specified quantitative attribute value among the node's neighbors. (Thanks to Marion Hoffman for the idea!) + } + \item{ + A new family of terms, \ergmTerm{ergm}{nodefactordistinct}{()}, \ergmTerm{ergm}{nodeofactordistinct}{()}, \ergmTerm{ergm}{nodeifactordistinct}{()}, \ergmTerm{ergm}{b1factordistinct}{()}, and \ergmTerm{ergm}{b2factordistinct}{()}, to evaluate the sum over the nodes of the number of distinct values of a categorical factor among the node's neighbors. (Thanks to Marion Hoffman for the idea!) + } + \item{ + The \ergmConstraint{ergm}{fixallbut}{()} now supports an \code{\link{rlebdm}} (in addition to an edge list matrix and a \code{\link[network]{network}}) for its argument. + } + } + } + + + \subsection{BUG FIXES}{ + \itemize{ + \item{ + Fix in stochastic approximation code for compatibility with most recent \CRANpkg{coda}. + } + \item{ + In \code{\link[ergm]{ergm()}}, a bug in initialization of MH proposals for constrained sampling requesting auxiliaries has been fixed. + } + } + } + + + \subsection{OTHER USER-VISIBLE CHANGES}{ + \itemize{ + \item{ + Precision of geometrically weighted statistics (degrees and shared partners) has been improved, particularly for large decay parameters. + } + \item{ + Dynamic term documentation is now more robust to extension developer error. + } + \item{ + A new \code{\link[=as.rle.rlebdm]{as.rle()}} method for \code{\link{rlebdm}} objects to cast it back to an \code{\link{rle}}. + } + } + } + + \subsection{C-LEVEL FACILITIES}{ + \itemize{ + \item{ + Degree-conditioned proposals are now exported in \file{inst/include/}. + } + \item{ + The M-H proposal API now makes lengths of input vectors available to the proposals, paralleling the change statistics API. + } + } + } + + %% \subsection{UTILITIES}{ + %% \itemize{ + %% \item Likewise, "UTILITIES" is probably unnecessary. + %% } + %% } + + %% \subsection{INSTALLATION}{ + %% \itemize{ + %% \item ... as is "INSTALLATION". + %% } + %% } + + %% \subsection{PACKAGE INSTALLATION}{ + %% \itemize{ + %% \item ditto. + %% } + %% } + +} + \section{Changes in version 4.7.5}{ @@ -105,7 +191,7 @@ \code{\link[=blockdiag-ergmConstraint]{blockdiag}} constraint initialization now detects when blocks are non-contiguous and stops with an error. (Previously, it would behave in undefined ways.) } \item{ - \code{\link[=Sum-ergmTerm]{Sum}} operator now handles \code{\link{I}()} label specifications correctly; this also fixes an error in the \code{\link[=Prod-ergmTerm]{Prod}} term. + \ergmTerm{ergm}{Sum}{} operator now handles \code{\link{I}()} label specifications correctly; this also fixes an error in the \ergmTerm{ergm}{Prod}{} term. } \item{ Detection of dyadic dependence now ignores dyadic dependence of hints (since they do not affect the sample space) and auxiliaries (since they do not, in and of themselves, add dyad-dependent statistics). @@ -123,7 +209,7 @@ In proposal help, listing of proposal table entries works again. } \item{ - \code{\link[=nodemix-ergmTerm]{nodemix}()} now uses correct parameter names. + \ergmTerm{ergm}{nodemix}{()} now uses correct parameter names. } \item{ Godambe information for the MPLE now works if the model has an offset. (Thanks, Cornelius Fritz for reporting and MichaƂ Bojanowski for fixing.) @@ -132,7 +218,7 @@ When constructing the starting networks for missing data MCMC, imputation of dyads is skipped if the constraints are dyad-dependent, since inserting edges may break the constraint. } \item{ - \code{\link[=edgecov-ergmTerm]{edgecov}()} and \code{\link[=dyadcov-ergmTerm]{dyadcov}()} now handle \code{\link[network]{network}}-format input correctly. + \ergmTerm{ergm}{edgecov}{()} and \ergmTerm{ergm}{dyadcov}{()} now handle \code{\link[network]{network}}-format input correctly. } \item{ Missing data MLE code can now handle the scenarios in which the statistics in the constrained sample are constant and/or the statistics in the unconstrained sample are highly correlated. @@ -144,10 +230,10 @@ Likelihood calculation is now robust to dropped parameters and parameters fixed at infinity. } \item{ - \code{\link[Label-ergmTerm]{Label}()} operator now documents its behavior when the model is curved more clearly. + \ergmTerm{ergm}{Label}{()} operator now documents its behavior when the model is curved more clearly. } \item{ - Invalid \code{levels2} specification for \code{\link[=mm-ergmTerm]{mm()}} no longer causes memory errors. + Invalid \code{levels2} specification for \ergmTerm{ergm}{mm}{()} no longer causes memory errors. } \item{ Valued proposal updater function was not being passed the current edge state. @@ -167,7 +253,7 @@ \code{\link{LARGEST}}, \code{\link{SMALLEST}}, and \code{\link{COLLAPSE_SMALLEST}} now break ties lexicographically with a warning. } \item{ - \code{\link[=absdiffcat-ergmTerm]{absdiffcat}} is now more memory-efficient during initialization. + \ergmTerm{ergm}{absdiffcat}{} is now more memory-efficient during initialization. } \item{ MCMLE estimation code for missing data MLE with high missingness fraction is more robust. @@ -191,7 +277,7 @@ \code{\link{ergm_Init_stop}()}, \code{\link{ergm_Init_warning}()}, and \code{\link{ergm_Init_message}()} that behave more like their \pkg{base} counterparts have been added to the API. \code{\link{ergm_Init_abort}()}, \code{\link{ergm_Init_warn}()}, and \code{\link{ergm_Init_info}()} will eventually change to use their \CRANpkg{rlang} semantics. } \item{ - \code{\link[=edgecov-ergmTerm]{edgecov}()} and \code{\link[=dyadcov-ergmTerm]{dyadcov}()} now check that their covariate matrix has the correct dimension. + \ergmTerm{ergm}{edgecov}{()} and \ergmTerm{ergm}{dyadcov}{()} now check that their covariate matrix has the correct dimension. } \item{ Constraints \code{\link[fixedas-ergmConstraint]{fixedas}()} and \code{\link[fixedas-ergmConstraint]{fixallbut}()} now warn when given a network whose size does not match the LHS network's. @@ -493,7 +579,7 @@ New functions \code{\link{search.ergmHints}()} and \code{\link{search.ergmReferences}()} can now be used to search available \code{\link{ergmHints}} and \code{\link{ergmReferences}}, respectively. } \item{ - A new \code{\link[=For-ergmTerm]{For}()} term operator to construct a list of terms with some varying parameter. + A new \ergmTerm{ergm}{For}{()} term operator to construct a list of terms with some varying parameter. } \item{ Term API vignette is now more complete, and includes all API elements, not just the post-4.0 ones. @@ -594,7 +680,7 @@ \subsection{NEW FEATURES}{ \itemize{ \item{ - In terms \code{\link[=edgecov-ergmTerm]{edgecov}()} and \code{\link[=dyadcov-ergmTerm]{dyadcov}()}, the argument can now be a network. + In terms \ergmTerm{ergm}{edgecov}{()} and \ergmTerm{ergm}{dyadcov}{()}, the argument can now be a network. } } } @@ -704,7 +790,7 @@ } \item{ - Term \code{\link[=nodemix-ergmTerm]{nodemix}} can now be passed a \code{levels2} argument that is a \code{\link{factor}} or \code{\link{character}} and optionally a matrix, allowing multiple cells to be mapped to the same statistic. (Joyce Cheng) + Term \ergmTerm{ergm}{nodemix}{} can now be passed a \code{levels2} argument that is a \code{\link{factor}} or \code{\link{character}} and optionally a matrix, allowing multiple cells to be mapped to the same statistic. (Joyce Cheng) } \item{ @@ -733,7 +819,7 @@ } \item{ - \code{\link[=edgecov-ergmTerm]{edgecov}} and \code{\link[=dyadcov-ergmTerm]{dyadcov}} now detect and stop with an error when the specified network attribute is not found. + \ergmTerm{ergm}{edgecov}{} and \ergmTerm{ergm}{dyadcov}{} now detect and stop with an error when the specified network attribute is not found. } \item{ @@ -813,7 +899,7 @@ } \item{ - \code{\link{check.ErgmTerm}()} helper function can now also capture the expressions resulting in term arguments. This incidentally fixes a regression in statistic naming of \code{\link{edgecov-ergmTerm}} and \code{\link{dyadcov-ergmTerm}} effects. + \code{\link{check.ErgmTerm}()} helper function can now also capture the expressions resulting in term arguments. This incidentally fixes a regression in statistic naming of \ergmTerm{ergm}{edgecov}{()} and \ergmTerm{ergm}{dyadcov}{()} effects. } \item{ From d0c5e5c22f954d22e1a664a2ac50e355e7eb0d03 Mon Sep 17 00:00:00 2001 From: "Pavel N. Krivitsky" Date: Tue, 24 Dec 2024 09:50:37 +1100 Subject: [PATCH 7/7] In the C implementation of the ergm.eta() family, fixed the memory error when the dimension of theta is 0. --- src/etamap.c | 27 +++++++++++++++------------ tests/testthat/test-C-curved.R | 6 +++--- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/etamap.c b/src/etamap.c index 953e0b5b2..d61c6c3f1 100644 --- a/src/etamap.c +++ b/src/etamap.c @@ -10,21 +10,24 @@ #include "ergm_etamap.h" #include -#define SETUP_CALL(fun) \ - SEXP cm = VECTOR_ELT(curved, i); \ - SEXP toR = getListElement(cm, "to"); \ - unsigned int to = INTEGER(toR)[0]; \ - unsigned int nto = length(toR); \ - SEXP fromR = getListElement(cm, "from"); \ - unsigned int from = INTEGER(fromR)[0]; \ - unsigned int nfrom = length(fromR); \ - SEXP cov = getListElement(cm, "cov"); \ - SEXP fun = getListElement(cm, #fun); \ - \ +/* UINT_MAX's are there to segfault as soon as possible if empty from + and to vectors are actually used rather than return misleading + results. */ +#define SETUP_CALL(fun) \ + SEXP cm = VECTOR_ELT(curved, i); \ + SEXP toR = getListElement(cm, "to"); \ + unsigned int nto = length(toR); \ + unsigned int to = nto ? INTEGER(toR)[0] : UINT_MAX; \ + SEXP fromR = getListElement(cm, "from"); \ + unsigned int nfrom = length(fromR); \ + unsigned int from = nfrom ? INTEGER(fromR)[0] : UINT_MAX; \ + SEXP cov = getListElement(cm, "cov"); \ + SEXP fun = getListElement(cm, #fun); \ + \ SEXP pos = call, arg; \ SETCAR(pos, fun); pos = CDR(pos); \ SETCAR(pos, (arg = allocVector(REALSXP, nfrom))); pos = CDR(pos); /* Don't need to PROTECT the vector this way. */ \ - memcpy(REAL(arg), theta1+from, nfrom*sizeof(double)); \ + if(nfrom) memcpy(REAL(arg), theta1+from, nfrom*sizeof(double)); \ SETCAR(pos, ScalarInteger(nto)); pos = CDR(pos); \ SETCAR(pos, cov); diff --git a/tests/testthat/test-C-curved.R b/tests/testthat/test-C-curved.R index 72dc83bfc..8682a58c6 100644 --- a/tests/testthat/test-C-curved.R +++ b/tests/testthat/test-C-curved.R @@ -52,9 +52,9 @@ ergm.etagradmult.R <- function(theta, v, etamap) { } data(faux.mesa.high) -flom <- ergm_model(~edges+gwesp()+gwdegree()+absdiffcat("Grade")+Offset(~nodefactor("Grade"),c(+1,-1), c(2,3))+gwesp()+NodematchFilter(~gwesp()+nodefactor("Grade"), "Grade"), faux.mesa.high) -(neta <- nparam(flom, canonical=TRUE)) -(ntheta <- nparam(flom, canonical=FALSE)) +flom <- ergm_model(~edges+gwesp()+gwdegree()+absdiffcat("Grade")+Offset(~edges+edges,c(+1,-1))+Offset(~nodefactor("Grade"),c(+1,-1), c(2,3))+gwesp()+NodematchFilter(~gwesp()+nodefactor("Grade"), "Grade"), faux.mesa.high) +neta <- nparam(flom, canonical=TRUE) +ntheta <- nparam(flom, canonical=FALSE) test_that("C implementation of ergm.eta gives the same answer as R implementation.", { expect_equal(ergm.eta(1:ntheta, flom$etamap), ergm.eta.R(1:ntheta, flom$etamap))