Empecemos por cargar de nuevo los datos
library(igraph)
edgelist <- readRDS("dta/edgelist-diputados.RDS")
atributos <- readRDS("dta/atributos-diputados.RDS")
La lista de aristas que hemos creado antes se puede convertir en una red en igraph
utilizando la función graph_from_edgelist
.
g <- graph_from_edgelist(edgelist, directed=FALSE)
print(g)
Exploremos ahora la red que acabamos de crear. Por ejemplo, podemos ver la red del diputado 22
.
plot(make_ego_graph(g, nodes="22", order=1)[[1]])
Lo que vemos es que nuestra red contiene muchas aristas redundantes. Para cada iniciativa, hemos definido una arista de colaboración, pero lo cierto es que lo que nos interesa es si dos diputados han colaborado alguna vez. Por eso, deberíamos simplificar el conjunto de aristas que tenemos en la red:
g <- simplify(g)
plot(make_ego_graph(g, nodes="22", order=1)[[1]])
Ahora que ya tenemos la red tal y como la queremos, empecemos a explorar las redes ego de algunos diputados. Por ejemplo, podemos explorar la red alrededor de 41
.
ego41 <- make_ego_graph(g, nodes="41", order=1)[[1]]
plot(ego41)
y ver la red de segundo orden
ego41 <- make_ego_graph(g, nodes="41", order=2)[[1]]
plot(ego41)
Lo que vemos es que aunque tenga una red de primer orden con pocos diputados, su red de segundo orden es muy amplia. El diputado 41
tiene pocos amigos, pero sus amigos son muy populares. Podemos echar un vistazo a otros diputados para ver si es un patron común. Por ejemplo, el diputado 22
que ya hemos visto antes.
ego(g, nodes="22", order=1)
ego(g, nodes="22", order=2)
A diferencia de 41
, 22
tiene una red de segundo orden que es casi igual a su red de primer orden. Sus amigos apenas tienen amigos que no sean 22
mismo. La diferencia entre 41
y 22
es crucial y nos habla de dos tipos diferentes de relaciones. 22
pertenece a una comunidad casi cerrada, mientras que 41
parece un elemento de gran importancia dentro de las conexiones de la red.
Veamos ahora cómo capturar este tipo de información de manera más general. Podemos, por ejemplo, empezar con un modo de contar el número de conexiones que tiene cada individuo en la red.
degdip <- degree(g)
y podemos ver que ese número coincide con el número de vértices que podemos recuperar a través de la red ego de un individuo determinado:
degdip[names(degdip) == "22"]
ego(g, nodes="22", order=1)
Y podemos tambien ver cómo se distribuye esta variable a lo largo de la red.
hist(degdip)
Lo que apreciamos es que existe un grupo de usuarios muy densamente conectados y una mayoría que tiene un número de conexiones mucho menor.
Este concepto de “conectividad” lo podemos capturar un poco mejor a través de la transitividad de cada nodo.
trdip <- transitivity(g, "local")
Veamos los datos aplicados a nuestro ejemplo de antes
trdip[names(degdip) == "22"] ## Alta densidad
plot(make_ego_graph(g, nodes="22", order=2)[[1]])
Lo que podemos apreciar es que la red alrededor de 22
está muy densamente conectada: los amigos de los amigos de 22
casi siempre amigos de 22
.
Podemos contrastar esta situación con el diputado 186
.
trdip[names(degdip) == "186"]
plot(make_ego_graph(g, nodes="186", order=1)[[1]]) ## Densa en primer grado
plot(make_ego_graph(g, nodes="186", order=2)[[1]]) ## No densa en segundo
En este caso, vemos que su red de primer orden también es muy densa, pero sin embargo es poco probable que el amigo de un amigo de 186
sea amigo de 186
.
¿Qué tipo de relación es más frecuente en la red? Para eso podemos pedir la transitividad global de la red
transitivity(g, "global")
Otra medida interesante es la distancia media entre dos nodos cualesquiera.
mean_distance(g)
Pero ya hemos visto que esta información puede ser poco informativa si existen grandes desigualdades en las conexiones de diferentes nodos, tal y como es el caso. Por eso quizás sea más útil ver la tabla completa de distancias. La tabla nos da el número de pares que están a una determinada distancia.
distance_table(g)
El máximo de ese vector es el diámetro de la red.
diameter(g)
Otra forma de atacar el problema es midiendo la distancia más corta entre dos vértices.
shortest_paths(g, "187", "171")
aunque por supuesto esta es una tarea que no querríamos hacer para cada par de nodos.
En su lugar, podemos mirar a las medidas de centralidad. Las tres medidas más frecuentes son la intermediación y la distancia en autovector, muy próxima a al Page rank. Es importante importante recordar que estas medidas capturan diferentes nociones de centralidad, tal y como hemos visto antes.
bc <- betweenness(g)
head(bc)
ec <- eigen_centrality(g)$vector
head(ec)
pg <- page_rank(g)$vector
head(pg)
Pero podemos verlo de forma más práctica si miramos a la centralidad de los dos diputados que hemos estudiado antes
bc[names(bc) == "22"]
bc[names(bc) == "186"]
Esto nos viene a confirmar que, quizás precisamente porque la red de 22
está muy conectada entre ella, la centralidad de 22
es en realidad muy baja. Por el contrario, 186
tiene una enorme centralidad precisamente porque su red se exiende muy rápido, a pesar de que tiene pocos amigos.
Sin embargo, las dos medidas están muy poco relacionadas:
plot(bc, ec)
¿Cómo es eso posible? Investiguemos un caso en el que las dos medidas nos dan diferentes interpretaciones:
names(bc[bc < 100 & ec > .8])
plot(make_ego_graph(g, nodes="176", order=1)[[1]])
y veamos cómo es su red ego:
ego(g, nodes="176", order=1)
ego(g, nodes="176", order=2)
ego(g, nodes="176", order=3)
¿Cómo interpretamos esto?
Detección de comunidades
Una forma de aproximarnos a la paradoja anterior es a través de la noción de comunidades. igraph
ofrece varias formas de calcular las comunidades que existen en nuestro grafo. Como hemos visto, existen varias alternativas para aproximarnos a este concepto. Una de ellas, el método de Newman-Girvan, elimina enlaces según su grado de centralidad de intermediación.
cl_ng <- cluster_edge_betweenness(g)
cl_ng
Podemos visualizar las comunidades de forma muy sencilla
plot(cl_ng, g)
Otra alternativa muy popular usa el método de Lovaina.
cl_lov <- cluster_louvain(g)
cl_lov
member <- membership(cl_lov)
head(member)
Podemos echar un vistazo a la distribución de los miembros de cada una de las comunidades que hemos estimado
table(member)
y podemos comparar los resultados de uno y otro método
table(member, membership(cl_ng))
Aunque haya algunas diferencias, mantienen una enorme similitud entre los dos.
Pero miremos a ver si entendemos cuál es la lógica de estas comunidades. Para eso echaremos mano de la base de datos de atributos que creamos antes.
louvain <- data.frame("comunidad"=as.vector(member),
"id"=attr(member, "names"))
comunidad <- merge(atributos, louvain)
Ahora, veamos qué ocurre si tabulamos cada una de las comunidades con el partido al que pertenece cada uno de los miembros:
sapply(split(comunidad, list(comunidad$comunidad)), function(x) table(x$grupo))
Los resultados que hemos visto hasta ahora deberían tener mucho más sentido.
LS0tIAp0aXRsZTogIkFuw6FsaXNpcyBkZSBsYXMgcmVkZXMgZGUgY29sYWJvcmFjacOzbiIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJUIgJWQsICVZJylgIgotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFLCBjYWNoZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGV2YWwgPSBGQUxTRSkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy5wYXRoID0gJy4vYXNzZXRzLycpCmBgYAoKRW1wZWNlbW9zIHBvciBjYXJnYXIgZGUgbnVldm8gbG9zIGRhdG9zCmBgYHtyfQpsaWJyYXJ5KGlncmFwaCkKZWRnZWxpc3QgPC0gcmVhZFJEUygiZHRhL2VkZ2VsaXN0LWRpcHV0YWRvcy5SRFMiKQphdHJpYnV0b3MgPC0gcmVhZFJEUygiZHRhL2F0cmlidXRvcy1kaXB1dGFkb3MuUkRTIikKYGBgCgpMYSBsaXN0YSBkZSBhcmlzdGFzIHF1ZSBoZW1vcyBjcmVhZG8gYW50ZXMgc2UgcHVlZGUgY29udmVydGlyIGVuIHVuYSByZWQgZW4KYGlncmFwaGAgdXRpbGl6YW5kbyBsYSBmdW5jacOzbiBgZ3JhcGhfZnJvbV9lZGdlbGlzdGAuIAoKYGBge3J9CmcgPC0gZ3JhcGhfZnJvbV9lZGdlbGlzdChlZGdlbGlzdCwgZGlyZWN0ZWQ9RkFMU0UpCnByaW50KGcpCmBgYAoKRXhwbG9yZW1vcyBhaG9yYSBsYSByZWQgcXVlIGFjYWJhbW9zIGRlIGNyZWFyLiBQb3IgZWplbXBsbywgcG9kZW1vcyB2ZXIgbGEgcmVkCmRlbCBkaXB1dGFkbyBgMjJgLiAKCmBgYHtyfQpwbG90KG1ha2VfZWdvX2dyYXBoKGcsIG5vZGVzPSIyMiIsIG9yZGVyPTEpW1sxXV0pCmBgYAoKTG8gcXVlIHZlbW9zIGVzIHF1ZSBudWVzdHJhIHJlZCBjb250aWVuZSBtdWNoYXMgYXJpc3RhcyByZWR1bmRhbnRlcy4gUGFyYSBjYWRhCmluaWNpYXRpdmEsIGhlbW9zIGRlZmluaWRvIHVuYSBhcmlzdGEgZGUgY29sYWJvcmFjacOzbiwgcGVybyBsbyBjaWVydG8gZXMgcXVlIGxvCnF1ZSBub3MgaW50ZXJlc2EgZXMgc2kgZG9zIGRpcHV0YWRvcyBoYW4gY29sYWJvcmFkbyBhbGd1bmEgdmV6LiBQb3IgZXNvLApkZWJlcsOtYW1vcyBzaW1wbGlmaWNhciBlbCBjb25qdW50byBkZSBhcmlzdGFzIHF1ZSB0ZW5lbW9zIGVuIGxhIHJlZDoKCmBgYHtyfQpnIDwtIHNpbXBsaWZ5KGcpCnBsb3QobWFrZV9lZ29fZ3JhcGgoZywgbm9kZXM9IjIyIiwgb3JkZXI9MSlbWzFdXSkKYGBgCgpBaG9yYSBxdWUgeWEgdGVuZW1vcyBsYSByZWQgdGFsIHkgY29tbyBsYSBxdWVyZW1vcywgZW1wZWNlbW9zIGEgZXhwbG9yYXIgbGFzCnJlZGVzIF9lZ29fIGRlIGFsZ3Vub3MgZGlwdXRhZG9zLiBQb3IgZWplbXBsbywgcG9kZW1vcyBleHBsb3JhciBsYSByZWQgYWxyZWRlZG9yCmRlIGA0MWAuCgpgYGB7cn0KZWdvNDEgPC0gbWFrZV9lZ29fZ3JhcGgoZywgbm9kZXM9IjQxIiwgb3JkZXI9MSlbWzFdXQpwbG90KGVnbzQxKQpgYGAKCnkgdmVyIGxhIHJlZCBkZSBzZWd1bmRvIG9yZGVuCgpgYGB7cn0KZWdvNDEgPC0gbWFrZV9lZ29fZ3JhcGgoZywgbm9kZXM9IjQxIiwgb3JkZXI9MilbWzFdXSAKcGxvdChlZ280MSkKYGBgCgpMbyBxdWUgdmVtb3MgZXMgcXVlIGF1bnF1ZSB0ZW5nYSB1bmEgcmVkIGRlIHByaW1lciBvcmRlbiBjb24gcG9jb3MgZGlwdXRhZG9zLCBzdQpyZWQgZGUgc2VndW5kbyBvcmRlbiBlcyBtdXkgYW1wbGlhLiBFbCBkaXB1dGFkbyBgNDFgIHRpZW5lIHBvY29zIF9hbWlnb3NfLCBwZXJvCnN1cyBfYW1pZ29zXyBzb24gbXV5IHBvcHVsYXJlcy4gUG9kZW1vcyBlY2hhciB1biB2aXN0YXpvIGEgb3Ryb3MgZGlwdXRhZG9zIHBhcmEKdmVyIHNpIGVzIHVuIHBhdHJvbiBjb23Dum4uIFBvciBlamVtcGxvLCBlbCBkaXB1dGFkbyBgMjJgIHF1ZSB5YSBoZW1vcyB2aXN0bwphbnRlcy4gCgpgYGB7cn0KZWdvKGcsIG5vZGVzPSIyMiIsIG9yZGVyPTEpCmVnbyhnLCBub2Rlcz0iMjIiLCBvcmRlcj0yKQpgYGAKCkEgZGlmZXJlbmNpYSBkZSBgNDFgLCBgMjJgIHRpZW5lIHVuYSByZWQgZGUgc2VndW5kbyBvcmRlbiBxdWUgZXMgY2FzaSBpZ3VhbCBhIHN1CnJlZCBkZSBwcmltZXIgb3JkZW4uIFN1cyBfYW1pZ29zXyBhcGVuYXMgdGllbmVuIF9hbWlnb3NfIHF1ZSBubyBzZWFuIGAyMmAgbWlzbW8uCkxhIGRpZmVyZW5jaWEgZW50cmUgYDQxYCB5IGAyMmAgZXMgY3J1Y2lhbCB5IG5vcyBoYWJsYSBkZSBkb3MgdGlwb3MgZGlmZXJlbnRlcwpkZSByZWxhY2lvbmVzLiBgMjJgIHBlcnRlbmVjZSBhIHVuYSBfY29tdW5pZGFkXyBjYXNpIGNlcnJhZGEsIG1pZW50cmFzIHF1ZSBgNDFgCnBhcmVjZSB1biBlbGVtZW50byBkZSBncmFuIGltcG9ydGFuY2lhIGRlbnRybyBkZSBsYXMgY29uZXhpb25lcyBkZSBsYSByZWQuIAoKVmVhbW9zIGFob3JhIGPDs21vIGNhcHR1cmFyIGVzdGUgdGlwbyBkZSBpbmZvcm1hY2nDs24gZGUgbWFuZXJhIG3DoXMgZ2VuZXJhbC4KUG9kZW1vcywgcG9yIGVqZW1wbG8sIGVtcGV6YXIgY29uIHVuIG1vZG8gZGUgY29udGFyIGVsIG7Dum1lcm8gZGUgY29uZXhpb25lcyBxdWUKdGllbmUgY2FkYSBpbmRpdmlkdW8gZW4gbGEgcmVkLiAKCmBgYHtyfQpkZWdkaXAgPC0gZGVncmVlKGcpCmBgYAp5IHBvZGVtb3MgdmVyIHF1ZSBlc2UgbsO6bWVybyBjb2luY2lkZSBjb24gZWwgbsO6bWVybyBkZSB2w6lydGljZXMgcXVlIHBvZGVtb3MgcmVjdXBlcmFyIGEgdHJhdsOpcyBkZSBsYSByZWQgX2Vnb18gZGUgdW4gaW5kaXZpZHVvIGRldGVybWluYWRvOiAKCmBgYHtyfQpkZWdkaXBbbmFtZXMoZGVnZGlwKSA9PSAiMjIiXQplZ28oZywgbm9kZXM9IjIyIiwgb3JkZXI9MSkKYGBgCgpZIHBvZGVtb3MgdGFtYmllbiB2ZXIgY8OzbW8gc2UgZGlzdHJpYnV5ZSBlc3RhIHZhcmlhYmxlIGEgbG8gbGFyZ28gZGUgbGEgcmVkLgpgYGB7cn0KaGlzdChkZWdkaXApCmBgYApMbyBxdWUgYXByZWNpYW1vcyBlcyBxdWUgZXhpc3RlIHVuIGdydXBvIGRlIHVzdWFyaW9zIG11eSBkZW5zYW1lbnRlIGNvbmVjdGFkb3MgeQp1bmEgbWF5b3LDrWEgcXVlIHRpZW5lIHVuIG7Dum1lcm8gZGUgY29uZXhpb25lcyBtdWNobyBtZW5vci4gCgpFc3RlIGNvbmNlcHRvIGRlICJjb25lY3RpdmlkYWQiIGxvIHBvZGVtb3MgY2FwdHVyYXIgdW4gcG9jbyBtZWpvciBhIHRyYXbDqXMgZGUgbGEKX3RyYW5zaXRpdmlkYWRfIGRlIGNhZGEgbm9kby4gCgpgYGB7cn0KdHJkaXAgPC0gdHJhbnNpdGl2aXR5KGcsICJsb2NhbCIpCmBgYAoKVmVhbW9zIGxvcyBkYXRvcyBhcGxpY2Fkb3MgYSBudWVzdHJvIGVqZW1wbG8gZGUgYW50ZXMKYGBge3J9CnRyZGlwW25hbWVzKGRlZ2RpcCkgPT0gIjIyIl0gIyMgQWx0YSBkZW5zaWRhZCAKcGxvdChtYWtlX2Vnb19ncmFwaChnLCBub2Rlcz0iMjIiLCBvcmRlcj0yKVtbMV1dKQpgYGAKCkxvIHF1ZSBwb2RlbW9zIGFwcmVjaWFyIGVzIHF1ZSBsYSByZWQgYWxyZWRlZG9yIGRlIGAyMmAgZXN0w6EgbXV5IGRlbnNhbWVudGUKY29uZWN0YWRhOiBsb3MgX2FtaWdvc18gZGUgbG9zIF9hbWlnb3NfIGRlIGAyMmAgY2FzaSBzaWVtcHJlIGFtaWdvcyBkZSBgMjJgLiAKClBvZGVtb3MgY29udHJhc3RhciBlc3RhIHNpdHVhY2nDs24gY29uIGVsIGRpcHV0YWRvIGAxODZgLiAKCmBgYHtyfQp0cmRpcFtuYW1lcyhkZWdkaXApID09ICIxODYiXQpwbG90KG1ha2VfZWdvX2dyYXBoKGcsIG5vZGVzPSIxODYiLCBvcmRlcj0xKVtbMV1dKSAjIyBEZW5zYSBlbiBwcmltZXIgZ3JhZG8KcGxvdChtYWtlX2Vnb19ncmFwaChnLCBub2Rlcz0iMTg2Iiwgb3JkZXI9MilbWzFdXSkgIyMgTm8gZGVuc2EgZW4gc2VndW5kbwpgYGAKCkVuIGVzdGUgY2FzbywgdmVtb3MgcXVlIHN1IHJlZCBkZSBwcmltZXIgb3JkZW4gdGFtYmnDqW4gZXMgbXV5IGRlbnNhLCBwZXJvIHNpbgplbWJhcmdvIGVzIHBvY28gcHJvYmFibGUgcXVlIGVsIF9hbWlnb18gZGUgdW4gX2FtaWdvXyBkZSBgMTg2YCBzZWEgYW1pZ28gZGUKYDE4NmAuIAoKwr9RdcOpIHRpcG8gZGUgcmVsYWNpw7NuIGVzIG3DoXMgZnJlY3VlbnRlIGVuIGxhIHJlZD8gUGFyYSBlc28gcG9kZW1vcyBwZWRpciBsYQp0cmFuc2l0aXZpZGFkIF9nbG9iYWxfIGRlIGxhIHJlZAoKYGBge3J9CnRyYW5zaXRpdml0eShnLCAiZ2xvYmFsIikKYGBgCgpPdHJhIG1lZGlkYSBpbnRlcmVzYW50ZSBlcyBsYSBkaXN0YW5jaWEgbWVkaWEgZW50cmUgZG9zIG5vZG9zIGN1YWxlc3F1aWVyYS4gCgpgYGB7cn0KbWVhbl9kaXN0YW5jZShnKQpgYGAKClBlcm8geWEgaGVtb3MgdmlzdG8gcXVlIGVzdGEgaW5mb3JtYWNpw7NuIHB1ZWRlIHNlciBwb2NvIGluZm9ybWF0aXZhIHNpIGV4aXN0ZW4KZ3JhbmRlcyBkZXNpZ3VhbGRhZGVzIGVuIGxhcyBjb25leGlvbmVzIGRlIGRpZmVyZW50ZXMgbm9kb3MsIHRhbCB5IGNvbW8gZXMgZWwKY2Fzby4gUG9yIGVzbyBxdWl6w6FzIHNlYSBtw6FzIMO6dGlsIHZlciBsYSB0YWJsYSBjb21wbGV0YSBkZSBkaXN0YW5jaWFzLiBMYSB0YWJsYQpub3MgZGEgZWwgbsO6bWVybyBkZSBwYXJlcyBxdWUgZXN0w6FuIGEgdW5hIGRldGVybWluYWRhIGRpc3RhbmNpYS4KCmBgYHtyfQpkaXN0YW5jZV90YWJsZShnKQpgYGAKCkVsIG3DoXhpbW8gZGUgZXNlIHZlY3RvciBlcyBlbCBfZGnDoW1ldHJvXyBkZSBsYSByZWQuIApgYGB7cn0KZGlhbWV0ZXIoZykKYGBgCgpPdHJhIGZvcm1hIGRlIGF0YWNhciBlbCBwcm9ibGVtYSBlcyBtaWRpZW5kbyBsYSBkaXN0YW5jaWEgbcOhcyBjb3J0YSBlbnRyZSBkb3MKdsOpcnRpY2VzLiAKYGBge3J9CnNob3J0ZXN0X3BhdGhzKGcsICIxODciLCAiMTcxIikKYGBgCmF1bnF1ZSBwb3Igc3VwdWVzdG8gZXN0YSBlcyB1bmEgdGFyZWEgcXVlIG5vIHF1ZXJyw61hbW9zIGhhY2VyIHBhcmEgY2FkYSBwYXIgZGUKbm9kb3MuIAoKRW4gc3UgbHVnYXIsIHBvZGVtb3MgbWlyYXIgYSBsYXMgbWVkaWRhcyBkZSBjZW50cmFsaWRhZC4gTGFzIHRyZXMgbWVkaWRhcyBtw6FzCmZyZWN1ZW50ZXMgc29uIGxhIF9pbnRlcm1lZGlhY2nDs25fIHkgbGEgZGlzdGFuY2lhIGVuIF9hdXRvdmVjdG9yXywgbXV5IHByw7N4aW1hIGEKYWwgX1BhZ2UgcmFua18uIEVzIGltcG9ydGFudGUgaW1wb3J0YW50ZSByZWNvcmRhciBxdWUgZXN0YXMgbWVkaWRhcyBjYXB0dXJhbgpkaWZlcmVudGVzIG5vY2lvbmVzIGRlIGNlbnRyYWxpZGFkLCB0YWwgeSBjb21vIGhlbW9zIHZpc3RvIGFudGVzLiAKCmBgYHtyfQpiYyA8LSBiZXR3ZWVubmVzcyhnKQpoZWFkKGJjKQplYyA8LSBlaWdlbl9jZW50cmFsaXR5KGcpJHZlY3RvcgpoZWFkKGVjKQpwZyA8LSBwYWdlX3JhbmsoZykkdmVjdG9yCmhlYWQocGcpCmBgYAoKUGVybyBwb2RlbW9zIHZlcmxvIGRlIGZvcm1hIG3DoXMgcHLDoWN0aWNhIHNpIG1pcmFtb3MgYSBsYSBjZW50cmFsaWRhZCBkZSBsb3MgZG9zCmRpcHV0YWRvcyBxdWUgaGVtb3MgZXN0dWRpYWRvIGFudGVzCmBgYHtyfQpiY1tuYW1lcyhiYykgPT0gIjIyIl0KYmNbbmFtZXMoYmMpID09ICIxODYiXQpgYGAKCkVzdG8gbm9zIHZpZW5lIGEgY29uZmlybWFyIHF1ZSwgcXVpesOhcyBwcmVjaXNhbWVudGUgcG9ycXVlIGxhIHJlZCBkZSBgMjJgIGVzdMOhCm11eSBjb25lY3RhZGEgZW50cmUgZWxsYSwgbGEgY2VudHJhbGlkYWQgZGUgYDIyYCBlcyBlbiByZWFsaWRhZCBtdXkgYmFqYS4KUG9yIGVsIGNvbnRyYXJpbywgYDE4NmAgdGllbmUgdW5hIGVub3JtZSBjZW50cmFsaWRhZCBwcmVjaXNhbWVudGUgcG9ycXVlIHN1IHJlZApzZSBleGllbmRlIG11eSByw6FwaWRvLCBhIHBlc2FyIGRlIHF1ZSB0aWVuZSBwb2NvcyBfYW1pZ29zXy4gCgpTaW4gZW1iYXJnbywgbGFzIGRvcyBtZWRpZGFzIGVzdMOhbiBtdXkgcG9jbyByZWxhY2lvbmFkYXM6CgpgYGB7cn0KcGxvdChiYywgZWMpCmBgYAoKwr9Dw7NtbyBlcyBlc28gcG9zaWJsZT8gSW52ZXN0aWd1ZW1vcyB1biBjYXNvIGVuIGVsIHF1ZSBsYXMgZG9zIG1lZGlkYXMgbm9zIGRhbgpkaWZlcmVudGVzIGludGVycHJldGFjaW9uZXM6CgpgYGB7cn0KbmFtZXMoYmNbYmMgPCAxMDAgJiBlYyA+IC44XSkKcGxvdChtYWtlX2Vnb19ncmFwaChnLCBub2Rlcz0iMTc2Iiwgb3JkZXI9MSlbWzFdXSkKYGBgCgp5IHZlYW1vcyBjw7NtbyBlcyBzdSByZWQgX2Vnb186CmBgYHtyfQplZ28oZywgbm9kZXM9IjE3NiIsIG9yZGVyPTEpCmVnbyhnLCBub2Rlcz0iMTc2Iiwgb3JkZXI9MikKZWdvKGcsIG5vZGVzPSIxNzYiLCBvcmRlcj0zKQpgYGAKCsK/Q8OzbW8gaW50ZXJwcmV0YW1vcyBlc3RvPwoKIyMgRGV0ZWNjacOzbiBkZSBjb211bmlkYWRlcwpVbmEgZm9ybWEgZGUgYXByb3hpbWFybm9zIGEgbGEgcGFyYWRvamEgYW50ZXJpb3IgZXMgYSB0cmF2w6lzIGRlIGxhIG5vY2nDs24gZGUKY29tdW5pZGFkZXMuIGBpZ3JhcGhgIG9mcmVjZSB2YXJpYXMgZm9ybWFzIGRlIGNhbGN1bGFyIGxhcyBjb211bmlkYWRlcyBxdWUKZXhpc3RlbiBlbiBudWVzdHJvIGdyYWZvLiBDb21vIGhlbW9zIHZpc3RvLCBleGlzdGVuIHZhcmlhcyBhbHRlcm5hdGl2YXMgcGFyYQphcHJveGltYXJub3MgYSBlc3RlIGNvbmNlcHRvLiBVbmEgZGUgZWxsYXMsIGVsIG3DqXRvZG8gZGUgTmV3bWFuLUdpcnZhbiwgZWxpbWluYQplbmxhY2VzIHNlZ8O6biBzdSBncmFkbyBkZSBjZW50cmFsaWRhZCBkZSBpbnRlcm1lZGlhY2nDs24uIAoKYGBge3J9CmNsX25nIDwtIGNsdXN0ZXJfZWRnZV9iZXR3ZWVubmVzcyhnKQpjbF9uZwpgYGAKClBvZGVtb3MgdmlzdWFsaXphciBsYXMgY29tdW5pZGFkZXMgZGUgZm9ybWEgbXV5IHNlbmNpbGxhCgpgYGB7cn0KcGxvdChjbF9uZywgZykKYGBgCgpPdHJhIGFsdGVybmF0aXZhIG11eSBwb3B1bGFyIHVzYSBlbCBtw6l0b2RvIGRlIExvdmFpbmEuIAoKYGBge3J9CmNsX2xvdiA8LSBjbHVzdGVyX2xvdXZhaW4oZykKY2xfbG92Cm1lbWJlciA8LSBtZW1iZXJzaGlwKGNsX2xvdikKaGVhZChtZW1iZXIpCmBgYAoKUG9kZW1vcyBlY2hhciB1biB2aXN0YXpvIGEgbGEgZGlzdHJpYnVjacOzbiBkZSBsb3MgbWllbWJyb3MgZGUgY2FkYSB1bmEgZGUgbGFzCmNvbXVuaWRhZGVzIHF1ZSBoZW1vcyBlc3RpbWFkbwpgYGB7cn0KdGFibGUobWVtYmVyKQpgYGAKCnkgcG9kZW1vcyBjb21wYXJhciBsb3MgcmVzdWx0YWRvcyBkZSB1bm8geSBvdHJvIG3DqXRvZG8KYGBge3J9CnRhYmxlKG1lbWJlciwgbWVtYmVyc2hpcChjbF9uZykpCmBgYAoKQXVucXVlIGhheWEgYWxndW5hcyBkaWZlcmVuY2lhcywgbWFudGllbmVuIHVuYSBlbm9ybWUgc2ltaWxpdHVkIGVudHJlIGxvcyBkb3MuIAoKUGVybyBtaXJlbW9zIGEgdmVyIHNpIGVudGVuZGVtb3MgY3XDoWwgZXMgbGEgbMOzZ2ljYSBkZSBlc3RhcyBjb211bmlkYWRlcy4gUGFyYQplc28gZWNoYXJlbW9zIG1hbm8gZGUgbGEgYmFzZSBkZSBkYXRvcyBkZSBhdHJpYnV0b3MgcXVlIGNyZWFtb3MgYW50ZXMuIAoKYGBge3J9CmxvdXZhaW4gPC0gZGF0YS5mcmFtZSgiY29tdW5pZGFkIj1hcy52ZWN0b3IobWVtYmVyKSwKICAgICAgICAgICAgICAgICAgICAgICJpZCI9YXR0cihtZW1iZXIsICJuYW1lcyIpKQpjb211bmlkYWQgPC0gbWVyZ2UoYXRyaWJ1dG9zLCBsb3V2YWluKQpgYGAKCkFob3JhLCB2ZWFtb3MgcXXDqSBvY3VycmUgc2kgdGFidWxhbW9zIGNhZGEgdW5hIGRlIGxhcyBjb211bmlkYWRlcyBjb24gZWwgcGFydGlkbwphbCBxdWUgcGVydGVuZWNlIGNhZGEgdW5vIGRlIGxvcyBtaWVtYnJvczogCgpgYGB7cn0Kc2FwcGx5KHNwbGl0KGNvbXVuaWRhZCwgbGlzdChjb211bmlkYWQkY29tdW5pZGFkKSksIGZ1bmN0aW9uKHgpIHRhYmxlKHgkZ3J1cG8pKQpgYGAKCkxvcyByZXN1bHRhZG9zIHF1ZSBoZW1vcyB2aXN0byBoYXN0YSBhaG9yYSBkZWJlcsOtYW4gdGVuZXIgbXVjaG8gbcOhcyBzZW50aWRvLiAK