Instructions

Using R, keep counters of equal-width grid-cells (base counters for micro-cluster definitions) of a 2-dimensional continuous data stream using different window models (landmark, sliding, weighted, fading).

The code should work with an evolving stream from a single record of the PhysioNet Challenge https://physionetchallenges.github.io/2020/

This training set consists of 6,877 (male: 3,699; female: 3,178) 12-ECG recordings lasting from 6 seconds to 60 seconds. Each recording was sampled at 500 Hz. All data is provided in WFDB format with a MATLAB v4 file and a header containing patient sex, age, and diagnosis (Dx) information at the end of the header file. The code should be applicable to one 12-dimensional record file.

Packages ‘R.matlab’ (for reading data from ‘.mat’ files) and ‘stream’ (for accessing the data as a stream) should be used.

Submitted code file should include comments to improve readability.

Setup

First, load the libraries:

Import data using readMat from R.matlab package. The data comes in a List container, so we drop it to work directly with the matrix:

Now we create the data stream interface for the leads I and V2:

Memory Stream Interface
Class: DSD_Memory, DSD_R, DSD_data.frame, DSD 
With NA clusters in 2 dimensions 
Contains 21500 data points - currently at position 1 - loop is FALSE 

Next, let’s create the matrix that will hold the counters for the grid. The database is stored in 16-bits so we can expect that the boundaries will be from −32768 to 32767. Nevertheless, for the purpose of this exercise, a quick inspection of .mat files shows that a boundary of -8192 to 8191 (14-bits) is enough.

Now we are ready to implement the four algorithms for the grid counting: landmark, sliding, weighted, fading.

Landmark

The landmark is actually not a ‘window’. It just keep updating the counters as new points arrive:


Wait for the animation (about 3 sec of aparently no changes)
The cells that have a value > 0 are plotted in ice-white color.
The maximum value is green.

Sliding Window

The sliding window is a little bit different. We use a fixed window size and at each new observation, we drop the oldest one that doesn’t fit the window size. In this case, as this is a counter matrix, the counter won’t have a value larger than the window size, and in every step we subtract one value from each counter:

# Sliding Window algorithm
f_n <- 3000 # The normalizing factor, so we focus on a smaller interval, just for better plotting
n <- 300 # we don't want to reach the end of the dataset in this example.
grid <- grid_base # let's make a copy of the original grid.
reset_stream(stream)

w <- 100 # the window size
window <- list()

invisible(saveGIF( # save all graphics in an animated gif
  for (i in seq_len(n)) {
    # retrieve one observation from stream
    points <- get_points(stream)

    # normalize the value to add to the matrix
    points <- points / f_n

    # transform the values to match the matrix indexes
    points <- floor((points + 1) * d / 2 + 0.5)

    # at the beginning, just count as the landmark did
    grid[points[, 1], points[, 2]] <- grid[points[, 1], points[, 2]] + 1

    # store the current points in window. `as.character` is used to store them with labels instead of index,
    # avoiding the creation of several NULL's if `i` doesn't follow the sequence.
    window[[as.character(i)]] <- points

    if (length(window) > w) {
      # as the window advances, start subtracting values of the oldest points
      grid[window[[1]][, 1], window[[1]][, 2]] <- grid[window[[1]][, 1], window[[1]][, 2]] - 1
      window <- window[-1] # removes the oldest point
    }

    # plot the grid. Using pallete Greens 2, so the white is actually a 'ice' color
    image(grid,
      main = "Sliding Window", col = hcl.colors(2^14, palette = "Greens 2", rev = T),
      zlim = c(1e-5, max(grid)), xlab = names(points)[1], ylab = names(points)[2],
      xaxt = "n", yaxt = "n"
    )
    axis(1, at = seq(0, 1, length.out = d), labels = seq(-f_n, f_n, length.out = d))
    axis(2, at = seq(0, 1, length.out = d), labels = seq(-f_n, f_n, length.out = d))
  },
  "window.gif",
  interval = 0.01, autobrowse = FALSE, ani.res = 96, ani.height = 500
))

Wait for the animation (about 3 sec of aparently no changes)
The cells that have a value > 0 are plotted in ice-white color.
The maximum value is green.

Weighted Window

The weighted window applies an alpha factor that reduces the weight of older observations, we still have to keep the observations in the window array.

# Weighted Sliding Window algorithm
f_n <- 3000 # The normalizing factor, so we focus on a smaller interval, just for better plotting
n <- 300 # nrow(data) # we don't want to reach the end of the dataset in this example.
grid <- grid_base # let's make a copy of the original grid.
reset_stream(stream)

w <- 100 # the window size
eps <- 0.05
alpha <- eps^(1 / w)
window <- list()

invisible(saveGIF( # save all graphics in an animated gif
  for (i in seq_len(n)) {
    # retrieve one observation from stream
    points <- get_points(stream)

    # normalize the value to add to the matrix
    points <- points / f_n

    # transform the values to match the matrix indexes
    points <- floor((points + 1) * d / 2 + 0.5)

    # apply alpha to the entire grid and then sum the next one
    grid <- grid * alpha
    grid[points[, 1], points[, 2]] <- grid[points[, 1], points[, 2]] + 1

    # store the current points in window. `as.character` is used to store them with labels instead of index,
    # avoiding the creation of several NULL's if `i` doesn't follow the sequence.
    window[[as.character(i)]] <- points

    if (length(window) > w) {
      # as the window advances, start subtracting values of the oldest points
      grid[window[[1]][, 1], window[[1]][, 2]] <- grid[window[[1]][, 1], window[[1]][, 2]] - alpha^(w - 1)
      window <- window[-1] # removes the oldest point
    }

    # plot the grid. Using pallete Greens 2, so the white is actually a 'ice' color
    image(grid,
      main = "Weighted Sliding Window", col = hcl.colors(2^14, palette = "Greens 2", rev = T),
      zlim = c(1e-5, max(grid)), xlab = names(points)[1], ylab = names(points)[2],
      xaxt = "n", yaxt = "n"
    )
    axis(1, at = seq(0, 1, length.out = d), labels = seq(-f_n, f_n, length.out = d))
    axis(2, at = seq(0, 1, length.out = d), labels = seq(-f_n, f_n, length.out = d))
  },
  "weighted.gif",
  interval = 0.01, autobrowse = FALSE, ani.res = 96, ani.height = 500
))

Wait for the animation (about 3 sec of aparently no changes)
The cells that have a value > 0 are plotted in ice-white color.
The maximum value is green.

Fading Window

Finally, the fading window is quite similar to the weighted window. The only difference is that we don’t keep any window, just apply the alpha to all counters.


Wait for the animation (about 3 sec of aparently no changes)
The cells that have a value > 0 are plotted in ice-white color.
The maximum value is green.

EOF<<

LS0tCnRpdGxlOiAiSEVBRFMgLSBISURBOiBBc3NpZ25tZW50IDEiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzCiAgICB0aGVtZTogdW5pdGVkCiAgICB0b2M6IHllcwphdXRob3I6IEZyYW5jaXNjbyBCaXNjaG9mZgotLS0KCiMjIEluc3RydWN0aW9ucwoKVXNpbmcgUiwga2VlcCBjb3VudGVycyBvZiBlcXVhbC13aWR0aCBncmlkLWNlbGxzIChiYXNlIGNvdW50ZXJzIGZvciBtaWNyby1jbHVzdGVyIGRlZmluaXRpb25zKSBvZiBhIDItZGltZW5zaW9uYWwgY29udGludW91cyBkYXRhIHN0cmVhbSB1c2luZyBkaWZmZXJlbnQgd2luZG93IG1vZGVscyAobGFuZG1hcmssIHNsaWRpbmcsIHdlaWdodGVkLCBmYWRpbmcpLgoKVGhlIGNvZGUgc2hvdWxkIHdvcmsgd2l0aCBhbiBldm9sdmluZyBzdHJlYW0gZnJvbSBhIHNpbmdsZSByZWNvcmQgb2YgdGhlIFBoeXNpb05ldCBDaGFsbGVuZ2UgaHR0cHM6Ly9waHlzaW9uZXRjaGFsbGVuZ2VzLmdpdGh1Yi5pby8yMDIwLwoKVGhpcyB0cmFpbmluZyBzZXQgY29uc2lzdHMgb2YgNiw4NzcgKG1hbGU6IDMsNjk5OyBmZW1hbGU6IDMsMTc4KSAxMi1FQ0cgcmVjb3JkaW5ncyBsYXN0aW5nIGZyb20gNiBzZWNvbmRzIHRvIDYwIHNlY29uZHMuIEVhY2ggcmVjb3JkaW5nIHdhcyBzYW1wbGVkIGF0IDUwMCBIei4gQWxsIGRhdGEgaXMgcHJvdmlkZWQgaW4gV0ZEQiBmb3JtYXQgd2l0aCBhIE1BVExBQiB2NCBmaWxlIGFuZCBhIGhlYWRlciBjb250YWluaW5nIHBhdGllbnQgc2V4LCBhZ2UsIGFuZCBkaWFnbm9zaXMgKER4KSBpbmZvcm1hdGlvbiBhdCB0aGUgZW5kIG9mIHRoZSBoZWFkZXIgZmlsZS4gVGhlIGNvZGUgc2hvdWxkIGJlIGFwcGxpY2FibGUgdG8gb25lIDEyLWRpbWVuc2lvbmFsIHJlY29yZCBmaWxlLgoKUGFja2FnZXMgJ1IubWF0bGFiJyAoZm9yIHJlYWRpbmcgZGF0YSBmcm9tICcubWF0JyBmaWxlcykgYW5kICdzdHJlYW0nIChmb3IgYWNjZXNzaW5nIHRoZSBkYXRhIGFzIGEgc3RyZWFtKSBzaG91bGQgYmUgdXNlZC4KClN1Ym1pdHRlZCBjb2RlIGZpbGUgc2hvdWxkIGluY2x1ZGUgY29tbWVudHMgdG8gaW1wcm92ZSByZWFkYWJpbGl0eS4KCiMjIFNldHVwCgpGaXJzdCwgbG9hZCB0aGUgbGlicmFyaWVzOgoKYGBge3Igc2V0dXAsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeShSLm1hdGxhYikKbGlicmFyeShzdHJlYW0pCmxpYnJhcnkoYW5pbWF0aW9uKSAjIGFuaW1hdGVkIGdpZnMhCmBgYAoKSW1wb3J0IGRhdGEgdXNpbmcgYHJlYWRNYXRgIGZyb20gYFIubWF0bGFiYCBwYWNrYWdlLiBUaGUgZGF0YSBjb21lcyBpbiBhIGBMaXN0YCBjb250YWluZXIsIHNvIHdlIGRyb3AgaXQgdG8gd29yayBkaXJlY3RseSB3aXRoIHRoZSBgbWF0cml4YDoKCmBgYHtyIGltcG9ydH0KIyBJbXBvcnQgdGhlIGRhdGEgLS0tLQpkYXRhIDwtIHJlYWRNYXQoIkEyMDIwLm1hdCIpCmRhdGEgPC0gZGF0YVtbMV1dICMgZ2V0IHJpZCBvZiBMaXN0CmRhdGEgPC0gdChkYXRhKSAjIHRyYW5zcG9zZSB0aGUgbWF0cml4LCBzbyBlYWNoIGNvbHVtbiByZXByZXNlbnRzIG9uZSBsZWFkLgpkYXRhIDwtIGFzLmRhdGEuZnJhbWUoZGF0YSkKY29sbmFtZXMoZGF0YSkgPC0gYygiSSIsICJJSSIsICJJSUkiLCAiYVZSIiwgImFWTCIsICJhVkYiLCAiVjEiLCAiVjIiLCAiVjMiLCAiVjQiLCAiVjUiLCAiVjYiKQpgYGAKCk5vdyB3ZSBjcmVhdGUgdGhlIGRhdGEgc3RyZWFtIGludGVyZmFjZSBmb3IgdGhlIGxlYWRzIGBJYCBhbmQgIGBWMmA6CgpgYGB7ciBzdHJlYW1pbmd9CiMgU2V0IHNlZWQgZm9yIHJlcGxpY2F0aW9uIHB1cnBvc2VzIC0tLS0Kc2V0LnNlZWQoMjAyMCkKCiMgQ3JlYXRlIHRoZSBEYXRhIFN0cmVhbWluZyBvYmotLS0tCnN0cmVhbSA8LSBEU0RfTWVtb3J5KGRhdGFbLCBjKCJJIiwgIlYyIildLCBuID0gTlVMTCkgIyBuIGlzIE5VTEwganVzdCB0byBzaWxlbmNlIGxpbnRlciB3YXJuaW5ncwpzdHJlYW0KYGBgCgpOZXh0LCBsZXQncyBjcmVhdGUgdGhlIG1hdHJpeCB0aGF0IHdpbGwgaG9sZCB0aGUgY291bnRlcnMgZm9yIHRoZSBncmlkLiBUaGUgZGF0YWJhc2UgaXMgc3RvcmVkIGluIDE2LWJpdHMgc28gd2UgY2FuIGV4cGVjdCB0aGF0IHRoZSBib3VuZGFyaWVzIHdpbGwgYmUgZnJvbSDiiJIzMjc2OCB0byAzMjc2Ny4gTmV2ZXJ0aGVsZXNzLCBmb3IgdGhlIHB1cnBvc2Ugb2YgdGhpcyBleGVyY2lzZSwgYSBxdWljayBpbnNwZWN0aW9uIG9mIC5tYXQgZmlsZXMgc2hvd3MgdGhhdCBhIGJvdW5kYXJ5IG9mIC04MTkyIHRvIDgxOTEgKDE0LWJpdHMpIGlzIGVub3VnaC4KCmBgYHtyIHRoZV9ncmlkfQojIENyZWF0ZSB0aGUgZ3JpZCAtLS0tCmQgPC0gMTcgIyB0aGlzIGlzIHRoZSBkaW1lbnRpb24gb2YgdGhlIGdyaWQgKDE3eDE3KQpncmlkX2Jhc2UgPC0gbWF0cml4KDAsIG5yb3cgPSBkLCBuY29sID0gZCkgIyBtYXRyaXggZmlsbGVkIHdpdGggemVyb2VzCmBgYAoKTm93IHdlIGFyZSByZWFkeSB0byBpbXBsZW1lbnQgdGhlIGZvdXIgYWxnb3JpdGhtcyBmb3IgdGhlIGdyaWQgY291bnRpbmc6IGxhbmRtYXJrLCBzbGlkaW5nLCB3ZWlnaHRlZCwgZmFkaW5nLgoKIyMgTGFuZG1hcmsKClRoZSBsYW5kbWFyayBpcyBhY3R1YWxseSBub3QgYSAnd2luZG93Jy4gSXQganVzdCBrZWVwIHVwZGF0aW5nIHRoZSBjb3VudGVycyBhcyBuZXcgcG9pbnRzIGFycml2ZToKCmBgYHtyIGxhbmRtYXJrLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD01fQojIExhbmRtYXJrIGFsZ29yaXRobQpmX24gPC0gMzAwMCAjIFRoZSBub3JtYWxpemluZyBmYWN0b3IsIHNvIHdlIGZvY3VzIG9uIGEgc21hbGxlciBpbnRlcnZhbCwganVzdCBmb3IgYmV0dGVyIHBsb3R0aW5nCm4gPC0gMzAwICMgd2UgZG9uJ3Qgd2FudCB0byByZWFjaCB0aGUgZW5kIG9mIHRoZSBkYXRhc2V0IGluIHRoaXMgZXhhbXBsZS4KZ3JpZCA8LSBncmlkX2Jhc2UgIyBsZXQncyBtYWtlIGEgY29weSBvZiB0aGUgb3JpZ2luYWwgZ3JpZC4KcmVzZXRfc3RyZWFtKHN0cmVhbSkKCmludmlzaWJsZShzYXZlR0lGKCAjIHNhdmUgYWxsIGdyYXBoaWNzIGluIGFuIGFuaW1hdGVkIGdpZgogIGZvciAoaSBpbiBzZXFfbGVuKG4pKSB7CiAgICAjIHJldHJpZXZlIG9uZSBvYnNlcnZhdGlvbiBmcm9tIHN0cmVhbQogICAgcG9pbnRzIDwtIGdldF9wb2ludHMoc3RyZWFtKQoKICAgICMgbm9ybWFsaXplIHRoZSB2YWx1ZSB0byBhZGQgdG8gdGhlIG1hdHJpeAogICAgcG9pbnRzIDwtIHBvaW50cyAvIGZfbgoKICAgICMgdHJhbnNmb3JtIHRoZSB2YWx1ZXMgdG8gbWF0Y2ggdGhlIG1hdHJpeCBpbmRleGVzCiAgICBwb2ludHMgPC0gZmxvb3IoKHBvaW50cyArIDEpICogZCAvIDIgKyAwLjUpCgogICAgZ3JpZFtwb2ludHNbLCAxXSwgcG9pbnRzWywgMl1dIDwtIGdyaWRbcG9pbnRzWywgMV0sIHBvaW50c1ssIDJdXSArIDEKCiAgICAjIHBsb3QgdGhlIGdyaWQuIFVzaW5nIHBhbGxldGUgR3JlZW5zIDIsIHNvIHRoZSB3aGl0ZSBpcyBhY3R1YWxseSBhICdpY2UnIGNvbG9yCiAgICBpbWFnZShncmlkLAogICAgICBtYWluID0gIkxhbmRtYXJrIiwgY29sID0gaGNsLmNvbG9ycygyXjE0LCBwYWxldHRlID0gIkdyZWVucyAyIiwgcmV2ID0gVCksCiAgICAgIHpsaW0gPSBjKDFlLTUsIG1heChncmlkKSksIHhsYWIgPSBuYW1lcyhwb2ludHMpWzFdLCB5bGFiID0gbmFtZXMocG9pbnRzKVsyXSwKICAgICAgeGF4dCA9ICJuIiwgeWF4dCA9ICJuIgogICAgKQogICAgYXhpcygxLCBhdCA9IHNlcSgwLCAxLCBsZW5ndGgub3V0ID0gZCksIGxhYmVscyA9IHNlcSgtZl9uLCBmX24sIGxlbmd0aC5vdXQgPSBkKSkKICAgIGF4aXMoMiwgYXQgPSBzZXEoMCwgMSwgbGVuZ3RoLm91dCA9IGQpLCBsYWJlbHMgPSBzZXEoLWZfbiwgZl9uLCBsZW5ndGgub3V0ID0gZCkpCiAgfSwKICAibGFuZG1hcmsuZ2lmIiwKICBpbnRlcnZhbCA9IDAuMDEsIGF1dG9icm93c2UgPSBGQUxTRSwgYW5pLnJlcyA9IDk2LCBhbmkuaGVpZ2h0ID0gNTAwCikpCmBgYAoKPGNlbnRlcj4hW10obGFuZG1hcmsuZ2lmKQo8YnI+V2FpdCBmb3IgdGhlIGFuaW1hdGlvbiAoYWJvdXQgMyBzZWMgb2YgYXBhcmVudGx5IG5vIGNoYW5nZXMpCjxicj5UaGUgY2VsbHMgdGhhdCBoYXZlIGEgdmFsdWUgPiAwIGFyZSBwbG90dGVkIGluIGljZS13aGl0ZSBjb2xvci4KPGJyPlRoZSBtYXhpbXVtIHZhbHVlIGlzIGdyZWVuLgo8L2NlbnRlcj4KCiMjIFNsaWRpbmcgV2luZG93CgpUaGUgc2xpZGluZyB3aW5kb3cgaXMgYSBsaXR0bGUgYml0IGRpZmZlcmVudC4gV2UgdXNlIGEgZml4ZWQgd2luZG93IHNpemUgYW5kIGF0IGVhY2ggbmV3IG9ic2VydmF0aW9uLCB3ZSBkcm9wIHRoZSBvbGRlc3Qgb25lIHRoYXQgZG9lc24ndCBmaXQgdGhlIHdpbmRvdyBzaXplLiBJbiB0aGlzIGNhc2UsIGFzIHRoaXMgaXMgYSBjb3VudGVyIG1hdHJpeCwgdGhlIGNvdW50ZXIgd29uJ3QgaGF2ZSBhIHZhbHVlIGxhcmdlciB0aGFuIHRoZSB3aW5kb3cgc2l6ZSwgYW5kIGluIGV2ZXJ5IHN0ZXAgd2Ugc3VidHJhY3Qgb25lIHZhbHVlIGZyb20gZWFjaCBjb3VudGVyOgoKYGBge3Igc2xpZGluZ30KIyBTbGlkaW5nIFdpbmRvdyBhbGdvcml0aG0KZl9uIDwtIDMwMDAgIyBUaGUgbm9ybWFsaXppbmcgZmFjdG9yLCBzbyB3ZSBmb2N1cyBvbiBhIHNtYWxsZXIgaW50ZXJ2YWwsIGp1c3QgZm9yIGJldHRlciBwbG90dGluZwpuIDwtIDMwMCAjIHdlIGRvbid0IHdhbnQgdG8gcmVhY2ggdGhlIGVuZCBvZiB0aGUgZGF0YXNldCBpbiB0aGlzIGV4YW1wbGUuCmdyaWQgPC0gZ3JpZF9iYXNlICMgbGV0J3MgbWFrZSBhIGNvcHkgb2YgdGhlIG9yaWdpbmFsIGdyaWQuCnJlc2V0X3N0cmVhbShzdHJlYW0pCgp3IDwtIDEwMCAjIHRoZSB3aW5kb3cgc2l6ZQp3aW5kb3cgPC0gbGlzdCgpCgppbnZpc2libGUoc2F2ZUdJRiggIyBzYXZlIGFsbCBncmFwaGljcyBpbiBhbiBhbmltYXRlZCBnaWYKICBmb3IgKGkgaW4gc2VxX2xlbihuKSkgewogICAgIyByZXRyaWV2ZSBvbmUgb2JzZXJ2YXRpb24gZnJvbSBzdHJlYW0KICAgIHBvaW50cyA8LSBnZXRfcG9pbnRzKHN0cmVhbSkKCiAgICAjIG5vcm1hbGl6ZSB0aGUgdmFsdWUgdG8gYWRkIHRvIHRoZSBtYXRyaXgKICAgIHBvaW50cyA8LSBwb2ludHMgLyBmX24KCiAgICAjIHRyYW5zZm9ybSB0aGUgdmFsdWVzIHRvIG1hdGNoIHRoZSBtYXRyaXggaW5kZXhlcwogICAgcG9pbnRzIDwtIGZsb29yKChwb2ludHMgKyAxKSAqIGQgLyAyICsgMC41KQoKICAgICMgYXQgdGhlIGJlZ2lubmluZywganVzdCBjb3VudCBhcyB0aGUgbGFuZG1hcmsgZGlkCiAgICBncmlkW3BvaW50c1ssIDFdLCBwb2ludHNbLCAyXV0gPC0gZ3JpZFtwb2ludHNbLCAxXSwgcG9pbnRzWywgMl1dICsgMQoKICAgICMgc3RvcmUgdGhlIGN1cnJlbnQgcG9pbnRzIGluIHdpbmRvdy4gYGFzLmNoYXJhY3RlcmAgaXMgdXNlZCB0byBzdG9yZSB0aGVtIHdpdGggbGFiZWxzIGluc3RlYWQgb2YgaW5kZXgsCiAgICAjIGF2b2lkaW5nIHRoZSBjcmVhdGlvbiBvZiBzZXZlcmFsIE5VTEwncyBpZiBgaWAgZG9lc24ndCBmb2xsb3cgdGhlIHNlcXVlbmNlLgogICAgd2luZG93W1thcy5jaGFyYWN0ZXIoaSldXSA8LSBwb2ludHMKCiAgICBpZiAobGVuZ3RoKHdpbmRvdykgPiB3KSB7CiAgICAgICMgYXMgdGhlIHdpbmRvdyBhZHZhbmNlcywgc3RhcnQgc3VidHJhY3RpbmcgdmFsdWVzIG9mIHRoZSBvbGRlc3QgcG9pbnRzCiAgICAgIGdyaWRbd2luZG93W1sxXV1bLCAxXSwgd2luZG93W1sxXV1bLCAyXV0gPC0gZ3JpZFt3aW5kb3dbWzFdXVssIDFdLCB3aW5kb3dbWzFdXVssIDJdXSAtIDEKICAgICAgd2luZG93IDwtIHdpbmRvd1stMV0gIyByZW1vdmVzIHRoZSBvbGRlc3QgcG9pbnQKICAgIH0KCiAgICAjIHBsb3QgdGhlIGdyaWQuIFVzaW5nIHBhbGxldGUgR3JlZW5zIDIsIHNvIHRoZSB3aGl0ZSBpcyBhY3R1YWxseSBhICdpY2UnIGNvbG9yCiAgICBpbWFnZShncmlkLAogICAgICBtYWluID0gIlNsaWRpbmcgV2luZG93IiwgY29sID0gaGNsLmNvbG9ycygyXjE0LCBwYWxldHRlID0gIkdyZWVucyAyIiwgcmV2ID0gVCksCiAgICAgIHpsaW0gPSBjKDFlLTUsIG1heChncmlkKSksIHhsYWIgPSBuYW1lcyhwb2ludHMpWzFdLCB5bGFiID0gbmFtZXMocG9pbnRzKVsyXSwKICAgICAgeGF4dCA9ICJuIiwgeWF4dCA9ICJuIgogICAgKQogICAgYXhpcygxLCBhdCA9IHNlcSgwLCAxLCBsZW5ndGgub3V0ID0gZCksIGxhYmVscyA9IHNlcSgtZl9uLCBmX24sIGxlbmd0aC5vdXQgPSBkKSkKICAgIGF4aXMoMiwgYXQgPSBzZXEoMCwgMSwgbGVuZ3RoLm91dCA9IGQpLCBsYWJlbHMgPSBzZXEoLWZfbiwgZl9uLCBsZW5ndGgub3V0ID0gZCkpCiAgfSwKICAid2luZG93LmdpZiIsCiAgaW50ZXJ2YWwgPSAwLjAxLCBhdXRvYnJvd3NlID0gRkFMU0UsIGFuaS5yZXMgPSA5NiwgYW5pLmhlaWdodCA9IDUwMAopKQpgYGAKCjxjZW50ZXI+IVtdKHdpbmRvdy5naWYpCjxicj5XYWl0IGZvciB0aGUgYW5pbWF0aW9uIChhYm91dCAzIHNlYyBvZiBhcGFyZW50bHkgbm8gY2hhbmdlcykKPGJyPlRoZSBjZWxscyB0aGF0IGhhdmUgYSB2YWx1ZSA+IDAgYXJlIHBsb3R0ZWQgaW4gaWNlLXdoaXRlIGNvbG9yLgo8YnI+VGhlIG1heGltdW0gdmFsdWUgaXMgZ3JlZW4uCjwvY2VudGVyPgoKIyMgV2VpZ2h0ZWQgV2luZG93CgpUaGUgd2VpZ2h0ZWQgd2luZG93IGFwcGxpZXMgYW4gYWxwaGEgZmFjdG9yIHRoYXQgcmVkdWNlcyB0aGUgd2VpZ2h0IG9mIG9sZGVyIG9ic2VydmF0aW9ucywgd2Ugc3RpbGwgaGF2ZSB0byBrZWVwIHRoZSBvYnNlcnZhdGlvbnMgaW4gdGhlIHdpbmRvdyBhcnJheS4KCmBgYHtyIHdlaWdodGVkfQojIFdlaWdodGVkIFNsaWRpbmcgV2luZG93IGFsZ29yaXRobQpmX24gPC0gMzAwMCAjIFRoZSBub3JtYWxpemluZyBmYWN0b3IsIHNvIHdlIGZvY3VzIG9uIGEgc21hbGxlciBpbnRlcnZhbCwganVzdCBmb3IgYmV0dGVyIHBsb3R0aW5nCm4gPC0gMzAwICMgd2UgZG9uJ3Qgd2FudCB0byByZWFjaCB0aGUgZW5kIG9mIHRoZSBkYXRhc2V0IGluIHRoaXMgZXhhbXBsZS4KZ3JpZCA8LSBncmlkX2Jhc2UgIyBsZXQncyBtYWtlIGEgY29weSBvZiB0aGUgb3JpZ2luYWwgZ3JpZC4KcmVzZXRfc3RyZWFtKHN0cmVhbSkKCncgPC0gMTAwICMgdGhlIHdpbmRvdyBzaXplCmVwcyA8LSAwLjA1CmFscGhhIDwtIGVwc14oMSAvIHcpCndpbmRvdyA8LSBsaXN0KCkKCmludmlzaWJsZShzYXZlR0lGKCAjIHNhdmUgYWxsIGdyYXBoaWNzIGluIGFuIGFuaW1hdGVkIGdpZgogIGZvciAoaSBpbiBzZXFfbGVuKG4pKSB7CiAgICAjIHJldHJpZXZlIG9uZSBvYnNlcnZhdGlvbiBmcm9tIHN0cmVhbQogICAgcG9pbnRzIDwtIGdldF9wb2ludHMoc3RyZWFtKQoKICAgICMgbm9ybWFsaXplIHRoZSB2YWx1ZSB0byBhZGQgdG8gdGhlIG1hdHJpeAogICAgcG9pbnRzIDwtIHBvaW50cyAvIGZfbgoKICAgICMgdHJhbnNmb3JtIHRoZSB2YWx1ZXMgdG8gbWF0Y2ggdGhlIG1hdHJpeCBpbmRleGVzCiAgICBwb2ludHMgPC0gZmxvb3IoKHBvaW50cyArIDEpICogZCAvIDIgKyAwLjUpCgoKICAgICMgYXBwbHkgYWxwaGEgdG8gdGhlIGVudGlyZSBncmlkIGFuZCB0aGVuIHN1bSB0aGUgbmV4dCBvbmUKICAgIGdyaWQgPC0gZ3JpZCAqIGFscGhhCiAgICBncmlkW3BvaW50c1ssIDFdLCBwb2ludHNbLCAyXV0gPC0gZ3JpZFtwb2ludHNbLCAxXSwgcG9pbnRzWywgMl1dICsgMQoKICAgICMgc3RvcmUgdGhlIGN1cnJlbnQgcG9pbnRzIGluIHdpbmRvdy4gYGFzLmNoYXJhY3RlcmAgaXMgdXNlZCB0byBzdG9yZSB0aGVtIHdpdGggbGFiZWxzIGluc3RlYWQgb2YgaW5kZXgsCiAgICAjIGF2b2lkaW5nIHRoZSBjcmVhdGlvbiBvZiBzZXZlcmFsIE5VTEwncyBpZiBgaWAgZG9lc24ndCBmb2xsb3cgdGhlIHNlcXVlbmNlLgogICAgd2luZG93W1thcy5jaGFyYWN0ZXIoaSldXSA8LSBwb2ludHMKCiAgICBpZiAobGVuZ3RoKHdpbmRvdykgPiB3KSB7CiAgICAgICMgYXMgdGhlIHdpbmRvdyBhZHZhbmNlcywgc3RhcnQgc3VidHJhY3RpbmcgdmFsdWVzIG9mIHRoZSBvbGRlc3QgcG9pbnRzCiAgICAgIGdyaWRbd2luZG93W1sxXV1bLCAxXSwgd2luZG93W1sxXV1bLCAyXV0gPC0gZ3JpZFt3aW5kb3dbWzFdXVssIDFdLCB3aW5kb3dbWzFdXVssIDJdXSAtIGFscGhhXih3IC0gMSkKICAgICAgd2luZG93IDwtIHdpbmRvd1stMV0gIyByZW1vdmVzIHRoZSBvbGRlc3QgcG9pbnQKICAgIH0KCiAgICAjIHBsb3QgdGhlIGdyaWQuIFVzaW5nIHBhbGxldGUgR3JlZW5zIDIsIHNvIHRoZSB3aGl0ZSBpcyBhY3R1YWxseSBhICdpY2UnIGNvbG9yCiAgICBpbWFnZShncmlkLAogICAgICBtYWluID0gIldlaWdodGVkIFNsaWRpbmcgV2luZG93IiwgY29sID0gaGNsLmNvbG9ycygyXjE0LCBwYWxldHRlID0gIkdyZWVucyAyIiwgcmV2ID0gVCksCiAgICAgIHpsaW0gPSBjKDFlLTUsIG1heChncmlkKSksIHhsYWIgPSBuYW1lcyhwb2ludHMpWzFdLCB5bGFiID0gbmFtZXMocG9pbnRzKVsyXSwKICAgICAgeGF4dCA9ICJuIiwgeWF4dCA9ICJuIgogICAgKQogICAgYXhpcygxLCBhdCA9IHNlcSgwLCAxLCBsZW5ndGgub3V0ID0gZCksIGxhYmVscyA9IHNlcSgtZl9uLCBmX24sIGxlbmd0aC5vdXQgPSBkKSkKICAgIGF4aXMoMiwgYXQgPSBzZXEoMCwgMSwgbGVuZ3RoLm91dCA9IGQpLCBsYWJlbHMgPSBzZXEoLWZfbiwgZl9uLCBsZW5ndGgub3V0ID0gZCkpCiAgfSwKICAid2VpZ2h0ZWQuZ2lmIiwKICBpbnRlcnZhbCA9IDAuMDEsIGF1dG9icm93c2UgPSBGQUxTRSwgYW5pLnJlcyA9IDk2LCBhbmkuaGVpZ2h0ID0gNTAwCikpCmBgYAoKPGNlbnRlcj4hW10od2VpZ2h0ZWQuZ2lmKQo8YnI+V2FpdCBmb3IgdGhlIGFuaW1hdGlvbiAoYWJvdXQgMyBzZWMgb2YgYXBhcmVudGx5IG5vIGNoYW5nZXMpCjxicj5UaGUgY2VsbHMgdGhhdCBoYXZlIGEgdmFsdWUgPiAwIGFyZSBwbG90dGVkIGluIGljZS13aGl0ZSBjb2xvci4KPGJyPlRoZSBtYXhpbXVtIHZhbHVlIGlzIGdyZWVuLgo8L2NlbnRlcj4KCiMjIEZhZGluZyBXaW5kb3cKCkZpbmFsbHksIHRoZSBmYWRpbmcgd2luZG93IGlzIHF1aXRlIHNpbWlsYXIgdG8gdGhlIHdlaWdodGVkIHdpbmRvdy4gVGhlIG9ubHkgZGlmZmVyZW5jZSBpcyB0aGF0IHdlIGRvbid0IGtlZXAgYW55IHdpbmRvdywganVzdCBhcHBseSB0aGUgYWxwaGEgdG8gYWxsIGNvdW50ZXJzLgoKYGBge3IgZmFkaW5nfQojIFdlaWdodGVkIFNsaWRpbmcgV2luZG93IGFsZ29yaXRobQpmX24gPC0gMzAwMCAjIFRoZSBub3JtYWxpemluZyBmYWN0b3IsIHNvIHdlIGZvY3VzIG9uIGEgc21hbGxlciBpbnRlcnZhbCwganVzdCBmb3IgYmV0dGVyIHBsb3R0aW5nCm4gPC0gMzAwICMgd2UgZG9uJ3Qgd2FudCB0byByZWFjaCB0aGUgZW5kIG9mIHRoZSBkYXRhc2V0IGluIHRoaXMgZXhhbXBsZS4KZ3JpZCA8LSBncmlkX2Jhc2UgIyBsZXQncyBtYWtlIGEgY29weSBvZiB0aGUgb3JpZ2luYWwgZ3JpZC4KcmVzZXRfc3RyZWFtKHN0cmVhbSkKCncgPC0gMTAwICMgdGhlIHdpbmRvdyBzaXplCmVwcyA8LSAwLjA1CmFscGhhIDwtIGVwc14oMSAvIHcpCgppbnZpc2libGUoc2F2ZUdJRiggIyBzYXZlIGFsbCBncmFwaGljcyBpbiBhbiBhbmltYXRlZCBnaWYKICBmb3IgKGkgaW4gc2VxX2xlbihuKSkgewogICAgIyByZXRyaWV2ZSBvbmUgb2JzZXJ2YXRpb24gZnJvbSBzdHJlYW0KICAgIHBvaW50cyA8LSBnZXRfcG9pbnRzKHN0cmVhbSkKCiAgICAjIG5vcm1hbGl6ZSB0aGUgdmFsdWUgdG8gYWRkIHRvIHRoZSBtYXRyaXgKICAgIHBvaW50cyA8LSBwb2ludHMgLyBmX24KCiAgICAjIHRyYW5zZm9ybSB0aGUgdmFsdWVzIHRvIG1hdGNoIHRoZSBtYXRyaXggaW5kZXhlcwogICAgcG9pbnRzIDwtIGZsb29yKChwb2ludHMgKyAxKSAqIGQgLyAyICsgMC41KQoKICAgICMgYXBwbHkgYWxwaGEgdG8gdGhlIGVudGlyZSBncmlkIGFuZCB0aGVuIHN1bSB0aGUgbmV4dCBvbmUKICAgIGdyaWQgPC0gZ3JpZCAqIGFscGhhCiAgICBncmlkW3BvaW50c1ssIDFdLCBwb2ludHNbLCAyXV0gPC0gZ3JpZFtwb2ludHNbLCAxXSwgcG9pbnRzWywgMl1dICsgMQoKICAgICMgcGxvdCB0aGUgZ3JpZC4gVXNpbmcgcGFsbGV0ZSBHcmVlbnMgMiwgc28gdGhlIHdoaXRlIGlzIGFjdHVhbGx5IGEgJ2ljZScgY29sb3IKICAgIGltYWdlKGdyaWQsCiAgICAgIG1haW4gPSAiRmFkaW5nIFdpbmRvdyIsIGNvbCA9IGhjbC5jb2xvcnMoMl4xNCwgcGFsZXR0ZSA9ICJHcmVlbnMgMiIsIHJldiA9IFQpLAogICAgICB6bGltID0gYygxZS01LCBtYXgoZ3JpZCkpLCB4bGFiID0gbmFtZXMocG9pbnRzKVsxXSwgeWxhYiA9IG5hbWVzKHBvaW50cylbMl0sCiAgICAgIHhheHQgPSAibiIsIHlheHQgPSAibiIKICAgICkKICAgIGF4aXMoMSwgYXQgPSBzZXEoMCwgMSwgbGVuZ3RoLm91dCA9IGQpLCBsYWJlbHMgPSBzZXEoLWZfbiwgZl9uLCBsZW5ndGgub3V0ID0gZCkpCiAgICBheGlzKDIsIGF0ID0gc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSBkKSwgbGFiZWxzID0gc2VxKC1mX24sIGZfbiwgbGVuZ3RoLm91dCA9IGQpKQogIH0sCiAgImZhZGluZy5naWYiLAogIGludGVydmFsID0gMC4wMSwgYXV0b2Jyb3dzZSA9IEZBTFNFLCBhbmkucmVzID0gOTYsIGFuaS5oZWlnaHQgPSA1MDAKKSkKYGBgCgo8Y2VudGVyPiFbXShmYWRpbmcuZ2lmKQo8YnI+V2FpdCBmb3IgdGhlIGFuaW1hdGlvbiAoYWJvdXQgMyBzZWMgb2YgYXBhcmVudGx5IG5vIGNoYW5nZXMpCjxicj5UaGUgY2VsbHMgdGhhdCBoYXZlIGEgdmFsdWUgPiAwIGFyZSBwbG90dGVkIGluIGljZS13aGl0ZSBjb2xvci4KPGJyPlRoZSBtYXhpbXVtIHZhbHVlIGlzIGdyZWVuLgo8L2NlbnRlcj4KCkVPRjw8Cg==