# Validation `ergmx` is tested against R's ergm 4.12, ergm.multi 0.3.0 and tergm 4.2.2 on ergm's own networks (flomarriage, samplk1 to samplk3, faux.mesa.high and faux.dixon.high, the latter two also with missing dyads), multinets' `linked_sim`, Davis's Southern Women, a simulated bipartite network, Sampson's monks as a sample and as a series of networks, and ergm.multi's 225 weekday household networks (Goeyvaerts), and against exact results on networks small enough to enumerate every possible network. The R scripts in `scripts/` store R's results in `tests/data/`, and the test suite compares. ## Against R's ergm | Check | Result | |---|---| | Statistics of ergm's terms and operators, with their options (`levels=`, `by=`, `homophily=`, `attr=`, `nodes=`...), and interactions, 97 models | identical to R's `summary()` (to 1e-12), names included, except `transitive`, `intransitive`, `dyadcov`'s `utri` and `ltri`, and the edgewise RTP statistics (below) | | MPLE, 60 models, with offsets, interactions, `F()`, `S()`, `N()` (with `subset`, `offset` and `label`), tergm's operators (also with missing dyads imputed by `NA.impute`), `blocks`, `Dyads`, `fixedas`, `fixallbut`, `blockdiag`, bipartite networks and curved terms | identical to R (to 1e-6; curved models to 1e-3, where R's optimizer stops on a flat optimum: ergmx's pseudo-likelihood is at least R's) | | Dyad-independent MLE, standard errors, log-likelihood and BIC, 25 models, with offsets, interactions, `N()`'s `subset` and `offset`, `NA.impute`, `blocks`, `Dyads`, `fixedas`, `fixallbut`, `blockdiag`, `S()`, missing dyads, bipartite networks, samples and series of networks | identical to R (to 1e-6; standard errors to 1e-3, the tolerance of R's `glm`) | | Monte Carlo MLE, 7 models (4 undirected, 3 directed), 3 to 10 seeds each | within 0.12 standard errors of R's estimates; standard errors 0.89 to 1.12 times R's | | Monte Carlo MLE with constraints (`bd`, `blocks`, `degrees`, `odegrees`), missing dyads, offsets, `F()`, `esp` and multilevel models, 12 models x 5 seeds | all converged, within 0.17 standard errors of R's estimates; standard errors 0.91 to 1.09 times R's | | Monte Carlo MLE of `concurrent`, `twopath`, OSP shared partners, bipartite models and curved models (gwesp, directed and undirected, and gwb1degree), 10 models x 3 to 5 seeds | all converged, within 0.2 standard errors of R's estimates; standard errors 0.90 to 1.12 times R's, except the curved bipartite model, whose likelihood is nearly flat in the decay (R's standard error of the decay is twice its estimate) | | Monte Carlo MLE of samples of networks (Sampson's monks; 225 households with `N()` linear models) and of series (tergm's CMLE, one and two transitions), 4 models x 5 seeds | all converged, within 0.10 standard errors of R's estimates; standard errors 0.94 to 1.05 times R's | | Monte Carlo MLE with `N()`'s `subset` and `label`, and of a series with missing dyads imputed (`NA.impute="next"`) and missing in the networks transitioned to, 2 models x 5 seeds | all converged, within 0.09 standard errors of R's estimates; standard errors 0.93 to 1.03 times R's | | Monte Carlo MLE of a multilevel model with `S()` (each level and the ties between them, with gwesp and gwb1dsp), 5 seeds | all converged, within 0.08 standard errors of R's estimates; standard errors 0.96 to 1.04 times R's | | Monte Carlo MLE with `degree(by=)`, and the constraints `edges`, `b1degrees` and `bd(attribs=)`, 4 models x 5 seeds | all converged, within 0.16 standard errors of R's estimates; standard errors 0.93 to 1.11 times R's | | Goodness of fit, directed and undirected | observed distributions and p-values identical to R's; simulated distributions agree within Monte Carlo error | | ergm.multi's `gofN()`, 225 households, the model's statistics and five others, 2,000 simulations | observed statistics identical to R's (but `degree0` and `isolates`, below); fitted values, variances and Pearson residuals agree within R's Monte Carlo error | | tergm's EGMME, formation and persistence of edges with a mean duration, and with `degree(1)` too | within 0.4 standard errors of the mean of R's estimates over 3 seeds; standard errors within R's range, which spans a factor of 1.7 between its seeds; the edges model's estimate is also the exact one (below) | | Conditional tie probabilities (`predict()`), 5 models: undirected, directed, missing dyads, curved, 20 household networks | identical to R's `predict()` (to 1e-12), on the same dyads (R's formula method leaves out missing dyads) | | Average marginal effects, 4 models | identical to ergMargins' (to its 5 significant digits); the standard errors match a numerical delta method (to 1e-5), and ergMargins' when the probabilities are held fixed, as it holds them | | Confidence intervals, 4 models | identical to R's `confint()` (to 1e-3, the accuracy of R's `glm` standard errors) | | Tables of results | identical to texreg's `screenreg()`, `texreg()` and `htmlreg()`, character for character, on exactly fitted models | | Contrastive divergence | a fixed point of its defining equation; the Monte Carlo MLE from a CD start matches R's | R's own standard errors vary by about 15% between seeds on the small networks, so the comparison of standard errors is only as tight as R allows. ergm imputes missing dyads at random before its MPLE, so MPLEs of dyad-dependent models with missing dyads are not compared. R fits two of the curved models only from a contrastive divergence start; ergmx fits them from its default start. ergm.multi and tergm can't fit curved terms inside `N()` or `Form()` (their MPLE's gradient is not finite, and contrastive divergence stops with an error), so only their statistics are compared; ergmx's fits of them are checked below. ### Discrepancies in ergm 4.12.0 - **Edgewise RTP statistics.** ergm 4.12.0's `esp`, `gwesp` and `nsp` with `type = "RTP"` depend on the order of the vertices: relabeling faux.dixon.high changes `summary(net ~ esp(0:3, type = "RTP"))` from 889, 269, 37, 2 to 886, 273, 36, 2. Its shared-partner cache, on by default, reads them with the wrong key; ergm's development version fixes it ([statnet/ergm#656](https://github.com/statnet/ergm/pull/656)). With the cache off (`term.options = list(cache.sp = FALSE)`), ergm 4.12.0 gives 847, 309, 39, 2, as the definition computed in R does, and ergmx's `esp`, `gwesp`, `nsp` and `gwnsp` of type RTP are identical to ergm's. - **`transitive`.** ergm documents it as the number of transitive triads (types 030T, 120D, 120U and 300: 371 on faux.dixon.high, 13 on samplk3, counted by R's igraph) but computes the number of transitive triples, the same as `ttriple` (1,254 and 49). ergmx counts the triads, which match the triad census on every directed network of 4 vertices, and warns that ergm differs; `ttriple` reproduces ergm's statistic. - **`intransitive`.** The same: documented as the number of intransitive triads (types 111D, 201, 111U, 021C and 030C: 5,991 on faux.dixon.high, 70 on samplk3, by igraph's triad census) but computed as the number of intransitive triples, twopath minus ttriple (6,752 and 92). ergmx counts the triads, which match R's `triadcensus` and igraph's, and warns. - **`dyadcov`.** Documented as the covariate summed over mutual, upper-triangular asymmetric and lower-triangular asymmetric dyads; on a network whose only tie goes from vertex 1 to vertex 2, in the upper triangle, ergm's `utri` is 0 and its `ltri` 1. ergmx follows the documentation, so its `utri` and `ltri` are ergm's `ltri` and `utri`, and warns. - **`gofN()`'s `degree0` and `isolates`.** ergm.multi's `gofN()` leaves out the statistics of the empty network, which for `degree0` and `isolates` are the network size: in the households, it reports the observed and fitted isolates as their counts minus the size (-4 for a household of four without isolates). The Pearson residuals are unaffected. ergmx reports the counts, and warns. - **`smalldiff`** is documented as counting differences less than the cutoff, but its code counts those at most the cutoff (193 ties of faux.mesa.high with grades at most 2 apart, against 178 strictly less); ergmx follows the code, as the argument's description, "maximum", does. ## Against exact results | Check | Result | |---|---| | MCMC stationary distribution, with 3 mixtures of TNT and triadic proposals | expected statistics match exact enumeration of every network on 3 and 6 vertices (undirected) and 4 vertices (directed) | | Constrained MCMC: `bd` (directed and undirected, also by alter class), `degrees` (both), `odegrees`, `idegrees`, `b1degrees`, `b2degrees`, `edges`, `blocks`, and sampling conditional on observed dyads | expected statistics match exact enumeration of every network each constraint allows; a test that only reversing cyclic triples can pass checks that directed `degrees` reaches every network | | `-inf` offsets | the MLE equals the logistic regression without the forbidden dyads | | Bipartite MCMC | expected statistics match exact enumeration of the 512 networks of a 3 x 3 bipartite network | | Curved terms | eta . counts equals theta times the fixed-decay statistic exactly, and the Jacobian matches finite differences; inside `N()`, with a linear model, too | | Several networks: `Networks()` and `NetSeries()` | the MCMC matches exact enumeration of every network with ties within the networks (two undirected networks; two transitions of directed networks, with discordant-dyad and triadic proposals), and never ties networks together | | Curved terms inside `N()` and `Form()` | at the curved MPLE's decay, the fixed-decay MPLE has the same coefficients (to 1e-8); a series simulated with decay 0.7 gives back 0.64 | | Dynamic simulation | one time step is a draw from the transition's model (exact enumeration); with tergm's stopping rule, ties form and dissolve at the dyad-independent model's exact rates (within 5% and 8%) | | Dyad-independent CMLE | the log-odds that non-ties became ties and that ties persisted, exactly | | Forward simulation with time trends (`lm=~.Time`) | each step's formation and persistence rates are those predicted for its time, within Monte Carlo error | | `NA.impute` | each option fills the dyads it can, in turn, as tergm's (`next` from the next wave, also through a wave missing them too) | | `N()`'s `offset` | the offset statistics' coefficients are 1 and the Jacobian matches finite differences, for curved terms too; the MCMC with `subset` and `offset` matches exact enumeration | | `gofN()` | each network's fitted value and variance of its edges are the dyad-independent model's exact ones (within Monte Carlo error); with missing dyads, the observed value and its variance are the exact conditional ones | | EGMME | the edges model, whose equilibrium is known (formation and persistence probabilities that give the density and mean duration), within 0.5 standard errors; its standard errors are the delta method's from the exact stationary covariances; tie ages follow every tie of a simulation | | The sampler's tracked statistics for every new term (degree ranges, by attribute and with homophily, triad census, trails, Simmelian and transitive ties, covariate ranges, distinct neighbour types, the bipartite terms...) | equal the statistics recomputed from scratch on the sampled networks (to 1e-9), so the change statistics of removing ties are right too | | Constraints `edges`, `b1degrees`, `b2degrees`, `bd(attribs=)`, `fixedas`, `fixallbut`, `Dyads`, `blockdiag`, `observed` | every simulated network keeps what the constraint fixes, and moves elsewhere | | MPNet's directed configurations (35, from its manual) and EXTA, EXTB and ASAXASB | equal to their definitions computed from adjacency matrices (to 1e-12) on random directed two-level networks; the MCMC matches exact enumeration of every directed network on 2 + 2 vertices with affiliations from A to B; `S()` between two sets of a directed network matches R's | | Estimated decays of the multilevel configurations (18 terms) | eta . counts equals theta times the fixed-decay statistic exactly; at the curved MPLE's decay, the fixed-decay MPLE has the same coefficients; networks simulated with a decay of 0.7 give it back within 1.1 standard errors | | Goodness of fit by level | the observed distributions within each level and of the affiliations equal igraph's | | MPNet's 16 multilevel configurations, which no R package has | equal to their definitions computed from the levels' adjacency matrices (to 1e-12), on random two-level networks and `linked_sim`, for three decays; the MCMC matches exact enumeration of every network on 6 vertices of two levels (and one of neither), with S() too | | Log-likelihood, directed and undirected | unbiased against exact enumeration (6 and 4 vertices), with standard errors that match the spread across seeds | ## The log-likelihood For 5 dyad-dependent models, `ergmx`'s log-likelihood is within one standard error of a high-precision estimate (128 points along the path, 4 times the samples). R's ergm, which integrates with a 16-point midpoint rule, is off by 0.1 to 2.1 units: | Model | R ergm | High-precision | ergmx (3 seeds) | |---|---|---|---| | samplk3, edges + mutual | -133.99 | -133.88 | -133.86, -133.90, -133.87 | | samplk3, + gwesp(0.5) | -131.36 | -131.04 | -130.99, -131.04, -131.15 | | faux.mesa.high, gwesp(0.5) | -867.56 | -867.09 | -867.13, -867.15, -867.09 | | faux.mesa.high, gwdegree + gwesp | -866.01 | -865.27 | -865.36, -865.20, -865.74 | | faux.dixon.high (directed), gwesp(0.1) | -4206.10 | -4208.20 | -4208.56, -4208.57, -4207.76 | ## Performance `benchmarks/benchmark.R` and `benchmarks/benchmark.py` fit three models with both packages' defaults, which include the log-likelihood; median of 3 seeds on an Apple M4 Pro. R's ergm runs on one thread, and the single-threaded ergmx run is limited to one thread too. | Model | Vertices | R ergm 4.12 | ergmx, 1 thread | ergmx, all threads | Largest difference | |---|---|---|---|---|---| | faux.mesa.high, gwesp(0.5) | 205 | 16.8 s | 10.1 s (1.7x) | 2.4 s (7.1x) | 0.04 SE | | faux.magnolia.high, gwesp(0.25) | 1,461 | 17.3 s | 14.0 s (1.2x) | 3.3 s (5.2x) | 0.07 SE | | faux.dixon.high (directed), gwesp(0.1) | 248 | 142 s | 80 s (1.8x) | 17 s (8.6x) | 0.07 SE | | 225 household networks (ergm.multi), with `N()` linear models | 2 to 7 each | 23.3 s | 13.2 s (1.8x) | 2.9 s (8.0x) | 0.06 SE | The last column is the largest difference between the two packages' estimates, in R's standard errors (the households: one R run, from `scripts/r_reference.R`). On small models, such as the series of Sampson's monks, both take about a second. `ergmx`'s log-likelihood samples about 17 times more than ergm's, which is what makes it accurate; without the log-likelihood on either side, a single thread was 1.9 to 3.6 times faster than R on these models. ### Larger networks `benchmarks/make_scale.py`, `scale.R` and `scale.py` fit the magnolia model to faux.magnolia.high and to networks like it of 2,500 to 10,000 vertices (simulated from R's fit, with the same mean degree), and a model with homophily and gwesp to 500 classrooms of 20 students, combined with `Networks()` (`N()` in ergm.multi); one seed each, the same machine: | Network | Vertices | MPLE: R | ergmx | MLE: R | ergmx, 1 thread | ergmx, all threads | Largest difference | |---|---|---|---|---|---|---|---| | faux.magnolia.high | 1,461 | 0.2 s | 0.01 s | 17.2 s | 13.9 s (1.2x) | 3.2 s (5.5x) | 0.10 SE | | magnolia-like | 2,500 | 0.4 s | 0.02 s | 11.4 s | 6.6 s (1.7x) | 1.4 s (8.4x) | 0.06 SE | | magnolia-like | 5,000 | 1.4 s | 0.08 s | 12.0 s | 26.8 s (0.4x) | 5.8 s (2.1x) | 0.11 SE | | magnolia-like | 10,000 | 5.6 s | 0.3 s | 36.9 s | 27.5 s (1.3x) | 5.6 s (6.6x) | 0.07 SE | | 500 classrooms | 10,000 | 2.6 s | 0.02 s | 91.3 s | 59.7 s (1.5x) | 15.3 s (6.0x) | 0.02 SE | The MPLE is 17 to 110 times faster: its data are the distinct rows of change statistics, built in parallel threads, and the logistic regression runs on those. The Monte Carlo MLEs take a varying number of iterations: on 5,000 vertices, ergmx's took 8 where the others took 4 or 5, and half of each fit's time is the log-likelihood. Memory stays below 0.6 GB for all of them.