Antes de embarcarnos en el análisis de una red real veamos los fundamentos del paquete igraph para R.

library(igraph)

Hay múltiples modos de definir una red. En los siguientes scripts veremos otras funciones, pero por ahora empezaremos definiendo la red manualmente usando graph_from_literal. Como argumentos, pasaremos cada uno de los vértices entre dos nodos. Para ello usaremos el signo + para definir la cabeza de un vértice dirigido. Por ejemplo, podemos definir una red muy sencilla tal y como sigue.

g <- graph_from_literal(A--D, B--C, C--A, E--A, C--E, E--B)
print(g)

y podemos dirigir algunas aristas:

g <- graph_from_literal(A-+D, B++C, C+-A, E+-A, C-+E, E-+B)
print(g)

e incluso podemos utilizar etiquetas más descriptivas

g <- graph_from_literal(Daniel-+Andres, 
                       Teresa++Carlos, 
                       Carlos+-Daniel, 
                       Elena+-Daniel, 
                       Carlos-+Elena, 
                       Elena-+Teresa)
print(g)

Aunque es posible utilizar ggplot2 para visualizar redes, igraph implementa métodos de visualización usando la librería básica. Veamos por ejemplo el grafo de nuestra red

plot(g)

Podemos adaptar los gráficos para adaptarnos a nuestros gustos:

plot(g, edge.arrow.size=0.5, vertex.color="red", vertex.size=25, 
     vertex.label.color="black", vertex.label.dist=3, edge.curved=0.2)

y podemos también acceder a la matriz de adyacencias asociada a un grafo:

g[]

igraph ofrece formas sencillas de acceder a vértices y aristas con las funciones V (vertex) y E (edge).

V(g)
E(g)

Con estas funciones podemos acceder y asignar atributos. Los atributos están almacenados como si fuesen parte de una lista.

V(g)$name

Pensemos por ejemplo que la red anterior es una red de amigos, que pueden ser hombres o mujeres y que tienen edades entre 18 y 50 años. Para simplificar las cosas usaremos números aleatorios de una distribución uniforme.

V(g)$genero <- c("hombre", "hombre", "mujer", "hombre", "mujer")
V(g)$edad <- sample(18:50, 5)

Ahora podemos visualizar los atributos que acabamos de crear

vertex_attr(g)
print(g)

Del mismo modo, podemos crear atributos para los vértices. Supongamos, por ejemplo, que conocemos para cada relación de amistad cómo se han conocido, si a través de un equipo deportivo o en la universidad.

E(g)$relationship <- sample(c("universidad", "equipo"), 7, TRUE)
edge_attr(g)
print(g)

Estos atributos pueden ser usados para crear grafos más informativos:

V(g)$color <- c("brown", "green")[(V(g)$genero == "hombre") + 1]
V(g)$size <- V(g)$edad
plot(g, edge.arrow.size=0.5, vertex.color=V(g)$color, vertex.size=V(g)$size, 
     vertex.label.color="black", vertex.label.dist=2, edge.curved=0.2)

Ahora que sabemos cómo definir la red, veamos como extraer alguna información que será útil para entenderla mejor. Por ejemplo, podemos tomar la red y extraer subredes definidos por aristas, utilizando la notación especial %--%.

E(g)["Teresa" %--% "Carlos"]
E(g)["Teresa" %->% "Carlos"]
E(g)["Teresa" %<-% "Carlos"]

Más interesante es recuperar subredes definidas por características de los vértices. Por ejemplo, podemos extraer la subred formada únicamente por hombres.

hombres <- V(g)$genero == "hombre"
sg <- induced_subgraph(g, hombres)
print(sg)
plot(sg)

Para hacer exploración es importante poder extraer la red ego, es decir, la red relativa a un vértice. Esta red la podemos definir en relación a un orden: mis amigos (orden 1), los amigos de mis amigos (orden 2), los amigos de los amigos de mis amigos (orden 3).

egoB <- ego(g, nodes=c("Andres"), order=1)
print(egoB)

Para poder visualizar esta red deberemos usar una función diferente:

egoB <- make_ego_graph(g, nodes="Andres", order=1)[[1]]
print(egoB)
plot(egoB)

Grafos especiales

igraph ofrece la posibilidad de simular algunos tipos de grafos especiales.

Grafo vacío:

plot(make_empty_graph(10), vertex.label=NA)

Grafo completo

plot(make_full_graph(10), vertex.label=NA)

Grafo en árbol

plot(make_tree(25, mode='undirected'), vertex.label=NA)

Grafo de mundo pequeño (un grafo que conecta a los vecinos (nei) y con probabilidad p conecta a dos nodos diferentes)

g <- sample_smallworld(dim=2, size=10, nei=1, p=0.05)
plot(g, layout=layout_in_circle, vertex.label=NA, vertex.size=5)

Grafo de Erdos-Renyi (un grafo de formación aleatoria con n nodos y m aristas)

plot(sample_gnm(n=50, m=75), vertex.label=NA)

Modificar la representación gráfica

Hemos visto en la parte teórica la relevancia de la forma en la que presentamos el grafo. Aunque veremos herramientas para cuantificar los grafos, la inspección visual es muy importante para entender qué ocurre en nuestra base de datos. Aquí veremos algunos de los layouts más conocidos.

g <- sample_gnm(n=50, m=75)
V(g)$label <- ""
V(g)$size <- 8
plot(g)

igraph tiene la posibilidad de usar uno de los algoritmos como un parámetro más de la función o podemos pasar a la función la localización de los nodos. Por ejemplo:

plot(g, layout=layout_randomly)

o

pos <- layout_in_circle(g)
head(pos)
plot(g, layout=pos)

Los dos modos de layout que más nos interesan son los que utilizan para la disposición de los nodos bien un algoritmo o un modelo de escalado.

Entre los algoritmos quizás el más conocido es el de Fruchterman-Reingold que, como vimos, intenta optimizar la localización de los nodos en función de varias condiciones. Puede costar mucho esfuerzo computacional localizar todos los nodos en redes grandes.

plot(g, layout=layout_with_fr)

Otra alternativa similar es el algoritmo de Kamada-Kawai, que suele usarse como estimación iniciar para el cálculo de las localizaciones de Fruchterman-Reingold ya que es mucho más rápido:

plot(g, layout=layout_with_kk)

Como veíamos, una alternativa razonable es aprovechar métodos de escalado como los que veremos en la próxima sesión que calcula la disposición óptima en dos dimensiones:

plot(g, layout=layout_with_mds)
LS0tIAp0aXRsZTogIkludHJvZHVjY2nDs24gYWwgYW7DoWxpc2lzIGRlIHJlZGVzIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZCwgJVknKWAiCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0UsIGNhY2hlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZXZhbCA9IEZBTFNFKQprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLnBhdGggPSAnLi9hc3NldHMvJykKYGBgCgpBbnRlcyBkZSBlbWJhcmNhcm5vcyBlbiBlbCBhbsOhbGlzaXMgZGUgdW5hIHJlZCByZWFsIHZlYW1vcyBsb3MgZnVuZGFtZW50b3MgZGVsCnBhcXVldGUgYGlncmFwaGAgcGFyYSBgUmAuIAoKYGBge3J9CmxpYnJhcnkoaWdyYXBoKQpgYGAKCkhheSBtw7psdGlwbGVzIG1vZG9zIGRlIGRlZmluaXIgdW5hIHJlZC4gRW4gbG9zIHNpZ3VpZW50ZXMgc2NyaXB0cyB2ZXJlbW9zIG90cmFzCmZ1bmNpb25lcywgcGVybyBwb3IgYWhvcmEgZW1wZXphcmVtb3MgZGVmaW5pZW5kbyBsYSByZWQgbWFudWFsbWVudGUgdXNhbmRvCmBncmFwaF9mcm9tX2xpdGVyYWxgLiBDb21vIGFyZ3VtZW50b3MsIHBhc2FyZW1vcyBjYWRhIHVubyBkZSBsb3MgdsOpcnRpY2VzIGVudHJlCmRvcyBub2Rvcy4gUGFyYSBlbGxvIHVzYXJlbW9zIGVsIHNpZ25vIGArYCBwYXJhIGRlZmluaXIgbGEgY2FiZXphIGRlIHVuIHbDqXJ0aWNlCmRpcmlnaWRvLiBQb3IgZWplbXBsbywgcG9kZW1vcyBkZWZpbmlyIHVuYSByZWQgbXV5IHNlbmNpbGxhIHRhbCB5IGNvbW8gc2lndWUuIAoKYGBge3J9CmcgPC0gZ3JhcGhfZnJvbV9saXRlcmFsKEEtLUQsIEItLUMsIEMtLUEsIEUtLUEsIEMtLUUsIEUtLUIpCnByaW50KGcpCmBgYAoKeSBwb2RlbW9zIGRpcmlnaXIgYWxndW5hcyBhcmlzdGFzOgoKYGBge3J9CmcgPC0gZ3JhcGhfZnJvbV9saXRlcmFsKEEtK0QsIEIrK0MsIEMrLUEsIEUrLUEsIEMtK0UsIEUtK0IpCnByaW50KGcpCmBgYAoKZSBpbmNsdXNvIHBvZGVtb3MgdXRpbGl6YXIgZXRpcXVldGFzIG3DoXMgZGVzY3JpcHRpdmFzCgpgYGB7cn0KZyA8LSBncmFwaF9mcm9tX2xpdGVyYWwoRGFuaWVsLStBbmRyZXMsIAogICAgICAgICAgICAgICAgICAgICAgIFRlcmVzYSsrQ2FybG9zLCAKICAgICAgICAgICAgICAgICAgICAgICBDYXJsb3MrLURhbmllbCwgCiAgICAgICAgICAgICAgICAgICAgICAgRWxlbmErLURhbmllbCwgCiAgICAgICAgICAgICAgICAgICAgICAgQ2FybG9zLStFbGVuYSwgCiAgICAgICAgICAgICAgICAgICAgICAgRWxlbmEtK1RlcmVzYSkKcHJpbnQoZykKYGBgCgpBdW5xdWUgZXMgcG9zaWJsZSB1dGlsaXphciBgZ2dwbG90MmAgcGFyYSB2aXN1YWxpemFyIHJlZGVzLCBgaWdyYXBoYCBpbXBsZW1lbnRhCm3DqXRvZG9zIGRlIHZpc3VhbGl6YWNpw7NuIHVzYW5kbyBsYSBsaWJyZXLDrWEgYsOhc2ljYS4gVmVhbW9zIHBvciBlamVtcGxvIGVsIGdyYWZvCmRlIG51ZXN0cmEgcmVkCgpgYGB7cn0KcGxvdChnKQpgYGAKClBvZGVtb3MgYWRhcHRhciBsb3MgZ3LDoWZpY29zIHBhcmEgYWRhcHRhcm5vcyBhIG51ZXN0cm9zIGd1c3RvczoKCmBgYHtyfQpwbG90KGcsIGVkZ2UuYXJyb3cuc2l6ZT0wLjUsIHZlcnRleC5jb2xvcj0icmVkIiwgdmVydGV4LnNpemU9MjUsIAogICAgIHZlcnRleC5sYWJlbC5jb2xvcj0iYmxhY2siLCB2ZXJ0ZXgubGFiZWwuZGlzdD0zLCBlZGdlLmN1cnZlZD0wLjIpCmBgYAoKeSBwb2RlbW9zIHRhbWJpw6luIGFjY2VkZXIgYSBsYSBtYXRyaXogZGUgYWR5YWNlbmNpYXMgYXNvY2lhZGEgYSB1biBncmFmbzoKCmBgYHtyfQpnW10KYGBgCgpgaWdyYXBoYCBvZnJlY2UgZm9ybWFzIHNlbmNpbGxhcyBkZSBhY2NlZGVyIGEgdsOpcnRpY2VzIHkgYXJpc3RhcyBjb24gbGFzIGZ1bmNpb25lcwpgVmAgKHZlcnRleCkgeSBgRWAgKGVkZ2UpLiAKCmBgYHtyfQpWKGcpCkUoZykKYGBgCgpDb24gZXN0YXMgZnVuY2lvbmVzIHBvZGVtb3MgYWNjZWRlciB5IGFzaWduYXIgYXRyaWJ1dG9zLiBMb3MgYXRyaWJ1dG9zIGVzdMOhbiBhbG1hY2VuYWRvcyBjb21vIHNpIGZ1ZXNlbiBwYXJ0ZSBkZSB1bmEgbGlzdGEuIAoKYGBge3J9ClYoZykkbmFtZQpgYGAKClBlbnNlbW9zIHBvciBlamVtcGxvIHF1ZSBsYSByZWQgYW50ZXJpb3IgZXMgdW5hIHJlZCBkZSBhbWlnb3MsIHF1ZSBwdWVkZW4gc2VyCmhvbWJyZXMgbyBtdWplcmVzIHkgcXVlIHRpZW5lbiBlZGFkZXMgZW50cmUgMTggeSA1MCBhw7Fvcy4gUGFyYSBzaW1wbGlmaWNhciBsYXMKY29zYXMgdXNhcmVtb3MgbsO6bWVyb3MgYWxlYXRvcmlvcyBkZSB1bmEgZGlzdHJpYnVjacOzbiB1bmlmb3JtZS4KCmBgYHtyfQpWKGcpJGdlbmVybyA8LSBjKCJob21icmUiLCAiaG9tYnJlIiwgIm11amVyIiwgImhvbWJyZSIsICJtdWplciIpClYoZykkZWRhZCA8LSBzYW1wbGUoMTg6NTAsIDUpCmBgYAoKQWhvcmEgcG9kZW1vcyB2aXN1YWxpemFyIGxvcyBhdHJpYnV0b3MgcXVlIGFjYWJhbW9zIGRlIGNyZWFyCmBgYHtyfQp2ZXJ0ZXhfYXR0cihnKQpwcmludChnKQpgYGAKCkRlbCBtaXNtbyBtb2RvLCBwb2RlbW9zIGNyZWFyIGF0cmlidXRvcyBwYXJhIGxvcyB2w6lydGljZXMuIFN1cG9uZ2Ftb3MsIHBvcgplamVtcGxvLCBxdWUgY29ub2NlbW9zIHBhcmEgY2FkYSByZWxhY2nDs24gZGUgYW1pc3RhZCBjw7NtbyBzZSBoYW4gY29ub2NpZG8sIHNpIGEKdHJhdsOpcyBkZSB1biBlcXVpcG8gZGVwb3J0aXZvIG8gZW4gbGEgdW5pdmVyc2lkYWQuCgpgYGB7cn0KRShnKSRyZWxhdGlvbnNoaXAgPC0gc2FtcGxlKGMoInVuaXZlcnNpZGFkIiwgImVxdWlwbyIpLCA3LCBUUlVFKQplZGdlX2F0dHIoZykKcHJpbnQoZykKYGBgCgpFc3RvcyBhdHJpYnV0b3MgcHVlZGVuIHNlciB1c2Fkb3MgcGFyYSBjcmVhciBncmFmb3MgbcOhcyBpbmZvcm1hdGl2b3M6CgpgYGB7cn0KVihnKSRjb2xvciA8LSBjKCJicm93biIsICJncmVlbiIpWyhWKGcpJGdlbmVybyA9PSAiaG9tYnJlIikgKyAxXQpWKGcpJHNpemUgPC0gVihnKSRlZGFkCnBsb3QoZywgZWRnZS5hcnJvdy5zaXplPTAuNSwgdmVydGV4LmNvbG9yPVYoZykkY29sb3IsIHZlcnRleC5zaXplPVYoZykkc2l6ZSwgCiAgICAgdmVydGV4LmxhYmVsLmNvbG9yPSJibGFjayIsIHZlcnRleC5sYWJlbC5kaXN0PTIsIGVkZ2UuY3VydmVkPTAuMikKYGBgCgpBaG9yYSBxdWUgc2FiZW1vcyBjw7NtbyBkZWZpbmlyIGxhIHJlZCwgdmVhbW9zIGNvbW8gZXh0cmFlciBhbGd1bmEgaW5mb3JtYWNpw7NuCnF1ZSBzZXLDoSDDunRpbCBwYXJhIGVudGVuZGVybGEgbWVqb3IuIFBvciBlamVtcGxvLCBwb2RlbW9zIHRvbWFyIGxhIHJlZCB5IGV4dHJhZXIKc3VicmVkZXMgZGVmaW5pZG9zIHBvciBhcmlzdGFzLCB1dGlsaXphbmRvIGxhIG5vdGFjacOzbiBlc3BlY2lhbCBgJS0tJWAuCgpgYGB7cn0KRShnKVsiVGVyZXNhIiAlLS0lICJDYXJsb3MiXQpFKGcpWyJUZXJlc2EiICUtPiUgIkNhcmxvcyJdCkUoZylbIlRlcmVzYSIgJTwtJSAiQ2FybG9zIl0KYGBgCgpNw6FzIGludGVyZXNhbnRlIGVzIHJlY3VwZXJhciBzdWJyZWRlcyBkZWZpbmlkYXMgcG9yIGNhcmFjdGVyw61zdGljYXMgZGUgbG9zCnbDqXJ0aWNlcy4gUG9yIGVqZW1wbG8sIHBvZGVtb3MgZXh0cmFlciBsYSBzdWJyZWQgZm9ybWFkYSDDum5pY2FtZW50ZSBwb3IgaG9tYnJlcy4gCgpgYGB7cn0KaG9tYnJlcyA8LSBWKGcpJGdlbmVybyA9PSAiaG9tYnJlIgpzZyA8LSBpbmR1Y2VkX3N1YmdyYXBoKGcsIGhvbWJyZXMpCnByaW50KHNnKQpwbG90KHNnKQpgYGAKClBhcmEgaGFjZXIgZXhwbG9yYWNpw7NuIGVzIGltcG9ydGFudGUgcG9kZXIgZXh0cmFlciBsYSByZWQgYGVnb2AsIGVzIGRlY2lyLCBsYQpyZWQgcmVsYXRpdmEgYSB1biB2w6lydGljZS4gRXN0YSByZWQgbGEgcG9kZW1vcyBkZWZpbmlyIGVuIHJlbGFjacOzbiBhIHVuIG9yZGVuOgptaXMgYW1pZ29zIChvcmRlbiAxKSwgbG9zIGFtaWdvcyBkZSBtaXMgYW1pZ29zIChvcmRlbiAyKSwgbG9zIGFtaWdvcyBkZSBsb3MKYW1pZ29zIGRlIG1pcyBhbWlnb3MgKG9yZGVuIDMpLiAKCmBgYHtyfQplZ29CIDwtIGVnbyhnLCBub2Rlcz1jKCJBbmRyZXMiKSwgb3JkZXI9MSkKcHJpbnQoZWdvQikKYGBgCgpQYXJhIHBvZGVyIHZpc3VhbGl6YXIgZXN0YSByZWQgZGViZXJlbW9zIHVzYXIgdW5hIGZ1bmNpw7NuIGRpZmVyZW50ZToKYGBge3J9CmVnb0IgPC0gbWFrZV9lZ29fZ3JhcGgoZywgbm9kZXM9IkFuZHJlcyIsIG9yZGVyPTEpW1sxXV0KcHJpbnQoZWdvQikKcGxvdChlZ29CKQpgYGAKCiMjIEdyYWZvcyBlc3BlY2lhbGVzCgpgaWdyYXBoYCBvZnJlY2UgbGEgcG9zaWJpbGlkYWQgZGUgc2ltdWxhciBhbGd1bm9zIHRpcG9zIGRlIGdyYWZvcyBlc3BlY2lhbGVzLiAKCkdyYWZvIHZhY8OtbzoKCmBgYHtyfQpwbG90KG1ha2VfZW1wdHlfZ3JhcGgoMTApLCB2ZXJ0ZXgubGFiZWw9TkEpCmBgYAoKR3JhZm8gY29tcGxldG8KCmBgYHtyfQpwbG90KG1ha2VfZnVsbF9ncmFwaCgxMCksIHZlcnRleC5sYWJlbD1OQSkKYGBgCgpHcmFmbyBlbiDDoXJib2wKCmBgYHtyfQpwbG90KG1ha2VfdHJlZSgyNSwgbW9kZT0ndW5kaXJlY3RlZCcpLCB2ZXJ0ZXgubGFiZWw9TkEpCmBgYAoKR3JhZm8gZGUgbXVuZG8gcGVxdWXDsW8gKHVuIGdyYWZvIHF1ZSBjb25lY3RhIGEgbG9zIHZlY2lub3MgKGBuZWlgKSB5IGNvbgpwcm9iYWJpbGlkYWQgYHBgIGNvbmVjdGEgYSBkb3Mgbm9kb3MgZGlmZXJlbnRlcykKCmBgYHtyfQpnIDwtIHNhbXBsZV9zbWFsbHdvcmxkKGRpbT0yLCBzaXplPTEwLCBuZWk9MSwgcD0wLjA1KQpwbG90KGcsIGxheW91dD1sYXlvdXRfaW5fY2lyY2xlLCB2ZXJ0ZXgubGFiZWw9TkEsIHZlcnRleC5zaXplPTUpCmBgYAoKR3JhZm8gZGUgRXJkb3MtUmVueWkgKHVuIGdyYWZvIGRlIGZvcm1hY2nDs24gYWxlYXRvcmlhIGNvbiBgbmAgbm9kb3MgeSBgbWAgYXJpc3RhcykKCmBgYHtyfQpwbG90KHNhbXBsZV9nbm0obj01MCwgbT03NSksIHZlcnRleC5sYWJlbD1OQSkKYGBgCgojIyBNb2RpZmljYXIgbGEgcmVwcmVzZW50YWNpw7NuIGdyw6FmaWNhCgpIZW1vcyB2aXN0byBlbiBsYSBwYXJ0ZSB0ZcOzcmljYSBsYSByZWxldmFuY2lhIGRlIGxhIGZvcm1hIGVuIGxhIHF1ZSBwcmVzZW50YW1vcwplbCBncmFmby4gQXVucXVlIHZlcmVtb3MgaGVycmFtaWVudGFzIHBhcmEgY3VhbnRpZmljYXIgbG9zIGdyYWZvcywgbGEKaW5zcGVjY2nDs24gdmlzdWFsIGVzIG11eSBpbXBvcnRhbnRlIHBhcmEgZW50ZW5kZXIgcXXDqSBvY3VycmUgZW4gbnVlc3RyYSBiYXNlIGRlCmRhdG9zLiBBcXXDrSB2ZXJlbW9zIGFsZ3Vub3MgZGUgbG9zIF9sYXlvdXRzXyBtw6FzIGNvbm9jaWRvcy4KCmBgYHtyfQpnIDwtIHNhbXBsZV9nbm0obj01MCwgbT03NSkKVihnKSRsYWJlbCA8LSAiIgpWKGcpJHNpemUgPC0gOApwbG90KGcpCmBgYAoKYGlncmFwaGAgdGllbmUgbGEgcG9zaWJpbGlkYWQgZGUgdXNhciB1bm8gZGUgbG9zIGFsZ29yaXRtb3MgY29tbyB1biBwYXLDoW1ldHJvCm3DoXMgZGUgbGEgZnVuY2nDs24gbyBwb2RlbW9zIHBhc2FyIGEgbGEgZnVuY2nDs24gbGEgbG9jYWxpemFjacOzbiBkZSBsb3Mgbm9kb3MuIFBvcgplamVtcGxvOgoKYGBge3J9CnBsb3QoZywgbGF5b3V0PWxheW91dF9yYW5kb21seSkKYGBgCgpvIAoKYGBge3J9CnBvcyA8LSBsYXlvdXRfaW5fY2lyY2xlKGcpCmhlYWQocG9zKQpwbG90KGcsIGxheW91dD1wb3MpCmBgYAoKTG9zIGRvcyBtb2RvcyBkZSBfbGF5b3V0XyBxdWUgbcOhcyBub3MgaW50ZXJlc2FuIHNvbiBsb3MgcXVlIHV0aWxpemFuIHBhcmEgbGEKZGlzcG9zaWNpw7NuIGRlIGxvcyBub2RvcyBiaWVuIHVuIGFsZ29yaXRtbyBvIHVuIG1vZGVsbyBkZSBlc2NhbGFkby4gCgpFbnRyZSBsb3MgYWxnb3JpdG1vcyBxdWl6w6FzIGVsIG3DoXMgY29ub2NpZG8gZXMgZWwgZGUgRnJ1Y2h0ZXJtYW4tUmVpbmdvbGQgcXVlLApjb21vIHZpbW9zLCBpbnRlbnRhIG9wdGltaXphciBsYSBsb2NhbGl6YWNpw7NuIGRlIGxvcyBub2RvcyBlbiBmdW5jacOzbiBkZSB2YXJpYXMKY29uZGljaW9uZXMuIFB1ZWRlIGNvc3RhciBtdWNobyBlc2Z1ZXJ6byBjb21wdXRhY2lvbmFsIGxvY2FsaXphciB0b2RvcyBsb3Mgbm9kb3MKZW4gcmVkZXMgZ3JhbmRlcy4gCgpgYGB7cn0KcGxvdChnLCBsYXlvdXQ9bGF5b3V0X3dpdGhfZnIpCmBgYAoKT3RyYSBhbHRlcm5hdGl2YSBzaW1pbGFyIGVzIGVsIGFsZ29yaXRtbyBkZSBLYW1hZGEtS2F3YWksIHF1ZSBzdWVsZSB1c2Fyc2UgY29tbwplc3RpbWFjacOzbiBpbmljaWFyIHBhcmEgZWwgY8OhbGN1bG8gZGUgbGFzIGxvY2FsaXphY2lvbmVzIGRlIEZydWNodGVybWFuLVJlaW5nb2xkCnlhIHF1ZSBlcyBtdWNobyBtw6FzIHLDoXBpZG86CgpgYGB7cn0KcGxvdChnLCBsYXlvdXQ9bGF5b3V0X3dpdGhfa2spCmBgYAoKQ29tbyB2ZcOtYW1vcywgdW5hIGFsdGVybmF0aXZhIHJhem9uYWJsZSBlcyBhcHJvdmVjaGFyIG3DqXRvZG9zIGRlIGVzY2FsYWRvIGNvbW8KbG9zIHF1ZSB2ZXJlbW9zIGVuIGxhIHByw7N4aW1hIHNlc2nDs24gcXVlIGNhbGN1bGEgbGEgZGlzcG9zaWNpw7NuIMOzcHRpbWEgZW4gZG9zCmRpbWVuc2lvbmVzOgoKYGBge3J9CnBsb3QoZywgbGF5b3V0PWxheW91dF93aXRoX21kcykKYGBgCg==