Deterministic Geostatistics

Homework #4


Markdown Author: Jessie Bell

Download this Rmd: Top right corner → Code → Download Rmd

🌵 California Precipitation

Let’s turn to the California precipitation data that Hijmans uses for the nearest neighbor interpolation but we will do something cooler. We will make a continuous 10x10km surface of precipitation from the 432 locations of long-term precipitation records.

Data

#had to download the package and install as a local file: 
#install.packages("rspat_1.0-1.tar.gz", repos = NULL, type="source")

# precip point data
prcpCA <- readRDS("prcpCA.rds")
# empty grid to interpolate into
gridCA <- readRDS("gridCA.rds")

# make as sf
prcpCA_sf <- prcpCA |> st_as_sf(coords = c("X", "Y")) |>
  st_set_crs(value = 3310)

# simple map -- see postscript below for a fancy map
prcpCA_sf %>% ggplot() + 
  geom_sf(aes(fill=ANNUAL,size=ANNUAL),color="white",
          shape=21,alpha=0.8) + 
  scale_fill_continuous(type = "viridis",name="mm") + 
  labs(title="Total Annual Precipitation") +
  scale_size(guide="none")

IDW

idw_CA_model <- gstat(formula=ANNUAL~1, 
                      locations=prcpCA_sf, 
                      set=list(idp=2)) #using p of 2


idw_CA_model_1 <- gstat(formula=ANNUAL~1, 
                      locations=prcpCA_sf, 
                      set=list(idp=1)) #using p of 1

idw_CA_model_half <- gstat(formula=ANNUAL~1, 
                      locations=prcpCA_sf, 
                      set=list(idp=1)) #using p of 0.5

#turn grid into sf
gridCA_sf <- st_as_sf(gridCA, 
                          coords = c("X","Y"), 
                          crs = (value = 3310))


pred_IDW_CA_sf <- predict(idw_CA_model, gridCA_sf)
## [inverse distance weighted interpolation]
pred_IDW_CA_sf_1 <- predict(idw_CA_model_1, gridCA_sf)
## [inverse distance weighted interpolation]
pred_IDW_CA_sf_half <- predict(idw_CA_model_half, gridCA_sf)
## [inverse distance weighted interpolation]
Convert to SpatRaster
sf_CA_rast <-function(sfObject,variableIndex = 1){
  # coerce sf to a data.frame
  dfObject <- data.frame(st_coordinates(sfObject),
                         z=as.data.frame(sfObject)[,variableIndex])
  # coerce data.frame to SpatRaster
  rastObject <- rast(dfObject,crs=crs(sfObject))
  
  names(rastObject) <- names(sfObject)[variableIndex]
  
  return(rastObject)
}

IDW_CA_rast <- sf_CA_rast(pred_IDW_CA_sf)
IDW_CA_rast
## class       : SpatRaster 
## dimensions  : 105, 91, 1  (nrow, ncol, nlyr)
## resolution  : 10000, 10000  (x, y)
## extent      : -371692.4, 538307.6, -599520.9, 450479.1  (xmin, xmax, ymin, ymax)
## coord. ref. : NAD83 / California Albers (EPSG:3310) 
## source(s)   : memory
## name        :  var1.pred 
## min value   :   62.70143 
## max value   : 2270.15526
IDW_CA_rast_1 <- sf_CA_rast(pred_IDW_CA_sf_1)
IDW_CA_rast_half <- sf_CA_rast(pred_IDW_CA_sf_half)

# and plot
ggplot() +
  geom_spatraster(data=IDW_CA_rast, mapping = aes(fill=var1.pred),alpha=0.8) +
  scale_fill_continuous(type = "viridis",name="Precipitation(mm)",na.value = "transparent") + 
  labs(title="ANNUAL PRECIPITATION", subtitle = "IDW with p=2") +
  theme_minimal()

Make it better:

ggplot() +
  geom_spatraster_contour_filled(data=IDW_CA_rast,
                                 breaks = seq(from=0, to=2000,by=200),
                                 alpha = 0.9) +
  scale_fill_discrete(name="Precipitation (mm)",na.value = "transparent") + 
  labs(title="Annual Precipitation", subtitle = "IDW with p=2") +
  theme_minimal()

ggplot() +
  geom_spatraster_contour_filled(data=IDW_CA_rast_1,
                                 breaks = seq(from=0, to=2000,by=200),
                                 alpha = 0.9) +
  scale_fill_discrete(name="Precipitation (mm)",na.value = "transparent") + 
  labs(title="Annual Precipitation", subtitle = "IDW with p=1") +
  theme_minimal()

ggplot() +
  geom_spatraster_contour_filled(data=IDW_CA_rast_half,
                                 breaks = seq(from=0, to=2000,by=200),
                                 alpha = 0.9) +
  scale_fill_discrete(name="Precipitation (mm)",na.value = "transparent") + 
  labs(title="Annual Precipitation", subtitle = "IDW with p=0.5") +
  theme_minimal()

Skill

obs <- prcpCA_sf$ANNUAL

preds <- terra::extract(IDW_CA_rast, prcpCA_sf, na.rm=T) %>% pull(var1.pred)

rsq <- cor(obs,preds,use="complete.obs")^2
rmse <- sqrt(mean((preds - obs)^2,na.rm=TRUE))
rsq
## [1] 0.9828998
rmse
## [1] 65.29779
#p=2 has highest R^2
ggplot() +
  geom_abline(slope=1,intercept = 0) +
  geom_point(aes(x=obs,y=preds), color="plum") + 
  coord_fixed(ratio=1, xlim = range(preds,obs),ylim = range(preds,obs)) +
  labs(x="Observed Values",
       y="Predicted Values",
       title="Annual Precipitation (mm) CA")

Withhold Data

Witholding 25% just like in the example:

n <- nrow(prcpCA_sf)
rows4test <- sample(x = 1:n,size = n*0.25)
CATest <- prcpCA_sf[rows4test,]
CATrain <- prcpCA_sf[-rows4test,]

# note that we build the model with CATrain
idw_p2_model <- gstat(formula=ANNUAL~1, 
                      locations = CATrain,
                      set=list(idp = 2))
prcpIDW_p2_sf <- predict(idw_p2_model,gridCA_sf)
## [inverse distance weighted interpolation]
prcpIDW_p2_rast <- sf_CA_rast(prcpIDW_p2_sf)

# and plot
ggplot() +
  geom_spatraster(data=prcpIDW_p2_rast, mapping = aes(fill=var1.pred),alpha=0.8) +
  scale_fill_continuous(type = "viridis",name="Precipitation (mm)",na.value = "transparent") + 
  labs(title="Annual Precipitation", subtitle = "IDW with p=2") +
  theme_minimal()

We built the model with the training data CATrain which had 75% of the original data. We kept 25% of the data hidden from the model in CATest. By comparing the predictions from the training data to the withheld values from the testing data we can assess the out-of-sample skill.

# note use of CATest here
obs <- CATest$ANNUAL
preds <- terra::extract(prcpIDW_p2_rast, CATest) %>% pull(var1.pred)
rsq <- cor(obs,preds,use="complete.obs")^2
rmse <- sqrt(mean((preds - obs)^2,na.rm=TRUE))
rsq
## [1] 0.7510978
rmse
## [1] 218.0212
ggplot() +
  geom_abline(slope=1,intercept = 0) +
  geom_point(aes(x=obs,y=preds), color="steelblue") + 
  coord_fixed(ratio=1, xlim = range(preds,obs),ylim = range(preds,obs)) +
  labs(x="Observed Values",
       y="Predicted Values",
       title="Annual Precipitation (mm)")

Looking at the plot above you can see that the skill of the model still has not decreased by very much, with an \(R^2\) = 0.8198488, and RMSE = 232.5713. You can also see that both predicted and observed values fall equally above and below the line for these points.

RMSE Model

Here is the RMSE of the NULL case where everything is predicted as the mean:

obs <- prcpCA_sf$ANNUAL
preds <- mean(prcpCA_sf$ANNUAL)

rmseNULL <- sqrt(mean((preds - obs)^2))
rmseNULL
## [1] 438.947
rmse
## [1] 218.0212

Here you can see that rmse is quite a bit lower that rmseNULL. Now, let’s calculate the relative performance of rmse:

1 - (rmse / rmseNULL)
## [1] 0.5033085

Tmap

tmap_mode("view")
## tmap mode set to interactive viewing
#turn sf to rast
CAIDW_p2_rast <- sf_CA_rast(pred_IDW_CA_sf)

tm_shape(CAIDW_p2_rast) +
  tm_raster(col="var1.pred", palette = "viridis")+
  tm_shape(prcpCA_sf)+
  tm_symbols(col="tomato",alpha=0.5,size =  0.5)
LS0tDQp0aXRsZTogIiAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNzczogc3R5bGUuY3NzDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGLCBjYWNoZSA9IFQsIHNpemU9InNtYWxsIikNCmxpYnJhcnkoZ3N0YXQpDQpsaWJyYXJ5KHNwKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRlcnJhKQ0KbGlicmFyeSh0aWR5dGVycmEpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShyc3BhdCkNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHRtYXApDQoNCmBgYA0KDQojIyBEZXRlcm1pbmlzdGljIEdlb3N0YXRpc3RpY3MNCg0KIyMjIyBIb21ld29yayAjNA0KDQo8YnI+DQoNCioqTWFya2Rvd24gQXV0aG9yOioqIEplc3NpZSBCZWxsDQoNCnwgDQoNCioqRG93bmxvYWQgdGhpcyBSbWQ6KiogVG9wIHJpZ2h0IGNvcm5lciDihpIgQ29kZSDihpIgRG93bmxvYWQgUm1kDQoNCiMjIyDwn4y1IENhbGlmb3JuaWEgUHJlY2lwaXRhdGlvbiB7LnRhYnNldH0NCg0KTGV04oCZcyB0dXJuIHRvIHRoZSBDYWxpZm9ybmlhIHByZWNpcGl0YXRpb24gZGF0YSB0aGF0IEhpam1hbnMgdXNlcyBmb3IgdGhlIG5lYXJlc3QgbmVpZ2hib3IgaW50ZXJwb2xhdGlvbiBidXQgd2Ugd2lsbCBkbyBzb21ldGhpbmcgY29vbGVyLiBXZSB3aWxsIG1ha2UgYSBjb250aW51b3VzIDEweDEwa20gc3VyZmFjZSBvZiBwcmVjaXBpdGF0aW9uIGZyb20gdGhlIDQzMiBsb2NhdGlvbnMgb2YgbG9uZy10ZXJtIHByZWNpcGl0YXRpb24gcmVjb3Jkcy4NCg0KIyMjIyBEYXRhDQoNCmBgYHtyIGRhdGF9DQoNCiNoYWQgdG8gZG93bmxvYWQgdGhlIHBhY2thZ2UgYW5kIGluc3RhbGwgYXMgYSBsb2NhbCBmaWxlOiANCiNpbnN0YWxsLnBhY2thZ2VzKCJyc3BhdF8xLjAtMS50YXIuZ3oiLCByZXBvcyA9IE5VTEwsIHR5cGU9InNvdXJjZSIpDQoNCiMgcHJlY2lwIHBvaW50IGRhdGENCnByY3BDQSA8LSByZWFkUkRTKCJwcmNwQ0EucmRzIikNCiMgZW1wdHkgZ3JpZCB0byBpbnRlcnBvbGF0ZSBpbnRvDQpncmlkQ0EgPC0gcmVhZFJEUygiZ3JpZENBLnJkcyIpDQoNCiMgbWFrZSBhcyBzZg0KcHJjcENBX3NmIDwtIHByY3BDQSB8PiBzdF9hc19zZihjb29yZHMgPSBjKCJYIiwgIlkiKSkgfD4NCiAgc3Rfc2V0X2Nycyh2YWx1ZSA9IDMzMTApDQoNCiMgc2ltcGxlIG1hcCAtLSBzZWUgcG9zdHNjcmlwdCBiZWxvdyBmb3IgYSBmYW5jeSBtYXANCnByY3BDQV9zZiAlPiUgZ2dwbG90KCkgKyANCiAgZ2VvbV9zZihhZXMoZmlsbD1BTk5VQUwsc2l6ZT1BTk5VQUwpLGNvbG9yPSJ3aGl0ZSIsDQogICAgICAgICAgc2hhcGU9MjEsYWxwaGE9MC44KSArIA0KICBzY2FsZV9maWxsX2NvbnRpbnVvdXModHlwZSA9ICJ2aXJpZGlzIixuYW1lPSJtbSIpICsgDQogIGxhYnModGl0bGU9IlRvdGFsIEFubnVhbCBQcmVjaXBpdGF0aW9uIikgKw0KICBzY2FsZV9zaXplKGd1aWRlPSJub25lIikNCmBgYA0KDQojIyMjIElEVyANCg0KYGBge3IgaWR3fQ0KaWR3X0NBX21vZGVsIDwtIGdzdGF0KGZvcm11bGE9QU5OVUFMfjEsIA0KICAgICAgICAgICAgICAgICAgICAgIGxvY2F0aW9ucz1wcmNwQ0Ffc2YsIA0KICAgICAgICAgICAgICAgICAgICAgIHNldD1saXN0KGlkcD0yKSkgI3VzaW5nIHAgb2YgMg0KDQoNCmlkd19DQV9tb2RlbF8xIDwtIGdzdGF0KGZvcm11bGE9QU5OVUFMfjEsIA0KICAgICAgICAgICAgICAgICAgICAgIGxvY2F0aW9ucz1wcmNwQ0Ffc2YsIA0KICAgICAgICAgICAgICAgICAgICAgIHNldD1saXN0KGlkcD0xKSkgI3VzaW5nIHAgb2YgMQ0KDQppZHdfQ0FfbW9kZWxfaGFsZiA8LSBnc3RhdChmb3JtdWxhPUFOTlVBTH4xLCANCiAgICAgICAgICAgICAgICAgICAgICBsb2NhdGlvbnM9cHJjcENBX3NmLCANCiAgICAgICAgICAgICAgICAgICAgICBzZXQ9bGlzdChpZHA9MSkpICN1c2luZyBwIG9mIDAuNQ0KDQojdHVybiBncmlkIGludG8gc2YNCmdyaWRDQV9zZiA8LSBzdF9hc19zZihncmlkQ0EsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBjb29yZHMgPSBjKCJYIiwiWSIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgY3JzID0gKHZhbHVlID0gMzMxMCkpDQoNCg0KcHJlZF9JRFdfQ0Ffc2YgPC0gcHJlZGljdChpZHdfQ0FfbW9kZWwsIGdyaWRDQV9zZikNCg0KDQoNCnByZWRfSURXX0NBX3NmXzEgPC0gcHJlZGljdChpZHdfQ0FfbW9kZWxfMSwgZ3JpZENBX3NmKQ0KDQpwcmVkX0lEV19DQV9zZl9oYWxmIDwtIHByZWRpY3QoaWR3X0NBX21vZGVsX2hhbGYsIGdyaWRDQV9zZikNCmBgYA0KIyMjIyMgQ29udmVydCB0byBgYGBTcGF0UmFzdGVyYGBgDQpgYGB7ciB0b3NwYXR9DQpzZl9DQV9yYXN0IDwtZnVuY3Rpb24oc2ZPYmplY3QsdmFyaWFibGVJbmRleCA9IDEpew0KICAjIGNvZXJjZSBzZiB0byBhIGRhdGEuZnJhbWUNCiAgZGZPYmplY3QgPC0gZGF0YS5mcmFtZShzdF9jb29yZGluYXRlcyhzZk9iamVjdCksDQogICAgICAgICAgICAgICAgICAgICAgICAgej1hcy5kYXRhLmZyYW1lKHNmT2JqZWN0KVssdmFyaWFibGVJbmRleF0pDQogICMgY29lcmNlIGRhdGEuZnJhbWUgdG8gU3BhdFJhc3Rlcg0KICByYXN0T2JqZWN0IDwtIHJhc3QoZGZPYmplY3QsY3JzPWNycyhzZk9iamVjdCkpDQogIA0KICBuYW1lcyhyYXN0T2JqZWN0KSA8LSBuYW1lcyhzZk9iamVjdClbdmFyaWFibGVJbmRleF0NCiAgDQogIHJldHVybihyYXN0T2JqZWN0KQ0KfQ0KDQpJRFdfQ0FfcmFzdCA8LSBzZl9DQV9yYXN0KHByZWRfSURXX0NBX3NmKQ0KSURXX0NBX3Jhc3QNCg0KSURXX0NBX3Jhc3RfMSA8LSBzZl9DQV9yYXN0KHByZWRfSURXX0NBX3NmXzEpDQpJRFdfQ0FfcmFzdF9oYWxmIDwtIHNmX0NBX3Jhc3QocHJlZF9JRFdfQ0Ffc2ZfaGFsZikNCg0KIyBhbmQgcGxvdA0KZ2dwbG90KCkgKw0KICBnZW9tX3NwYXRyYXN0ZXIoZGF0YT1JRFdfQ0FfcmFzdCwgbWFwcGluZyA9IGFlcyhmaWxsPXZhcjEucHJlZCksYWxwaGE9MC44KSArDQogIHNjYWxlX2ZpbGxfY29udGludW91cyh0eXBlID0gInZpcmlkaXMiLG5hbWU9IlByZWNpcGl0YXRpb24obW0pIixuYS52YWx1ZSA9ICJ0cmFuc3BhcmVudCIpICsgDQogIGxhYnModGl0bGU9IkFOTlVBTCBQUkVDSVBJVEFUSU9OIiwgc3VidGl0bGUgPSAiSURXIHdpdGggcD0yIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpNYWtlIGl0IGJldHRlcjogDQoNCmBgYHtyIGJldHRlcn0NCmdncGxvdCgpICsNCiAgZ2VvbV9zcGF0cmFzdGVyX2NvbnRvdXJfZmlsbGVkKGRhdGE9SURXX0NBX3Jhc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoZnJvbT0wLCB0bz0yMDAwLGJ5PTIwMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IDAuOSkgKw0KICBzY2FsZV9maWxsX2Rpc2NyZXRlKG5hbWU9IlByZWNpcGl0YXRpb24gKG1tKSIsbmEudmFsdWUgPSAidHJhbnNwYXJlbnQiKSArIA0KICBsYWJzKHRpdGxlPSJBbm51YWwgUHJlY2lwaXRhdGlvbiIsIHN1YnRpdGxlID0gIklEVyB3aXRoIHA9MiIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCg0KZ2dwbG90KCkgKw0KICBnZW9tX3NwYXRyYXN0ZXJfY29udG91cl9maWxsZWQoZGF0YT1JRFdfQ0FfcmFzdF8xLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2VxKGZyb209MCwgdG89MjAwMCxieT0yMDApLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjkpICsNCiAgc2NhbGVfZmlsbF9kaXNjcmV0ZShuYW1lPSJQcmVjaXBpdGF0aW9uIChtbSkiLG5hLnZhbHVlID0gInRyYW5zcGFyZW50IikgKyANCiAgbGFicyh0aXRsZT0iQW5udWFsIFByZWNpcGl0YXRpb24iLCBzdWJ0aXRsZSA9ICJJRFcgd2l0aCBwPTEiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fc3BhdHJhc3Rlcl9jb250b3VyX2ZpbGxlZChkYXRhPUlEV19DQV9yYXN0X2hhbGYsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoZnJvbT0wLCB0bz0yMDAwLGJ5PTIwMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IDAuOSkgKw0KICBzY2FsZV9maWxsX2Rpc2NyZXRlKG5hbWU9IlByZWNpcGl0YXRpb24gKG1tKSIsbmEudmFsdWUgPSAidHJhbnNwYXJlbnQiKSArIA0KICBsYWJzKHRpdGxlPSJBbm51YWwgUHJlY2lwaXRhdGlvbiIsIHN1YnRpdGxlID0gIklEVyB3aXRoIHA9MC41IikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KDQpgYGANCg0KIyMjIyBTa2lsbA0KDQpgYGB7ciBza2lsczJ9DQoNCm9icyA8LSBwcmNwQ0Ffc2YkQU5OVUFMDQoNCnByZWRzIDwtIHRlcnJhOjpleHRyYWN0KElEV19DQV9yYXN0LCBwcmNwQ0Ffc2YsIG5hLnJtPVQpICU+JSBwdWxsKHZhcjEucHJlZCkNCg0KcnNxIDwtIGNvcihvYnMscHJlZHMsdXNlPSJjb21wbGV0ZS5vYnMiKV4yDQpybXNlIDwtIHNxcnQobWVhbigocHJlZHMgLSBvYnMpXjIsbmEucm09VFJVRSkpDQpyc3ENCnJtc2UNCg0KI3A9MiBoYXMgaGlnaGVzdCBSXjINCg0KYGBgDQoNCmBgYHtyIGdncGxvdCwgd2FybmluZz1GfQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fYWJsaW5lKHNsb3BlPTEsaW50ZXJjZXB0ID0gMCkgKw0KICBnZW9tX3BvaW50KGFlcyh4PW9icyx5PXByZWRzKSwgY29sb3I9InBsdW0iKSArIA0KICBjb29yZF9maXhlZChyYXRpbz0xLCB4bGltID0gcmFuZ2UocHJlZHMsb2JzKSx5bGltID0gcmFuZ2UocHJlZHMsb2JzKSkgKw0KICBsYWJzKHg9Ik9ic2VydmVkIFZhbHVlcyIsDQogICAgICAgeT0iUHJlZGljdGVkIFZhbHVlcyIsDQogICAgICAgdGl0bGU9IkFubnVhbCBQcmVjaXBpdGF0aW9uIChtbSkgQ0EiKQ0KYGBgDQoNCiMjIyMgV2l0aGhvbGQgRGF0YQ0KDQpXaXRob2xkaW5nIDI1JSBqdXN0IGxpa2UgaW4gdGhlIGV4YW1wbGU6DQoNCmBgYHtyIGRhdGF3aXRob2xkfQ0KbiA8LSBucm93KHByY3BDQV9zZikNCnJvd3M0dGVzdCA8LSBzYW1wbGUoeCA9IDE6bixzaXplID0gbiowLjI1KQ0KQ0FUZXN0IDwtIHByY3BDQV9zZltyb3dzNHRlc3QsXQ0KQ0FUcmFpbiA8LSBwcmNwQ0Ffc2ZbLXJvd3M0dGVzdCxdDQoNCiMgbm90ZSB0aGF0IHdlIGJ1aWxkIHRoZSBtb2RlbCB3aXRoIENBVHJhaW4NCmlkd19wMl9tb2RlbCA8LSBnc3RhdChmb3JtdWxhPUFOTlVBTH4xLCANCiAgICAgICAgICAgICAgICAgICAgICBsb2NhdGlvbnMgPSBDQVRyYWluLA0KICAgICAgICAgICAgICAgICAgICAgIHNldD1saXN0KGlkcCA9IDIpKQ0KcHJjcElEV19wMl9zZiA8LSBwcmVkaWN0KGlkd19wMl9tb2RlbCxncmlkQ0Ffc2YpDQoNCg0KcHJjcElEV19wMl9yYXN0IDwtIHNmX0NBX3Jhc3QocHJjcElEV19wMl9zZikNCg0KIyBhbmQgcGxvdA0KZ2dwbG90KCkgKw0KICBnZW9tX3NwYXRyYXN0ZXIoZGF0YT1wcmNwSURXX3AyX3Jhc3QsIG1hcHBpbmcgPSBhZXMoZmlsbD12YXIxLnByZWQpLGFscGhhPTAuOCkgKw0KICBzY2FsZV9maWxsX2NvbnRpbnVvdXModHlwZSA9ICJ2aXJpZGlzIixuYW1lPSJQcmVjaXBpdGF0aW9uIChtbSkiLG5hLnZhbHVlID0gInRyYW5zcGFyZW50IikgKyANCiAgbGFicyh0aXRsZT0iQW5udWFsIFByZWNpcGl0YXRpb24iLCBzdWJ0aXRsZSA9ICJJRFcgd2l0aCBwPTIiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCldlIGJ1aWx0IHRoZSBtb2RlbCB3aXRoIHRoZSB0cmFpbmluZyBkYXRhIGBgYENBVHJhaW5gYGAgd2hpY2ggaGFkIDc1JSBvZiB0aGUgb3JpZ2luYWwgZGF0YS4gV2Uga2VwdCAyNSUgb2YgdGhlIGRhdGEgaGlkZGVuIGZyb20gdGhlIG1vZGVsIGluIGBgYENBVGVzdGBgYC4gQnkgY29tcGFyaW5nIHRoZSBwcmVkaWN0aW9ucyBmcm9tIHRoZSB0cmFpbmluZyBkYXRhIHRvIHRoZSB3aXRoaGVsZCB2YWx1ZXMgZnJvbSB0aGUgdGVzdGluZyBkYXRhIHdlIGNhbiBhc3Nlc3MgdGhlIG91dC1vZi1zYW1wbGUgc2tpbGwuDQoNCmBgYHtyIHNraWxsIGFnYWlufQ0KIyBub3RlIHVzZSBvZiBDQVRlc3QgaGVyZQ0Kb2JzIDwtIENBVGVzdCRBTk5VQUwNCnByZWRzIDwtIHRlcnJhOjpleHRyYWN0KHByY3BJRFdfcDJfcmFzdCwgQ0FUZXN0KSAlPiUgcHVsbCh2YXIxLnByZWQpDQpyc3EgPC0gY29yKG9icyxwcmVkcyx1c2U9ImNvbXBsZXRlLm9icyIpXjINCnJtc2UgPC0gc3FydChtZWFuKChwcmVkcyAtIG9icyleMixuYS5ybT1UUlVFKSkNCnJzcQ0KDQpybXNlDQpgYGANCg0KYGBge3IgZ2dwbG90IHRoZSBtb2RlbCwgd2FybmluZz1GfQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fYWJsaW5lKHNsb3BlPTEsaW50ZXJjZXB0ID0gMCkgKw0KICBnZW9tX3BvaW50KGFlcyh4PW9icyx5PXByZWRzKSwgY29sb3I9InN0ZWVsYmx1ZSIpICsgDQogIGNvb3JkX2ZpeGVkKHJhdGlvPTEsIHhsaW0gPSByYW5nZShwcmVkcyxvYnMpLHlsaW0gPSByYW5nZShwcmVkcyxvYnMpKSArDQogIGxhYnMoeD0iT2JzZXJ2ZWQgVmFsdWVzIiwNCiAgICAgICB5PSJQcmVkaWN0ZWQgVmFsdWVzIiwNCiAgICAgICB0aXRsZT0iQW5udWFsIFByZWNpcGl0YXRpb24gKG1tKSIpDQpgYGANCg0KTG9va2luZyBhdCB0aGUgcGxvdCBhYm92ZSB5b3UgY2FuIHNlZSB0aGF0IHRoZSBza2lsbCBvZiB0aGUgbW9kZWwgc3RpbGwgaGFzIG5vdCBkZWNyZWFzZWQgYnkgdmVyeSBtdWNoLCB3aXRoIGFuICRSXjIkID0gMC44MTk4NDg4LCBhbmQgUk1TRSA9IDIzMi41NzEzLiBZb3UgY2FuIGFsc28gc2VlIHRoYXQgYm90aCBwcmVkaWN0ZWQgYW5kIG9ic2VydmVkIHZhbHVlcyBmYWxsIGVxdWFsbHkgYWJvdmUgYW5kIGJlbG93IHRoZSBsaW5lIGZvciB0aGVzZSBwb2ludHMuIA0KDQojIyMjIFJNU0UgTW9kZWwNCg0KSGVyZSBpcyB0aGUgUk1TRSBvZiB0aGUgTlVMTCBjYXNlIHdoZXJlIGV2ZXJ5dGhpbmcgaXMgcHJlZGljdGVkIGFzIHRoZSBtZWFuOg0KDQpgYGB7ciBudWxsfQ0Kb2JzIDwtIHByY3BDQV9zZiRBTk5VQUwNCnByZWRzIDwtIG1lYW4ocHJjcENBX3NmJEFOTlVBTCkNCg0Kcm1zZU5VTEwgPC0gc3FydChtZWFuKChwcmVkcyAtIG9icyleMikpDQpybXNlTlVMTA0KDQpybXNlDQpgYGANCkhlcmUgeW91IGNhbiBzZWUgdGhhdCBgYGBybXNlYGBgIGlzIHF1aXRlIGEgYml0IGxvd2VyIHRoYXQgYGBgcm1zZU5VTExgYGAuIE5vdywgbGV0J3MgY2FsY3VsYXRlIHRoZSByZWxhdGl2ZSBwZXJmb3JtYW5jZSBvZiBgYGBybXNlYGBgOg0KDQpgYGB7ciBybXNlfQ0KMSAtIChybXNlIC8gcm1zZU5VTEwpDQpgYGANCg0KIyMjIyBUbWFwDQoNCmBgYHtyIHRtYXB9DQp0bWFwX21vZGUoInZpZXciKQ0KDQojdHVybiBzZiB0byByYXN0DQpDQUlEV19wMl9yYXN0IDwtIHNmX0NBX3Jhc3QocHJlZF9JRFdfQ0Ffc2YpDQoNCnRtX3NoYXBlKENBSURXX3AyX3Jhc3QpICsNCiAgdG1fcmFzdGVyKGNvbD0idmFyMS5wcmVkIiwgcGFsZXR0ZSA9ICJ2aXJpZGlzIikrDQogIHRtX3NoYXBlKHByY3BDQV9zZikrDQogIHRtX3N5bWJvbHMoY29sPSJ0b21hdG8iLGFscGhhPTAuNSxzaXplID0gIDAuNSkNCmBgYA0KDQoNCg0KPGRpdiBjbGFzcz0idG9jaWZ5LWV4dGVuZC1wYWdlIiBkYXRhLXVuaXF1ZT0idG9jaWZ5LWV4dGVuZC1wYWdlIiBzdHlsZT0iaGVpZ2h0OiAwOyI+PC9kaXY+