diff --git a/DESCRIPTION b/DESCRIPTION index b5c7bdf..c9135e8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: ergm.userterms Version: 4.0.0 -Date: 2019-05-15 +Date: 2025-02-10 Title: User-specified Terms for the statnet Suite of Packages Authors@R: c( person("Mark S.", "Handcock", role=c("aut"), email="handcock@stat.ucla.edu"), diff --git a/R/InitErgmTerm.users.R b/R/InitErgmTerm.users.R index 550edad..1ebad23 100644 --- a/R/InitErgmTerm.users.R +++ b/R/InitErgmTerm.users.R @@ -62,7 +62,8 @@ #' same value of the `by` attribute. #' ### Import the standard ergmTerm documentation template so that it -### gets indexed in ?ergmTerm. +### gets indexed in ?ergmTerm. This and other templates can be copied +### from 'ergm' source code on GitHub, in the man-roxygen/ directory. ### #' @template ergmTerm-general #' @@ -82,7 +83,6 @@ ### #' @concept undirected #' @concept categorical nodal attribute -#' @concept frequently-used ### ### No @export! InitErgmTerm.mindegree <- function(nw, arglist, ...) { @@ -116,8 +116,71 @@ InitErgmTerm.mindegree <- function(nw, arglist, ...) { ) } +#' @templateVar name sqrt.triangle +#' +#' @title Square root of the number of triangles +#' +#' @description These terms add one network statistic to the model, +#' the square root of the number of triangles. They demonstrate the +#' private and auxiliary storage mechanisms. The change statistic +#' for the square root of the number of triangles depends on the +#' initial number of triangles, so it is costly to compute -- unless +#' one can keep track of the number of triangles. These terms can +#' only be used with undirected networks. +#' +#' @usage +#' # binary: sqrt.triangle +#' +#' @template ergmTerm-general +#' +#' @examples +#' +#' data(florentine) +#' +#' sqrt(summary(flomarriage~triangle)) +#' summary(flomarriage~sqrt.triangle) +#' +#' stopifnot(sqrt(summary(flomarriage~triangle)) == summary(flomarriage~sqrt.triangle)) +#' +#' @concept undirected +#' @concept triadic +InitErgmTerm.sqrt.triangle <- function(nw, arglist, ...) { + # No arguments: + a <- check.ErgmTerm(nw, arglist, directed=FALSE, bipartite=FALSE) + # coef.names can have nonstandard characters: + list(name = "sqrt_triangle", coef.names = "sqrt(triangle)", dependence = TRUE) +} +### This also demonstrates how to document multiple terms in one +### file. The following two Roxygen lines generate code to merge the +### documentation into the sqrt.triangle-ergmTerm documentation. Note +### that the alias has to be set manually. +#' @templateVar name sqrt.triangle +#' @template ergmTerm-rdname +#' @aliases sqrt.triangle.aux-ergmTerm +#' @usage +#' # binary: sqrt.triangle.aux +#' +#' @examples +#' summary(flomarriage~sqrt.triangle.aux) +#' +#' stopifnot(sqrt(summary(flomarriage~triangle)) == summary(flomarriage~sqrt.triangle.aux)) +InitErgmTerm.sqrt.triangle.aux <- function(nw, arglist, ...) { + # No arguments: + a <- check.ErgmTerm(nw, arglist, directed=FALSE, bipartite=FALSE) + # coef.names can have nonstandard characters: + list(name = "sqrt_triangle_aux", coef.names = "sqrt(triangle)", dependence = TRUE, + auxiliaries = ~.triangle) # Request the triangles auxiliary. +} +### Auxiliaries don't generally get public documentation, since they +### are not invoked by end-users directly. +InitErgmTerm..triangle <- function(nw, arglist, ...) { + # No arguments: + a <- check.ErgmTerm(nw, arglist, directed=FALSE, bipartite=FALSE) + # coef.names can have nonstandard characters: + list(name = "_triangle") # coef.names is an empty vector -> auxiliary +} diff --git a/man-roxygen/ergmTerm-rdname.R b/man-roxygen/ergmTerm-rdname.R new file mode 100644 index 0000000..b668f21 --- /dev/null +++ b/man-roxygen/ergmTerm-rdname.R @@ -0,0 +1,11 @@ +# File man-roxygen/ergmTerm-rdname.R in package ergm, part of the +# Statnet suite of packages for network analysis, https://statnet.org . +# +# This software is distributed under the GPL-3 license. It is free, +# open source, and has the attribution requirements (GPL Section 7) at +# https://statnet.org/attribution . +# +# Copyright 2003-2025 Statnet Commons +################################################################################ +#' <% name <- if(startsWith(name, "'")) substr(name, 2, 1000) else name %> +#' @rdname <%= ergm:::.term.rdname("ergmTerm", name) %> diff --git a/man/mindegree-ergmTerm-eabdf279.Rd b/man/mindegree-ergmTerm-eabdf279.Rd index b5b0305..2535fd8 100644 --- a/man/mindegree-ergmTerm-eabdf279.Rd +++ b/man/mindegree-ergmTerm-eabdf279.Rd @@ -40,5 +40,4 @@ and conventions. \Sexpr[results=rd,stage=render]{ergm:::.formatTermKeywords("ergmTerm", "mindegree", "subsection")} } \concept{categorical nodal attribute} -\concept{frequently-used} \concept{undirected} diff --git a/man/sqrttriangle-ergmTerm-c7f8ea09.Rd b/man/sqrttriangle-ergmTerm-c7f8ea09.Rd new file mode 100644 index 0000000..046bf43 --- /dev/null +++ b/man/sqrttriangle-ergmTerm-c7f8ea09.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/InitErgmTerm.users.R +\name{sqrt.triangle-ergmTerm} +\alias{sqrt.triangle-ergmTerm} +\alias{InitErgmTerm.sqrt.triangle} +\alias{InitErgmTerm.sqrt.triangle.aux} +\alias{sqrt.triangle.aux-ergmTerm} +\title{Square root of the number of triangles} +\usage{ +# binary: sqrt.triangle + +# binary: sqrt.triangle.aux +} +\description{ +These terms add one network statistic to the model, +the square root of the number of triangles. They demonstrate the +private and auxiliary storage mechanisms. The change statistic +for the square root of the number of triangles depends on the +initial number of triangles, so it is costly to compute -- unless +one can keep track of the number of triangles. These terms can +only be used with undirected networks. +} +\examples{ + +data(florentine) + +sqrt(summary(flomarriage~triangle)) +summary(flomarriage~sqrt.triangle) + +stopifnot(sqrt(summary(flomarriage~triangle)) == summary(flomarriage~sqrt.triangle)) + +summary(flomarriage~sqrt.triangle.aux) + +stopifnot(sqrt(summary(flomarriage~triangle)) == summary(flomarriage~sqrt.triangle.aux)) +} +\seealso{ +\code{\link[ergm:ergmTerm]{ergmTerm}} for index of model terms currently visible to the package. + +\Sexpr[results=rd,stage=render]{ergm:::.formatTermKeywords("ergmTerm", "sqrt.triangle", "subsection")} +} +\concept{triadic} +\concept{undirected} diff --git a/src/changestats.users.c b/src/changestats.users.c index d81688d..f6576be 100644 --- a/src/changestats.users.c +++ b/src/changestats.users.c @@ -7,7 +7,8 @@ * * Copyright 2012-2019 Statnet Commons */ -#include "ergm_changestat.h" +#include "ergm_changestat.h" // change statistics API and helper functions +#include "ergm_storage.h" // storage API and helper functions C_CHANGESTAT_FN(c_mindegree) { Rboolean attrflag = IINPUT_PARAM[0]; @@ -39,3 +40,111 @@ C_CHANGESTAT_FN(c_mindegree) { } } + +/***************** + Square root of triangles: The private storage approach. +*****************/ + +/* Initializer: store the number of triangles in its private storage. */ +I_CHANGESTAT_FN(i_sqrt_triangle) { + ALLOC_STORAGE(1, double, ntri); /* Allocate private storage for 1 number to store the current number of triangles. */ + + /* NB: In an undirected network, tail < head. */ + EXEC_THROUGH_NET_EDGES(tail, head, e1, { /* For each edge (tail < head) in the network... */ + EXEC_THROUGH_FOUTEDGES(head, e, node3, { /* and each edge (head < node3)... */ + *ntri += IS_OUTEDGE(tail, node3); /* a triangle is formed if an edge exists between node3 and tail. */ + }); + }); + + /* *ntri now contains the number of triangles in the network. */ +} + +/* Updater: will be called when toggling (tail, head) with state edgestate is imminent. */ +U_CHANGESTAT_FN(u_sqrt_triangle) { + GET_STORAGE(double, ntri); /* Obtain a pointer to private storage and cast it to the correct type. */ + double change = 0; + + EXEC_THROUGH_EDGES(head, e, node3, { /* For each edge of head... */ + change += IS_UNDIRECTED_EDGE(node3, tail); /* A shared neighbor between tail and head exists if an edge exists between node3 and tail. */ + }); + + /* Thus the toggle of (tail, head) will change the triangle statistic. */ + *ntri += edgestate ? -change : change; +} + +/* Cleanup; done automatically when STORAGE != NULL, so not needed in this case. */ +/* F_CHANGESTAT_FN(f_sqrt_triangle) { */ +/* Free(STORAGE); */ +/* } */ + +/* Change statistic: can refer to its storage. */ +C_CHANGESTAT_FN(c_sqrt_triangle) { + GET_STORAGE(double, ntri); /* Obtain a pointer to private storage and cast it to the correct type. */ + double change = 0; + + EXEC_THROUGH_EDGES(head, e, node3, { /* For each edge of head... */ + change += IS_UNDIRECTED_EDGE(node3, tail); /* A shared neighbor between tail and head exists if an edge exists between node3 and tail. */ + }); + + /* Now, we add the new value of the statistic and subtract the old. */ + CHANGE_STAT[0] += sqrt(*ntri + (edgestate ? -change : change)) - sqrt(*ntri); +} + + +/***************** + Square root of triangles: The auxiliary approach. +*****************/ + +/***** + This is an auxiliary term .triangle which exports the number of + triangles that other change statistics can use. Notice that it + doesn't have a c_ or a d_ function. + *****/ + +/* Initializer: store the number of triangles in its private storage. */ +I_CHANGESTAT_FN(i__triangle) { + ALLOC_AUX_STORAGE(1, double, ntri); /* Allocate public storage for 1 number to store the current number of triangles. */ + + /* NB: In an undirected network, tail < head. */ + EXEC_THROUGH_NET_EDGES(tail, head, e1, { /* For each edge (tail < head) in the network... */ + EXEC_THROUGH_FOUTEDGES(head, e, node3, { /* and each edge (head < node3)... */ + *ntri += IS_OUTEDGE(tail, node3); /* a triangle is formed if an edge exists between node3 and tail. */ + }); + }); + + /* *ntri now contains the number of triangles in the network. */ +} + +/* Updater: will be called when toggling (tail, head) with state edgestate is imminent. */ +U_CHANGESTAT_FN(u__triangle) { + GET_AUX_STORAGE(double, ntri); /* Obtain a pointer to its public storage and cast it to the correct type. */ + double change = 0; + + EXEC_THROUGH_EDGES(head, e, node3, { /* For each edge of head... */ + change += IS_UNDIRECTED_EDGE(node3, tail); /* A shared neighbor between tail and head exists if an edge exists between node3 and tail. */ + }); + + /* Thus the toggle of (tail, head) will change the triangle statistic. */ + *ntri += edgestate ? -change : change; +} + +/* Cleanup; done automatically when STORAGE != NULL, so not needed in this case. */ +/* F_CHANGESTAT_FN(f__triangle) { */ +/* Free(STORAGE); */ +/* } */ + +/***** + This is a change statistic that expects a the number of triangles in its public storage. + *****/ + +C_CHANGESTAT_FN(c_sqrt_triangle_aux) { + GET_AUX_STORAGE(double, ntri); /* Obtain a pointer to public storage and cast it to the correct type. */ + double change = 0; + + EXEC_THROUGH_EDGES(head, e, node3, { /* For each edge of head... */ + change += IS_UNDIRECTED_EDGE(node3, tail); /* A shared neighbor between tail and head exists if an edge exists between node3 and tail. */ + }); + + /* Now, we add the new value of the statistic and subtract the old. */ + CHANGE_STAT[0] += sqrt(*ntri + (edgestate ? -change : change)) - sqrt(*ntri); +}