Overview

To work through this, clone the repository (an RStudio project) at https://github.com/eriqande/make-a-BGP-map to get all the necessary input files, etc. Then open up the RStudio project and run though Make-a-BGP-map-Notebook.Rmd.

This chronicles the steps taken whilst making a genoscape for willow flycatchers. The order in which we discuss building up this map is different from the order in which we put layers down to actually make the map. Here, we will start with making the genoscape, which is actually the part that sits atop the whole map. But we do that because that is the part that will actually change from species to species, and we want everyone to be relatively fresh for understanding those mutable parts. Once the genoscape is dealt with, we will then talk about the next layer of country and state boundaries, and also coastline polygons. We do this, because it doesn’t take too long to plot these features, and using them is a good way to figure out the desired extent of your map and to decide upon a projection. Finally, we will talk about the bottom layer of the map, which is the raster with earthforms and shading from Natural Earth Data.

Packages Needed

Non-standard Packages

This work draws on a few functions that I have in packages that I have up on GitHub, namely:

  1. my fork of tess3r. Note that you can’t use the default version of tess3r, you have to use my fork of it, which has some extra functionality.
  2. my package genoscapeRtools

Get those packages like this:

remotes::install_github("eriqande/TESS3_encho_sen")  # for special version of tess3r
remotes::install_github("eriqande/genoscapeRtools")  # for Eric's genoscapeRtools

Standard Packages

The rest of the packages you need can be downloaded from CRAN. If you don’t have them you should get them: raster, sf, fields, downloader, and tidyverse. The last one there gets ggplot2 and a number of other packages by Hadley Wickham.

You can get those like this:

install.packages(c("raster", "sf", "tidyverse", "fields", "downloader"))

Load the Packages We will work with

library(raster)  # important to load before tidyverse, otherwise it masks select()
library(tidyverse)
library(sf)
library(ggspatial)

Making the genoscape

The genoscape can be thought of as the bright colors smeared across space that show where different genetically identifiable groups of birds reside on the breeding grounds. These genoscapes are stored as rasters, and transparency is used to indicate how much confidence one has in the genetic identification of individuals in different areas. These rasters are made by interpolating Q-values from a program like STRUCTURE or ADMIXTURE between individuals that were sampled in space.

Input Data

Breeding bird Q-values

We need a matrix of Q-values for individuals. We have one that we will read in as a tibble

Q_tibble <- read_table("inputs/breeding-bird-Q-values.txt")
Q_tibble

Column id is the sample name and the rest are ancestry fractions to different clusters (named with three characters) estimated by STRUCTURE.

Breeding Bird Lat-Longs

We also need to know where those individuals were sampled in space. We have that here:

LatLong_tibble <- read_tsv("inputs/breeding-bird-lat-longs.tsv")
LatLong_tibble

Breeding Bird Cluster Colors

The Q-values correspond to different clusters, as shown above. We must specify the colors that we wish to assign to each of those clusters. We do that with a named vector like this:

cluster_colors <-  c(
  INW = "#984ea3",
  EST = "#377eb8",
  PNW = "#4daf4a",
  SSW = "#ff7f00",
  SCC = "#ffff33",
  KER = "#e41a1c",
  WMT = "#00ffff"
) 

For fun, we can plot these to see what they look like:

enframe(cluster_colors) %>%
  mutate(x = 1:n(),
         y = 1:n()) %>%
  ggplot(aes(x = x, y = y, fill = name)) +
  geom_point(pch = 21, size = 12) +
  scale_fill_manual(values = cluster_colors)

Breeding Bird Shapefile

Finally, we need to have a GIS Shapefile that tells us the range of the breeding birds, so that genoscape can be clipped properly. We read this shapefile with the st_read() function from package sf.

breeding_range <- st_read("inputs/wifl-shapefiles-revised/WIFLrev.shp")

For grins, we can plot those polygons to see what they look like:

ggplot(breeding_range) + 
  geom_sf() +
  theme_bw()

Preparing the data for tess3Q_map_rasters

Within Eric’s fork of tess3r is a function called tess3Q_map_rasters. It takes input from the objects we have above, but it takes that input as matrices rather than data frames, etc. so there is a little finagling to be done.

First, make sure the lat longs are in the correct order and arrangement

We need to ensure that we have values for birds in the right order (a job for aleft_join), and we also have to make it a matrix with Longitude in the first column and Lat in the second.

long_lat_tibble <- Q_tibble %>%
  select(id) %>%
  left_join(LatLong_tibble, by = "id") %>%
  select(Long, Lat) 

long_lat_matrix <- long_lat_tibble %>%
  as.matrix()

Then, make a matrix of the Q values

Pull off the names of individuals and make a matrix of it:

Q_matrix <- Q_tibble %>%
  select(-id) %>%
  as.matrix()

Interpolate the Q-values by Kriging

For this, we use the above variables in tess3r::tess3Q_map_rasters(). Note the use of namespace addressing for this function rather than loading the whole tess3r package with the library() command.

genoscape_brick <- tess3r::tess3Q_map_rasters(
  x = Q_matrix, 
  coord = long_lat_matrix,  
  map.polygon = breeding_range,
  window = extent(breeding_range)[1:4],
  resolution = c(300,300), # if you want more cells in your raster, set higher
  # this next lines need to to be here, but don't do much...
  col.palette = tess3r::CreatePalette(cluster_colors, length(cluster_colors)), 
  method = "map.max", 
  interpol = tess3r::FieldsKrigModel(10),  
  main = "Ancestry coefficients",
  xlab = "Longitude", 
  ylab = "Latitude", 
  cex = .4
)

# after that, we need to add names of the clusters back onto this raster brick
names(genoscape_brick) <- names(Q_tibble)[-1]

That gives us a raster brick of Q-values associated with each cell in the raster, but those values are not always constrained between 0 and 1, so we have to massage them a little bit in the next section.

Scaling and cleaning the genoscape_brick

For this we use the function genoscapeRtools::qprob_rando_raster(). This takes the raster brick that comes out of tess3Q_map_rasters() and does some rescaling and (maybe) some random sampling to return a raster of colors that I hope will do a reliable job of representing (in some way) predicted assignment accuracy over space. See ?genoscapeRtools::qprob_rando_raster to learn about the scaling options, etc. (However, I am not convinced that all of those options are reliably estimated.)

This will squash the raster brick down to a single RGBA (i.e., four channels, red, green, blue and alpha) raster brick.

genoscape_rgba <- genoscapeRtools::qprob_rando_raster(
  TRB = genoscape_brick,
  cols = cluster_colors, 
  alpha_scale = 2.0, 
  abs_thresh = 0.0, 
  alpha_exp = 1.55, 
  alpha_chop_max = 230
)

# at this point, we must be explicit about adding a projection to the raster.
# This adds the info for a regular lat-long projection
crs(genoscape_rgba) <- "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0"

We can easily plot this with the function layer_spatial from the ggspatial package:

ggplot() + 
  ggspatial::layer_spatial(genoscape_rgba) + 
  theme_bw() +
  coord_sf()

Note that if we wanted to add the actual sampling points to this (that are given in long_lat_tibble), we can use ggspatial’s geom_spatial_point() function.

ggplot() + 
  layer_spatial(genoscape_rgba) + 
  geom_spatial_point(data = long_lat_tibble, mapping = aes(x = Long, y = Lat)) + 
  theme_bw() +
  coord_sf()

It would probably be better to jigger those points a little bit. That could be done by mutating each of them a random bit away.

Coastlines, Countries, States, and Provinces

Since we will be using the Natural Earth Data Set for the raster background of our map, we will also use the Natural Earth lines and polygons. The Natural Earth data set is an amazing, open-source resource for making beautiful maps. Check it out at naturalearthdata.com.

Downloading the shapefiles

For coastlines, countries, and states/provinces, you will need to download three different shape files. We will be working with the highest resolution Natural Earth data sets which are the 10m versions. Download and unzip them to a directory within this project called ne_shapefiles. You can do that with R like this:

dir.create("ne_shapefiles", showWarnings = FALSE)

tmpfile <- tempfile()
downloader::download(
  url = "https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/physical/ne_10m_coastline.zip",
  dest = tmpfile
)
unzip(zipfile = tmpfile, exdir = "ne_shapefiles")

We do the same for country boundaries, and then state/province boundaries, too. Note, in both cases we just want to get the boundary lines that are internal to the coast. Otherwise you can end up getting a thick line around the coast that is not so great…

# country boundaries
tmpfile <- tempfile()
downloader::download(
  url = "https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_0_boundary_lines_land.zip",
  dest = tmpfile
)
unzip(zipfile = tmpfile, exdir = "ne_shapefiles")


# state and province boundaries
# country boundaries
tmpfile <- tempfile()
downloader::download(
  url = "https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces_lines.zip",
  dest = tmpfile
)
unzip(zipfile = tmpfile, exdir = "ne_shapefiles")

Plot the Coastlines and estimate where we want to clip

First read it in:

coastlines <- st_read("ne_shapefiles/ne_10m_coastline.shp")
Reading layer `ne_10m_coastline' from data source `/Users/eriq/Documents/git-repos/make-a-BGP-map/ne_shapefiles/ne_10m_coastline.shp' using driver `ESRI Shapefile'
Simple feature collection with 4133 features and 3 fields
Geometry type: LINESTRING
Dimension:     XY
Bounding box:  xmin: -180 ymin: -85.22194 xmax: 180 ymax: 83.6341
Geodetic CRS:  WGS 84

Now, let’s just crop out the part that we want. This is somewhat key: right here we will define the extent in lat-long of the region that we want to plot:

# note! it is important to put the elements of domain in this 
# order, because the function raster::extent() is expecting thing
# to be in this order, and doesn't parse the names of the vectors
# the way sf::st_crop() does.
domain <- c(
  xmin = -135, 
  xmax = -60,
  ymin = 22,
  ymax = 60
)

coast_cropped <- st_crop(coastlines, domain)
Warning: attribute variables are assumed to be spatially constant throughout all geometries

Then plot the cropped part:

ggplot(coast_cropped) + 
  geom_sf() +
  coord_sf()

All the lines, plus the genoscape

OK, that was pretty reasonable. Now crop all the lines to domain and plot them, and put the genoscape on top of that, too.

countries_cropped <-  st_read("ne_shapefiles/ne_10m_admin_0_boundary_lines_land.shp") %>%
  st_crop(domain)
Reading layer `ne_10m_admin_0_boundary_lines_land' from data source 
  `/Users/eriq/Documents/git-repos/make-a-BGP-map/ne_shapefiles/ne_10m_admin_0_boundary_lines_land.shp' using driver `ESRI Shapefile'
Simple feature collection with 462 features and 18 fields
Geometry type: MULTILINESTRING
Dimension:     XY
Bounding box:  xmin: -141.0055 ymin: -55.12092 xmax: 140.9776 ymax: 70.07531
Geodetic CRS:  WGS 84
Warning: attribute variables are assumed to be spatially constant throughout all geometries
states_cropped <- st_read("ne_shapefiles/ne_10m_admin_1_states_provinces_lines.shp") %>%
  st_crop(domain)
Reading layer `ne_10m_admin_1_states_provinces_lines' from data source 
  `/Users/eriq/Documents/git-repos/make-a-BGP-map/ne_shapefiles/ne_10m_admin_1_states_provinces_lines.shp' using driver `ESRI Shapefile'
Simple feature collection with 10114 features and 21 fields
Geometry type: MULTILINESTRING
Dimension:     XY
Bounding box:  xmin: -178.1371 ymin: -49.25087 xmax: 178.4486 ymax: 81.12853
Geodetic CRS:  WGS 84
Warning: attribute variables are assumed to be spatially constant throughout all geometries

Now, plot it all. Notice we do it by just adding layers.

mapg <- ggplot() +
  geom_sf(data = coast_cropped) +
  geom_sf(data = countries_cropped, fill = NA) +
  geom_sf(data = states_cropped, fill = NA) +
  ggspatial::layer_spatial(genoscape_rgba) +
  theme_bw() 

# now plot it under default lat-long projection
mapg +
  coord_sf() 

That is looking like it should, but the projection is pretty ugly. In the next section we will consider projection.

Projecting

The new version of ggplot2 makes it super easy to project everything on the fly by passing the projection to coord_sf(). For things in North America, it seems that a Lambert conic projection does a nice job of keeping British Columbia and Alaska from looking way too big. We can define such a projection with a string, like this:

lamproj <- "+proj=lcc +lat_1=20 +lat_2=60 +lat_0=40 +lon_0=-100 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs"

Now, let’s see how our developing map looks under such a projection. We just define lamproj as a coordinate reference system and pass it in to coord_sf():

mapg +
  coord_sf(crs = st_crs(lamproj))

I would call that decidedly better.

The pretty-map background

Now, all that remains is to put all of this on top of a nicely tinted map that shows landforms and things. Note that once we have such a layer under the rest of this stuff, we will probably omit the coastline layer, since it gets a little dark where the coastline is highly dissected.

Downloading Natural Earth rasters

I am going to recommed that you get the hypsometrically tinted Natural Earth map with water bodies and rivers on it, and you might as well get the one that has some ocean basin coloring too. We will put it into a directory in the project called ne_rasters. Note, this expands to about half a gig of data.

dir.create("ne_rasters", showWarnings = FALSE)

tmpfile <- tempfile()
downloader::download(
  url = "https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/raster/HYP_HR_SR_OB_DR.zip",
  dest = tmpfile
)
unzip(zipfile = tmpfile, exdir = "ne_rasters")

Read in the raster and crop it

Reading a large raster in with the raster package does not take up much memory, because it leaves it on disk. So we will open a connection to the big, massive raster using the brick function from the raster package and then crop it:

hypso <- brick("ne_rasters/HYP_HR_SR_OB_DR/HYP_HR_SR_OB_DR.tif")
hypso_cropped <- crop(hypso, extent(domain)) 

Now, we just add hypso_cropped in with ggspatial::spatial_layer() below all of our other layers (dropping the coastlines), and we might make country and state lines thinner (and different sizes):

big_one <- ggplot() +
  ggspatial::layer_spatial(hypso_cropped) +
  geom_sf(data = countries_cropped, fill = NA, size = 0.15) +
  geom_sf(data = states_cropped, fill = NA, size = 0.1) +
  ggspatial::layer_spatial(genoscape_rgba) +
  theme_bw() + 
  coord_sf(crs = st_crs(lamproj))

big_one

Seeing that sort of small on the screen does not really do justice to it. You can ggsave it in whatever size is desired. For whatever the final product is going to be, you will want to mess with the line thickness on those country and state borders…

Clipping a rectangle out of that

While that looks nice, most people might prefer to have the background part there in a rectangle, rather than a conic surface. We can do that by setting the xlim and ylim to coord_sf(). But, note that we have to do that in terms of the units that the Lambert projection is referring to, not just in simple lat-long coordinates (i.e. we have to project those too!)

So, imagine that we want to chop out a rectangle from Haida Gwai, off the coast of British Columbia, straight down and straight across to the right (on the image). And we would want the bottom and right sides determined by a point in the bottom right that has the y-value of the tip of Florida, and an x-value which is essentially at St. John, New Brunswick. Then, we need to determine the x and y coordinates of those points under our Lambert conformal conic projection. We do that by making a simple features tibble of the lat-longs for those points and then projecting it.

# Here are lat longs for those points
pts <- tribble(
  ~place, ~long, ~lat,
  "HaidaGwai", -132.3143526, 53.7298050,
  "FloridaTip", -80.8032237, 25.0909780, 
  "St.Johns",  -65.943453, 45.291222
)

# here we turn that into an sf object
pts_sf <- st_as_sf(pts, coords = c("long", "lat"), 
                   crs = 4326)  # 4326 is shorthand for "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0"

# and now we transform it to lambert
pts_lambert <- st_transform(pts_sf, crs = lamproj)

# when we print it in a notebook, we can see that the geometry is a list
# column, but can't see the values:
pts_lambert
Simple feature collection with 3 features and 1 field
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -2010114 ymin: -1366911 xmax: 2452877 ymax: 1821869
Projected CRS: +proj=lcc +lat_1=20 +lat_2=60 +lat_0=40 +lon_0=-100 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs

So, for you to be able to see the values we can just print it like this:

# just note the values:
st_as_text(pts_lambert$geometry)
[1] "POINT (-2010114 1821869)" "POINT (1871262 -1366911)" "POINT (2452877 1037658)" 

That means we want xlim = c(-2010114, 2452877) and ylim = c(-1366911, 1821869). Let’s try that:

rectangled <- ggplot() +
  ggspatial::layer_spatial(hypso_cropped) +
  geom_sf(data = countries_cropped, fill = NA, size = 0.15) +
  geom_sf(data = states_cropped, fill = NA, size = 0.1) +
  ggspatial::layer_spatial(genoscape_rgba) +
  theme_bw() + 
  coord_sf(
    crs = st_crs(lamproj), 
    xlim = c(-2010114, 2452877), 
    ylim = c(-1366911, 1821869),
    expand = FALSE)  # expand = FALSE means put the axes right at the xlim and ylim

rectangled

Okay, that does it. In reality, you would probably want to expand the original domain to be a bigger chunk of the earth, so that when you cut stuff off there was more left…

A final note

If you want to add more elements to the figure you can do so by giving them a lat-long and making a simple features tibble out of it and then putting them there using geom_sf(). It all fits nicely into the ggplot framework.

Session Info

sessioninfo::session_info()
─ Session info ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.3.3 (2024-02-29)
 os       macOS Monterey 12.7.5
 system   aarch64, darwin20
 ui       RStudio
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       America/Denver
 date     2024-11-16
 rstudio  2024.09.0+375 Cranberry Hibiscus (desktop)
 pandoc   3.1.12.1 @ /Users/eriq/mambaforge-arm64/bin/pandoc

─ Packages ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 package         * version   date (UTC) lib source
 abind             1.4-5     2016-07-21 [1] CRAN (R 4.3.0)
 bit               4.0.5     2022-11-15 [1] CRAN (R 4.3.0)
 bit64             4.0.5     2020-08-30 [1] CRAN (R 4.3.0)
 class             7.3-22    2023-05-03 [2] CRAN (R 4.3.3)
 classInt          0.4-9     2023-02-28 [1] CRAN (R 4.3.0)
 cli               3.6.1     2023-03-23 [1] CRAN (R 4.3.0)
 codetools         0.2-19    2023-02-01 [2] CRAN (R 4.3.3)
 colorspace        2.1-0     2023-01-23 [1] CRAN (R 4.3.0)
 crayon            1.5.2     2022-09-29 [1] CRAN (R 4.3.0)
 DBI               1.2.0     2023-12-21 [1] CRAN (R 4.3.1)
 digest            0.6.33    2023-07-07 [1] CRAN (R 4.3.0)
 dotCall64         1.2       2024-10-04 [1] CRAN (R 4.3.3)
 downloader        0.4       2015-07-09 [1] CRAN (R 4.3.0)
 dplyr           * 1.1.4     2023-11-17 [1] CRAN (R 4.3.1)
 e1071             1.7-13    2023-02-01 [1] CRAN (R 4.3.0)
 fansi             1.0.4     2023-01-22 [1] CRAN (R 4.3.0)
 farver            2.1.1     2022-07-06 [1] CRAN (R 4.3.0)
 fields            16.3      2024-09-30 [1] CRAN (R 4.3.3)
 forcats         * 1.0.0     2023-01-29 [1] CRAN (R 4.3.0)
 generics          0.1.3     2022-07-05 [1] CRAN (R 4.3.0)
 genoscapeRtools   0.1.0     2024-11-15 [1] Github (eriqande/genoscapeRtools@a15006b)
 ggplot2         * 3.4.4     2023-10-12 [1] CRAN (R 4.3.1)
 ggspatial       * 1.1.9     2023-08-17 [1] CRAN (R 4.3.0)
 glue              1.6.2     2022-02-24 [1] CRAN (R 4.3.0)
 gtable            0.3.3     2023-03-21 [1] CRAN (R 4.3.0)
 hms               1.1.3     2023-03-21 [1] CRAN (R 4.3.0)
 KernSmooth        2.23-22   2023-07-10 [2] CRAN (R 4.3.3)
 knitr             1.43      2023-05-25 [1] CRAN (R 4.3.0)
 labeling          0.4.2     2020-10-20 [1] CRAN (R 4.3.0)
 lattice           0.22-5    2023-10-24 [2] CRAN (R 4.3.3)
 lifecycle         1.0.3     2022-10-07 [1] CRAN (R 4.3.0)
 lubridate       * 1.9.3     2023-09-27 [1] CRAN (R 4.3.1)
 magrittr          2.0.3     2022-03-30 [1] CRAN (R 4.3.0)
 maps              3.4.2     2023-12-15 [1] CRAN (R 4.3.1)
 Matrix            1.6-1     2023-08-14 [1] CRAN (R 4.3.0)
 munsell           0.5.0     2018-06-12 [1] CRAN (R 4.3.0)
 pillar            1.9.0     2023-03-22 [1] CRAN (R 4.3.0)
 pkgconfig         2.0.3     2019-09-22 [1] CRAN (R 4.3.0)
 proxy             0.4-27    2022-06-09 [1] CRAN (R 4.3.0)
 purrr           * 1.0.2     2023-08-10 [1] CRAN (R 4.3.0)
 R6                2.5.1     2021-08-19 [1] CRAN (R 4.3.0)
 raster          * 3.6-30    2024-10-02 [1] CRAN (R 4.3.3)
 Rcpp              1.0.11    2023-07-06 [1] CRAN (R 4.3.0)
 RcppEigen         0.3.3.9.3 2022-11-05 [1] CRAN (R 4.3.0)
 readr           * 2.1.4     2023-02-10 [1] CRAN (R 4.3.0)
 rlang             1.1.2     2023-11-04 [1] CRAN (R 4.3.1)
 rstudioapi        0.15.0    2023-07-07 [1] CRAN (R 4.3.0)
 s2                1.1.4     2023-05-17 [1] CRAN (R 4.3.0)
 scales            1.2.1     2022-08-20 [1] CRAN (R 4.3.0)
 sessioninfo       1.2.2     2021-12-06 [1] CRAN (R 4.3.0)
 sf              * 1.0-14    2023-07-11 [1] CRAN (R 4.3.0)
 sp              * 2.1-2     2023-11-26 [1] CRAN (R 4.3.1)
 spam              2.11-0    2024-10-03 [1] CRAN (R 4.3.3)
 stringi           1.7.12    2023-01-11 [1] CRAN (R 4.3.0)
 stringr         * 1.5.1     2023-11-14 [1] CRAN (R 4.3.1)
 terra             1.7-65    2023-12-15 [1] CRAN (R 4.3.1)
 tess3r            1.1.0     2024-11-15 [1] Github (eriqande/TESS3_encho_sen@2a0ce6c)
 tibble          * 3.2.1     2023-03-20 [1] CRAN (R 4.3.0)
 tidyr           * 1.3.0     2023-01-24 [1] CRAN (R 4.3.0)
 tidyselect        1.2.0     2022-10-10 [1] CRAN (R 4.3.0)
 tidyverse       * 2.0.0     2023-02-22 [1] CRAN (R 4.3.0)
 timechange        0.2.0     2023-01-11 [1] CRAN (R 4.3.0)
 tzdb              0.4.0     2023-05-12 [1] CRAN (R 4.3.0)
 units             0.8-3     2023-08-10 [1] CRAN (R 4.3.0)
 utf8              1.2.3     2023-01-31 [1] CRAN (R 4.3.0)
 vctrs             0.6.5     2023-12-01 [1] CRAN (R 4.3.1)
 viridisLite       0.4.2     2023-05-02 [1] CRAN (R 4.3.0)
 vroom             1.6.3     2023-04-28 [1] CRAN (R 4.3.0)
 withr             2.5.0     2022-03-03 [1] CRAN (R 4.3.0)
 wk                0.8.0     2023-08-25 [1] CRAN (R 4.3.0)
 xfun              0.39      2023-04-20 [1] CRAN (R 4.3.0)

 [1] /Users/eriq/Library/R/4.3/library
 [2] /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
LS0tCnRpdGxlOiAiTWFrZSBhIEJHUCBtYXAgTm90ZWJvb2siCmF1dGhvcjogIkVyaWMgQy4gQW5kZXJzb24iCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQotLS0KCiMjIE92ZXJ2aWV3CgpUbyB3b3JrIHRocm91Z2ggdGhpcywgY2xvbmUgdGhlIHJlcG9zaXRvcnkgKGFuIFJTdHVkaW8gcHJvamVjdCkgYXQKW2h0dHBzOi8vZ2l0aHViLmNvbS9lcmlxYW5kZS9tYWtlLWEtQkdQLW1hcF0oaHR0cHM6Ly9naXRodWIuY29tL2VyaXFhbmRlL21ha2UtYS1CR1AtbWFwKSB0bwpnZXQgYWxsIHRoZSBuZWNlc3NhcnkgaW5wdXQgZmlsZXMsIGV0Yy4gIFRoZW4gb3BlbiB1cCB0aGUgUlN0dWRpbyBwcm9qZWN0IGFuZCBydW4KdGhvdWdoIGBNYWtlLWEtQkdQLW1hcC1Ob3RlYm9vay5SbWRgLgoKVGhpcyBjaHJvbmljbGVzIHRoZSBzdGVwcyB0YWtlbiB3aGlsc3QgbWFraW5nIGEgZ2Vub3NjYXBlIGZvciB3aWxsb3cgZmx5Y2F0Y2hlcnMuClRoZSBvcmRlciBpbiB3aGljaCB3ZSBkaXNjdXNzIGJ1aWxkaW5nIHVwIHRoaXMgbWFwIGlzIGRpZmZlcmVudCBmcm9tIHRoZSBvcmRlciBpbiB3aGljaAp3ZSBwdXQgbGF5ZXJzIGRvd24gdG8gYWN0dWFsbHkgbWFrZSB0aGUgbWFwLiBIZXJlLCB3ZSB3aWxsIHN0YXJ0IHdpdGggbWFraW5nIHRoZQpnZW5vc2NhcGUsIHdoaWNoIGlzIGFjdHVhbGx5IHRoZSBwYXJ0IHRoYXQgc2l0cyBhdG9wIHRoZSB3aG9sZSBtYXAuICBCdXQgd2UgZG8gdGhhdApiZWNhdXNlIHRoYXQgaXMgdGhlIHBhcnQgdGhhdCB3aWxsIGFjdHVhbGx5IGNoYW5nZSBmcm9tIHNwZWNpZXMgdG8gc3BlY2llcywgYW5kIHdlIAp3YW50IGV2ZXJ5b25lIHRvIGJlIHJlbGF0aXZlbHkgZnJlc2ggZm9yIHVuZGVyc3RhbmRpbmcgdGhvc2UgbXV0YWJsZSBwYXJ0cy4gIE9uY2UgdGhlCmdlbm9zY2FwZSBpcyBkZWFsdCB3aXRoLCB3ZSB3aWxsIHRoZW4gdGFsayBhYm91dCB0aGUgbmV4dCBsYXllciBvZiBjb3VudHJ5IGFuZCBzdGF0ZQpib3VuZGFyaWVzLCBhbmQgYWxzbyBjb2FzdGxpbmUgcG9seWdvbnMuICBXZSBkbyB0aGlzLCBiZWNhdXNlIGl0IGRvZXNuJ3QgdGFrZSB0b28gbG9uZwp0byBwbG90IHRoZXNlIGZlYXR1cmVzLCBhbmQgdXNpbmcgdGhlbSBpcyBhIGdvb2Qgd2F5IHRvIGZpZ3VyZSBvdXQgdGhlIGRlc2lyZWQgZXh0ZW50IG9mCnlvdXIgbWFwIGFuZCB0byBkZWNpZGUgdXBvbiBhIHByb2plY3Rpb24uICBGaW5hbGx5LCB3ZSB3aWxsIHRhbGsgYWJvdXQgdGhlIGJvdHRvbQpsYXllciBvZiB0aGUgbWFwLCB3aGljaCBpcyB0aGUgcmFzdGVyIHdpdGggZWFydGhmb3JtcyBhbmQgc2hhZGluZyBmcm9tIE5hdHVyYWwgRWFydGggRGF0YS4KCiMjIFBhY2thZ2VzIE5lZWRlZAoKIyMjIE5vbi1zdGFuZGFyZCBQYWNrYWdlcwoKVGhpcyB3b3JrIGRyYXdzIG9uIGEgZmV3IGZ1bmN0aW9ucyB0aGF0IEkgaGF2ZSBpbiBwYWNrYWdlcyB0aGF0IEkgaGF2ZSB1cCBvbiBHaXRIdWIsCm5hbWVseToKCjEuICoqbXkgZm9yayoqIG9mIGB0ZXNzM3JgLiAgTm90ZSB0aGF0IHlvdSBjYW4ndCB1c2UgdGhlIGRlZmF1bHQgdmVyc2lvbiBvZiBgdGVzczNyYCwKeW91IGhhdmUgdG8gdXNlIG15IGZvcmsgb2YgaXQsIHdoaWNoIGhhcyBzb21lIGV4dHJhIGZ1bmN0aW9uYWxpdHkuCjIuIG15IHBhY2thZ2UgYGdlbm9zY2FwZVJ0b29sc2AKCkdldCB0aG9zZSBwYWNrYWdlcyBsaWtlIHRoaXM6CmBgYHtyLCBldmFsPUZBTFNFfQpyZW1vdGVzOjppbnN0YWxsX2dpdGh1YigiZXJpcWFuZGUvVEVTUzNfZW5jaG9fc2VuIikgICMgZm9yIHNwZWNpYWwgdmVyc2lvbiBvZiB0ZXNzM3IKcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoImVyaXFhbmRlL2dlbm9zY2FwZVJ0b29scyIpICAjIGZvciBFcmljJ3MgZ2Vub3NjYXBlUnRvb2xzCmBgYAoKIyMjIFN0YW5kYXJkIFBhY2thZ2VzCgpUaGUgcmVzdCBvZiB0aGUgcGFja2FnZXMgeW91IG5lZWQgY2FuIGJlIGRvd25sb2FkZWQgZnJvbSBDUkFOLiAgSWYgeW91IGRvbid0IGhhdmUKdGhlbSB5b3Ugc2hvdWxkIGdldCB0aGVtOiBgcmFzdGVyYCwgYHNmYCwgYGZpZWxkc2AsIGBkb3dubG9hZGVyYCwgYW5kIGB0aWR5dmVyc2VgLiBUaGUgbGFzdCBvbmUgdGhlcmUgZ2V0cyBnZ3Bsb3QyCmFuZCBhIG51bWJlciBvZiBvdGhlciBwYWNrYWdlcyBieSBIYWRsZXkgV2lja2hhbS4KCllvdSBjYW4gZ2V0IHRob3NlIGxpa2UgdGhpczoKYGBge3IsIGV2YWw9RkFMU0V9Cmluc3RhbGwucGFja2FnZXMoYygicmFzdGVyIiwgInNmIiwgInRpZHl2ZXJzZSIsICJmaWVsZHMiLCAiZG93bmxvYWRlciIpKQpgYGAKCiMjIyBMb2FkIHRoZSBQYWNrYWdlcyBXZSB3aWxsIHdvcmsgd2l0aAoKYGBge3IsIGNvbW1lbnQ9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkocmFzdGVyKSAgIyBpbXBvcnRhbnQgdG8gbG9hZCBiZWZvcmUgdGlkeXZlcnNlLCBvdGhlcndpc2UgaXQgbWFza3Mgc2VsZWN0KCkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2YpCmxpYnJhcnkoZ2dzcGF0aWFsKQpgYGAKCiMjIE1ha2luZyB0aGUgZ2Vub3NjYXBlCgpUaGUgZ2Vub3NjYXBlIGNhbiBiZSB0aG91Z2h0IG9mIGFzIHRoZSBicmlnaHQgY29sb3JzIHNtZWFyZWQgYWNyb3NzIHNwYWNlIHRoYXQgc2hvdyB3aGVyZSBkaWZmZXJlbnQKZ2VuZXRpY2FsbHkgaWRlbnRpZmlhYmxlIGdyb3VwcyBvZiBiaXJkcyByZXNpZGUgb24gdGhlIGJyZWVkaW5nIGdyb3VuZHMuICBUaGVzZSBnZW5vc2NhcGVzCmFyZSBzdG9yZWQgYXMgcmFzdGVycywgYW5kIHRyYW5zcGFyZW5jeSBpcyB1c2VkIHRvIGluZGljYXRlIGhvdyBtdWNoIGNvbmZpZGVuY2Ugb25lIGhhcwppbiB0aGUgZ2VuZXRpYyBpZGVudGlmaWNhdGlvbiBvZiBpbmRpdmlkdWFscyBpbiBkaWZmZXJlbnQgYXJlYXMuICBUaGVzZSByYXN0ZXJzIGFyZSBtYWRlCmJ5IGludGVycG9sYXRpbmcgUS12YWx1ZXMgZnJvbSBhIHByb2dyYW0gbGlrZSBTVFJVQ1RVUkUgb3IgQURNSVhUVVJFIGJldHdlZW4gaW5kaXZpZHVhbHMKdGhhdCB3ZXJlIHNhbXBsZWQgaW4gc3BhY2UuIAoKIyMjIElucHV0IERhdGEKCiMjIyMgQnJlZWRpbmcgYmlyZCBRLXZhbHVlcwpXZSBuZWVkIGEgbWF0cml4IG9mIFEtdmFsdWVzIGZvciBpbmRpdmlkdWFscy4gIFdlIGhhdmUgb25lIHRoYXQgd2Ugd2lsbCByZWFkIGluIGFzIGEKdGliYmxlCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpRX3RpYmJsZSA8LSByZWFkX3RhYmxlKCJpbnB1dHMvYnJlZWRpbmctYmlyZC1RLXZhbHVlcy50eHQiKQpRX3RpYmJsZQpgYGAKCkNvbHVtbiBgaWRgIGlzIHRoZSBzYW1wbGUgbmFtZSBhbmQgdGhlIHJlc3QgYXJlIGFuY2VzdHJ5IGZyYWN0aW9ucyB0byBkaWZmZXJlbnQgY2x1c3RlcnMKKG5hbWVkIHdpdGggdGhyZWUgY2hhcmFjdGVycykgZXN0aW1hdGVkIGJ5IFNUUlVDVFVSRS4KCiMjIyMgQnJlZWRpbmcgQmlyZCBMYXQtTG9uZ3MKV2UgYWxzbyBuZWVkIHRvIGtub3cgd2hlcmUgdGhvc2UgaW5kaXZpZHVhbHMgd2VyZSBzYW1wbGVkIGluIHNwYWNlLiBXZSBoYXZlIHRoYXQgaGVyZToKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CkxhdExvbmdfdGliYmxlIDwtIHJlYWRfdHN2KCJpbnB1dHMvYnJlZWRpbmctYmlyZC1sYXQtbG9uZ3MudHN2IikKTGF0TG9uZ190aWJibGUKYGBgCgojIyMjIEJyZWVkaW5nIEJpcmQgQ2x1c3RlciBDb2xvcnMKClRoZSBRLXZhbHVlcyBjb3JyZXNwb25kIHRvIGRpZmZlcmVudCBjbHVzdGVycywgYXMgc2hvd24gYWJvdmUuICBXZSBtdXN0IHNwZWNpZnkKdGhlIGNvbG9ycyB0aGF0IHdlIHdpc2ggdG8gYXNzaWduIHRvIGVhY2ggb2YgdGhvc2UgY2x1c3RlcnMuICBXZSBkbyB0aGF0IHdpdGggYSBuYW1lZAp2ZWN0b3IgbGlrZSB0aGlzOgpgYGB7cn0KY2x1c3Rlcl9jb2xvcnMgPC0gIGMoCiAgSU5XID0gIiM5ODRlYTMiLAogIEVTVCA9ICIjMzc3ZWI4IiwKICBQTlcgPSAiIzRkYWY0YSIsCiAgU1NXID0gIiNmZjdmMDAiLAogIFNDQyA9ICIjZmZmZjMzIiwKICBLRVIgPSAiI2U0MWExYyIsCiAgV01UID0gIiMwMGZmZmYiCikgCmBgYAoKRm9yIGZ1biwgd2UgY2FuIHBsb3QgdGhlc2UgdG8gc2VlIHdoYXQgdGhleSBsb29rIGxpa2U6CmBgYHtyfQplbmZyYW1lKGNsdXN0ZXJfY29sb3JzKSAlPiUKICBtdXRhdGUoeCA9IDE6bigpLAogICAgICAgICB5ID0gMTpuKCkpICU+JQogIGdncGxvdChhZXMoeCA9IHgsIHkgPSB5LCBmaWxsID0gbmFtZSkpICsKICBnZW9tX3BvaW50KHBjaCA9IDIxLCBzaXplID0gMTIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjbHVzdGVyX2NvbG9ycykKYGBgCgojIyMjIEJyZWVkaW5nIEJpcmQgU2hhcGVmaWxlCgpGaW5hbGx5LCB3ZSBuZWVkIHRvIGhhdmUgYSBHSVMgU2hhcGVmaWxlIHRoYXQgdGVsbHMgdXMgdGhlIHJhbmdlIG9mIHRoZSBicmVlZGluZwpiaXJkcywgc28gdGhhdCBnZW5vc2NhcGUgY2FuIGJlIGNsaXBwZWQgcHJvcGVybHkuICBXZSByZWFkIHRoaXMgc2hhcGVmaWxlIHdpdGggdGhlIApgc3RfcmVhZCgpYCBmdW5jdGlvbiBmcm9tIHBhY2thZ2UgYHNmYC4KYGBge3IsIHJlc3VsdHM9J2hpZGUnfQpicmVlZGluZ19yYW5nZSA8LSBzdF9yZWFkKCJpbnB1dHMvd2lmbC1zaGFwZWZpbGVzLXJldmlzZWQvV0lGTHJldi5zaHAiKQpgYGAKCkZvciBncmlucywgd2UgY2FuIHBsb3QgdGhvc2UgcG9seWdvbnMgdG8gc2VlIHdoYXQgdGhleSBsb29rIGxpa2U6CmBgYHtyfQpnZ3Bsb3QoYnJlZWRpbmdfcmFuZ2UpICsgCiAgZ2VvbV9zZigpICsKICB0aGVtZV9idygpCmBgYAoKCiMjIyBQcmVwYXJpbmcgdGhlIGRhdGEgZm9yIHRlc3MzUV9tYXBfcmFzdGVycwoKV2l0aGluIEVyaWMncyBmb3JrIG9mIGB0ZXNzM3JgIGlzIGEgZnVuY3Rpb24gY2FsbGVkIGB0ZXNzM1FfbWFwX3Jhc3RlcnNgLiAgSXQKdGFrZXMgaW5wdXQgZnJvbSB0aGUgb2JqZWN0cyB3ZSBoYXZlIGFib3ZlLCBidXQgaXQgdGFrZXMgdGhhdCBpbnB1dCBhcyAKbWF0cmljZXMgcmF0aGVyIHRoYW4gZGF0YSBmcmFtZXMsIGV0Yy4gc28gdGhlcmUgaXMgYSBsaXR0bGUgZmluYWdsaW5nIHRvIGJlIGRvbmUuCgojIyMjIEZpcnN0LCBtYWtlIHN1cmUgdGhlIGxhdCBsb25ncyBhcmUgaW4gdGhlIGNvcnJlY3Qgb3JkZXIgYW5kIGFycmFuZ2VtZW50CgpXZSBuZWVkIHRvIGVuc3VyZSB0aGF0IHdlIGhhdmUgdmFsdWVzIGZvciBiaXJkcyBpbiB0aGUgcmlnaHQgb3JkZXIgKGEgam9iIGZvciBhYCBsZWZ0X2pvaW5gKSwgYW5kIHdlCmFsc28gaGF2ZSB0byBtYWtlIGl0IGEgbWF0cml4IHdpdGggTG9uZ2l0dWRlIGluIHRoZSBmaXJzdCBjb2x1bW4gYW5kIExhdCBpbiB0aGUKc2Vjb25kLiAKYGBge3J9CmxvbmdfbGF0X3RpYmJsZSA8LSBRX3RpYmJsZSAlPiUKICBzZWxlY3QoaWQpICU+JQogIGxlZnRfam9pbihMYXRMb25nX3RpYmJsZSwgYnkgPSAiaWQiKSAlPiUKICBzZWxlY3QoTG9uZywgTGF0KSAKCmxvbmdfbGF0X21hdHJpeCA8LSBsb25nX2xhdF90aWJibGUgJT4lCiAgYXMubWF0cml4KCkKYGBgCgojIyMjIFRoZW4sIG1ha2UgYSBtYXRyaXggb2YgdGhlIFEgdmFsdWVzCgpQdWxsIG9mZiB0aGUgbmFtZXMgb2YgaW5kaXZpZHVhbHMgYW5kIG1ha2UgYSBtYXRyaXggb2YgaXQ6CmBgYHtyfQpRX21hdHJpeCA8LSBRX3RpYmJsZSAlPiUKICBzZWxlY3QoLWlkKSAlPiUKICBhcy5tYXRyaXgoKQpgYGAKCiMjIyBJbnRlcnBvbGF0ZSB0aGUgUS12YWx1ZXMgYnkgS3JpZ2luZwoKRm9yIHRoaXMsIHdlIHVzZSB0aGUgYWJvdmUgdmFyaWFibGVzIGluIGB0ZXNzM3I6OnRlc3MzUV9tYXBfcmFzdGVycygpYC4gIE5vdGUgdGhlCnVzZSBvZiBuYW1lc3BhY2UgYWRkcmVzc2luZyBmb3IgdGhpcyBmdW5jdGlvbiByYXRoZXIgdGhhbiBsb2FkaW5nIHRoZSB3aG9sZSBgdGVzczNyYCBwYWNrYWdlCndpdGggdGhlIGBsaWJyYXJ5KClgIGNvbW1hbmQuCmBgYHtyfQpnZW5vc2NhcGVfYnJpY2sgPC0gdGVzczNyOjp0ZXNzM1FfbWFwX3Jhc3RlcnMoCiAgeCA9IFFfbWF0cml4LCAKICBjb29yZCA9IGxvbmdfbGF0X21hdHJpeCwgIAogIG1hcC5wb2x5Z29uID0gYnJlZWRpbmdfcmFuZ2UsCiAgd2luZG93ID0gZXh0ZW50KGJyZWVkaW5nX3JhbmdlKVsxOjRdLAogIHJlc29sdXRpb24gPSBjKDMwMCwzMDApLCAjIGlmIHlvdSB3YW50IG1vcmUgY2VsbHMgaW4geW91ciByYXN0ZXIsIHNldCBoaWdoZXIKICAjIHRoaXMgbmV4dCBsaW5lcyBuZWVkIHRvIHRvIGJlIGhlcmUsIGJ1dCBkb24ndCBkbyBtdWNoLi4uCiAgY29sLnBhbGV0dGUgPSB0ZXNzM3I6OkNyZWF0ZVBhbGV0dGUoY2x1c3Rlcl9jb2xvcnMsIGxlbmd0aChjbHVzdGVyX2NvbG9ycykpLCAKICBtZXRob2QgPSAibWFwLm1heCIsIAogIGludGVycG9sID0gdGVzczNyOjpGaWVsZHNLcmlnTW9kZWwoMTApLCAgCiAgbWFpbiA9ICJBbmNlc3RyeSBjb2VmZmljaWVudHMiLAogIHhsYWIgPSAiTG9uZ2l0dWRlIiwgCiAgeWxhYiA9ICJMYXRpdHVkZSIsIAogIGNleCA9IC40CikKCiMgYWZ0ZXIgdGhhdCwgd2UgbmVlZCB0byBhZGQgbmFtZXMgb2YgdGhlIGNsdXN0ZXJzIGJhY2sgb250byB0aGlzIHJhc3RlciBicmljawpuYW1lcyhnZW5vc2NhcGVfYnJpY2spIDwtIG5hbWVzKFFfdGliYmxlKVstMV0KYGBgCgpUaGF0IGdpdmVzIHVzIGEgcmFzdGVyIGJyaWNrIG9mIFEtdmFsdWVzIGFzc29jaWF0ZWQgd2l0aCBlYWNoIGNlbGwgaW4gdGhlIHJhc3RlciwgYnV0CnRob3NlIHZhbHVlcyBhcmUgbm90IGFsd2F5cyBjb25zdHJhaW5lZCBiZXR3ZWVuIDAgYW5kIDEsIHNvIHdlIGhhdmUgdG8gbWFzc2FnZSB0aGVtIAphIGxpdHRsZSBiaXQgaW4gdGhlIG5leHQgc2VjdGlvbi4KCiMjIyBTY2FsaW5nIGFuZCBjbGVhbmluZyB0aGUgZ2Vub3NjYXBlX2JyaWNrCgpGb3IgdGhpcyB3ZSB1c2UgdGhlIGZ1bmN0aW9uIGBnZW5vc2NhcGVSdG9vbHM6OnFwcm9iX3JhbmRvX3Jhc3RlcigpYC4gIFRoaXMgdGFrZXMgdGhlIHJhc3RlcgpicmljayB0aGF0IGNvbWVzIG91dCBvZiB0ZXNzM1FfbWFwX3Jhc3RlcnMoKSBhbmQgZG9lcyBzb21lIHJlc2NhbGluZyBhbmQgKG1heWJlKSBzb21lIHJhbmRvbSBzYW1wbGluZwp0byByZXR1cm4gYSByYXN0ZXIgb2YgY29sb3JzIHRoYXQgSSBob3BlIHdpbGwgZG8gYSByZWxpYWJsZSBqb2Igb2YgcmVwcmVzZW50aW5nIChpbiBzb21lIHdheSkgcHJlZGljdGVkCmFzc2lnbm1lbnQgYWNjdXJhY3kgb3ZlciBzcGFjZS4KU2VlIGA/Z2Vub3NjYXBlUnRvb2xzOjpxcHJvYl9yYW5kb19yYXN0ZXJgIHRvIGxlYXJuIGFib3V0IHRoZSBzY2FsaW5nIG9wdGlvbnMsIGV0Yy4gKEhvd2V2ZXIsIEkgYW0Kbm90IGNvbnZpbmNlZCB0aGF0IGFsbCBvZiB0aG9zZSBvcHRpb25zIGFyZSByZWxpYWJseSBlc3RpbWF0ZWQuKQoKVGhpcyB3aWxsIHNxdWFzaCB0aGUgcmFzdGVyIGJyaWNrIGRvd24gdG8gYSBzaW5nbGUKUkdCQSAoaS5lLiwgZm91ciBjaGFubmVscywgcmVkLCBncmVlbiwgYmx1ZSBhbmQgYWxwaGEpIHJhc3RlciBicmljay4KYGBge3J9Cmdlbm9zY2FwZV9yZ2JhIDwtIGdlbm9zY2FwZVJ0b29sczo6cXByb2JfcmFuZG9fcmFzdGVyKAogIFRSQiA9IGdlbm9zY2FwZV9icmljaywKICBjb2xzID0gY2x1c3Rlcl9jb2xvcnMsIAogIGFscGhhX3NjYWxlID0gMi4wLCAKICBhYnNfdGhyZXNoID0gMC4wLCAKICBhbHBoYV9leHAgPSAxLjU1LCAKICBhbHBoYV9jaG9wX21heCA9IDIzMAopCgojIGF0IHRoaXMgcG9pbnQsIHdlIG11c3QgYmUgZXhwbGljaXQgYWJvdXQgYWRkaW5nIGEgcHJvamVjdGlvbiB0byB0aGUgcmFzdGVyLgojIFRoaXMgYWRkcyB0aGUgaW5mbyBmb3IgYSByZWd1bGFyIGxhdC1sb25nIHByb2plY3Rpb24KY3JzKGdlbm9zY2FwZV9yZ2JhKSA8LSAiK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQgK25vX2RlZnMgK2VsbHBzPVdHUzg0ICt0b3dnczg0PTAsMCwwIgoKCgpgYGAKCldlIGNhbiBlYXNpbHkgcGxvdCB0aGlzIHdpdGggdGhlIGZ1bmN0aW9uIGBsYXllcl9zcGF0aWFsYCBmcm9tIHRoZSBgZ2dzcGF0aWFsYCBwYWNrYWdlOgpgYGB7cn0KZ2dwbG90KCkgKyAKICBnZ3NwYXRpYWw6OmxheWVyX3NwYXRpYWwoZ2Vub3NjYXBlX3JnYmEpICsgCiAgdGhlbWVfYncoKSArCiAgY29vcmRfc2YoKQpgYGAKCk5vdGUgdGhhdCBpZiB3ZSB3YW50ZWQgdG8gYWRkIHRoZSBhY3R1YWwgc2FtcGxpbmcgcG9pbnRzIHRvIHRoaXMgKHRoYXQgYXJlIGdpdmVuCmluIGBsb25nX2xhdF90aWJibGVgKSwgd2UgY2FuIHVzZSBnZ3NwYXRpYWwncyBgZ2VvbV9zcGF0aWFsX3BvaW50KClgIGZ1bmN0aW9uLgpgYGB7cn0KZ2dwbG90KCkgKyAKICBsYXllcl9zcGF0aWFsKGdlbm9zY2FwZV9yZ2JhKSArIAogIGdlb21fc3BhdGlhbF9wb2ludChkYXRhID0gbG9uZ19sYXRfdGliYmxlLCBtYXBwaW5nID0gYWVzKHggPSBMb25nLCB5ID0gTGF0KSkgKyAKICB0aGVtZV9idygpICsKICBjb29yZF9zZigpCmBgYAoKSXQgd291bGQgcHJvYmFibHkgYmUgYmV0dGVyIHRvIGppZ2dlciB0aG9zZSBwb2ludHMgYSBsaXR0bGUgYml0LiAgVGhhdCBjb3VsZCBiZSBkb25lIGJ5IG11dGF0aW5nCmVhY2ggb2YgdGhlbSBhIHJhbmRvbSBiaXQgYXdheS4KCiMjIENvYXN0bGluZXMsIENvdW50cmllcywgU3RhdGVzLCBhbmQgUHJvdmluY2VzCgpTaW5jZSB3ZSB3aWxsIGJlIHVzaW5nIHRoZSBOYXR1cmFsIEVhcnRoIERhdGEgU2V0IGZvciB0aGUgcmFzdGVyIGJhY2tncm91bmQgb2Ygb3VyIG1hcCwKd2Ugd2lsbCBhbHNvIHVzZSB0aGUgTmF0dXJhbCBFYXJ0aCBsaW5lcyBhbmQgcG9seWdvbnMuICBUaGUgTmF0dXJhbCBFYXJ0aCBkYXRhIHNldCBpcwphbiBhbWF6aW5nLCBvcGVuLXNvdXJjZSByZXNvdXJjZSBmb3IgbWFraW5nIGJlYXV0aWZ1bCBtYXBzLiAgQ2hlY2sgaXQgb3V0IGF0CltuYXR1cmFsZWFydGhkYXRhLmNvbV0oaHR0cHM6Ly93d3cubmF0dXJhbGVhcnRoZGF0YS5jb20vKS4gIAoKIyMjIERvd25sb2FkaW5nIHRoZSBzaGFwZWZpbGVzCgpGb3IgY29hc3RsaW5lcywgY291bnRyaWVzLCBhbmQgc3RhdGVzL3Byb3ZpbmNlcywgeW91IHdpbGwgbmVlZCB0byBkb3dubG9hZAp0aHJlZSBkaWZmZXJlbnQgc2hhcGUgZmlsZXMuICBXZSB3aWxsIGJlIHdvcmtpbmcgd2l0aCB0aGUgaGlnaGVzdCByZXNvbHV0aW9uCk5hdHVyYWwgRWFydGggZGF0YSBzZXRzIHdoaWNoIGFyZSB0aGUgYDEwbWAgdmVyc2lvbnMuICBEb3dubG9hZCBhbmQgdW56aXAgdGhlbQp0byBhIGRpcmVjdG9yeSB3aXRoaW4gdGhpcyBwcm9qZWN0IGNhbGxlZCBgbmVfc2hhcGVmaWxlc2AuICBZb3UgY2FuIGRvIHRoYXQKd2l0aCBSIGxpa2UgdGhpczoKYGBge3IsIGV2YWw9RkFMU0V9CmRpci5jcmVhdGUoIm5lX3NoYXBlZmlsZXMiLCBzaG93V2FybmluZ3MgPSBGQUxTRSkKCnRtcGZpbGUgPC0gdGVtcGZpbGUoKQpkb3dubG9hZGVyOjpkb3dubG9hZCgKICB1cmwgPSAiaHR0cHM6Ly93d3cubmF0dXJhbGVhcnRoZGF0YS5jb20vaHR0cC8vd3d3Lm5hdHVyYWxlYXJ0aGRhdGEuY29tL2Rvd25sb2FkLzEwbS9waHlzaWNhbC9uZV8xMG1fY29hc3RsaW5lLnppcCIsCiAgZGVzdCA9IHRtcGZpbGUKKQp1bnppcCh6aXBmaWxlID0gdG1wZmlsZSwgZXhkaXIgPSAibmVfc2hhcGVmaWxlcyIpCmBgYAoKV2UgZG8gdGhlIHNhbWUgZm9yIGNvdW50cnkgYm91bmRhcmllcywgYW5kIHRoZW4gc3RhdGUvcHJvdmluY2UgYm91bmRhcmllcywgdG9vLiAgTm90ZSwgaW4gYm90aCBjYXNlcwp3ZSBqdXN0IHdhbnQgdG8gZ2V0IHRoZSBib3VuZGFyeSBsaW5lcyB0aGF0IGFyZSBpbnRlcm5hbCB0byB0aGUgY29hc3QuICBPdGhlcndpc2UgeW91IGNhbiBlbmQgdXAgZ2V0dGluZwphIHRoaWNrIGxpbmUgYXJvdW5kIHRoZSBjb2FzdCB0aGF0IGlzIG5vdCBzbyBncmVhdC4uLgpgYGB7ciwgZXZhbD1GQUxTRX0KIyBjb3VudHJ5IGJvdW5kYXJpZXMKdG1wZmlsZSA8LSB0ZW1wZmlsZSgpCmRvd25sb2FkZXI6OmRvd25sb2FkKAogIHVybCA9ICJodHRwczovL3d3dy5uYXR1cmFsZWFydGhkYXRhLmNvbS9odHRwLy93d3cubmF0dXJhbGVhcnRoZGF0YS5jb20vZG93bmxvYWQvMTBtL2N1bHR1cmFsL25lXzEwbV9hZG1pbl8wX2JvdW5kYXJ5X2xpbmVzX2xhbmQuemlwIiwKICBkZXN0ID0gdG1wZmlsZQopCnVuemlwKHppcGZpbGUgPSB0bXBmaWxlLCBleGRpciA9ICJuZV9zaGFwZWZpbGVzIikKCgojIHN0YXRlIGFuZCBwcm92aW5jZSBib3VuZGFyaWVzCiMgY291bnRyeSBib3VuZGFyaWVzCnRtcGZpbGUgPC0gdGVtcGZpbGUoKQpkb3dubG9hZGVyOjpkb3dubG9hZCgKICB1cmwgPSAiaHR0cHM6Ly93d3cubmF0dXJhbGVhcnRoZGF0YS5jb20vaHR0cC8vd3d3Lm5hdHVyYWxlYXJ0aGRhdGEuY29tL2Rvd25sb2FkLzEwbS9jdWx0dXJhbC9uZV8xMG1fYWRtaW5fMV9zdGF0ZXNfcHJvdmluY2VzX2xpbmVzLnppcCIsCiAgZGVzdCA9IHRtcGZpbGUKKQp1bnppcCh6aXBmaWxlID0gdG1wZmlsZSwgZXhkaXIgPSAibmVfc2hhcGVmaWxlcyIpCmBgYAoKIyMjIFBsb3QgdGhlIENvYXN0bGluZXMgYW5kIGVzdGltYXRlIHdoZXJlIHdlIHdhbnQgdG8gY2xpcAoKRmlyc3QgcmVhZCBpdCBpbjoKYGBge3J9CmNvYXN0bGluZXMgPC0gc3RfcmVhZCgibmVfc2hhcGVmaWxlcy9uZV8xMG1fY29hc3RsaW5lLnNocCIpCmBgYApOb3csIGxldCdzIGp1c3QgY3JvcCBvdXQgdGhlIHBhcnQgdGhhdCB3ZSB3YW50LiAgVGhpcyBpcyBzb21ld2hhdCBrZXk6CnJpZ2h0IGhlcmUgd2Ugd2lsbCBkZWZpbmUgdGhlIGV4dGVudCBpbiBsYXQtbG9uZyBvZiB0aGUgcmVnaW9uIHRoYXQKd2Ugd2FudCB0byBwbG90OgpgYGB7cn0KIyBub3RlISBpdCBpcyBpbXBvcnRhbnQgdG8gcHV0IHRoZSBlbGVtZW50cyBvZiBkb21haW4gaW4gdGhpcyAKIyBvcmRlciwgYmVjYXVzZSB0aGUgZnVuY3Rpb24gcmFzdGVyOjpleHRlbnQoKSBpcyBleHBlY3RpbmcgdGhpbmcKIyB0byBiZSBpbiB0aGlzIG9yZGVyLCBhbmQgZG9lc24ndCBwYXJzZSB0aGUgbmFtZXMgb2YgdGhlIHZlY3RvcnMKIyB0aGUgd2F5IHNmOjpzdF9jcm9wKCkgZG9lcy4KZG9tYWluIDwtIGMoCiAgeG1pbiA9IC0xMzUsIAogIHhtYXggPSAtNjAsCiAgeW1pbiA9IDIyLAogIHltYXggPSA2MAopCgpjb2FzdF9jcm9wcGVkIDwtIHN0X2Nyb3AoY29hc3RsaW5lcywgZG9tYWluKQoKYGBgCgpUaGVuIHBsb3QgdGhlIGNyb3BwZWQgcGFydDoKYGBge3J9CmdncGxvdChjb2FzdF9jcm9wcGVkKSArIAogIGdlb21fc2YoKSArCiAgY29vcmRfc2YoKQpgYGAKCiMjIyBBbGwgdGhlIGxpbmVzLCBwbHVzIHRoZSBnZW5vc2NhcGUKCk9LLCB0aGF0IHdhcyBwcmV0dHkgcmVhc29uYWJsZS4gIE5vdyBjcm9wIGFsbCB0aGUgbGluZXMgdG8gYGRvbWFpbmAgYW5kIHBsb3QKdGhlbSwgYW5kIHB1dCB0aGUgZ2Vub3NjYXBlIG9uIHRvcCBvZiB0aGF0LCB0b28uCmBgYHtyfQpjb3VudHJpZXNfY3JvcHBlZCA8LSAgc3RfcmVhZCgibmVfc2hhcGVmaWxlcy9uZV8xMG1fYWRtaW5fMF9ib3VuZGFyeV9saW5lc19sYW5kLnNocCIpICU+JQogIHN0X2Nyb3AoZG9tYWluKQpzdGF0ZXNfY3JvcHBlZCA8LSBzdF9yZWFkKCJuZV9zaGFwZWZpbGVzL25lXzEwbV9hZG1pbl8xX3N0YXRlc19wcm92aW5jZXNfbGluZXMuc2hwIikgJT4lCiAgc3RfY3JvcChkb21haW4pCmBgYAoKTm93LCBwbG90IGl0IGFsbC4gIE5vdGljZSB3ZSBkbyBpdCBieSBqdXN0IGFkZGluZyBsYXllcnMuCmBgYHtyfQptYXBnIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBjb2FzdF9jcm9wcGVkKSArCiAgZ2VvbV9zZihkYXRhID0gY291bnRyaWVzX2Nyb3BwZWQsIGZpbGwgPSBOQSkgKwogIGdlb21fc2YoZGF0YSA9IHN0YXRlc19jcm9wcGVkLCBmaWxsID0gTkEpICsKICBnZ3NwYXRpYWw6OmxheWVyX3NwYXRpYWwoZ2Vub3NjYXBlX3JnYmEpICsKICB0aGVtZV9idygpIAoKIyBub3cgcGxvdCBpdCB1bmRlciBkZWZhdWx0IGxhdC1sb25nIHByb2plY3Rpb24KbWFwZyArCiAgY29vcmRfc2YoKSAKYGBgCgpUaGF0IGlzIGxvb2tpbmcgbGlrZSBpdCBzaG91bGQsIGJ1dCB0aGUgcHJvamVjdGlvbiBpcyBwcmV0dHkgdWdseS4gIEluIHRoZSBuZXh0IHNlY3Rpb24Kd2Ugd2lsbCBjb25zaWRlciBwcm9qZWN0aW9uLgoKIyMgUHJvamVjdGluZwoKVGhlIG5ldyB2ZXJzaW9uIG9mIGBnZ3Bsb3QyYCBtYWtlcyBpdCBzdXBlciBlYXN5IHRvIHByb2plY3QgZXZlcnl0aGluZyBvbiB0aGUgZmx5CmJ5IHBhc3NpbmcgdGhlIHByb2plY3Rpb24gdG8gYGNvb3JkX3NmKClgLiAgIEZvciB0aGluZ3MgaW4gTm9ydGggQW1lcmljYSwKaXQgc2VlbXMgdGhhdCBhIExhbWJlcnQgY29uaWMgcHJvamVjdGlvbiBkb2VzIGEgbmljZSBqb2Igb2Yga2VlcGluZyBCcml0aXNoIENvbHVtYmlhCmFuZCBBbGFza2EgZnJvbSBsb29raW5nIHdheSB0b28gYmlnLiBXZSBjYW4gZGVmaW5lIHN1Y2ggYSBwcm9qZWN0aW9uIHdpdGggYSBzdHJpbmcsIGxpa2UKdGhpczoKYGBge3J9CmxhbXByb2ogPC0gIitwcm9qPWxjYyArbGF0XzE9MjAgK2xhdF8yPTYwICtsYXRfMD00MCArbG9uXzA9LTEwMCAreF8wPTAgK3lfMD0wICtlbGxwcz1HUlM4MCArZGF0dW09TkFEODMgK3VuaXRzPW0gK25vX2RlZnMiCmBgYAoKTm93LCBsZXQncyBzZWUgaG93IG91ciBkZXZlbG9waW5nIG1hcCBsb29rcyB1bmRlciBzdWNoIGEgcHJvamVjdGlvbi4gIFdlIGp1c3QgZGVmaW5lIGBsYW1wcm9qYCBhcyAKYSBjb29yZGluYXRlIHJlZmVyZW5jZSBzeXN0ZW0gYW5kIHBhc3MgaXQgaW4gdG8gYGNvb3JkX3NmKClgOgpgYGB7cn0KbWFwZyArCiAgY29vcmRfc2YoY3JzID0gc3RfY3JzKGxhbXByb2opKQpgYGAKCkkgd291bGQgY2FsbCB0aGF0IGRlY2lkZWRseSBiZXR0ZXIuCgojIyBUaGUgcHJldHR5LW1hcCBiYWNrZ3JvdW5kCgpOb3csIGFsbCB0aGF0IHJlbWFpbnMgaXMgdG8gcHV0IGFsbCBvZiB0aGlzIG9uIHRvcCBvZiBhIG5pY2VseSB0aW50ZWQgbWFwIHRoYXQKc2hvd3MgbGFuZGZvcm1zIGFuZCB0aGluZ3MuICBOb3RlIHRoYXQgb25jZSB3ZSBoYXZlIHN1Y2ggYSBsYXllciB1bmRlciB0aGUgcmVzdCBvZiB0aGlzCnN0dWZmLCB3ZSB3aWxsIHByb2JhYmx5IG9taXQgdGhlIGNvYXN0bGluZSBsYXllciwgc2luY2UgaXQgZ2V0cyBhIGxpdHRsZSBkYXJrIHdoZXJlIHRoZQpjb2FzdGxpbmUgaXMgaGlnaGx5IGRpc3NlY3RlZC4KCiMjIyBEb3dubG9hZGluZyBOYXR1cmFsIEVhcnRoIHJhc3RlcnMKCkkgYW0gZ29pbmcgdG8gcmVjb21tZWQgdGhhdCB5b3UgZ2V0IHRoZSBoeXBzb21ldHJpY2FsbHkgdGludGVkIE5hdHVyYWwgRWFydGggbWFwIHdpdGgKd2F0ZXIgYm9kaWVzIGFuZCByaXZlcnMgb24gaXQsIGFuZCB5b3UgbWlnaHQgYXMgd2VsbCBnZXQgdGhlIG9uZSB0aGF0IGhhcyBzb21lIG9jZWFuIApiYXNpbiBjb2xvcmluZyB0b28uICBXZSB3aWxsIHB1dCBpdCBpbnRvIGEgZGlyZWN0b3J5IGluIHRoZSBwcm9qZWN0IGNhbGxlZApgbmVfcmFzdGVyc2AuICBOb3RlLCB0aGlzIGV4cGFuZHMgdG8gYWJvdXQgaGFsZiBhIGdpZyBvZiBkYXRhLgpgYGB7cn0KZGlyLmNyZWF0ZSgibmVfcmFzdGVycyIsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQoKdG1wZmlsZSA8LSB0ZW1wZmlsZSgpCmRvd25sb2FkZXI6OmRvd25sb2FkKAogIHVybCA9ICJodHRwczovL3d3dy5uYXR1cmFsZWFydGhkYXRhLmNvbS9odHRwLy93d3cubmF0dXJhbGVhcnRoZGF0YS5jb20vZG93bmxvYWQvMTBtL3Jhc3Rlci9IWVBfSFJfU1JfT0JfRFIuemlwIiwKICBkZXN0ID0gdG1wZmlsZQopCnVuemlwKHppcGZpbGUgPSB0bXBmaWxlLCBleGRpciA9ICJuZV9yYXN0ZXJzIikKYGBgCgoKIyMjIFJlYWQgaW4gdGhlIHJhc3RlciBhbmQgY3JvcCBpdAoKUmVhZGluZyBhIGxhcmdlIHJhc3RlciBpbiB3aXRoIHRoZSByYXN0ZXIgcGFja2FnZSBkb2VzIG5vdCB0YWtlIHVwIG11Y2gKbWVtb3J5LCBiZWNhdXNlIGl0IGxlYXZlcyBpdCBvbiBkaXNrLiAgU28gd2Ugd2lsbCBvcGVuIGEgY29ubmVjdGlvbiB0bwp0aGUgYmlnLCBtYXNzaXZlIHJhc3RlciB1c2luZyB0aGUgYGJyaWNrYCBmdW5jdGlvbiBmcm9tIHRoZSBgcmFzdGVyYCBwYWNrYWdlCmFuZCB0aGVuIGNyb3AgaXQ6CmBgYHtyfQpoeXBzbyA8LSBicmljaygibmVfcmFzdGVycy9IWVBfSFJfU1JfT0JfRFIvSFlQX0hSX1NSX09CX0RSLnRpZiIpCmh5cHNvX2Nyb3BwZWQgPC0gY3JvcChoeXBzbywgZXh0ZW50KGRvbWFpbikpIApgYGAKCk5vdywgd2UganVzdCBhZGQgYGh5cHNvX2Nyb3BwZWRgIGluIHdpdGggYGdnc3BhdGlhbDo6c3BhdGlhbF9sYXllcigpYCBiZWxvdyBhbGwgb2Ygb3VyCm90aGVyIGxheWVycyAoZHJvcHBpbmcgdGhlIGNvYXN0bGluZXMpLCBhbmQgd2UgbWlnaHQgbWFrZSBjb3VudHJ5IGFuZCBzdGF0ZSBsaW5lcyB0aGlubmVyIChhbmQgZGlmZmVyZW50IHNpemVzKToKYGBge3J9CmJpZ19vbmUgPC0gZ2dwbG90KCkgKwogIGdnc3BhdGlhbDo6bGF5ZXJfc3BhdGlhbChoeXBzb19jcm9wcGVkKSArCiAgZ2VvbV9zZihkYXRhID0gY291bnRyaWVzX2Nyb3BwZWQsIGZpbGwgPSBOQSwgc2l6ZSA9IDAuMTUpICsKICBnZW9tX3NmKGRhdGEgPSBzdGF0ZXNfY3JvcHBlZCwgZmlsbCA9IE5BLCBzaXplID0gMC4xKSArCiAgZ2dzcGF0aWFsOjpsYXllcl9zcGF0aWFsKGdlbm9zY2FwZV9yZ2JhKSArCiAgdGhlbWVfYncoKSArIAogIGNvb3JkX3NmKGNycyA9IHN0X2NycyhsYW1wcm9qKSkKCmJpZ19vbmUKYGBgCgpTZWVpbmcgdGhhdCBzb3J0IG9mIHNtYWxsIG9uIHRoZSBzY3JlZW4gZG9lcyBub3QgcmVhbGx5IGRvIGp1c3RpY2UgdG8gaXQuICBZb3UgY2FuIGBnZ3NhdmVgIGl0CmluIHdoYXRldmVyIHNpemUgaXMgZGVzaXJlZC4gIEZvciB3aGF0ZXZlciB0aGUgZmluYWwgcHJvZHVjdCBpcyBnb2luZyB0byBiZSwgeW91IHdpbGwKd2FudCB0byBtZXNzIHdpdGggdGhlIGxpbmUgdGhpY2tuZXNzIG9uIHRob3NlIGNvdW50cnkgYW5kIHN0YXRlIGJvcmRlcnMuLi4KCiMjIENsaXBwaW5nIGEgcmVjdGFuZ2xlIG91dCBvZiB0aGF0CgpXaGlsZSB0aGF0IGxvb2tzIG5pY2UsIG1vc3QgcGVvcGxlIG1pZ2h0IHByZWZlciB0byBoYXZlIHRoZQpiYWNrZ3JvdW5kIHBhcnQgdGhlcmUgaW4gYSByZWN0YW5nbGUsIHJhdGhlciB0aGFuIGEgY29uaWMgc3VyZmFjZS4gIFdlIGNhbgpkbyB0aGF0IGJ5IHNldHRpbmcgdGhlIHhsaW0gYW5kIHlsaW0gdG8gYGNvb3JkX3NmKClgLiAgQnV0LCBub3RlIHRoYXQKd2UgaGF2ZSB0byBkbyB0aGF0IGluIHRlcm1zIG9mIHRoZSB1bml0cyB0aGF0IHRoZSBMYW1iZXJ0IHByb2plY3Rpb24gaXMgcmVmZXJyaW5nIHRvLApub3QganVzdCBpbiBzaW1wbGUgbGF0LWxvbmcgY29vcmRpbmF0ZXMgKGkuZS4gd2UgaGF2ZSB0byBwcm9qZWN0IHRob3NlIHRvbyEpCgpTbywgaW1hZ2luZSB0aGF0IHdlIHdhbnQgdG8gY2hvcCBvdXQgYSByZWN0YW5nbGUgZnJvbSBIYWlkYSBHd2FpLCBvZmYgdGhlIGNvYXN0IG9mCkJyaXRpc2ggQ29sdW1iaWEsIHN0cmFpZ2h0IGRvd24gYW5kIHN0cmFpZ2h0IGFjcm9zcyB0byB0aGUgcmlnaHQgKG9uIHRoZSBpbWFnZSkuCkFuZCB3ZSB3b3VsZCB3YW50IHRoZSBib3R0b20gYW5kIHJpZ2h0IHNpZGVzIGRldGVybWluZWQgYnkgYSBwb2ludCBpbiB0aGUgYm90dG9tCnJpZ2h0IHRoYXQgaGFzIHRoZSB5LXZhbHVlIG9mIHRoZSB0aXAgb2YgRmxvcmlkYSwgYW5kIGFuIHgtdmFsdWUgd2hpY2ggaXMgZXNzZW50aWFsbHkKYXQgU3QuIEpvaG4sIE5ldyBCcnVuc3dpY2suICBUaGVuLCB3ZSBuZWVkIHRvIGRldGVybWluZSB0aGUgeCBhbmQgeSBjb29yZGluYXRlcyBvZiB0aG9zZQpwb2ludHMgdW5kZXIgb3VyIExhbWJlcnQgY29uZm9ybWFsIGNvbmljIHByb2plY3Rpb24uICBXZSBkbyB0aGF0IGJ5IG1ha2luZyBhIHNpbXBsZSBmZWF0dXJlcwp0aWJibGUgb2YgdGhlIGxhdC1sb25ncyBmb3IgdGhvc2UgcG9pbnRzIGFuZCB0aGVuIHByb2plY3RpbmcgaXQuCgpgYGB7cn0KIyBIZXJlIGFyZSBsYXQgbG9uZ3MgZm9yIHRob3NlIHBvaW50cwpwdHMgPC0gdHJpYmJsZSgKICB+cGxhY2UsIH5sb25nLCB+bGF0LAogICJIYWlkYUd3YWkiLCAtMTMyLjMxNDM1MjYsIDUzLjcyOTgwNTAsCiAgIkZsb3JpZGFUaXAiLCAtODAuODAzMjIzNywgMjUuMDkwOTc4MCwgCiAgIlN0LkpvaG5zIiwgIC02NS45NDM0NTMsIDQ1LjI5MTIyMgopCgojIGhlcmUgd2UgdHVybiB0aGF0IGludG8gYW4gc2Ygb2JqZWN0CnB0c19zZiA8LSBzdF9hc19zZihwdHMsIGNvb3JkcyA9IGMoImxvbmciLCAibGF0IiksIAogICAgICAgICAgICAgICAgICAgY3JzID0gNDMyNikgICMgNDMyNiBpcyBzaG9ydGhhbmQgZm9yICIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCArbm9fZGVmcyArZWxscHM9V0dTODQgK3Rvd2dzODQ9MCwwLDAiCgojIGFuZCBub3cgd2UgdHJhbnNmb3JtIGl0IHRvIGxhbWJlcnQKcHRzX2xhbWJlcnQgPC0gc3RfdHJhbnNmb3JtKHB0c19zZiwgY3JzID0gbGFtcHJvaikKCiMgd2hlbiB3ZSBwcmludCBpdCBpbiBhIG5vdGVib29rLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIGdlb21ldHJ5IGlzIGEgbGlzdAojIGNvbHVtbiwgYnV0IGNhbid0IHNlZSB0aGUgdmFsdWVzOgpwdHNfbGFtYmVydApgYGAKClNvLCBmb3IgeW91IHRvIGJlIGFibGUgdG8gc2VlIHRoZSB2YWx1ZXMgd2UgY2FuIGp1c3QgcHJpbnQgaXQgbGlrZSB0aGlzOgpgYGB7cn0KIyBqdXN0IG5vdGUgdGhlIHZhbHVlczoKc3RfYXNfdGV4dChwdHNfbGFtYmVydCRnZW9tZXRyeSkKYGBgCgpUaGF0IG1lYW5zIHdlIHdhbnQgeGxpbSA9IGMoLTIwMTAxMTQsIDI0NTI4NzcpIGFuZCB5bGltID0gYygtMTM2NjkxMSwgMTgyMTg2OSkuCkxldCdzIHRyeSB0aGF0OgpgYGB7cn0KcmVjdGFuZ2xlZCA8LSBnZ3Bsb3QoKSArCiAgZ2dzcGF0aWFsOjpsYXllcl9zcGF0aWFsKGh5cHNvX2Nyb3BwZWQpICsKICBnZW9tX3NmKGRhdGEgPSBjb3VudHJpZXNfY3JvcHBlZCwgZmlsbCA9IE5BLCBzaXplID0gMC4xNSkgKwogIGdlb21fc2YoZGF0YSA9IHN0YXRlc19jcm9wcGVkLCBmaWxsID0gTkEsIHNpemUgPSAwLjEpICsKICBnZ3NwYXRpYWw6OmxheWVyX3NwYXRpYWwoZ2Vub3NjYXBlX3JnYmEpICsKICB0aGVtZV9idygpICsgCiAgY29vcmRfc2YoCiAgICBjcnMgPSBzdF9jcnMobGFtcHJvaiksIAogICAgeGxpbSA9IGMoLTIwMTAxMTQsIDI0NTI4NzcpLCAKICAgIHlsaW0gPSBjKC0xMzY2OTExLCAxODIxODY5KSwKICAgIGV4cGFuZCA9IEZBTFNFKSAgIyBleHBhbmQgPSBGQUxTRSBtZWFucyBwdXQgdGhlIGF4ZXMgcmlnaHQgYXQgdGhlIHhsaW0gYW5kIHlsaW0KCnJlY3RhbmdsZWQKYGBgCgpPa2F5LCB0aGF0IGRvZXMgaXQuICBJbiByZWFsaXR5LCB5b3Ugd291bGQgcHJvYmFibHkgd2FudCB0byBleHBhbmQgdGhlIG9yaWdpbmFsIGBkb21haW5gCnRvIGJlIGEgYmlnZ2VyIGNodW5rIG9mIHRoZSBlYXJ0aCwgc28gdGhhdCB3aGVuIHlvdSBjdXQgc3R1ZmYgb2ZmIHRoZXJlIHdhcyBtb3JlIGxlZnQuLi4KCgojIyBBIGZpbmFsIG5vdGUKCklmIHlvdSB3YW50IHRvIGFkZCBtb3JlIGVsZW1lbnRzIHRvIHRoZSBmaWd1cmUgeW91IGNhbiBkbyBzbyBieSBnaXZpbmcgdGhlbQphIGxhdC1sb25nIGFuZCBtYWtpbmcgYSBzaW1wbGUgZmVhdHVyZXMgdGliYmxlIG91dCBvZiBpdCBhbmQgdGhlbiBwdXR0aW5nIHRoZW0KdGhlcmUgdXNpbmcgZ2VvbV9zZigpLiAgSXQgYWxsIGZpdHMgbmljZWx5CmludG8gdGhlIGdncGxvdCBmcmFtZXdvcmsuCgojIyBTZXNzaW9uIEluZm8KCmBgYHtyfQpzZXNzaW9uaW5mbzo6c2Vzc2lvbl9pbmZvKCkKYGBg