Background

When analysing Ta exposure for the SEPAGES cohort, Emie and Arianne noticed that some exposures are missing. The problem seems to be caused by the fact that the 1 km grid cell coordinates vary between years.

Exploration

knitr::opts_knit$set(root.dir = here::here())
knitr::opts_knit$set(echo = FALSE)
library(magrittr)
library(glue)
library(fst)
library(data.table)

1. Check whether grid coordinates change between 2015 and 2017

We start with a quick check on one day of data for 2015 and 2017.

# Set the path to the directory that contains the final 1 km Ta model predictions
expo_dir <- "/media/summer_modeles_expo/temperature_hough_1km"
# Load first day of final 1 km Ta predictions for 2015 and 2017
ncells <- glue("{expo_dir}/mod3_2015_pred_1km.fst") %>% 
          fst() %>%
          nrow() %>%
          divide_by(365)
jan1_2015 <- glue("{expo_dir}/mod3_2015_pred_1km.fst") %>% 
             read_fst(to = ncells, as.data.table = TRUE)
jan1_2017 <- glue("{expo_dir}/mod3_2017_pred_1km.fst") %>% 
             read_fst(to = ncells, as.data.table = TRUE)
pryr::mem_used()
209 MB
# Confirm coordinates are same between years
print("Checking coordinates for Jan 1, 2015 / 2017")
[1] "Checking coordinates for Jan 1, 2015 / 2017"
identical(jan1_2015$grd_1km_id, jan1_2017$grd_1km_id) %>% 
  paste("Jan 1 2015 vs 2017: id identical?", .)
[1] "Jan 1 2015 vs 2017: id identical? TRUE"
identical(jan1_2015[, .(grd_1km_x, grd_1km_y)], jan1_2017[, .(grd_1km_x, grd_1km_y)]) %>% 
  paste("Jan 1 2015 vs 2017: x and y identical?", .)
[1] "Jan 1 2015 vs 2017: x and y identical? FALSE"

The IDs are identical, but the coordinates differ from 2015 to 2017. However, the coordinates are floating point numbers and so should not be assumed to be exactly equal. Comparing with all.equal shows that they differ by no more than 1 nanometer:

all.equal(
  jan1_2015[, .(grd_1km_x, grd_1km_y)],
  jan1_2017[, .(grd_1km_x, grd_1km_y)],
  tolerance = 1e-9
) %>% 
  paste("Jan 1 2015 vs 2017: x and y equal to within 1 nanometer?", .)
[1] "Jan 1 2015 vs 2017: x and y equal to within 1 nanometer? TRUE"
print("Jan 1 2015 vs 2017 grd_1km_x")
[1] "Jan 1 2015 vs 2017 grd_1km_x"
diff_idx <- which(jan1_2015$grd_1km_x != jan1_2017$grd_1km_x)[1:3]
c("2015:", jan1_2015[diff_idx, format(grd_1km_x, nsmall = 10)])
[1] "2015:"             "661279.2366867308" "658520.7071575987" "659451.2811964190"
c("2017:", jan1_2017[diff_idx, format(grd_1km_x, nsmall = 10)])
[1] "2017:"             "661279.2366867306" "658520.7071575986" "659451.2811964189"

2. Check coordinates for entire years

Now we confirm there are no substantial differences between the coordinates for the entire years.

# Load entire years
# cols <- c("date", "grd_1km_id", "grd_1km_x", "grd_1km_y", "tmin")
# ta_2015 <- read_fst(glue("{expo_dir}/mod3_2015_pred_1km.fst"), as.data.table = TRUE, columns = cols)
# ta_2017 <- read_fst(glue("{expo_dir}/mod3_2017_pred_1km.fst"), as.data.table = TRUE, columns = cols)
pryr::mem_used()
18.5 GB
# Confirm coordinates are same in 2015 and 2017
identical(ta_2015$grd_1km_id, ta_2017$grd_1km_id) %>% 
  paste("2015 vs 2017: id identical?", .)
[1] "2015 vs 2017: id identical? TRUE"
identical(ta_2015[, .(grd_1km_x, grd_1km_y)], ta_2017[, .(grd_1km_x, grd_1km_y)]) %>% 
  paste("2015 vs 2017: x and y identical?", .)
[1] "2015 vs 2017: x and y identical? FALSE"
all.equal(ta_2015[, .(grd_1km_x, grd_1km_y)], ta_2017[, .(grd_1km_x, grd_1km_y)]) %>% 
  paste("2015 vs 2017: x and y equal to within 1 nanometer?", .)

Again, the IDs are identical in all years and the coordinates differ by less than 1 nanometer between 2015 and 2017. Within a year the coordinates are identical.

setNumericRounding(0) # Confirm data.table does not round when checking uniqueness
ta_2015[, .(locs = uniqueN(.SD)), .SDcols = c("grd_1km_x", "grd_1km_y"), by = .(grd_1km_id)] %>%
  .[locs > 1, .N] %>%
  paste("2015 grid cells with varying coords:", .)
[1] "2015 grid cells with varying coords: 0"
ta_2017[, .(locs = uniqueN(.SD)), .SDcols = c("grd_1km_x", "grd_1km_y"), by = .(grd_1km_id)] %>%
  .[locs > 1, .N] %>%
  paste("2017 grid cells with varying coords:", .)
[1] "2017 grid cells with varying coords: 0"

3. Calculate exposure for SEPAGES

Now we check to see if the miniscule difference in coordinates is the source of the problems with the calculated exposures. First we load the matched grid coordinates for each participant address and confirm that the column mother_token_nbmoove is a unique row identifier.

# Load SEPAGES subject addresses matched to Ta model grid cell coordinates
db_address_expo <- readRDS("~/db_address_expo.rds") %>%
                   setkeyv(c("mother_token", "nbmoove", "start_date", "end_date"))
db_address_expo[, .N, by = .(mother_token_nbmoove)] %>%
  .[N > 1, .N] %>%
  paste("Non-unique mother_token_nbmoove:", .) %>%
  print()
[1] "Non-unique mother_token_nbmoove: 0"

Now we calculate exposure for 2015 and 2017 by matching the coordinates of each subject-location to the exposure model. This is dangerous because coordinates are floating-point values and so may differ very slightly if any calculation was performed on them (and maybe between machines???).

# Get Ta exposure for 2015 and 2017
setkeyv(ta_2015, c("date", "grd_1km_id", "grd_1km_x", "grd_1km_y"))
setkeyv(ta_2017, c("date", "grd_1km_id", "grd_1km_x", "grd_1km_y"))
day_xy <- db_address_expo[, .(grd_1km_x, grd_1km_y, date = seq.Date(start_date, end_date, by = 1)),
                          keyby = .(mother_token_nbmoove)]
expo_2015 <- day_xy[year(date) == 2015, ] %>%
             ta_2015[., on = .(date, grd_1km_x, grd_1km_y)] %>%
             setkeyv(c("mother_token_nbmoove", "date")) %>%
             setcolorder(c("mother_token_nbmoove", "date", "grd_1km_id"))
expo_2017 <- day_xy[year(date) == 2017, ] %>%
             ta_2017[., on = .(date, grd_1km_x, grd_1km_y)] %>%
             setkeyv(c("mother_token_nbmoove", "date")) %>%
             setcolorder(c("mother_token_nbmoove", "date", "grd_1km_id"))
paste("Subject location-days with no 2015 exposure:", expo_2015[is.na(grd_1km_id), .N])
[1] "Subject location-days with no 2015 exposure: 106"
paste("Subject location-days with no 2017 exposure:", expo_2017[is.na(grd_1km_id), .N])
[1] "Subject location-days with no 2017 exposure: 86724"

If we look at one subject-location for a few days in 2015 and 2017 we see that we have exposure for 2015 but not 2015, despite no apparent difference in the coordinates.

smpl <- rbind(
          expo_2015[mother_token_nbmoove == "AB054987_1", ][1:3, ],
          expo_2017[mother_token_nbmoove == "AB054987_1", ][1:3, ]
        )
smpl

However, if we look at the full coordinates we see that in 2015 they match but in 2017 they differ very slightly from the coordinates in the Ta model:

print("Full subject-location coordinates:")
[1] "Full subject-location coordinates:"
xy <- day_xy[mother_token_nbmoove == "AB054987_1", .(grd_1km_x, grd_1km_y)] %>%
      unique()
format(xy, nsmall = 10)
     grd_1km_x           grd_1km_y           
[1,] "718921.0712282599" "6494067.2242461294"
print("Corresponding 2015 Ta model coordinates:")
[1] "Corresponding 2015 Ta model coordinates:"
ta_2015[between(grd_1km_x, 718921, 718922) & between(grd_1km_y, 6494067, 6494068),
        .(grd_1km_x, grd_1km_y)] %>%
  unique() %>%
  format(nsmall = 10)
     grd_1km_x           grd_1km_y           
[1,] "718921.0712282599" "6494067.2242461294"
print("Corresponding 2017 Ta model coordinates:")
[1] "Corresponding 2017 Ta model coordinates:"
ta_2017[between(grd_1km_x, 718921, 718922) & between(grd_1km_y, 6494067, 6494068),
        .(grd_1km_x, grd_1km_y)] %>%
  unique() %>%
  format(nsmall = 10)
     grd_1km_x           grd_1km_y           
[1,] "718921.0712282599" "6494067.2242461275"

The robust solution to this problem is to join by the Ta model grid id, grd_1km_id, which is a string. In general, joining by floats is dangerous. In this case an alternative quick fix is to set data.table to round the least-significant 2 bytes off of floats when comparing:

setNumericRounding(2)
smpl[year(date) == 2017, .(mother_token_nbmoove, date, grd_1km_x, grd_1km_y)] %>% 
  ta_2017[., on = .(date, grd_1km_x, grd_1km_y)] %>% 
  setcolorder(c("mother_token_nbmoove", "date", "grd_1km_id")) %>% 
  print()

We now match the Ta exposure data. If we rematch for all subject-location-days we see that the number of missing exposures is unchanged for 2015, and greatly reduced in 2017:

expo_2015 <- day_xy[year(date) == 2015, ] %>%
             ta_2015[., on = .(date, grd_1km_x, grd_1km_y)] %>%
             setkeyv(c("mother_token_nbmoove", "date")) %>%
             setcolorder(c("mother_token_nbmoove", "date", "grd_1km_id"))
expo_2017 <- day_xy[year(date) == 2017, ] %>%
             ta_2017[., on = .(date, grd_1km_x, grd_1km_y)] %>%
             setkeyv(c("mother_token_nbmoove", "date")) %>%
             setcolorder(c("mother_token_nbmoove", "date", "grd_1km_id"))
paste("Subject location-days with no 2015 exposure:", expo_2015[is.na(grd_1km_id), .N])
[1] "Subject location-days with no 2015 exposure: 106"
paste("Subject location-days with no 2017 exposure:", expo_2017[is.na(grd_1km_id), .N])
[1] "Subject location-days with no 2017 exposure: 141"
LS0tCnRpdGxlOiAiMjAyMC0wNS0yOSBFeHBsb3JlIGRpZmZlcmVuY2VzIGluIDEga20gZ3JpZCBjb29yZGluYXRlcyBiZXR3ZWVuIHllYXJzIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKLS0tCgojIyBCYWNrZ3JvdW5kCgpXaGVuIGFuYWx5c2luZyBUYSBleHBvc3VyZSBmb3IgdGhlIFNFUEFHRVMgY29ob3J0LCBFbWllIGFuZCBBcmlhbm5lIG5vdGljZWQgdGhhdCBzb21lIGV4cG9zdXJlcyBhcmUgbWlzc2luZy4gVGhlIHByb2JsZW0gc2VlbXMgdG8gYmUgY2F1c2VkIGJ5IHRoZSBmYWN0IHRoYXQgdGhlIDEga20gZ3JpZCBjZWxsIGNvb3JkaW5hdGVzIHZhcnkgYmV0d2VlbiB5ZWFycy4KCiMjIEV4cGxvcmF0aW9uCgpgYGB7ciBzZXR1cH0Ka25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSBoZXJlOjpoZXJlKCkpCmtuaXRyOjpvcHRzX2tuaXQkc2V0KGVjaG8gPSBGQUxTRSkKCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkoZ2x1ZSkKbGlicmFyeShmc3QpCmxpYnJhcnkoZGF0YS50YWJsZSkKYGBgCgojIyMgMS4gQ2hlY2sgd2hldGhlciBncmlkIGNvb3JkaW5hdGVzIGNoYW5nZSBiZXR3ZWVuIDIwMTUgYW5kIDIwMTcKCldlIHN0YXJ0IHdpdGggYSBxdWljayBjaGVjayBvbiBvbmUgZGF5IG9mIGRhdGEgZm9yIDIwMTUgYW5kIDIwMTcuCgpgYGB7ciBjaGVjay1qYW4xfQojIFNldCB0aGUgcGF0aCB0byB0aGUgZGlyZWN0b3J5IHRoYXQgY29udGFpbnMgdGhlIGZpbmFsIDEga20gVGEgbW9kZWwgcHJlZGljdGlvbnMKZXhwb19kaXIgPC0gIi9tZWRpYS9zdW1tZXJfbW9kZWxlc19leHBvL3RlbXBlcmF0dXJlX2hvdWdoXzFrbSIKCiMgTG9hZCBmaXJzdCBkYXkgb2YgZmluYWwgMSBrbSBUYSBwcmVkaWN0aW9ucyBmb3IgMjAxNSBhbmQgMjAxNwpuY2VsbHMgPC0gZ2x1ZSgie2V4cG9fZGlyfS9tb2QzXzIwMTVfcHJlZF8xa20uZnN0IikgJT4lIAogICAgICAgICAgZnN0KCkgJT4lCiAgICAgICAgICBucm93KCkgJT4lCiAgICAgICAgICBkaXZpZGVfYnkoMzY1KQpqYW4xXzIwMTUgPC0gZ2x1ZSgie2V4cG9fZGlyfS9tb2QzXzIwMTVfcHJlZF8xa20uZnN0IikgJT4lIAogICAgICAgICAgICAgcmVhZF9mc3QodG8gPSBuY2VsbHMsIGFzLmRhdGEudGFibGUgPSBUUlVFKQpqYW4xXzIwMTcgPC0gZ2x1ZSgie2V4cG9fZGlyfS9tb2QzXzIwMTdfcHJlZF8xa20uZnN0IikgJT4lIAogICAgICAgICAgICAgcmVhZF9mc3QodG8gPSBuY2VsbHMsIGFzLmRhdGEudGFibGUgPSBUUlVFKQpwcnlyOjptZW1fdXNlZCgpCgoKIyBDb25maXJtIGNvb3JkaW5hdGVzIGFyZSBzYW1lIGJldHdlZW4geWVhcnMKcHJpbnQoIkNoZWNraW5nIGNvb3JkaW5hdGVzIGZvciBKYW4gMSwgMjAxNSAvIDIwMTciKQoKaWRlbnRpY2FsKGphbjFfMjAxNSRncmRfMWttX2lkLCBqYW4xXzIwMTckZ3JkXzFrbV9pZCkgJT4lIAogIHBhc3RlKCJKYW4gMSAyMDE1IHZzIDIwMTc6IGlkIGlkZW50aWNhbD8iLCAuKQoKaWRlbnRpY2FsKGphbjFfMjAxNVssIC4oZ3JkXzFrbV94LCBncmRfMWttX3kpXSwgamFuMV8yMDE3WywgLihncmRfMWttX3gsIGdyZF8xa21feSldKSAlPiUgCiAgcGFzdGUoIkphbiAxIDIwMTUgdnMgMjAxNzogeCBhbmQgeSBpZGVudGljYWw/IiwgLikKYGBgCgpUaGUgSURzIGFyZSBpZGVudGljYWwsIGJ1dCB0aGUgY29vcmRpbmF0ZXMgZGlmZmVyIGZyb20gMjAxNSB0byAyMDE3LiBIb3dldmVyLCB0aGUgY29vcmRpbmF0ZXMgYXJlIGZsb2F0aW5nIHBvaW50IG51bWJlcnMgYW5kIHNvIHNob3VsZCBub3QgYmUgYXNzdW1lZCB0byBiZSBleGFjdGx5IGVxdWFsLiBDb21wYXJpbmcgd2l0aCBgYWxsLmVxdWFsYCBzaG93cyB0aGF0IHRoZXkgZGlmZmVyIGJ5IG5vIG1vcmUgdGhhbiAxIG5hbm9tZXRlcjoKCmBgYHtyIGphbjEtZGV0YWlsc30KYWxsLmVxdWFsKAogIGphbjFfMjAxNVssIC4oZ3JkXzFrbV94LCBncmRfMWttX3kpXSwKICBqYW4xXzIwMTdbLCAuKGdyZF8xa21feCwgZ3JkXzFrbV95KV0sCiAgdG9sZXJhbmNlID0gMWUtOQopICU+JSAKICBwYXN0ZSgiSmFuIDEgMjAxNSB2cyAyMDE3OiB4IGFuZCB5IGVxdWFsIHRvIHdpdGhpbiAxIG5hbm9tZXRlcj8iLCAuKQoKCnByaW50KCJKYW4gMSAyMDE1IHZzIDIwMTcgZ3JkXzFrbV94IikKZGlmZl9pZHggPC0gd2hpY2goamFuMV8yMDE1JGdyZF8xa21feCAhPSBqYW4xXzIwMTckZ3JkXzFrbV94KVsxOjNdCmMoIjIwMTU6IiwgamFuMV8yMDE1W2RpZmZfaWR4LCBmb3JtYXQoZ3JkXzFrbV94LCBuc21hbGwgPSAxMCldKQpjKCIyMDE3OiIsIGphbjFfMjAxN1tkaWZmX2lkeCwgZm9ybWF0KGdyZF8xa21feCwgbnNtYWxsID0gMTApXSkKCmBgYAoKIyMjIDIuIENoZWNrIGNvb3JkaW5hdGVzIGZvciBlbnRpcmUgeWVhcnMKCk5vdyB3ZSBjb25maXJtIHRoZXJlIGFyZSBubyBzdWJzdGFudGlhbCBkaWZmZXJlbmNlcyBiZXR3ZWVuIHRoZSBjb29yZGluYXRlcyBmb3IgdGhlIGVudGlyZSB5ZWFycy4KCmBgYHtyIGNoZWNrLWJldHdlZW4teWVhcnN9CiMgTG9hZCBlbnRpcmUgeWVhcnMKY29scyA8LSBjKCJkYXRlIiwgImdyZF8xa21faWQiLCAiZ3JkXzFrbV94IiwgImdyZF8xa21feSIsICJ0bWluIikKdGFfMjAxNSA8LSByZWFkX2ZzdChnbHVlKCJ7ZXhwb19kaXJ9L21vZDNfMjAxNV9wcmVkXzFrbS5mc3QiKSwgYXMuZGF0YS50YWJsZSA9IFRSVUUsIGNvbHVtbnMgPSBjb2xzKQp0YV8yMDE3IDwtIHJlYWRfZnN0KGdsdWUoIntleHBvX2Rpcn0vbW9kM18yMDE3X3ByZWRfMWttLmZzdCIpLCBhcy5kYXRhLnRhYmxlID0gVFJVRSwgY29sdW1ucyA9IGNvbHMpCnByeXI6Om1lbV91c2VkKCkKCiMgQ29uZmlybSBjb29yZGluYXRlcyBhcmUgc2FtZSBpbiAyMDE1IGFuZCAyMDE3CmlkZW50aWNhbCh0YV8yMDE1JGdyZF8xa21faWQsIHRhXzIwMTckZ3JkXzFrbV9pZCkgJT4lIAogIHBhc3RlKCIyMDE1IHZzIDIwMTc6IGlkIGlkZW50aWNhbD8iLCAuKQoKaWRlbnRpY2FsKHRhXzIwMTVbLCAuKGdyZF8xa21feCwgZ3JkXzFrbV95KV0sIHRhXzIwMTdbLCAuKGdyZF8xa21feCwgZ3JkXzFrbV95KV0pICU+JSAKICBwYXN0ZSgiMjAxNSB2cyAyMDE3OiB4IGFuZCB5IGlkZW50aWNhbD8iLCAuKQoKYWxsLmVxdWFsKHRhXzIwMTVbLCAuKGdyZF8xa21feCwgZ3JkXzFrbV95KV0sIHRhXzIwMTdbLCAuKGdyZF8xa21feCwgZ3JkXzFrbV95KV0pICU+JSAKICBwYXN0ZSgiMjAxNSB2cyAyMDE3OiB4IGFuZCB5IGVxdWFsIHRvIHdpdGhpbiAxIG5hbm9tZXRlcj8iLCAuKQoKYGBgCgpBZ2FpbiwgdGhlIElEcyBhcmUgaWRlbnRpY2FsIGluIGFsbCB5ZWFycyBhbmQgdGhlIGNvb3JkaW5hdGVzIGRpZmZlciBieSBsZXNzIHRoYW4gMSBuYW5vbWV0ZXIgYmV0d2VlbiAyMDE1IGFuZCAyMDE3LiBXaXRoaW4gYSB5ZWFyIHRoZSBjb29yZGluYXRlcyBhcmUgaWRlbnRpY2FsLgoKYGBge3IgY2hlY2std2l0aGluLXllYXJ9CnNldE51bWVyaWNSb3VuZGluZygwKSAjIENvbmZpcm0gZGF0YS50YWJsZSBkb2VzIG5vdCByb3VuZCB3aGVuIGNoZWNraW5nIHVuaXF1ZW5lc3MKCnRhXzIwMTVbLCAuKGxvY3MgPSB1bmlxdWVOKC5TRCkpLCAuU0Rjb2xzID0gYygiZ3JkXzFrbV94IiwgImdyZF8xa21feSIpLCBieSA9IC4oZ3JkXzFrbV9pZCldICU+JQogIC5bbG9jcyA+IDEsIC5OXSAlPiUKICBwYXN0ZSgiMjAxNSBncmlkIGNlbGxzIHdpdGggdmFyeWluZyBjb29yZHM6IiwgLikKCnRhXzIwMTdbLCAuKGxvY3MgPSB1bmlxdWVOKC5TRCkpLCAuU0Rjb2xzID0gYygiZ3JkXzFrbV94IiwgImdyZF8xa21feSIpLCBieSA9IC4oZ3JkXzFrbV9pZCldICU+JQogIC5bbG9jcyA+IDEsIC5OXSAlPiUKICBwYXN0ZSgiMjAxNyBncmlkIGNlbGxzIHdpdGggdmFyeWluZyBjb29yZHM6IiwgLikKCmBgYAoKCiMjIyAzLiBDYWxjdWxhdGUgZXhwb3N1cmUgZm9yIFNFUEFHRVMKCk5vdyB3ZSBjaGVjayB0byBzZWUgaWYgdGhlIG1pbmlzY3VsZSBkaWZmZXJlbmNlIGluIGNvb3JkaW5hdGVzIGlzIHRoZSBzb3VyY2Ugb2YgdGhlIHByb2JsZW1zIHdpdGggdGhlIGNhbGN1bGF0ZWQgZXhwb3N1cmVzLiBGaXJzdCB3ZSBsb2FkIHRoZSBtYXRjaGVkIGdyaWQgY29vcmRpbmF0ZXMgZm9yIGVhY2ggcGFydGljaXBhbnQgYWRkcmVzcyBhbmQgY29uZmlybSB0aGF0IHRoZSBjb2x1bW4gYG1vdGhlcl90b2tlbl9uYm1vb3ZlYCBpcyBhIHVuaXF1ZSByb3cgaWRlbnRpZmllci4KCmBgYHtyIGxvYWQtY29ob3J0fQojIExvYWQgU0VQQUdFUyBzdWJqZWN0IGFkZHJlc3NlcyBtYXRjaGVkIHRvIFRhIG1vZGVsIGdyaWQgY2VsbCBjb29yZGluYXRlcwpkYl9hZGRyZXNzX2V4cG8gPC0gcmVhZFJEUygifi9kYl9hZGRyZXNzX2V4cG8ucmRzIikgJT4lCiAgICAgICAgICAgICAgICAgICBzZXRrZXl2KGMoIm1vdGhlcl90b2tlbiIsICJuYm1vb3ZlIiwgInN0YXJ0X2RhdGUiLCAiZW5kX2RhdGUiKSkKZGJfYWRkcmVzc19leHBvWywgLk4sIGJ5ID0gLihtb3RoZXJfdG9rZW5fbmJtb292ZSldICU+JQogIC5bTiA+IDEsIC5OXSAlPiUKICBwYXN0ZSgiTm9uLXVuaXF1ZSBtb3RoZXJfdG9rZW5fbmJtb292ZToiLCAuKSAlPiUKICBwcmludCgpCmBgYAoKTm93IHdlIGNhbGN1bGF0ZSBleHBvc3VyZSBmb3IgMjAxNSBhbmQgMjAxNyBieSBtYXRjaGluZyB0aGUgY29vcmRpbmF0ZXMgb2YgZWFjaCBzdWJqZWN0LWxvY2F0aW9uIHRvIHRoZSBleHBvc3VyZSBtb2RlbC4gVGhpcyBpcyBkYW5nZXJvdXMgYmVjYXVzZSBjb29yZGluYXRlcyBhcmUgZmxvYXRpbmctcG9pbnQgdmFsdWVzIGFuZCBzbyBtYXkgZGlmZmVyIHZlcnkgc2xpZ2h0bHkgaWYgYW55IGNhbGN1bGF0aW9uIHdhcyBwZXJmb3JtZWQgb24gdGhlbSAoYW5kIG1heWJlIGJldHdlZW4gbWFjaGluZXM/Pz8pLgoKYGBge3IgY2FsYy1leHBvc3VyZX0KIyBHZXQgVGEgZXhwb3N1cmUgZm9yIDIwMTUgYW5kIDIwMTcKc2V0a2V5dih0YV8yMDE1LCBjKCJkYXRlIiwgImdyZF8xa21faWQiLCAiZ3JkXzFrbV94IiwgImdyZF8xa21feSIpKQpzZXRrZXl2KHRhXzIwMTcsIGMoImRhdGUiLCAiZ3JkXzFrbV9pZCIsICJncmRfMWttX3giLCAiZ3JkXzFrbV95IikpCmRheV94eSA8LSBkYl9hZGRyZXNzX2V4cG9bLCAuKGdyZF8xa21feCwgZ3JkXzFrbV95LCBkYXRlID0gc2VxLkRhdGUoc3RhcnRfZGF0ZSwgZW5kX2RhdGUsIGJ5ID0gMSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGtleWJ5ID0gLihtb3RoZXJfdG9rZW5fbmJtb292ZSldCmV4cG9fMjAxNSA8LSBkYXlfeHlbeWVhcihkYXRlKSA9PSAyMDE1LCBdICU+JQogICAgICAgICAgICAgdGFfMjAxNVsuLCBvbiA9IC4oZGF0ZSwgZ3JkXzFrbV94LCBncmRfMWttX3kpXSAlPiUKICAgICAgICAgICAgIHNldGtleXYoYygibW90aGVyX3Rva2VuX25ibW9vdmUiLCAiZGF0ZSIpKSAlPiUKICAgICAgICAgICAgIHNldGNvbG9yZGVyKGMoIm1vdGhlcl90b2tlbl9uYm1vb3ZlIiwgImRhdGUiLCAiZ3JkXzFrbV9pZCIpKQpleHBvXzIwMTcgPC0gZGF5X3h5W3llYXIoZGF0ZSkgPT0gMjAxNywgXSAlPiUKICAgICAgICAgICAgIHRhXzIwMTdbLiwgb24gPSAuKGRhdGUsIGdyZF8xa21feCwgZ3JkXzFrbV95KV0gJT4lCiAgICAgICAgICAgICBzZXRrZXl2KGMoIm1vdGhlcl90b2tlbl9uYm1vb3ZlIiwgImRhdGUiKSkgJT4lCiAgICAgICAgICAgICBzZXRjb2xvcmRlcihjKCJtb3RoZXJfdG9rZW5fbmJtb292ZSIsICJkYXRlIiwgImdyZF8xa21faWQiKSkKcGFzdGUoIlN1YmplY3QgbG9jYXRpb24tZGF5cyB3aXRoIG5vIDIwMTUgZXhwb3N1cmU6IiwgZXhwb18yMDE1W2lzLm5hKGdyZF8xa21faWQpLCAuTl0pCnBhc3RlKCJTdWJqZWN0IGxvY2F0aW9uLWRheXMgd2l0aCBubyAyMDE3IGV4cG9zdXJlOiIsIGV4cG9fMjAxN1tpcy5uYShncmRfMWttX2lkKSwgLk5dKQpgYGAKCklmIHdlIGxvb2sgYXQgb25lIHN1YmplY3QtbG9jYXRpb24gZm9yIGEgZmV3IGRheXMgaW4gMjAxNSBhbmQgMjAxNyB3ZSBzZWUgdGhhdCB3ZSBoYXZlIGV4cG9zdXJlIGZvciAyMDE1IGJ1dCBub3QgMjAxNSwgZGVzcGl0ZSBubyBhcHBhcmVudCBkaWZmZXJlbmNlIGluIHRoZSBjb29yZGluYXRlcy4KCmBgYHtyIGNoZWNrLWV4cG9zdXJlfQpzbXBsIDwtIHJiaW5kKAogICAgICAgICAgZXhwb18yMDE1W21vdGhlcl90b2tlbl9uYm1vb3ZlID09ICJBQjA1NDk4N18xIiwgXVsxOjMsIF0sCiAgICAgICAgICBleHBvXzIwMTdbbW90aGVyX3Rva2VuX25ibW9vdmUgPT0gIkFCMDU0OTg3XzEiLCBdWzE6MywgXQogICAgICAgICkKc21wbApgYGAKCkhvd2V2ZXIsIGlmIHdlIGxvb2sgYXQgdGhlIGZ1bGwgY29vcmRpbmF0ZXMgd2Ugc2VlIHRoYXQgaW4gMjAxNSB0aGV5IG1hdGNoIGJ1dCBpbiAyMDE3IHRoZXkgZGlmZmVyIHZlcnkgc2xpZ2h0bHkgZnJvbSB0aGUgY29vcmRpbmF0ZXMgaW4gdGhlIFRhIG1vZGVsOgoKYGBge3IgZXhhbXBsZS1zdWJqLWxvY30KcHJpbnQoIkZ1bGwgc3ViamVjdC1sb2NhdGlvbiBjb29yZGluYXRlczoiKQp4eSA8LSBkYXlfeHlbbW90aGVyX3Rva2VuX25ibW9vdmUgPT0gIkFCMDU0OTg3XzEiLCAuKGdyZF8xa21feCwgZ3JkXzFrbV95KV0gJT4lCiAgICAgIHVuaXF1ZSgpCmZvcm1hdCh4eSwgbnNtYWxsID0gMTApCgpwcmludCgiQ29ycmVzcG9uZGluZyAyMDE1IFRhIG1vZGVsIGNvb3JkaW5hdGVzOiIpCnRhXzIwMTVbYmV0d2VlbihncmRfMWttX3gsIDcxODkyMSwgNzE4OTIyKSAmIGJldHdlZW4oZ3JkXzFrbV95LCA2NDk0MDY3LCA2NDk0MDY4KSwKICAgICAgICAuKGdyZF8xa21feCwgZ3JkXzFrbV95KV0gJT4lCiAgdW5pcXVlKCkgJT4lCiAgZm9ybWF0KG5zbWFsbCA9IDEwKQoKcHJpbnQoIkNvcnJlc3BvbmRpbmcgMjAxNyBUYSBtb2RlbCBjb29yZGluYXRlczoiKQp0YV8yMDE3W2JldHdlZW4oZ3JkXzFrbV94LCA3MTg5MjEsIDcxODkyMikgJiBiZXR3ZWVuKGdyZF8xa21feSwgNjQ5NDA2NywgNjQ5NDA2OCksCiAgICAgICAgLihncmRfMWttX3gsIGdyZF8xa21feSldICU+JQogIHVuaXF1ZSgpICU+JQogIGZvcm1hdChuc21hbGwgPSAxMCkKCmBgYAoKVGhlIHJvYnVzdCBzb2x1dGlvbiB0byB0aGlzIHByb2JsZW0gaXMgdG8gam9pbiBieSB0aGUgVGEgbW9kZWwgZ3JpZCBpZCwgYGdyZF8xa21faWRgLCB3aGljaCBpcyBhIHN0cmluZy4gSW4gZ2VuZXJhbCwgam9pbmluZyBieSBmbG9hdHMgaXMgZGFuZ2Vyb3VzLiBJbiB0aGlzIGNhc2UgYW4gYWx0ZXJuYXRpdmUgcXVpY2sgZml4IGlzIHRvIHNldCBkYXRhLnRhYmxlIHRvIHJvdW5kIHRoZSBsZWFzdC1zaWduaWZpY2FudCAyIGJ5dGVzIG9mZiBvZiBmbG9hdHMgd2hlbiBjb21wYXJpbmc6CgpgYGB7ciB1c2Utcm91bmRpbmd9CnNldE51bWVyaWNSb3VuZGluZygyKQpzbXBsW3llYXIoZGF0ZSkgPT0gMjAxNywgLihtb3RoZXJfdG9rZW5fbmJtb292ZSwgZGF0ZSwgZ3JkXzFrbV94LCBncmRfMWttX3kpXSAlPiUgCiAgdGFfMjAxN1suLCBvbiA9IC4oZGF0ZSwgZ3JkXzFrbV94LCBncmRfMWttX3kpXSAlPiUgCiAgc2V0Y29sb3JkZXIoYygibW90aGVyX3Rva2VuX25ibW9vdmUiLCAiZGF0ZSIsICJncmRfMWttX2lkIikpICU+JSAKICBwcmludCgpCgpgYGAKCldlIG5vdyBtYXRjaCB0aGUgVGEgZXhwb3N1cmUgZGF0YS4gSWYgd2UgcmVtYXRjaCBmb3IgYWxsIHN1YmplY3QtbG9jYXRpb24tZGF5cyB3ZSBzZWUgdGhhdCB0aGUgbnVtYmVyIG9mIG1pc3NpbmcgZXhwb3N1cmVzIGlzIHVuY2hhbmdlZCBmb3IgMjAxNSwgYW5kIGdyZWF0bHkgcmVkdWNlZCBpbiAyMDE3OgoKYGBge3IgcmVjYWxjLWV4cG9zdXJlfQpleHBvXzIwMTUgPC0gZGF5X3h5W3llYXIoZGF0ZSkgPT0gMjAxNSwgXSAlPiUKICAgICAgICAgICAgIHRhXzIwMTVbLiwgb24gPSAuKGRhdGUsIGdyZF8xa21feCwgZ3JkXzFrbV95KV0gJT4lCiAgICAgICAgICAgICBzZXRrZXl2KGMoIm1vdGhlcl90b2tlbl9uYm1vb3ZlIiwgImRhdGUiKSkgJT4lCiAgICAgICAgICAgICBzZXRjb2xvcmRlcihjKCJtb3RoZXJfdG9rZW5fbmJtb292ZSIsICJkYXRlIiwgImdyZF8xa21faWQiKSkKZXhwb18yMDE3IDwtIGRheV94eVt5ZWFyKGRhdGUpID09IDIwMTcsIF0gJT4lCiAgICAgICAgICAgICB0YV8yMDE3Wy4sIG9uID0gLihkYXRlLCBncmRfMWttX3gsIGdyZF8xa21feSldICU+JQogICAgICAgICAgICAgc2V0a2V5dihjKCJtb3RoZXJfdG9rZW5fbmJtb292ZSIsICJkYXRlIikpICU+JQogICAgICAgICAgICAgc2V0Y29sb3JkZXIoYygibW90aGVyX3Rva2VuX25ibW9vdmUiLCAiZGF0ZSIsICJncmRfMWttX2lkIikpCnBhc3RlKCJTdWJqZWN0IGxvY2F0aW9uLWRheXMgd2l0aCBubyAyMDE1IGV4cG9zdXJlOiIsIGV4cG9fMjAxNVtpcy5uYShncmRfMWttX2lkKSwgLk5dKQpwYXN0ZSgiU3ViamVjdCBsb2NhdGlvbi1kYXlzIHdpdGggbm8gMjAxNyBleHBvc3VyZToiLCBleHBvXzIwMTdbaXMubmEoZ3JkXzFrbV9pZCksIC5OXSkKCmBgYAo=