VIDEO
Bibliotecas
library (tidyverse)
library (sp)
library (sf)
library (gstat)
library (rgdal)
library (rworldxtra)
library (raster)
Ley de Tobler
“Todas las cosas están relacionadas entre sí, pero las cosas más próximas en el espacio tienen una relación mayor que las distantes.” Waldo Tobler
Bibliotecas
library (gstat)
library (raster)
library (rgdal)
library (rworldxtra)
library (sp)
library (sf)
library (tidyverse)
Datos meuse
Datos de ejemplo contenidos en la biblioteca sp.
El crs asignado es determinado por las unidades en las cuales está dada la longitud y latitud.
En este caso se transforman los datos a sf y a SpatialPoints .
# Data originakl
data ("meuse" )
# Data sf
meuse_sf <- meuse %>% st_as_sf (coords = c (1 , 2 ), crs = "+init=epsg:28992" )
# Data SpatialPoints
coordinates (meuse) <- ~ x + y
# Clase de objetos
class (meuse)
[1] "SpatialPointsDataFrame"
attr(,"package")
[1] "sp"
[1] "sf" "data.frame"
meuse_sf %>%
ggplot () +
geom_sf (aes (color = zinc)) +
theme_bw () +
scale_color_viridis_c ()
Ubicación meuse
library (leaflet)
leaflet (as_Spatial (st_transform (meuse_sf, crs = 4326 ))) %>% addTiles () %>% addCircles ()
Variograma
El Variograma es una medida de variación entre valores a distintas distancias.
A mayor distancia se espera mayor variación.
El logaritmo se adiciona para garantizar la obtención de valores positivos.
Es posible modelar la variación en función de diferentes fuentes o factores, dando lugar con ello a diferentes variogramas.
Modelo Nulo
#Modelo nulo
variograma_nulo <- variogram (object = log (zinc) ~ 1 , data = meuse)
variograma_nulo
Resultados de tabla anterior: la primera variable (np) proporciona información del número de puntos, es decir, que para la primera fila diremos que hay 57 puntos a una distancia (dist) de 79.29244 metros y la semi-varianza entre estos 57 puntos es 0.1234479 (gamma). El punto de mayor variación se da con 0.7033984 a una distancia de 1117.86 metros.
variograma_nulo %>%
ggplot (aes (x = dist, y = gamma)) +
geom_point () +
theme_bw () +
labs (x = "Distancia (metros)" , y = "Gamma (semi-varianza)" )
Comparando variogramas
# Variogramas
var_nulo <- variogram (log (zinc) ~ 1 , data = meuse) %>% mutate (Modelo = "Nulo" )
var_spat <- variogram (log (zinc) ~ x + y, data = meuse) %>% mutate (Modelo = "Espacial" )
var_dist <- variogram (log (zinc) ~ dist, data = meuse) %>% mutate (Modelo = "Distancia" )
var_sqrt <- variogram (log (zinc) ~ sqrt (dist), data = meuse) %>% mutate (Modelo = "Raíz Distancia" )
# Uniendo resultados
all_var <- list (var_nulo, var_spat, var_dist, var_sqrt) %>%
reduce (bind_rows)
all_var
all_var %>%
ggplot (aes (x = dist, y = gamma, color = Modelo)) +
geom_point (size = 1.7 ) +
theme_bw () +
labs (x = "Distancia (metros)" , y = "Gamma (semi-varianza)" )
Ajuste de variograma
psill: meseta parcial. Estabilización de la semivarianza.
range: separación o distancia entre pares de puntos en la cual ya no hay dependencia espacial.
nugget: semivarianza esperada a la separación o distancia de 0m
Los valores de psill, range y nugget sirven como valores iniciales para el ajuste del modelo.
ajuste_var <- fit.variogram (object = var_spat,
model = vgm (psill = 1 , model = "Sph" , range = 700 ,
nugget = 1 ))
ajuste_var
Resultados: el range proporcionado por el modelo indica la distancia esperada donde se estabiliza la semivarianza. La suma de psill (0.08234213 + 0.38866509) dará como resultado el valor donde se evidencia o espera la “meseta” total (estabilización de la semivarianza). Nugget brinda información acerca de la semivarianza esperada a una distancia de 0 metros (intercepto). A continuación se muestra el gráfico del modelo ajustado:
ggplot (variogramLine (ajuste_var, 1600 ), aes (x = dist, y = gamma)) +
geom_path () +
geom_point (data = var_spat, color = "red" ) +
geom_vline (xintercept = ajuste_var$ range[2 ], lty = 2 ) +
geom_text (x = ajuste_var$ range[2 ], y = ajuste_var$ psill[2 ]/ 2 , label = "range" ) +
theme_bw () +
geom_hline (yintercept = ajuste_var$ psill[2 ] + ajuste_var$ psill[1 ] , lty = 2 ) +
geom_text (x = ajuste_var$ range[2 ]/ 2 , y = ajuste_var$ psill[2 ] + ajuste_var$ psill[1 ], label = "psill" ) +
geom_text (x = ajuste_var$ range[1 ], y = ajuste_var$ psill[1 ], label = "Nugget" ) +
ylim (c (0 ,max (var_spat$ gamma)))
Otros modelos
# Modelo nulo
ajuste_nulo <- fit.variogram (object = var_nulo,
model = vgm (psill = 1 , model = "Sph" , range = 700 ,
nugget = 1 ))
# Modelo con distancia
ajuste_dist <- fit.variogram (object = var_dist,
model = vgm (psill = 1 , model = "Sph" , range = 700 ,
nugget = 1 ))
# Modelo con raíz cuadrada de la distancia
ajuste_sqrt <- fit.variogram (object = var_sqrt,
model = vgm (psill = 1 , model = "Sph" , range = 700 ,
nugget = 1 ))
Gráfico con cuatro modelos ajustados:
# Cuatro modelos
Abn_fit_null <- variogramLine (ajuste_nulo, 1600 ) %>% mutate (Modelo = "Nulo" )
Abn_line_Spat <- variogramLine (ajuste_var, 1600 ) %>% mutate (Modelo = "Espacial" )
Abn_line_Dist <- variogramLine (ajuste_dist, 1600 ) %>% mutate (Modelo = "Distancia" )
Abn_line_Dist_sq <- variogramLine (ajuste_sqrt, 1600 ) %>% mutate (Modelo = "Raíz Distancia" )
# Unión de datos
Abn_line <- list (Abn_fit_null, Abn_line_Spat, Abn_line_Dist, Abn_line_Dist_sq) %>%
reduce (bind_rows)
ggplot (Abn_line, aes (x = dist, y = gamma)) +
geom_path (aes (color = Modelo)) +
geom_point (data = all_var, aes (color = Modelo)) +
theme_bw () +
scale_color_brewer (palette = "Set1" )
Kriging
El krigeaje o regresión en procesos gaussianos permite realizar interporlación a partir de un variograma previamente ajustado.
En este caso se usa la base de datos meuse.grid que tiene información que podrá ser utilizada para la interpolación. Las distancias del siguiente gráfico indican proximidad a una fuente hídrica.
Datos
# Data inicial
data ("meuse.grid" )
# Conversión a sf
meusegrid_sf <- meuse.grid %>%
st_as_sf (coords = c (1 , 2 ), crs = "+init=epsg:28992" )
# Gráfico
meusegrid_sf %>%
ggplot () +
geom_sf (aes (color = dist)) +
scale_color_viridis_c () +
theme_bw ()
Predicciones (interpolación)
pred_spat <- krige (log (zinc) ~ 1 , meuse_sf, meusegrid_sf, model = ajuste_var)
[using ordinary kriging]
[1] "sf" "data.frame"
Simple feature collection with 3103 features and 2 fields
geometry type: POINT
dimension: XY
bbox: xmin: 178460 ymin: 329620 xmax: 181540 ymax: 333740
projected CRS: Amersfoort / RD New
First 10 features:
var1.pred var1.var geometry
1 6.535255 0.2461491 POINT (181180 333740)
2 6.626923 0.2093807 POINT (181140 333700)
3 6.528438 0.2197185 POINT (181180 333700)
4 6.429889 0.2315686 POINT (181220 333700)
5 6.730607 0.1709532 POINT (181100 333660)
6 6.622317 0.1818457 POINT (181140 333660)
7 6.508323 0.1944079 POINT (181180 333660)
8 6.396911 0.2074667 POINT (181220 333660)
9 6.835826 0.1364733 POINT (181060 333620)
10 6.728034 0.1437661 POINT (181100 333620)
Gráfico de predicciones: como los datos están en logaritmos se usa la función exp() para obtener valores en las mismas unidades
pred_spat %>%
ggplot () +
geom_sf (aes (color = exp (var1.pred))) +
scale_color_viridis_c (name = "Zinc (predicho)" )
Gráfico de variación en las predicciones: el gráfico permite evidencicar lugares de mayor incertidumbre para la predicción.
pred_spat %>%
ggplot () +
geom_sf (aes (color = exp (var1.var))) +
scale_color_viridis_c (name = "" )
Todos los modelos
Null_pred <- krige (log (zinc) ~ 1 , meuse_sf, meusegrid_sf, model = ajuste_nulo) %>%
mutate (Modelo = "Nulo" )
[using ordinary kriging]
Spat_pred <- krige (log (zinc) ~ 1 , meuse_sf, meusegrid_sf, model = ajuste_var) %>%
mutate (Modelo = "Espacial" )
[using ordinary kriging]
Dist_pred <- krige (log (zinc) ~ 1 , meuse_sf, meusegrid_sf, model = ajuste_dist) %>%
mutate (Modelo = "Distancia" )
[using ordinary kriging]
Dist_sq_pred <- krige (log (zinc) ~ 1 , meuse_sf, meusegrid_sf, model = ajuste_sqrt) %>%
mutate (Modelo = "Raíz Distancia" )
[using ordinary kriging]
Pred <- list (Null_pred, Spat_pred, Dist_pred, Dist_sq_pred) %>%
reduce (bind_rows)
Pred
Simple feature collection with 12412 features and 3 fields
geometry type: POINT
dimension: XY
bbox: xmin: 178460 ymin: 329620 xmax: 181540 ymax: 333740
projected CRS: Amersfoort / RD New
First 10 features:
var1.pred var1.var Modelo geometry
1 6.499611 0.3198081 Nulo POINT (181180 333740)
2 6.622347 0.2520183 Nulo POINT (181140 333700)
3 6.505156 0.2729842 Nulo POINT (181180 333700)
4 6.387580 0.2955285 Nulo POINT (181220 333700)
5 6.764490 0.1779374 Nulo POINT (181100 333660)
6 6.635508 0.2022009 Nulo POINT (181140 333660)
7 6.497544 0.2277371 Nulo POINT (181180 333660)
8 6.361471 0.2524902 Nulo POINT (181220 333660)
9 6.904628 0.1099817 Nulo POINT (181060 333620)
10 6.780233 0.1280782 Nulo POINT (181100 333620)
ggplot () +
geom_sf (data = Pred, aes (color = exp (var1.pred))) +
scale_color_viridis_c (name = "Zinc" ) +
facet_wrap (~ Modelo) +
theme_bw ()
Incertidumbre en predicciones: el modelo de mayor incertidumbre es el modelo nulo.
ggplot () +
geom_sf (data = Pred, aes (color = exp (var1.var))) +
scale_color_viridis_c (name = "Zinc" ) +
facet_wrap (~ Modelo) +
theme_bw ()
Mejor modelo
Una manera de seleccionar el modelo podría ser comparar la bondad de ajuste a través de una métrica que represente el error, por ejemplo, Raíz del Cuadrado Medio del Error (RSME - Root Square Mean Error).
También es posible implementar validación cruzada.
La función krige.cv permite ajustar modelos a través de validación cruzada.
Null_pred_cv <- krige.cv (log (zinc) ~ 1 , meuse_sf,
model = ajuste_nulo, nfold = 5 ) %>%
mutate (Modelo = "Nulo" )
Spat_pred_cv <- krige.cv (log (zinc) ~ 1 , meuse_sf,model = ajuste_var,
nfold = 5 ) %>%
mutate (Modelo = "Espacial" )
Dist_pred_cv <- krige.cv (log (zinc) ~ 1 , meuse_sf, model = ajuste_dist,
nfold = 5 ) %>%
mutate (Modelo = "Distancia" )
Dist_sq_pred_cv <- krige.cv (log (zinc) ~ 1 , meuse_sf, model = ajuste_sqrt,
nfold = 5 ) %>%
mutate (Modelo = "Raíz Distancia" )
Pred_cv <- list (Null_pred_cv, Spat_pred_cv, Dist_pred_cv, Dist_sq_pred_cv) %>%
reduce (bind_rows)
Pred_cv %>%
ggplot (aes (x = var1.pred, y = observed)) +
geom_point () +
theme_bw () +
labs (x = "Predichos" , y = "Observados" )
Comparando modelos a través de RMSE:
Pred_cv %>%
as.data.frame () %>%
group_by (Modelo) %>%
summarise (RMSE = sqrt (sum (residual^ 2 )/ length (residual)))
LS0tDQp0aXRsZTogIlNpc3RlbWFzIGRlIEluZm9ybWFjacOzbiBHZW9ncsOhZmljYSBjb24gUiINCmF1dGhvcjogIkVkaW1lciBEYXZpZCBKYXJhbWlsbG8iDQpzdWJ0aXRsZTogIkF1dG9jb3JyZWxhY2nDs24gRXNwYWNpYWwiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6DQogICAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgICAgIGNvbGxhcHNlZDogbm8NCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBoaWdobGlnaHQ6IGJyZWV6ZWRhcmsNCiAgICBkZl9wcmludDogcGFnZWQNCi0tLQ0KDQotIE5vdGFzIHRvbWFkYXMgZGVsIGN1cnNvIFsiU2lzdGVtYXMgZGUgSW5mb3JtYWNpw7NuIEdlb2dyw6FmaWNhIGVuIFIgKFNJRyBlbiBSKSIgY29uIERlcmVrIENvcmNvcmFuLl0oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1JWVVScWJ6S1ZrUSZhYl9jaGFubmVsPURlcmVrQ29yY29yYW4pDQoNCjxpZnJhbWUgd2lkdGg9IjU2MCIgaGVpZ2h0PSIzMTUiIHNyYz0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vZW1iZWQvSVlVUnFiektWa1EiIGZyYW1lYm9yZGVyPSIwIiBhbGxvd2Z1bGxzY3JlZW4+PC9pZnJhbWU+DQoNCiMgQmlibGlvdGVjYXMNCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc3ApDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShnc3RhdCkNCmxpYnJhcnkocmdkYWwpDQpsaWJyYXJ5KHJ3b3JsZHh0cmEpDQpsaWJyYXJ5KHJhc3RlcikNCmBgYA0KDQoNCiMgTGV5IGRlIFRvYmxlcg0KDQo+ICJUb2RhcyBsYXMgY29zYXMgZXN0w6FuIHJlbGFjaW9uYWRhcyBlbnRyZSBzw60sIHBlcm8gbGFzIGNvc2FzIG3DoXMgcHLDs3hpbWFzIGVuIGVsIGVzcGFjaW8gdGllbmVuIHVuYSByZWxhY2nDs24gbWF5b3IgcXVlIGxhcyBkaXN0YW50ZXMuIg0KV2FsZG8gVG9ibGVyDQoNCiMgQmlibGlvdGVjYXMNCg0KYGBge3J9DQpsaWJyYXJ5KGdzdGF0KQ0KbGlicmFyeShyYXN0ZXIpDQpsaWJyYXJ5KHJnZGFsKQ0KbGlicmFyeShyd29ybGR4dHJhKQ0KbGlicmFyeShzcCkNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQoNCiMgRGF0b3MgKm1ldXNlKg0KDQotIERhdG9zIGRlIGVqZW1wbG8gY29udGVuaWRvcyBlbiBsYSBiaWJsaW90ZWNhIHNwLg0KLSBFbCBjcnMgYXNpZ25hZG8gZXMgZGV0ZXJtaW5hZG8gcG9yIGxhcyB1bmlkYWRlcyBlbiBsYXMgY3VhbGVzIGVzdMOhIGRhZGEgbGEgbG9uZ2l0dWQgeSBsYXRpdHVkLg0KLSBFbiBlc3RlIGNhc28gc2UgdHJhbnNmb3JtYW4gbG9zIGRhdG9zIGEgKnNmKiB5IGEgKlNwYXRpYWxQb2ludHMqLg0KDQpgYGB7cn0NCiMgRGF0YSBvcmlnaW5ha2wNCmRhdGEoIm1ldXNlIikgDQoNCiMgRGF0YSBzZg0KbWV1c2Vfc2YgPC0gbWV1c2UgJT4lIHN0X2FzX3NmKGNvb3JkcyA9IGMoMSwgMiksIGNycyA9ICIraW5pdD1lcHNnOjI4OTkyIikNCg0KIyBEYXRhIFNwYXRpYWxQb2ludHMNCmNvb3JkaW5hdGVzKG1ldXNlKSA8LSB+IHggKyB5DQoNCiMgQ2xhc2UgZGUgb2JqZXRvcw0KY2xhc3MobWV1c2UpDQpjbGFzcyhtZXVzZV9zZikNCmBgYA0KDQotICoqQ29uY2VudHJhY2nDs24gZGUgWmluYzoqKg0KDQpgYGB7cn0NCm1ldXNlX3NmICU+JSANCiAgZ2dwbG90KCkgKw0KICBnZW9tX3NmKGFlcyhjb2xvciA9IHppbmMpKSArDQogIHRoZW1lX2J3KCkgKw0KICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKQ0KYGBgDQoNCiMgVWJpY2FjacOzbiBtZXVzZQ0KDQpgYGB7cn0NCmxpYnJhcnkobGVhZmxldCkNCmxlYWZsZXQoYXNfU3BhdGlhbChzdF90cmFuc2Zvcm0obWV1c2Vfc2YsIGNycyA9IDQzMjYpKSkgJT4lIGFkZFRpbGVzKCkgJT4lIGFkZENpcmNsZXMoKQ0KYGBgDQoNCg0KIyBWYXJpb2dyYW1hDQoNCi0gW0VsIFZhcmlvZ3JhbWFdKGh0dHBzOi8vZXMud2lraXBlZGlhLm9yZy93aWtpL1ZhcmlvZ3JhbWEjOn46dGV4dD1FbCUyMHZhcmlvZ3JhbWElMjBvJTIwc2VtaXZhcmlvZ3JhbWElMjBlcyxvdHJvJTIwcHVudG8lMjBhJTIwZGlmZXJlbnRlcyUyMGRpc3RhbmNpYXMuKSBlcyB1bmEgbWVkaWRhIGRlIHZhcmlhY2nDs24gZW50cmUgdmFsb3JlcyBhIGRpc3RpbnRhcyBkaXN0YW5jaWFzLg0KLSBBIG1heW9yIGRpc3RhbmNpYSBzZSBlc3BlcmEgbWF5b3IgdmFyaWFjacOzbi4NCi0gRWwgbG9nYXJpdG1vIHNlIGFkaWNpb25hIHBhcmEgZ2FyYW50aXphciBsYSBvYnRlbmNpw7NuIGRlIHZhbG9yZXMgcG9zaXRpdm9zLg0KLSBFcyBwb3NpYmxlIG1vZGVsYXIgbGEgdmFyaWFjacOzbiBlbiBmdW5jacOzbiBkZSBkaWZlcmVudGVzIGZ1ZW50ZXMgbyBmYWN0b3JlcywgZGFuZG8gbHVnYXIgY29uIGVsbG8gYSBkaWZlcmVudGVzIHZhcmlvZ3JhbWFzLg0KDQojIyBNb2RlbG8gTnVsbw0KDQoNCg0KYGBge3J9DQojTW9kZWxvIG51bG8NCnZhcmlvZ3JhbWFfbnVsbyA8LSB2YXJpb2dyYW0ob2JqZWN0ID0gbG9nKHppbmMpIH4gMSwgZGF0YSA9IG1ldXNlKQ0KdmFyaW9ncmFtYV9udWxvDQpgYGANCg0KLSAqKlJlc3VsdGFkb3MgZGUgdGFibGEgYW50ZXJpb3I6KiogbGEgcHJpbWVyYSB2YXJpYWJsZSAobnApIHByb3BvcmNpb25hIGluZm9ybWFjacOzbiBkZWwgbsO6bWVybyBkZSBwdW50b3MsIGVzIGRlY2lyLCBxdWUgcGFyYSBsYSBwcmltZXJhIGZpbGEgZGlyZW1vcyBxdWUgaGF5IDU3IHB1bnRvcyBhIHVuYSBkaXN0YW5jaWEgKGRpc3QpIGRlIDc5LjI5MjQ0IG1ldHJvcyB5IGxhIHNlbWktdmFyaWFuemEgZW50cmUgZXN0b3MgNTcgcHVudG9zIGVzIDAuMTIzNDQ3OSAoZ2FtbWEpLiBFbCBwdW50byBkZSBtYXlvciB2YXJpYWNpw7NuIHNlIGRhIGNvbiAwLjcwMzM5ODQgYSB1bmEgZGlzdGFuY2lhIGRlIDExMTcuODYgbWV0cm9zLg0KDQpgYGB7cn0NCnZhcmlvZ3JhbWFfbnVsbyAlPiUgDQogIGdncGxvdChhZXMoeCA9IGRpc3QsIHkgPSBnYW1tYSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgdGhlbWVfYncoKSArDQogIGxhYnMoeCA9ICJEaXN0YW5jaWEgKG1ldHJvcykiLCB5ID0gIkdhbW1hIChzZW1pLXZhcmlhbnphKSIpDQpgYGANCg0KIyMgQ29tcGFyYW5kbyB2YXJpb2dyYW1hcw0KDQpgYGB7cn0NCiMgVmFyaW9ncmFtYXMNCnZhcl9udWxvIDwtIHZhcmlvZ3JhbShsb2coemluYykgfiAxLCBkYXRhID0gbWV1c2UpICU+JSBtdXRhdGUoTW9kZWxvID0gIk51bG8iKQ0KdmFyX3NwYXQgPC0gdmFyaW9ncmFtKGxvZyh6aW5jKSB+IHggKyB5LCBkYXRhID0gbWV1c2UpICU+JSBtdXRhdGUoTW9kZWxvID0gIkVzcGFjaWFsIikNCnZhcl9kaXN0IDwtIHZhcmlvZ3JhbShsb2coemluYykgfiBkaXN0LCBkYXRhID0gbWV1c2UpICU+JSBtdXRhdGUoTW9kZWxvID0gIkRpc3RhbmNpYSIpDQp2YXJfc3FydCA8LSB2YXJpb2dyYW0obG9nKHppbmMpIH4gc3FydChkaXN0KSwgZGF0YSA9IG1ldXNlKSAlPiUgbXV0YXRlKE1vZGVsbyA9ICJSYcOteiBEaXN0YW5jaWEiKQ0KDQojIFVuaWVuZG8gcmVzdWx0YWRvcw0KYWxsX3ZhciA8LSBsaXN0KHZhcl9udWxvLCB2YXJfc3BhdCwgdmFyX2Rpc3QsIHZhcl9zcXJ0KSAlPiUgDQogIHJlZHVjZShiaW5kX3Jvd3MpDQphbGxfdmFyDQpgYGANCg0KLSAqKkdyw6FmaWNvIGRlIHZhcmlvZ3JhbWFzOioqDQoNCmBgYHtyfQ0KYWxsX3ZhciAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gZGlzdCwgeSA9IGdhbW1hLCBjb2xvciA9IE1vZGVsbykpICsNCiAgZ2VvbV9wb2ludChzaXplID0gMS43KSArDQogIHRoZW1lX2J3KCkgKw0KICBsYWJzKHggPSAiRGlzdGFuY2lhIChtZXRyb3MpIiwgeSA9ICJHYW1tYSAoc2VtaS12YXJpYW56YSkiKQ0KYGBgDQoNCiMgQWp1c3RlIGRlIHZhcmlvZ3JhbWENCg0KLSBwc2lsbDogbWVzZXRhIHBhcmNpYWwuIEVzdGFiaWxpemFjacOzbiBkZSBsYSBzZW1pdmFyaWFuemEuDQotIHJhbmdlOiBzZXBhcmFjacOzbiBvIGRpc3RhbmNpYSBlbnRyZSBwYXJlcyBkZSBwdW50b3MgZW4gbGEgY3VhbCB5YSBubyBoYXkgZGVwZW5kZW5jaWEgZXNwYWNpYWwuDQotIG51Z2dldDogc2VtaXZhcmlhbnphIGVzcGVyYWRhIGEgbGEgc2VwYXJhY2nDs24gbyBkaXN0YW5jaWEgZGUgMG0NCi0gTG9zIHZhbG9yZXMgZGUgcHNpbGwsIHJhbmdlIHkgbnVnZ2V0IHNpcnZlbiBjb21vIHZhbG9yZXMgaW5pY2lhbGVzIHBhcmEgZWwgYWp1c3RlIGRlbCBtb2RlbG8uDQoNCmBgYHtyfQ0KYWp1c3RlX3ZhciA8LSBmaXQudmFyaW9ncmFtKG9iamVjdCA9IHZhcl9zcGF0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gdmdtKHBzaWxsID0gMSwgbW9kZWwgPSAiU3BoIiwgcmFuZ2UgPSAgNzAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51Z2dldCA9ICAxKSkNCmFqdXN0ZV92YXINCmBgYA0KDQotICoqUmVzdWx0YWRvczoqKiBlbCAqcmFuZ2UqIHByb3BvcmNpb25hZG8gcG9yIGVsIG1vZGVsbyBpbmRpY2EgbGEgZGlzdGFuY2lhIGVzcGVyYWRhIGRvbmRlIHNlIGVzdGFiaWxpemEgbGEgc2VtaXZhcmlhbnphLiBMYSBzdW1hIGRlICpwc2lsbCogKDAuMDgyMzQyMTMgKyAwLjM4ODY2NTA5KSBkYXLDoSBjb21vIHJlc3VsdGFkbyBlbCB2YWxvciBkb25kZSBzZSBldmlkZW5jaWEgbyBlc3BlcmEgbGEgIm1lc2V0YSIgdG90YWwgKGVzdGFiaWxpemFjacOzbiBkZSBsYSBzZW1pdmFyaWFuemEpLiAqTnVnZ2V0KiBicmluZGEgaW5mb3JtYWNpw7NuIGFjZXJjYSBkZSBsYSBzZW1pdmFyaWFuemEgZXNwZXJhZGEgYSB1bmEgZGlzdGFuY2lhIGRlIDAgbWV0cm9zIChpbnRlcmNlcHRvKS4gQSBjb250aW51YWNpw7NuIHNlIG11ZXN0cmEgZWwgZ3LDoWZpY28gZGVsIG1vZGVsbyBhanVzdGFkbzoNCg0KYGBge3J9DQpnZ3Bsb3QodmFyaW9ncmFtTGluZShhanVzdGVfdmFyLCAxNjAwKSwgYWVzKHggPSBkaXN0LCB5ID0gZ2FtbWEpKSArDQogIGdlb21fcGF0aCgpICsgDQogIGdlb21fcG9pbnQoZGF0YSA9IHZhcl9zcGF0LCBjb2xvciA9ICJyZWQiKSArIA0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBhanVzdGVfdmFyJHJhbmdlWzJdLCBsdHkgPSAyKSArIA0KICBnZW9tX3RleHQoeCA9IGFqdXN0ZV92YXIkcmFuZ2VbMl0sIHkgPSBhanVzdGVfdmFyJHBzaWxsWzJdLzIsIGxhYmVsID0gInJhbmdlIikgKyANCiAgdGhlbWVfYncoKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IGFqdXN0ZV92YXIkcHNpbGxbMl0gKyBhanVzdGVfdmFyJHBzaWxsWzFdICwgbHR5ID0gMikgKyANCiAgZ2VvbV90ZXh0KHggPSBhanVzdGVfdmFyJHJhbmdlWzJdLzIsIHkgPSBhanVzdGVfdmFyJHBzaWxsWzJdICsgYWp1c3RlX3ZhciRwc2lsbFsxXSwgbGFiZWwgPSAicHNpbGwiKSArDQogIGdlb21fdGV4dCh4ID0gYWp1c3RlX3ZhciRyYW5nZVsxXSwgeSA9IGFqdXN0ZV92YXIkcHNpbGxbMV0sIGxhYmVsID0gIk51Z2dldCIpICsNCiAgeWxpbShjKDAsbWF4KHZhcl9zcGF0JGdhbW1hKSkpDQpgYGANCg0KIyBPdHJvcyBtb2RlbG9zDQoNCmBgYHtyfQ0KIyBNb2RlbG8gbnVsbw0KYWp1c3RlX251bG8gPC0gZml0LnZhcmlvZ3JhbShvYmplY3QgPSB2YXJfbnVsbywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSB2Z20ocHNpbGwgPSAxLCBtb2RlbCA9ICJTcGgiLCByYW5nZSA9ICA3MDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51Z2dldCA9ICAxKSkNCg0KIyBNb2RlbG8gY29uIGRpc3RhbmNpYQ0KYWp1c3RlX2Rpc3QgPC0gZml0LnZhcmlvZ3JhbShvYmplY3QgPSB2YXJfZGlzdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCA9IHZnbShwc2lsbCA9IDEsIG1vZGVsID0gIlNwaCIsIHJhbmdlID0gIDcwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudWdnZXQgPSAgMSkpDQoNCiMgTW9kZWxvIGNvbiByYcOteiBjdWFkcmFkYSBkZSBsYSBkaXN0YW5jaWENCmFqdXN0ZV9zcXJ0IDwtIGZpdC52YXJpb2dyYW0ob2JqZWN0ID0gdmFyX3NxcnQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSB2Z20ocHNpbGwgPSAxLCBtb2RlbCA9ICJTcGgiLCByYW5nZSA9ICA3MDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVnZ2V0ID0gIDEpKQ0KYGBgDQoNCi0gKipHcsOhZmljbyBjb24gY3VhdHJvIG1vZGVsb3MgYWp1c3RhZG9zOioqDQoNCmBgYHtyfQ0KIyBDdWF0cm8gbW9kZWxvcw0KQWJuX2ZpdF9udWxsIDwtIHZhcmlvZ3JhbUxpbmUoYWp1c3RlX251bG8sIDE2MDApICU+JSBtdXRhdGUoTW9kZWxvID0gIk51bG8iKQ0KQWJuX2xpbmVfU3BhdCA8LSB2YXJpb2dyYW1MaW5lKGFqdXN0ZV92YXIsIDE2MDApICU+JSBtdXRhdGUoTW9kZWxvID0gIkVzcGFjaWFsIikNCkFibl9saW5lX0Rpc3QgPC0gdmFyaW9ncmFtTGluZShhanVzdGVfZGlzdCwgMTYwMCkgJT4lIG11dGF0ZShNb2RlbG8gPSAiRGlzdGFuY2lhIikNCkFibl9saW5lX0Rpc3Rfc3EgPC0gdmFyaW9ncmFtTGluZShhanVzdGVfc3FydCwgMTYwMCkgJT4lIG11dGF0ZShNb2RlbG8gPSAiUmHDrXogRGlzdGFuY2lhIikNCg0KIyBVbmnDs24gZGUgZGF0b3MNCkFibl9saW5lIDwtIGxpc3QoQWJuX2ZpdF9udWxsLCBBYm5fbGluZV9TcGF0LCBBYm5fbGluZV9EaXN0LCBBYm5fbGluZV9EaXN0X3NxKSAlPiUNCiAgcmVkdWNlKGJpbmRfcm93cykNCg0KZ2dwbG90KEFibl9saW5lLCBhZXMoeCA9IGRpc3QsIHkgPSBnYW1tYSkpICsgDQogIGdlb21fcGF0aChhZXMoY29sb3IgPSBNb2RlbG8pKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IGFsbF92YXIsIGFlcyhjb2xvciA9IE1vZGVsbykpICsgDQogIHRoZW1lX2J3KCkgKw0KICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikNCmBgYA0KDQojIEtyaWdpbmcNCg0KLSBbRWwga3JpZ2VhamUgbyByZWdyZXNpw7NuIGVuIHByb2Nlc29zIGdhdXNzaWFub3NdKGh0dHBzOi8vZXMud2lraXBlZGlhLm9yZy93aWtpL0tyaWdlYWplIzp+OnRleHQ9RWwlMjBrcmlnZWFqZSUyQyUyMGtyaWdlYWRvJTIwbyUyMGtyaWdpbmcscmVmZXJlbmNpYSUyMHVzYWRvJTIwZW4lMjBsYSUyMGVzdGltYWNpJUMzJUIzbi4pIHBlcm1pdGUgcmVhbGl6YXIgaW50ZXJwb3JsYWNpw7NuIGEgcGFydGlyIGRlIHVuIHZhcmlvZ3JhbWEgcHJldmlhbWVudGUgYWp1c3RhZG8uDQotIEVuIGVzdGUgY2FzbyBzZSB1c2EgbGEgYmFzZSBkZSBkYXRvcyAqbWV1c2UuZ3JpZCogcXVlIHRpZW5lIGluZm9ybWFjacOzbiBxdWUgcG9kcsOhIHNlciB1dGlsaXphZGEgcGFyYSBsYSBpbnRlcnBvbGFjacOzbi4gTGFzIGRpc3RhbmNpYXMgZGVsIHNpZ3VpZW50ZSBncsOhZmljbyBpbmRpY2FuIHByb3hpbWlkYWQgYSB1bmEgZnVlbnRlIGjDrWRyaWNhLg0KDQojIyBEYXRvcw0KDQpgYGB7cn0NCiMgRGF0YSBpbmljaWFsDQpkYXRhKCJtZXVzZS5ncmlkIikNCg0KIyBDb252ZXJzacOzbiBhIHNmDQptZXVzZWdyaWRfc2YgPC0gbWV1c2UuZ3JpZCAlPiUgDQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoMSwgMiksIGNycyA9ICIraW5pdD1lcHNnOjI4OTkyIikNCg0KIyBHcsOhZmljbw0KbWV1c2VncmlkX3NmICU+JSANCiAgZ2dwbG90KCkgKw0KICBnZW9tX3NmKGFlcyhjb2xvciA9IGRpc3QpKSArDQogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCiMjIFByZWRpY2Npb25lcyAoaW50ZXJwb2xhY2nDs24pDQoNCmBgYHtyfQ0KcHJlZF9zcGF0IDwtIGtyaWdlKGxvZyh6aW5jKSB+IDEsIG1ldXNlX3NmLCBtZXVzZWdyaWRfc2YsIG1vZGVsID0gYWp1c3RlX3ZhcikNCmNsYXNzKHByZWRfc3BhdCkNCnByZWRfc3BhdA0KYGBgDQoNCi0gKipHcsOhZmljbyBkZSBwcmVkaWNjaW9uZXM6KiogY29tbyBsb3MgZGF0b3MgZXN0w6FuIGVuIGxvZ2FyaXRtb3Mgc2UgdXNhIGxhIGZ1bmNpw7NuIGV4cCgpIHBhcmEgb2J0ZW5lciB2YWxvcmVzIGVuIGxhcyBtaXNtYXMgdW5pZGFkZXMNCg0KYGBge3J9DQpwcmVkX3NwYXQgJT4lIA0KICBnZ3Bsb3QoKSArDQogIGdlb21fc2YoYWVzKGNvbG9yID0gZXhwKHZhcjEucHJlZCkpKSArDQogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYyhuYW1lID0gIlppbmMgKHByZWRpY2hvKSIpDQpgYGANCg0KLSAqKkdyw6FmaWNvIGRlIHZhcmlhY2nDs24gZW4gbGFzIHByZWRpY2Npb25lczoqKiBlbCBncsOhZmljbyBwZXJtaXRlIGV2aWRlbmNpY2FyIGx1Z2FyZXMgZGUgbWF5b3IgaW5jZXJ0aWR1bWJyZSBwYXJhIGxhIHByZWRpY2Npw7NuLg0KDQpgYGB7cn0NCnByZWRfc3BhdCAlPiUgDQogIGdncGxvdCgpICsNCiAgZ2VvbV9zZihhZXMoY29sb3IgPSBleHAodmFyMS52YXIpKSkgKw0KICBzY2FsZV9jb2xvcl92aXJpZGlzX2MobmFtZSA9ICIiKQ0KYGBgDQoNCiMjIFRvZG9zIGxvcyBtb2RlbG9zDQoNCmBgYHtyfQ0KTnVsbF9wcmVkIDwtIGtyaWdlKGxvZyh6aW5jKSB+IDEsICBtZXVzZV9zZiwgbWV1c2VncmlkX3NmLCBtb2RlbCA9IGFqdXN0ZV9udWxvKSAlPiUNCiAgbXV0YXRlKE1vZGVsbyA9ICJOdWxvIikNCg0KU3BhdF9wcmVkIDwtIGtyaWdlKGxvZyh6aW5jKSB+IDEsIG1ldXNlX3NmLCBtZXVzZWdyaWRfc2YsIG1vZGVsID0gYWp1c3RlX3ZhcikgJT4lDQogIG11dGF0ZShNb2RlbG8gPSAiRXNwYWNpYWwiKQ0KDQpEaXN0X3ByZWQgPC0ga3JpZ2UobG9nKHppbmMpIH4gMSwgIG1ldXNlX3NmLCBtZXVzZWdyaWRfc2YsIG1vZGVsID0gYWp1c3RlX2Rpc3QpICU+JSANCiAgbXV0YXRlKE1vZGVsbyA9ICJEaXN0YW5jaWEiKQ0KDQpEaXN0X3NxX3ByZWQgPC0ga3JpZ2UobG9nKHppbmMpIH4gMSwgbWV1c2Vfc2YsIG1ldXNlZ3JpZF9zZiwgbW9kZWwgPSBhanVzdGVfc3FydCkgICU+JQ0KICBtdXRhdGUoTW9kZWxvID0gIlJhw616IERpc3RhbmNpYSIpDQoNClByZWQgPC0gbGlzdChOdWxsX3ByZWQsIFNwYXRfcHJlZCwgRGlzdF9wcmVkLCBEaXN0X3NxX3ByZWQpICU+JQ0KICByZWR1Y2UoYmluZF9yb3dzKQ0KUHJlZA0KYGBgDQoNCi0gKipQcmVkaWNjaW9uZXM6KioNCg0KYGBge3IsIGZpZy53aWR0aD05fQ0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBQcmVkLCBhZXMoY29sb3IgPSBleHAodmFyMS5wcmVkKSkpICsNCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKG5hbWUgPSAiWmluYyIpICsNCiAgZmFjZXRfd3JhcCh+TW9kZWxvKSAgKyANCiAgdGhlbWVfYncoKQ0KYGBgDQoNCi0gKipJbmNlcnRpZHVtYnJlIGVuIHByZWRpY2Npb25lczoqKiBlbCBtb2RlbG8gZGUgbWF5b3IgaW5jZXJ0aWR1bWJyZSBlcyBlbCBtb2RlbG8gbnVsby4NCg0KYGBge3IsIGZpZy53aWR0aD05fQ0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBQcmVkLCBhZXMoY29sb3IgPSBleHAodmFyMS52YXIpKSkgKw0KICBzY2FsZV9jb2xvcl92aXJpZGlzX2MobmFtZSA9ICJaaW5jIikgKw0KICBmYWNldF93cmFwKH5Nb2RlbG8pICArIA0KICB0aGVtZV9idygpDQpgYGANCg0KIyBNZWpvciBtb2RlbG8NCg0KLSBVbmEgbWFuZXJhIGRlIHNlbGVjY2lvbmFyIGVsIG1vZGVsbyBwb2Ryw61hIHNlciBjb21wYXJhciBsYSBib25kYWQgZGUgYWp1c3RlIGEgdHJhdsOpcyBkZSB1bmEgbcOpdHJpY2EgcXVlIHJlcHJlc2VudGUgZWwgZXJyb3IsIHBvciBlamVtcGxvLCBSYcOteiBkZWwgQ3VhZHJhZG8gTWVkaW8gZGVsIEVycm9yIChSU01FIC0gUm9vdCBTcXVhcmUgTWVhbiBFcnJvcikuDQotIFRhbWJpw6luIGVzIHBvc2libGUgaW1wbGVtZW50YXIgdmFsaWRhY2nDs24gY3J1emFkYS4NCi0gTGEgZnVuY2nDs24ga3JpZ2UuY3YgcGVybWl0ZSBhanVzdGFyIG1vZGVsb3MgYSB0cmF2w6lzIGRlIHZhbGlkYWNpw7NuIGNydXphZGEuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KTnVsbF9wcmVkX2N2IDwtIGtyaWdlLmN2KGxvZyh6aW5jKSB+IDEsICBtZXVzZV9zZiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCA9IGFqdXN0ZV9udWxvLCBuZm9sZCA9IDUpICU+JQ0KICBtdXRhdGUoTW9kZWxvID0gIk51bG8iKQ0KDQpTcGF0X3ByZWRfY3YgPC0ga3JpZ2UuY3YobG9nKHppbmMpIH4gMSwgbWV1c2Vfc2YsbW9kZWwgPSBhanVzdGVfdmFyLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG5mb2xkID0gNSkgJT4lDQogIG11dGF0ZShNb2RlbG8gPSAiRXNwYWNpYWwiKQ0KDQpEaXN0X3ByZWRfY3YgPC0ga3JpZ2UuY3YobG9nKHppbmMpIH4gMSwgIG1ldXNlX3NmLCBtb2RlbCA9IGFqdXN0ZV9kaXN0LA0KICAgICAgICAgICAgICAgICAgICAgICAgIG5mb2xkID0gNSkgJT4lIA0KICBtdXRhdGUoTW9kZWxvID0gIkRpc3RhbmNpYSIpDQoNCkRpc3Rfc3FfcHJlZF9jdiA8LSBrcmlnZS5jdihsb2coemluYykgfiAxLCBtZXVzZV9zZiwgbW9kZWwgPSBhanVzdGVfc3FydCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBuZm9sZCA9IDUpICAlPiUNCiAgbXV0YXRlKE1vZGVsbyA9ICJSYcOteiBEaXN0YW5jaWEiKQ0KDQpQcmVkX2N2IDwtIGxpc3QoTnVsbF9wcmVkX2N2LCBTcGF0X3ByZWRfY3YsIERpc3RfcHJlZF9jdiwgRGlzdF9zcV9wcmVkX2N2KSAlPiUNCiAgcmVkdWNlKGJpbmRfcm93cykNCmBgYA0KDQoNCi0gKipQcmVkaWNob3MgdnMgb2JzZXJ2YWRvczoqKg0KDQpgYGB7cn0NClByZWRfY3YgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB2YXIxLnByZWQsIHkgPSBvYnNlcnZlZCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgdGhlbWVfYncoKSArDQogIGxhYnMoeCA9ICJQcmVkaWNob3MiLCB5ID0gIk9ic2VydmFkb3MiKQ0KYGBgDQoNCi0gKipDb21wYXJhbmRvIG1vZGVsb3MgYSB0cmF2w6lzIGRlIFJNU0U6KioNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpQcmVkX2N2ICU+JSANCiAgYXMuZGF0YS5mcmFtZSgpICU+JSANCiAgZ3JvdXBfYnkoTW9kZWxvKSAlPiUgDQogIHN1bW1hcmlzZShSTVNFID0gc3FydChzdW0ocmVzaWR1YWxeMikvbGVuZ3RoKHJlc2lkdWFsKSkpDQpgYGANCg0K