From 9083d918478957cbbff91c6d42bcb26626822221 Mon Sep 17 00:00:00 2001
From: "Pavel N. Krivitsky"
Date: Mon, 5 Feb 2024 10:40:09 +1100
Subject: [PATCH] DyadGen that requires intersection tracking now "upgrades"
UnsrtEL to HashEL after a certain (currently hardcoded) number of linear
searches.
fixes statnet/ergm#548
---
DESCRIPTION | 4 +-
inst/include/ergm_dyadgen.h | 63 ++++++++++++++-------
src/ergm_dyadgen.c | 106 +++++++++++++++++++++++++++---------
3 files changed, 125 insertions(+), 48 deletions(-)
diff --git a/DESCRIPTION b/DESCRIPTION
index aa3b30f44..42528d217 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,6 +1,6 @@
Package: ergm
-Version: 4.6-7325
-Date: 2024-02-03
+Version: 4.6-7337
+Date: 2024-02-05
Title: Fit, Simulate and Diagnose Exponential-Family Models for Networks
Authors@R: c(
person(c("Mark", "S."), "Handcock", role=c("aut"), email="handcock@stat.ucla.edu"),
diff --git a/inst/include/ergm_dyadgen.h b/inst/include/ergm_dyadgen.h
index 48e93bdb4..372297699 100644
--- a/inst/include/ergm_dyadgen.h
+++ b/inst/include/ergm_dyadgen.h
@@ -15,14 +15,17 @@
#include "ergm_wtedgetree.h"
#include "ergm_rlebdm.h"
#include "ergm_edgelist.h"
-#include "ergm_unsorted_edgelist.h"
+#include "ergm_hash_edgelist.h"
#include "ergm_changestat.h"
#include "ergm_wtchangestat.h"
typedef enum{RandDyadGen, WtRandDyadGen, RLEBDM1DGen, WtRLEBDM1DGen, EdgeListGen, WtEdgeListGen} DyadGenType;
+typedef enum{NoELDyadGen, UnsrtELDyadGen, HashELDyadGen} DyadGenInterType;
+#define DYADGEN_MISSES_BEFORE_UPGRADE 8
typedef struct {
DyadGenType type;
+ DyadGenInterType intertype;
union {
Network *b;
WtNetwork *w;
@@ -32,7 +35,10 @@ typedef struct {
int *el;
} dyads;
Dyad ndyads;
- UnsrtEL *intersect;
+ union {
+ UnsrtEL *uel;
+ HashEL *hel;
+ } inter;
Rboolean sleeping;
} DyadGen;
@@ -70,7 +76,7 @@ static inline void DyadGenRandDyad(Vertex *tail, Vertex *head, DyadGen *gen){
error("Undefined dyad generator type.");
}
- if(gen->intersect){ /* If we are maintaining an unsorted edgelist (which also implies that we are *not* in a *RandDyadGen mode)... */
+ if(gen->intertype == UnsrtELDyadGen){ /* If we are maintaining an unsorted edgelist (which also implies that we are *not* in a *RandDyadGen mode)... */
/* Use the appropriate function to check if we had selected an extant edge, */
Rboolean extant;
switch(gen->type){
@@ -87,15 +93,14 @@ static inline void DyadGenRandDyad(Vertex *tail, Vertex *head, DyadGen *gen){
}
/* ... and if so, reselect it from the unsorted edgelist. */
- if(extant) UnsrtELGetRand(tail, head, gen->intersect);
+ if(extant) UnsrtELGetRand(tail, head, gen->inter.uel);
}
}
static inline Edge DyadGenEdgecount(DyadGen *gen){
- if(gen->intersect){
- return gen->intersect->nedges;
- }else{
+ switch(gen->intertype){
+ case NoELDyadGen:
switch(gen->type){
case RandDyadGen:
case RLEBDM1DGen:
@@ -108,7 +113,10 @@ static inline Edge DyadGenEdgecount(DyadGen *gen){
default:
error("Undefined dyad generator type.");
}
+ case UnsrtELDyadGen: return gen->inter.uel->nedges;
+ case HashELDyadGen: return gen->inter.hel->list->nedges;
}
+ return 0; // Fix a warning.
}
@@ -126,16 +134,22 @@ static inline void DyadGenRandEdge(Vertex *tail, Vertex *head, DyadGen *gen){
case RLEBDM1DGen:
case EdgeListGen:
{
- if(gen->intersect) UnsrtELGetRand(tail, head, gen->intersect);
- else GetRandEdge(tail, head, gen->nwp.b);
+ switch(gen->intertype){
+ case NoELDyadGen: GetRandEdge(tail, head, gen->nwp.b); break;
+ case UnsrtELDyadGen: UnsrtELGetRand(tail, head, gen->inter.uel); break;
+ case HashELDyadGen: HashELGetRand(tail, head, gen->inter.hel); break;
+ }
}
break;
case WtRLEBDM1DGen:
case WtEdgeListGen:
{
double dummy;
- if(gen->intersect) UnsrtELGetRand(tail, head, gen->intersect);
- else WtGetRandEdge(tail, head, &dummy, gen->nwp.w);
+ switch(gen->intertype){
+ case NoELDyadGen: WtGetRandEdge(tail, head, &dummy, gen->nwp.w); break;
+ case UnsrtELDyadGen: UnsrtELGetRand(tail, head, gen->inter.uel); break;
+ case HashELDyadGen: HashELGetRand(tail, head, gen->inter.hel); break;
+ }
}
break;
default:
@@ -190,18 +204,27 @@ static inline void DyadGenRandWtEdge(Vertex *tail, Vertex *head, double *weight,
break;
case RLEBDM1DGen:
case EdgeListGen:
- if(gen->intersect) UnsrtELGetRand(tail, head, gen->intersect);
- else GetRandEdge(tail, head, gen->nwp.b);
+ switch(gen->intertype){
+ case NoELDyadGen: GetRandEdge(tail, head, gen->nwp.b); break;
+ case UnsrtELDyadGen: UnsrtELGetRand(tail, head, gen->inter.uel); break;
+ case HashELDyadGen: HashELGetRand(tail, head, gen->inter.hel); break;
+ }
*weight = 1;
break;
case WtRLEBDM1DGen:
case WtEdgeListGen:
- if(gen->intersect){
- UnsrtELGetRand(tail, head, gen->intersect);
- *weight = WtGetEdge(*tail, *head, gen->nwp.w);
- }
- else WtGetRandEdge(tail, head, weight, gen->nwp.w);
- break;
+ switch(gen->intertype){
+ case NoELDyadGen: WtGetRandEdge(tail, head, weight, gen->nwp.w); break;
+ case UnsrtELDyadGen:
+ UnsrtELGetRand(tail, head, gen->inter.uel);
+ *weight = WtGetEdge(*tail, *head, gen->nwp.w);
+ break;
+ case HashELDyadGen:
+ HashELGetRand(tail, head, gen->inter.hel);
+ *weight = WtGetEdge(*tail, *head, gen->nwp.w);
+ break;
+ }
+ break;
default:
error("Undefined dyad generator type.");
}
@@ -232,7 +255,7 @@ static inline void DyadGenSleep(DyadGen *gen){
that it must keep track of what they are.
FIXME: Maybe we should *always* maintain an UnsrtEL? Need to benchmark. */
- if(!gen->intersect) DyadGenSetUpIntersect(gen, NULL, TRUE);
+ if(gen->intertype != NoELDyadGen) DyadGenSetUpIntersect(gen, NULL, TRUE);
}
static inline void DyadGenWake(DyadGen *gen){
diff --git a/src/ergm_dyadgen.c b/src/ergm_dyadgen.c
index 645cc3437..0d4f8d4ad 100644
--- a/src/ergm_dyadgen.c
+++ b/src/ergm_dyadgen.c
@@ -16,23 +16,23 @@ void DyadGenSetUpIntersect(DyadGen *gen, void *track_nwp, Rboolean force){
switch(gen->type){
case RandDyadGen:
case WtRandDyadGen:
- gen->intersect = NULL;
+ gen->inter.uel = NULL;
break;
case EdgeListGen:
case RLEBDM1DGen:
{
Network *nwp = track_nwp ? track_nwp : gen->nwp.b;
gen->nwp.b = nwp;
- gen->intersect = UnsrtELInitialize(0, NULL, NULL, FALSE);
+ gen->inter.uel = UnsrtELInitialize(0, NULL, NULL, FALSE);
EXEC_THROUGH_NET_EDGES(t, h, e, {
if(DyadGenSearch(t, h, gen)){
- UnsrtELInsert(t, h, gen->intersect);
+ UnsrtELInsert(t, h, gen->inter.uel);
}
});
- if(!force && gen->intersect->nedges==EDGECOUNT(nwp)){ // There are no ties in the initial network that are fixed.
- UnsrtELDestroy(gen->intersect);
- gen->intersect = NULL; // "Signal" that there is no discordance network.
+ if(!force && gen->inter.uel->nedges==EDGECOUNT(nwp)){ // There are no ties in the initial network that are fixed.
+ UnsrtELDestroy(gen->inter.uel);
+ gen->inter.uel = NULL; // "Signal" that there is no discordance network.
}else{
AddOnNetworkEdgeChange(nwp, (OnNetworkEdgeChange) DyadGenUpdate, gen, INT_MAX);
}
@@ -43,17 +43,17 @@ void DyadGenSetUpIntersect(DyadGen *gen, void *track_nwp, Rboolean force){
{
WtNetwork *nwp = track_nwp ? track_nwp : gen->nwp.w;
gen->nwp.w = nwp;
- gen->intersect = UnsrtELInitialize(0, NULL, NULL, FALSE);
+ gen->inter.uel = UnsrtELInitialize(0, NULL, NULL, FALSE);
WtEXEC_THROUGH_NET_EDGES(t, h, w, e, {
(void) e;
if(w!=0 && DyadGenSearch(t, h, gen)){
- UnsrtELInsert(t, h, gen->intersect);
+ UnsrtELInsert(t, h, gen->inter.uel);
}
});
- if(!force && gen->intersect->nedges==EDGECOUNT(nwp)){ // There are no ties in the initial network that are fixed.
- UnsrtELDestroy(gen->intersect);
- gen->intersect = NULL; // "Signal" that there is no discordance network.
+ if(!force && gen->inter.uel->nedges==EDGECOUNT(nwp)){ // There are no ties in the initial network that are fixed.
+ UnsrtELDestroy(gen->inter.uel);
+ gen->inter.uel = NULL; // "Signal" that there is no discordance network.
}else{
AddOnWtNetworkEdgeChange(nwp, (OnWtNetworkEdgeChange) WtDyadGenUpdate, gen, INT_MAX);
}
@@ -64,21 +64,49 @@ void DyadGenSetUpIntersect(DyadGen *gen, void *track_nwp, Rboolean force){
}
}
+void DyadGenUpgradeIntersect(DyadGen *gen){
+ if(gen->intertype == UnsrtELDyadGen){
+ Rboolean directed;
+ switch(gen->type){
+ case RandDyadGen:
+ case EdgeListGen:
+ case RLEBDM1DGen:
+ {
+ Network *nwp = gen->nwp.b;
+ directed = DIRECTED;
+ }
+ break;
+ case WtRandDyadGen:
+ case WtEdgeListGen:
+ case WtRLEBDM1DGen:
+ {
+ WtNetwork *nwp = gen->nwp.w;
+ directed = DIRECTED;
+ }
+ break;
+ default:
+ error("Undefined dyad generator type.");
+ }
+ gen->inter.hel = UnsrtELIntoHashEL(gen->inter.uel, directed);
+ gen->intertype = HashELDyadGen;
+ }
+}
+
DyadGen *DyadGenInitialize(DyadGenType type, void *dyads, void *track_nwp){
DyadGen *gen = Calloc(1, DyadGen);
gen->type = type;
+ gen->intertype = NoELDyadGen;
gen->sleeping = FALSE;
+ gen->inter.uel = NULL;
switch(gen->type){
case RandDyadGen:
gen->nwp.b = dyads;
gen->ndyads = DYADCOUNT(gen->nwp.b);
- gen->intersect = NULL;
break;
case WtRandDyadGen:
gen->nwp.w = dyads;
gen->ndyads = DYADCOUNT(gen->nwp.w);
- gen->intersect = NULL;
break;
case RLEBDM1DGen:
case WtRLEBDM1DGen:
@@ -137,21 +165,21 @@ DyadGen *DyadGenInitializeR(SEXP pR, void *any_nwp, Rboolean el){
void DyadGenDestroy(DyadGen *gen){
- if(gen->intersect){
+ if(gen->intertype != NoELDyadGen){
+ switch(gen->intertype){
+ case UnsrtELDyadGen: UnsrtELDestroy(gen->inter.uel); break;
+ case HashELDyadGen: HashELDestroy(gen->inter.hel); break;
+ case NoELDyadGen: break;
+ }
+
switch(gen->type){
case RLEBDM1DGen:
case EdgeListGen:
- if(gen->intersect){
- UnsrtELDestroy(gen->intersect);
- DeleteOnNetworkEdgeChange(gen->nwp.b, (OnNetworkEdgeChange) DyadGenUpdate, gen);
- }
+ DeleteOnNetworkEdgeChange(gen->nwp.b, (OnNetworkEdgeChange) DyadGenUpdate, gen);
break;
case WtRLEBDM1DGen:
case WtEdgeListGen:
- if(gen->intersect){
- UnsrtELDestroy(gen->intersect);
- DeleteOnWtNetworkEdgeChange(gen->nwp.w, (OnWtNetworkEdgeChange) WtDyadGenUpdate, gen);
- }
+ DeleteOnWtNetworkEdgeChange(gen->nwp.w, (OnWtNetworkEdgeChange) WtDyadGenUpdate, gen);
break;
case RandDyadGen:
case WtRandDyadGen:
@@ -166,13 +194,39 @@ void DyadGenDestroy(DyadGen *gen){
void DyadGenUpdate(Vertex tail, Vertex head, DyadGen *gen, Network *nwp, Rboolean edgestate){
if(gen->sleeping) return;
- if(edgestate) UnsrtELDelete(tail, head, gen->intersect); // Deleting
- else UnsrtELInsert(tail, head, gen->intersect); // Inserting
+
+ switch(gen->intertype){
+ case UnsrtELDyadGen:
+ if(edgestate) UnsrtELDelete(tail, head, gen->inter.uel); // Deleting
+ else UnsrtELInsert(tail, head, gen->inter.uel); // Inserting
+ if(gen->inter.uel->linsearch >= DYADGEN_MISSES_BEFORE_UPGRADE) DyadGenUpgradeIntersect(gen);
+ break;
+
+ case HashELDyadGen:
+ if(edgestate) HashELDelete(tail, head, gen->inter.hel); // Deleting
+ else HashELInsert(tail, head, gen->inter.hel); // Inserting
+ break;
+
+ case NoELDyadGen: break;
+ }
}
void WtDyadGenUpdate(Vertex tail, Vertex head, double weight, DyadGen *gen, WtNetwork *nwp, double edgestate){
if(gen->sleeping) return;
- if(edgestate!=0 && weight==0) UnsrtELDelete(tail, head, gen->intersect); // Deleting
- else if(edgestate==0 && weight!=0) UnsrtELInsert(tail, head, gen->intersect); // Inserting
+
+ switch(gen->intertype){
+ case UnsrtELDyadGen:
+ if(edgestate!=0 && weight==0) UnsrtELDelete(tail, head, gen->inter.uel); // Deleting
+ else if(edgestate==0 && weight!=0) UnsrtELInsert(tail, head, gen->inter.uel); // Inserting
+ if(gen->inter.uel->linsearch >= DYADGEN_MISSES_BEFORE_UPGRADE) DyadGenUpgradeIntersect(gen);
+ break;
+
+ case HashELDyadGen:
+ if(edgestate!=0 && weight==0) HashELDelete(tail, head, gen->inter.hel); // Deleting
+ else if(edgestate==0 && weight!=0) HashELInsert(tail, head, gen->inter.hel); // Inserting
+ break;
+
+ case NoELDyadGen: break;
+ }
}