This website converted the following original .R scripts into .rmd files.

  • RScriptSNADescriptives.R
  • Rscript01DataFormat.R
  • Rscript02SienaVariableFormat.R
  • Rscript03SienaRunModel.R
  • Rscript04SienaBehaviour.R

Please visit GitHub for the latest .R files.

To download this .rmd file use the button in the upper right corner of your screen.
To copy the R code in the code-chunks, use the copy-paste button in the upper right corner of the code-chunks.


0.1 Data

All files (data, scripts, etc.) can also be found on Github


0.2 Contact

Specific questions with respect to the .rmd files can be addressed to: Jochem Tolsma.

For questions on RSiena please visit the designated GitHub page.


0.3 Introduction

RscriptSNADescriptives.R: a script for the introduction to RSiena

  • Rscript01DataFormat.R is followed by
  • RScriptSNADescriptives.R, code for descriptive analysis of the data, and
  • Rscript02SienaVariableFormat.R, which formats data and specifies the model, and
  • Rscript03SienaRunModel.R, which runs the model and estimates parameters
  • Rscript04SienaBehaviour.R, which illustrates an example of analysing the coevolution of networks and behaviour
  • The entire model fitting is summarised at the end of Rscript03SienaRunModel.R (without comments).

This is an R script for getting started with RSiena, written by Robin Gauthier, Tom Snijders, Ruth Ripley, Johan Koskinen, and Paulina Preciado, with some examples borrowed from Christian Steglich.
Lines starting with # are not processed by R but treated as comments. The script has a lot of explanation of R possibilities that will be familiar for readers well acquainted with R, and can be skipped by them.


0.4 Getting Started

For this script, you will need the data read and modified in the script Rscript01DataFormat.R. If you have already ran that script, you may load the required workspace:

load("WorkspaceRscript01.RData")

Alternatively, you may run the following code-chunk:

library(RSiena)  #to access the build-in data
library(network)  #to convert
friend.data.w1 <- s501
friend.data.w2 <- s502
friend.data.w3 <- s503
drink <- s50a
smoke <- s50s

net1 <- as.network(friend.data.w1)
net2 <- as.network(friend.data.w2)
net3 <- as.network(friend.data.w3)

0.5 Visual inspection

A visual inspection of the adjacency matrices can sometimes be useful. This will, for example, help in highlighting outliers with respect to outdegrees or indegrees, if there are any of such outliers.

sociomatrix net1

require(sna)
plot.sociomatrix(net1, drawlab = F, diaglab = F, xlab = "friendship t1")

sociomatrix net2

require(sna)
plot.sociomatrix(net2, drawlab = F, diaglab = F, xlab = "friendship t2")

sociomatrix net3

require(sna)
plot.sociomatrix(net3, drawlab = F, diaglab = F, xlab = "friendship t3")


0.6 Plotting network data

The class network with attributes names, class, has special methods associated with it. While plot( friend.data.w1 ) only produces a rather dull plot of the first two columns, plot( net1, xlab = 'friendship t1' ) produces a nice sociogram.

# The class,
class(net1)
#> [1] "network"
# with attributes
attributes(net1)
#> $names
#> [1] "mel" "gal" "val" "iel" "oel"
#> 
#> $class
#> [1] "network"
# has special methods associated with it.  While
plot(friend.data.w1, main = "plot( friend.data.w1)")

# only produces a rather dull plot of the first two columns,
plot(net1, xlab = "friendship t1", main = "plot(net1)")

# produces a nice sociogram

0.6.1 Tweaking network plot

  • Add the attribute drink to the network object: net1 %v% "drink" <- drink[ , 1 ]
  • Color the nodes by drink: plot( net1, vertex.col = "drink", xlab = 'friendship t1' )
  • Scale the vertex by degree of nodes!
    • First calculate degree: deg <- rowSums( as.matrix( net1 ) )# NB: rowSums() is defined for class matrix
    • Have a look at the degree distribution: table( deg, useNA = 'always' )

Now do the desired plot: plot( net1, vertex.col = "drink", vertex.cex = (deg + 1)/1.5 )

# add the attribute drink to the network object

net1 %v% "drink" <- drink[, 1]

# color the nodes by drink

plot(net1, vertex.col = "drink", xlab = "friendship t1")

# Now let's color the nodes by drink and scale the vertex by degree of nodes!  First calculate
# degree:

deg <- rowSums(as.matrix(net1))  # NB:  rowSums() is defined for class matrix

# have a look at the degree distribution

table(deg, useNA = "always")
#> deg
#>    0    1    2    3    4    5 <NA> 
#>    4    8   17   15    4    2    0
# Now do the desired plot:

plot(net1, vertex.col = "drink", vertex.cex = (deg + 1)/1.5)


0.7 Plot the three waves of data

# Add drink to waves 2 and 3
net2 %v% "drink" <- drink[, 2]
net3 %v% "drink" <- drink[, 3]
deg2 <- rowSums(as.matrix(net2))
deg3 <- rowSums(as.matrix(net3))

# Create a set of panels ( 1 row by 3 columns, or 3 columns by 1 row)
par(mfrow = c(1, 3))

# creating three plots after each other will place them in consecutive panels
plot(net1, vertex.col = "drink", vertex.cex = (deg + 1)/1.5)
plot(net2, vertex.col = "drink", vertex.cex = (deg2 + 1)/1.5)
plot(net3, vertex.col = "drink", vertex.cex = (deg3 + 1)/1.5)

Each time we make a plot the coordinates move - because always the starting values are random. We can also save coordinates and use them for later plotting:

par(mfrow = c(1, 3))
coordin <- plot(net1, vertex.col = "drink", vertex.cex = (deg + 1)/1.5)
plot(net2, coord = coordin, vertex.col = "drink", vertex.cex = (deg2 + 1)/1.5)
plot(net3, coord = coordin, vertex.col = "drink", vertex.cex = (deg3 + 1)/1.5)

To get coordinates based on all three waves: coordin <- plot( net1 + net2 + net3 ).
For more plotting options, try the gplot function in the sna library. And try ?gplot and ?gplot.layout.


0.8 Basic network statistics

The package sna can be used for a variety of descriptions and analyses. The following are examples of some important graph level statistics

0.8.1 density

gden(net1)  # density
#> [1] 0.04612245

0.8.2 symmetry and reciprocity

grecip(net1)  # proportion of dyads that are symmetric
#>       Mut 
#> 0.9714286
grecip(net1, measure = "dyadic.nonnull")  # reciprocity, ignoring the null dyads
#>      Mut 
#> 0.527027

0.8.3 transitivity

gtrans(net1)  # transitivity
#> [1] 0.3873874

0.9 Dyad and triad census

0.9.1 Dyad census

dyad.census(net1)
#>      Mut Asym Null
#> [1,]  39   35 1151

0.9.2 Triad census

triad.census(net1)
#>        003  012  102 021D 021U 021C 111D 111U 030T 030C 201 120D 120U 120C 210 300
#> [1,] 16243 1470 1724    5   18   21   42   30    5    0  15    6    5    2   9   5

0.10 (out)degree distribution

Of course for a symmetric network outdegree=indegree.

outdegree <- degree(net1, cmode = "outdegree")
outdegree  #outgoing ties of each note
#>  [1] 2 2 2 2 1 1 3 1 2 3 3 3 0 3 3 2 5 2 4 0 1 4 1 5 3 3 3 1 3 4 3 4 2 2 1 2 3 2 1 3 2 2 2 2 3 3 0 2
#> [49] 2 0
hist(outdegree)

quantile(outdegree)
#>   0%  25%  50%  75% 100% 
#>    0    2    2    3    5

0.11 distance and connectivity

0.11.1 distance

dist <- geodist(net1, inf.replace = Inf, count.paths = TRUE)
# calculate the geodesic distance (shortest path length) matrix
head(dist$gd)
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14] [,15] [,16] [,17]
#> [1,]    0    2  Inf  Inf  Inf  Inf    3  Inf  Inf     2     1   Inf   Inf     1     2     2   Inf
#> [2,]  Inf    0  Inf  Inf  Inf  Inf    1  Inf  Inf     3     1   Inf   Inf   Inf     2     2   Inf
#> [3,]  Inf  Inf    0    1  Inf  Inf  Inf  Inf    1   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf
#> [4,]  Inf  Inf    1    0  Inf  Inf  Inf  Inf    1   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf
#> [5,]  Inf    7  Inf  Inf    0  Inf    7  Inf  Inf     8     6   Inf   Inf   Inf     7     7     4
#> [6,]  Inf  Inf  Inf  Inf  Inf    0  Inf    1  Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf
#>      [,18] [,19] [,20] [,21] [,22] [,23] [,24] [,25] [,26] [,27] [,28] [,29] [,30] [,31] [,32]
#> [1,]   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf     5   Inf   Inf     5     4   Inf   Inf
#> [2,]   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf     6   Inf   Inf     6     5   Inf   Inf
#> [3,]   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf
#> [4,]   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf
#> [5,]     5     5   Inf     2     3     6     5     3     6   Inf   Inf     7     6     2     1
#> [6,]   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf
#>      [,33] [,34] [,35] [,36] [,37] [,38] [,39] [,40] [,41] [,42] [,43] [,44] [,45] [,46] [,47]
#> [1,]     3   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf     4   Inf     4   Inf   Inf   Inf
#> [2,]     4   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf     2   Inf     2   Inf   Inf   Inf
#> [3,]   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf
#> [4,]   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf
#> [5,]     7     3     6   Inf     2   Inf   Inf   Inf   Inf     8   Inf     7   Inf   Inf   Inf
#> [6,]   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf   Inf
#>      [,48] [,49] [,50]
#> [1,]   Inf   Inf   Inf
#> [2,]   Inf   Inf   Inf
#> [3,]   Inf   Inf   Inf
#> [4,]   Inf   Inf   Inf
#> [5,]   Inf   Inf   Inf
#> [6,]   Inf   Inf   Inf
# matrix of geodesic distances
head(dist$counts)
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14] [,15] [,16] [,17]
#> [1,]    1    1    0    0    0    0    1    0    0     1     1     0     0     1     1     1     0
#> [2,]    0    1    0    0    0    0    1    0    0     1     1     0     0     0     1     1     0
#> [3,]    0    0    1    1    0    0    0    0    1     0     0     0     0     0     0     0     0
#> [4,]    0    0    1    1    0    0    0    0    1     0     0     0     0     0     0     0     0
#> [5,]    0    1    0    0    1    0    1    0    0     2     1     0     0     0     1     1     1
#> [6,]    0    0    0    0    0    1    0    1    0     0     0     0     0     0     0     0     0
#>      [,18] [,19] [,20] [,21] [,22] [,23] [,24] [,25] [,26] [,27] [,28] [,29] [,30] [,31] [,32]
#> [1,]     0     0     0     0     0     0     0     0     1     0     0     1     1     0     0
#> [2,]     0     0     0     0     0     0     0     0     1     0     0     1     1     0     0
#> [3,]     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [4,]     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [5,]     1     1     0     1     1     1     1     1     1     0     0     2     1     1     1
#> [6,]     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#>      [,33] [,34] [,35] [,36] [,37] [,38] [,39] [,40] [,41] [,42] [,43] [,44] [,45] [,46] [,47]
#> [1,]     1     0     0     0     0     0     0     0     0     1     0     1     0     0     0
#> [2,]     1     0     0     0     0     0     0     0     0     1     0     1     0     0     0
#> [3,]     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [4,]     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [5,]     1     1     1     0     1     0     0     0     0     2     0     1     0     0     0
#> [6,]     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#>      [,48] [,49] [,50]
#> [1,]     0     0     0
#> [2,]     0     0     0
#> [3,]     0     0     0
#> [4,]     0     0     0
#> [5,]     0     0     0
#> [6,]     0     0     0
table(dist$counts)
#> 
#>    0    1    2    3    4    6 
#> 1811  558  100   20    8    3

0.11.2 connectivity

Reachability matrix, see ?reachability.

reach <- reachability(net1)  # calculate the reachability matrix
head(reach)
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14] [,15] [,16] [,17]
#> [1,]    1    1    0    0    0    0    1    0    0     1     1     0     0     1     1     1     0
#> [2,]    0    1    0    0    0    0    1    0    0     1     1     0     0     0     1     1     0
#> [3,]    0    0    1    1    0    0    0    0    1     0     0     0     0     0     0     0     0
#> [4,]    0    0    1    1    0    0    0    0    1     0     0     0     0     0     0     0     0
#> [5,]    0    1    0    0    1    0    1    0    0     1     1     0     0     0     1     1     1
#> [6,]    0    0    0    0    0    1    0    1    0     0     0     0     0     0     0     0     0
#>      [,18] [,19] [,20] [,21] [,22] [,23] [,24] [,25] [,26] [,27] [,28] [,29] [,30] [,31] [,32]
#> [1,]     0     0     0     0     0     0     0     0     1     0     0     1     1     0     0
#> [2,]     0     0     0     0     0     0     0     0     1     0     0     1     1     0     0
#> [3,]     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [4,]     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [5,]     1     1     0     1     1     1     1     1     1     0     0     1     1     1     1
#> [6,]     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#>      [,33] [,34] [,35] [,36] [,37] [,38] [,39] [,40] [,41] [,42] [,43] [,44] [,45] [,46] [,47]
#> [1,]     1     0     0     0     0     0     0     0     0     1     0     1     0     0     0
#> [2,]     1     0     0     0     0     0     0     0     0     1     0     1     0     0     0
#> [3,]     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [4,]     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [5,]     1     1     1     0     1     0     0     0     0     1     0     1     0     0     0
#> [6,]     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#>      [,48] [,49] [,50]
#> [1,]     0     0     0
#> [2,]     0     0     0
#> [3,]     0     0     0
#> [4,]     0     0     0
#> [5,]     0     0     0
#> [6,]     0     0     0

0.12 Network autocorrelation

Moran’s autocorrelation for outgoing ties:

nacf(net1, drink[, 1], type = "moran", neighborhood.type = "out")[2]
#>         1 
#> 0.4331431
nacf(net2, drink[, 2], type = "moran", neighborhood.type = "out")[2]
#>         1 
#> 0.3977745
nacf(net3, drink[, 3], type = "moran", neighborhood.type = "out")[2]
#>         1 
#> 0.4258017

Moran’s autocorrelation for outgoing and incoming ties:

nacf(net1, drink[, 1], type = "moran", neighborhood.type = "total")[2]
#>         1 
#> 0.3809011
nacf(net2, drink[, 2], type = "moran", neighborhood.type = "total")[2]
#>         1 
#> 0.3914719
nacf(net3, drink[, 3], type = "moran", neighborhood.type = "total")[2]
#>         1 
#> 0.4162185

Next: VariableFormat FOR PREPARING DATA FOR RSIENA


LS0tDQp0aXRsZTogIkRlc2NyaXB0aXZlcyINCmF1dGhvcjogJ1tKb2NoZW0gVG9sc21hXShodHRwczovL3d3dy5qb2NoZW10b2xzbWEubmwpIC0gUmFkYm91ZCBVbml2ZXJzaXR5LCB0aGUgTmV0aGVybGFuZHMnDQpiaWJsaW9ncmFwaHk6IHJlZmVyZW5jZXMuYmliDQpkYXRlOiAiTGFzdCBjb21waWxlZCBvbiBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCLCAlWScpYCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiAgdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KLS0tDQoNCmBgYHtyLCBnbG9iYWxzZXR0aW5ncywgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQpsaWJyYXJ5KGtuaXRyKQ0Kb3B0c19jaHVuayRzZXQodGlkeS5vcHRzPWxpc3Qod2lkdGguY3V0b2ZmPTEwMCksdGlkeT1UUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSxjb21tZW50ID0gIiM+IiwgY2FjaGU9VFJVRSwgY2xhc3Muc291cmNlPWMoInRlc3QiKSwgY2xhc3Mub3V0cHV0PWMoInRlc3QyIikpDQpvcHRpb25zKHdpZHRoID0gMTAwKQ0KcmdsOjpzZXR1cEtuaXRyKCkNCg0KDQpjb2xvcml6ZSA8LSBmdW5jdGlvbih4LCBjb2xvcikgew0KICBpZiAoa25pdHI6OmlzX2xhdGV4X291dHB1dCgpKSB7DQogICAgc3ByaW50ZigiXFx0ZXh0Y29sb3J7JXN9eyVzfSIsIGNvbG9yLCB4KQ0KICB9IGVsc2UgaWYgKGtuaXRyOjppc19odG1sX291dHB1dCgpKSB7DQogICAgc3ByaW50ZigiPHNwYW4gc3R5bGU9J2NvbG9yOiAlczsnPiVzPC9zcGFuPiIsIGNvbG9yLCANCiAgICAgIHgpDQogIH0gZWxzZSB4DQp9DQoNCmBgYA0KDQpgYGB7ciBrbGlwcHksIGVjaG89RkFMU0UsIGluY2x1ZGU9VFJVRX0NCmtsaXBweTo6a2xpcHB5KHBvc2l0aW9uID0gYygndG9wJywgJ3JpZ2h0JykpDQoja2xpcHB5OjprbGlwcHkoY29sb3IgPSAnZGFya3JlZCcpDQoja2xpcHB5OjprbGlwcHkodG9vbHRpcF9tZXNzYWdlID0gJ0NsaWNrIHRvIGNvcHknLCB0b29sdGlwX3N1Y2Nlc3MgPSAnRG9uZScpDQpgYGANCg0KYGBge2NzcywgZWNobz1GQUxTRX0NCnByZS50ZXN0IHsNCiAgbWF4LWhlaWdodDogMzAwcHg7DQogIG92ZXJmbG93LXk6IGF1dG87DQogIG92ZXJmbG93LXg6IGF1dG87DQogIG1hcmdpbjogMTBweDsNCn0NCg0KcHJlLnRlc3QyIHsNCiAgbWF4LWhlaWdodDogMzAwcHg7DQogIG92ZXJmbG93LXk6IGF1dG87DQogIG92ZXJmbG93LXg6IGF1dG87DQogIG1hcmdpbjogMTBweDsNCiAgYmFja2dyb3VuZC1jb2xvcjogd2hpdGUNCn0NCg0KDQpoMSwgLmgxLCBoMiwgLmgyLCBoMywgLmgzIHsNCiAgICBtYXJnaW4tdG9wOiAyNHB4Ow0KfQ0KDQoNCmBgYA0KDQotLS0tDQoNClRoaXMgd2Vic2l0ZSBjb252ZXJ0ZWQgdGhlIGZvbGxvd2luZyBvcmlnaW5hbCAuUiBzY3JpcHRzIGludG8gLnJtZCBmaWxlcy4gDQoNCi0gUlNjcmlwdFNOQURlc2NyaXB0aXZlcy5SICANCi0gUnNjcmlwdDAxRGF0YUZvcm1hdC5SICANCi0gUnNjcmlwdDAyU2llbmFWYXJpYWJsZUZvcm1hdC5SICANCi0gUnNjcmlwdDAzU2llbmFSdW5Nb2RlbC5SICANCi0gUnNjcmlwdDA0U2llbmFCZWhhdmlvdXIuUg0KDQpQbGVhc2UgdmlzaXQgW0dpdEh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL3NubGFiLW5sL3JzaWVuYS90cmVlL21haW4vaW5zdC9zY3JpcHRzKSBmb3IgdGhlIGxhdGVzdCAuUiBmaWxlcy4gDQoNCj4gVG8gZG93bmxvYWQgdGhpcyAucm1kIGZpbGUgdXNlIHRoZSBidXR0b24gaW4gdGhlIHVwcGVyIHJpZ2h0IGNvcm5lciBvZiB5b3VyIHNjcmVlbi4gIA0KPiBUbyBjb3B5IHRoZSBSIGNvZGUgaW4gdGhlIGNvZGUtY2h1bmtzLCB1c2UgdGhlIGNvcHktcGFzdGUgYnV0dG9uIGluIHRoZSB1cHBlciByaWdodCBjb3JuZXIgb2YgdGhlIGNvZGUtY2h1bmtzLiANCg0KLS0tLQ0KDQojIyBEYXRhDQpBbGwgZmlsZXMgKGRhdGEsIHNjcmlwdHMsIGV0Yy4pIGNhbiBhbHNvIGJlIGZvdW5kIG9uIFtHaXRodWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9Kb2NoZW1Ub2xzbWEvUnNpZW5hLXNjcmlwdHMpDQoNCi0tLS0NCg0KIyMgQ29udGFjdA0KU3BlY2lmaWMgcXVlc3Rpb25zIHdpdGggcmVzcGVjdCB0byB0aGUgLnJtZCBmaWxlcyBjYW4gYmUgYWRkcmVzc2VkIHRvOiBbSm9jaGVtIFRvbHNtYV0obWFpbHRvOmoudG9sc21hQHJ1Lm5sKS4gIA0KDQpGb3IgcXVlc3Rpb25zIG9uIFJTaWVuYSBwbGVhc2UgdmlzaXQgdGhlIGRlc2lnbmF0ZWQgW0dpdEh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL3NubGFiLW5sL3JzaWVuYSkgcGFnZS4gDQoNCi0tLS0gIA0KDQojIyBJbnRyb2R1Y3Rpb24gDQoNCioqUnNjcmlwdFNOQURlc2NyaXB0aXZlcy5SOiBhIHNjcmlwdCBmb3IgdGhlIGludHJvZHVjdGlvbiB0byBSU2llbmEqKiAgDQoNCi0gKipSc2NyaXB0MDFEYXRhRm9ybWF0LlIqKiBpcyBmb2xsb3dlZCBieSAgDQotICoqUlNjcmlwdFNOQURlc2NyaXB0aXZlcy5SKiosIGNvZGUgZm9yIGRlc2NyaXB0aXZlIGFuYWx5c2lzIG9mIHRoZSBkYXRhLCBhbmQgIA0KLSAqKlJzY3JpcHQwMlNpZW5hVmFyaWFibGVGb3JtYXQuUioqLCB3aGljaCBmb3JtYXRzIGRhdGEgYW5kIHNwZWNpZmllcyB0aGUgbW9kZWwsIGFuZCAgDQotICoqUnNjcmlwdDAzU2llbmFSdW5Nb2RlbC5SKiosIHdoaWNoIHJ1bnMgdGhlIG1vZGVsIGFuZCBlc3RpbWF0ZXMgcGFyYW1ldGVycyAgDQotICoqUnNjcmlwdDA0U2llbmFCZWhhdmlvdXIuUioqLCB3aGljaCBpbGx1c3RyYXRlcyBhbiBleGFtcGxlIG9mIGFuYWx5c2luZyB0aGUgY29ldm9sdXRpb24gb2YgbmV0d29ya3MgYW5kIGJlaGF2aW91ciAgDQotIFRoZSBlbnRpcmUgbW9kZWwgZml0dGluZyBpcyBzdW1tYXJpc2VkIGF0IHRoZSBlbmQgb2YgKipSc2NyaXB0MDNTaWVuYVJ1bk1vZGVsLlIqKiAod2l0aG91dCBjb21tZW50cykuIA0KDQpUaGlzIGlzIGFuIFIgc2NyaXB0IGZvciBnZXR0aW5nIHN0YXJ0ZWQgd2l0aCBSU2llbmEsIHdyaXR0ZW4gYnkgUm9iaW4gR2F1dGhpZXIsIFRvbSBTbmlqZGVycywgUnV0aCBSaXBsZXksIEpvaGFuIEtvc2tpbmVuLCBhbmQgUGF1bGluYSBQcmVjaWFkbywgd2l0aCBzb21lIGV4YW1wbGVzIGJvcnJvd2VkIGZyb20gQ2hyaXN0aWFuIFN0ZWdsaWNoLiAgDQpMaW5lcyBzdGFydGluZyB3aXRoICMgYXJlIG5vdCBwcm9jZXNzZWQgYnkgUiBidXQgdHJlYXRlZCBhcyBjb21tZW50cy4gVGhlIHNjcmlwdCBoYXMgYSBsb3Qgb2YgZXhwbGFuYXRpb24gb2YgUiBwb3NzaWJpbGl0aWVzIHRoYXQgd2lsbCBiZSBmYW1pbGlhciBmb3IgcmVhZGVycyB3ZWxsIGFjcXVhaW50ZWQgd2l0aCBSLCBhbmQgY2FuIGJlIHNraXBwZWQgYnkgdGhlbS4gIA0KDQotLS0gIA0KDQojIyBHZXR0aW5nIFN0YXJ0ZWQgIA0KDQpGb3IgdGhpcyBzY3JpcHQsIHlvdSB3aWxsIG5lZWQgdGhlIGRhdGEgcmVhZCBhbmQgbW9kaWZpZWQgaW4gdGhlIHNjcmlwdCBSc2NyaXB0MDFEYXRhRm9ybWF0LlIuIElmIHlvdSBoYXZlIGFscmVhZHkgcmFuIHRoYXQgc2NyaXB0LCB5b3UgbWF5IGxvYWQgdGhlIHJlcXVpcmVkIHdvcmtzcGFjZTogDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KbG9hZCgiV29ya3NwYWNlUnNjcmlwdDAxLlJEYXRhIikNCmBgYA0KICANCiAgDQpBbHRlcm5hdGl2ZWx5LCB5b3UgbWF5IHJ1biB0aGUgZm9sbG93aW5nIGNvZGUtY2h1bms6IA0KDQpgYGB7cn0NCmxpYnJhcnkoUlNpZW5hKSAjdG8gYWNjZXNzIHRoZSBidWlsZC1pbiBkYXRhDQpsaWJyYXJ5KG5ldHdvcmspICN0byBjb252ZXJ0DQpmcmllbmQuZGF0YS53MSA8LSBzNTAxDQpmcmllbmQuZGF0YS53MiA8LSBzNTAyDQpmcmllbmQuZGF0YS53MyA8LSBzNTAzDQpkcmluayA8LSBzNTBhDQpzbW9rZSA8LSBzNTBzDQoNCm5ldDEgPC0gYXMubmV0d29yayhmcmllbmQuZGF0YS53MSkNCm5ldDIgPC0gYXMubmV0d29yayhmcmllbmQuZGF0YS53MikNCm5ldDMgPC0gYXMubmV0d29yayhmcmllbmQuZGF0YS53MykNCmBgYA0KLS0tICANCg0KDQojIyBWaXN1YWwgaW5zcGVjdGlvbg0KDQpBIHZpc3VhbCBpbnNwZWN0aW9uIG9mIHRoZSBhZGphY2VuY3kgbWF0cmljZXMgY2FuIHNvbWV0aW1lcyBiZSB1c2VmdWwuIFRoaXMgd2lsbCwgZm9yIGV4YW1wbGUsIGhlbHAgaW4gaGlnaGxpZ2h0aW5nIG91dGxpZXJzIHdpdGggcmVzcGVjdCB0byBvdXRkZWdyZWVzIG9yIGluZGVncmVlcywgaWYgdGhlcmUgYXJlIGFueSBvZiBzdWNoIG91dGxpZXJzLiAgDQoNCioqc29jaW9tYXRyaXggbmV0MSoqIA0KDQpgYGB7cn0NCnJlcXVpcmUoc25hKQ0KcGxvdC5zb2Npb21hdHJpeCggbmV0MSwgZHJhd2xhYiA9IEYsIGRpYWdsYWIgPSBGLCB4bGFiID0gJ2ZyaWVuZHNoaXAgdDEnICkNCmBgYA0KDQoqKnNvY2lvbWF0cml4IG5ldDIqKg0KDQpgYGB7cn0NCnJlcXVpcmUoc25hKQ0KcGxvdC5zb2Npb21hdHJpeCggbmV0MixkcmF3bGFiID0gRiwgZGlhZ2xhYiA9IEYsIHhsYWIgPSAnZnJpZW5kc2hpcCB0MicgKQ0KYGBgDQoNCioqc29jaW9tYXRyaXggbmV0MyoqDQoNCmBgYHtyfQ0KcmVxdWlyZShzbmEpDQpwbG90LnNvY2lvbWF0cml4KCBuZXQzLGRyYXdsYWIgPSBGLCBkaWFnbGFiID0gRiwgeGxhYiA9ICdmcmllbmRzaGlwIHQzJyApDQoNCmBgYA0KDQotLS0gIA0KDQoNCiMjIFBsb3R0aW5nIG5ldHdvcmsgZGF0YQ0KDQpUaGUgY2xhc3MgKmByIGNsYXNzKG5ldDEpYCogd2l0aCBhdHRyaWJ1dGVzICpgciBuYW1lcyhhdHRyaWJ1dGVzKCBuZXQxICkpYCosIGhhcyBzcGVjaWFsIG1ldGhvZHMgYXNzb2NpYXRlZCB3aXRoIGl0LiBXaGlsZSAgYHBsb3QoIGZyaWVuZC5kYXRhLncxIClgIG9ubHkgcHJvZHVjZXMgYSByYXRoZXIgZHVsbCBwbG90IG9mDQp0aGUgZmlyc3QgdHdvIGNvbHVtbnMsIGBwbG90KCBuZXQxLCB4bGFiID0gJ2ZyaWVuZHNoaXAgdDEnIClgIHByb2R1Y2VzIGEgbmljZSBzb2Npb2dyYW0uIA0KDQpgYGB7cn0NCiMgVGhlIGNsYXNzLA0KY2xhc3MoIG5ldDEgKQ0KIyB3aXRoIGF0dHJpYnV0ZXMNCmF0dHJpYnV0ZXMoIG5ldDEgKQ0KIyBoYXMgc3BlY2lhbCBtZXRob2RzIGFzc29jaWF0ZWQgd2l0aCBpdC4NCiMgV2hpbGUgIA0KcGxvdCggZnJpZW5kLmRhdGEudzEsIG1haW49InBsb3QoIGZyaWVuZC5kYXRhLncxKSIgKSANCiMgb25seSBwcm9kdWNlcyBhIHJhdGhlciBkdWxsIHBsb3Qgb2YgdGhlIGZpcnN0IHR3byBjb2x1bW5zLA0KcGxvdCggbmV0MSwgeGxhYiA9ICdmcmllbmRzaGlwIHQxJywgbWFpbj0icGxvdChuZXQxKSIgKQ0KIyBwcm9kdWNlcyBhIG5pY2Ugc29jaW9ncmFtDQpgYGANCiAgDQogIA0KDQojIyMgVHdlYWtpbmcgbmV0d29yayBwbG90ICANCg0KLSBBZGQgdGhlIGF0dHJpYnV0ZSBkcmluayB0byB0aGUgbmV0d29yayBvYmplY3Q6IGBuZXQxICV2JSAiZHJpbmsiIDwtIGRyaW5rWyAsIDEgXWAgIA0KLSBDb2xvciB0aGUgbm9kZXMgYnkgZHJpbms6IGBwbG90KCBuZXQxLCB2ZXJ0ZXguY29sID0gImRyaW5rIiwgeGxhYiA9ICdmcmllbmRzaGlwIHQxJyApYCAgDQotIFNjYWxlIHRoZSB2ZXJ0ZXggYnkgZGVncmVlIG9mIG5vZGVzIQ0KICAqIEZpcnN0IGNhbGN1bGF0ZSBkZWdyZWU6IGBkZWcgPC0gcm93U3VtcyggYXMubWF0cml4KCBuZXQxICkgKSMgTkI6ICByb3dTdW1zKCkgaXMgZGVmaW5lZCBmb3IgY2xhc3MgbWF0cml4YCANCiAgKiBIYXZlIGEgbG9vayBhdCB0aGUgZGVncmVlIGRpc3RyaWJ1dGlvbjogYHRhYmxlKCBkZWcsIHVzZU5BID0gJ2Fsd2F5cycgKWAgDQoNCk5vdyBkbyB0aGUgZGVzaXJlZCBwbG90OiBgcGxvdCggbmV0MSwgdmVydGV4LmNvbCA9ICJkcmluayIsIHZlcnRleC5jZXggPSAoZGVnICsgMSkvMS41IClgIA0KDQpgYGB7cn0NCiMgYWRkIHRoZSBhdHRyaWJ1dGUgZHJpbmsgdG8gdGhlIG5ldHdvcmsgb2JqZWN0DQoNCm5ldDEgJXYlICJkcmluayIgPC0gZHJpbmtbICwgMSBdDQoNCiMgY29sb3IgdGhlIG5vZGVzIGJ5IGRyaW5rDQoNCnBsb3QoIG5ldDEsIHZlcnRleC5jb2wgPSAiZHJpbmsiLCB4bGFiID0gJ2ZyaWVuZHNoaXAgdDEnICkNCg0KDQojIE5vdyBsZXQncyBjb2xvciB0aGUgbm9kZXMgYnkgZHJpbmsgYW5kIHNjYWxlIHRoZSB2ZXJ0ZXggYnkgZGVncmVlIG9mIG5vZGVzIQ0KIw0KIyBGaXJzdCBjYWxjdWxhdGUgZGVncmVlOg0KDQpkZWcgPC0gcm93U3VtcyggYXMubWF0cml4KCBuZXQxICkgKSMgTkI6ICByb3dTdW1zKCkgaXMgZGVmaW5lZCBmb3IgY2xhc3MgbWF0cml4DQoNCiMgaGF2ZSBhIGxvb2sgYXQgdGhlIGRlZ3JlZSBkaXN0cmlidXRpb24NCg0KdGFibGUoIGRlZywgdXNlTkEgPSAnYWx3YXlzJyApDQoNCiMgTm93IGRvIHRoZSBkZXNpcmVkIHBsb3Q6DQoNCnBsb3QoIG5ldDEsIHZlcnRleC5jb2wgPSAiZHJpbmsiLCB2ZXJ0ZXguY2V4ID0gKGRlZyArIDEpLzEuNSApDQpgYGANCg0KLS0tICANCg0KIyMgUGxvdCB0aGUgdGhyZWUgd2F2ZXMgb2YgZGF0YSANCg0KYGBge3J9DQojIEFkZCBkcmluayB0byB3YXZlcyAyIGFuZCAzDQpuZXQyICV2JSAiZHJpbmsiIDwtIGRyaW5rWyAsIDIgXQ0KbmV0MyAldiUgImRyaW5rIiA8LSBkcmlua1sgLCAzIF0NCmRlZzIgPC0gcm93U3VtcyggYXMubWF0cml4KCBuZXQyICkgKQ0KZGVnMyA8LSByb3dTdW1zKCBhcy5tYXRyaXgoIG5ldDMgKSApDQoNCiMgQ3JlYXRlIGEgc2V0IG9mIHBhbmVscyAoIDEgcm93IGJ5IDMgY29sdW1ucywgb3IgMyBjb2x1bW5zIGJ5IDEgcm93KQ0KcGFyKCBtZnJvdyA9IGMoIDEsIDMgKSApDQoNCiMgY3JlYXRpbmcgdGhyZWUgcGxvdHMgYWZ0ZXIgZWFjaCBvdGhlciB3aWxsIHBsYWNlIHRoZW0gaW4gY29uc2VjdXRpdmUgcGFuZWxzDQpwbG90KCBuZXQxLCB2ZXJ0ZXguY29sID0gImRyaW5rIiwgdmVydGV4LmNleCA9IChkZWcgKyAxKS8xLjUgKQ0KcGxvdCggbmV0MiwgdmVydGV4LmNvbCA9ICJkcmluayIsIHZlcnRleC5jZXggPSAoZGVnMiArIDEpLzEuNSApDQpwbG90KCBuZXQzLCB2ZXJ0ZXguY29sID0gImRyaW5rIiwgdmVydGV4LmNleCA9IChkZWczICsgMSkvMS41ICkNCg0KYGBgDQoNCkVhY2ggdGltZSB3ZSBtYWtlIGEgcGxvdCB0aGUgY29vcmRpbmF0ZXMgbW92ZSAtIGJlY2F1c2UgYWx3YXlzIHRoZSBzdGFydGluZyB2YWx1ZXMgYXJlIHJhbmRvbS4gV2UgY2FuIGFsc28gc2F2ZSBjb29yZGluYXRlcyBhbmQgdXNlIHRoZW0gZm9yIGxhdGVyIHBsb3R0aW5nOg0KDQpgYGB7cn0NCnBhciggbWZyb3cgPSBjKCAxLCAzICkgKQ0KY29vcmRpbiA8LSAgcGxvdCggbmV0MSwgdmVydGV4LmNvbCA9ICJkcmluayIsIHZlcnRleC5jZXggPSAoZGVnICsxICkvMS41ICkNCnBsb3QoIG5ldDIsIGNvb3JkID0gY29vcmRpbiwgdmVydGV4LmNvbCA9ICJkcmluayIsIHZlcnRleC5jZXggPSAoZGVnMiArIDEpLzEuNSApDQpwbG90KCBuZXQzLCBjb29yZCA9IGNvb3JkaW4sIHZlcnRleC5jb2wgPSAiZHJpbmsiLCB2ZXJ0ZXguY2V4ID0gKGRlZzMgKyAxKSAvMS41ICkNCmBgYA0KDQpUbyBnZXQgY29vcmRpbmF0ZXMgYmFzZWQgb24gYWxsIHRocmVlIHdhdmVzOiBgY29vcmRpbiA8LSAgcGxvdCggbmV0MSArIG5ldDIgKyBuZXQzIClgLiAgDQpGb3IgbW9yZSBwbG90dGluZyBvcHRpb25zLCB0cnkgdGhlIGBncGxvdGAgZnVuY3Rpb24gaW4gdGhlIGBzbmFgIGxpYnJhcnkuIEFuZCB0cnkgYD9ncGxvdGAgYW5kIGA/Z3Bsb3QubGF5b3V0YC4NCg0KLS0tICANCg0KIyMgQmFzaWMgbmV0d29yayBzdGF0aXN0aWNzIHsudGFic2V0IC50YWJzZXQtZmFkZX0NCg0KVGhlIHBhY2thZ2UgYHNuYWAgY2FuIGJlIHVzZWQgZm9yIGEgdmFyaWV0eSBvZiBkZXNjcmlwdGlvbnMgYW5kIGFuYWx5c2VzLiBUaGUgZm9sbG93aW5nIGFyZSBleGFtcGxlcyBvZiBzb21lIGltcG9ydGFudCBncmFwaCBsZXZlbCBzdGF0aXN0aWNzDQoNCiMjIyBkZW5zaXR5DQoNCmBgYHtyfQ0KZ2RlbiggbmV0MSApICMgZGVuc2l0eQ0KYGBgDQotLS0gIA0KDQojIyMgc3ltbWV0cnkgYW5kIHJlY2lwcm9jaXR5DQoNCmBgYHtyfQ0KZ3JlY2lwKCBuZXQxICkgIyBwcm9wb3J0aW9uIG9mIGR5YWRzIHRoYXQgYXJlIHN5bW1ldHJpYw0KZ3JlY2lwKCBuZXQxLCBtZWFzdXJlID0gImR5YWRpYy5ub25udWxsIiApICMgcmVjaXByb2NpdHksIGlnbm9yaW5nIHRoZSBudWxsIGR5YWRzDQpgYGANCi0tLSAgDQoNCiMjIyB0cmFuc2l0aXZpdHkNCmBgYHtyLCB3YXJuaW5nPSJGQUxTRSJ9DQpndHJhbnMoIG5ldDEgKSAjIHRyYW5zaXRpdml0eQ0KYGBgDQotLS0gIA0KDQojIyBEeWFkIGFuZCB0cmlhZCBjZW5zdXMgey50YWJzZXQgLnRhYnNldC1mYWRlfQ0KDQojIyMgRHlhZCBjZW5zdXMNCg0KYGBge3J9DQpkeWFkLmNlbnN1cyggbmV0MSApDQpgYGANCi0tLSAgDQoNCg0KIyMjIFRyaWFkIGNlbnN1cw0KDQpgYGB7cn0NCnRyaWFkLmNlbnN1cyggbmV0MSApDQpgYGANCi0tLSAgDQoNCiMjIChvdXQpZGVncmVlIGRpc3RyaWJ1dGlvbiANCg0KT2YgY291cnNlIGZvciBhIHN5bW1ldHJpYyBuZXR3b3JrIG91dGRlZ3JlZT1pbmRlZ3JlZS4NCg0KYGBge3J9DQpvdXRkZWdyZWUgPC0gZGVncmVlKCBuZXQxLCBjbW9kZSA9ICJvdXRkZWdyZWUiICkNCm91dGRlZ3JlZSAjb3V0Z29pbmcgdGllcyBvZiBlYWNoIG5vdGUNCg0KaGlzdCggb3V0ZGVncmVlICkNCnF1YW50aWxlKCBvdXRkZWdyZWUgKQ0KYGBgDQotLS0gIA0KDQojIyBkaXN0YW5jZSBhbmQgY29ubmVjdGl2aXR5IHsudGFic2V0IC50YWJzZXQtZmFkZX0NCg0KIyMjIGRpc3RhbmNlIA0KDQpgYGB7cn0NCmRpc3QgPC0gZ2VvZGlzdChuZXQxLCBpbmYucmVwbGFjZSA9IEluZiwgY291bnQucGF0aHMgPSBUUlVFKQ0KIyBjYWxjdWxhdGUgdGhlIGdlb2Rlc2ljIGRpc3RhbmNlIChzaG9ydGVzdCBwYXRoIGxlbmd0aCkgbWF0cml4DQpoZWFkKGRpc3QkZ2QpDQojIG1hdHJpeCBvZiBnZW9kZXNpYyBkaXN0YW5jZXMNCmhlYWQoZGlzdCRjb3VudHMpDQp0YWJsZShkaXN0JGNvdW50cykNCmBgYA0KLS0tICANCg0KIyMjIGNvbm5lY3Rpdml0eSANCg0KUmVhY2hhYmlsaXR5IG1hdHJpeCwgc2VlIGA/cmVhY2hhYmlsaXR5YC4gDQoNCmBgYHtyfQ0KcmVhY2ggPC0gcmVhY2hhYmlsaXR5KCBuZXQxICkgICMgY2FsY3VsYXRlIHRoZSByZWFjaGFiaWxpdHkgbWF0cml4DQpoZWFkKHJlYWNoKQ0KYGBgDQotLS0gIA0KDQojIyBOZXR3b3JrIGF1dG9jb3JyZWxhdGlvbiAgDQoNCk1vcmFuJ3MgYXV0b2NvcnJlbGF0aW9uIGZvciBvdXRnb2luZyB0aWVzOg0KDQpgYGB7cn0NCg0KbmFjZihuZXQxLCBkcmlua1ssIDFdLCB0eXBlPSJtb3JhbiIsIG5laWdoYm9yaG9vZC50eXBlPSdvdXQnKVsyXQ0KbmFjZihuZXQyLCBkcmlua1ssIDJdLCB0eXBlPSJtb3JhbiIsIG5laWdoYm9yaG9vZC50eXBlPSdvdXQnKVsyXQ0KbmFjZihuZXQzLCBkcmlua1ssIDNdLCB0eXBlPSJtb3JhbiIsIG5laWdoYm9yaG9vZC50eXBlPSdvdXQnKVsyXQ0KYGBgDQoNCk1vcmFuJ3MgYXV0b2NvcnJlbGF0aW9uIGZvciBvdXRnb2luZyBhbmQgaW5jb21pbmcgdGllczoNCmBgYHtyfQ0KDQpuYWNmKG5ldDEsIGRyaW5rWywgMV0sIHR5cGU9Im1vcmFuIiwgbmVpZ2hib3Job29kLnR5cGU9J3RvdGFsJylbMl0NCm5hY2YobmV0MiwgZHJpbmtbLCAyXSwgdHlwZT0ibW9yYW4iLCBuZWlnaGJvcmhvb2QudHlwZT0ndG90YWwnKVsyXQ0KbmFjZihuZXQzLCBkcmlua1ssIDNdLCB0eXBlPSJtb3JhbiIsIG5laWdoYm9yaG9vZC50eXBlPSd0b3RhbCcpWzJdDQpgYGANCg0KLS0tIA0KDQpbKipOZXh0OiBWYXJpYWJsZUZvcm1hdCBGT1IgUFJFUEFSSU5HIERBVEEgRk9SIFJTSUVOQSoqXSgvcnNjcmlwdDAxLmh0bWwpDQoNCi0tLQ0K

Copyright © 2020 Jochem Tolsma