Time series production is a staple data type in the oil and gas industry. Geologist and engineers use these time series data that relate the volumes of fluids produced over time to reservoir performance or reservoir characteristics (e.g., primary or secondary porostiy). However, time series data is difficult to display in a 2D map. This project was an attempt to use unsupervised classification of the production curves through dynamic time warping to cluster/classify them and more easily map production.

This notebook contains a series of scripts used to tidy/clean production data for Alberta, filter data for specific formations/fields, visualize production curves, and then cluster/classify production curves. In the future this could be turned into a single script of functions or a ShinyApp. For now it will live here, like this.

Libraries used for this script are

#Data handling
library(dplyr)
library(readr)
library(tidyr)
library(RANN)

#Time series stuff
library(dtwclust)

#Plotting and geospatial stuff
library(ggplot2)
library(sf)
library(leaflet)

The first chunk below takes many related files and processes them a single tidied file for analysis. This takes input .csv in their raw form and manipulate them in one script to create the final data that will be used in the data analysis.

Inputs: CONTROL.TXT will be filtered to the key columns PRDHIST.TXT will be reformatted into a reasonable format that can be handled. Accessed at: http://www1.aer.ca/ProductCatalogue/271.html List_of_Horizontal_Wells_data.csv - information regarding horizontally drilled wells. Accessed at: https://www.aer.ca/providing-information/data-and-reports/activity-and-data/horizontal-well-list Well bottom hole location. Accessed at: https://www.aer.ca/providing-information/data-and-reports/statistical-reports/st37 . The text verion of the file is most useful but could be converted from shapefile to .csv

Outputs: prod_total - a tidied dataframe (quite large…watch out) that contains the volume of produced fluids. It is a long-format dataframe that contains individual rows for each year for each well.

Notes: Information regarding the structure of the CONTROL.TXT and PRDHIST.TXT files can be found in the following word document. https://static.aer.ca/prd/2020-09/well-production-data-all-alberta-layout-file.docx It is a wild format…

#1.1 Import the production well control document from the CONTROL.TXT
prodcontrol <- read.delim("~/R_data/ProductionMapping/Data/RawData/Production/CONTROL.TXT", header=FALSE, stringsAsFactors = FALSE)

#1.2 Select and rename desired columns, use regex gsub to remove special characters and spaces 
prodcontrol <- prodcontrol %>% 
  select(WellIdentifier=V2, WellName=V3, PoolCode=V12, PoolName=V13)
prodcontrol$PoolName <- gsub("[^A-Za-z0-9,;._-]","",prodcontrol$PoolName)

#2.0 Dealing with the production history
#2.1 Import the raw production history of Alberta from the big .txt and set up data types of the columns we want. 
proddata <- read_tsv("~/R_data/ProductionMapping/Data/RawData/Production/PRDHIST.TXT",
    col_names=FALSE,
    progress=TRUE,
    col_types = cols(
        X2 = "c", #X2 = WellIdentifier (rename to WellIdentifier)
        X3 = "i", #X3 = Fluid Year (rename to FluidYear)
        X54 = "i", #X54 = Fluid Code 1
        X55 = "d", #X55 = Annual Fluid Volume 1
        X70 = "i", #X70 = Fluid Code 2
        X71 = "d", #X71 = Annual Fluid Volume 2
        X86 = "i", #X86 = Fluid Code 3
        X87 = "d", #X87 = Annual Fluid Volume 3
        X102 = "i", #X102 = Fluid Code 4
        X103 = "d", #X103 = Annual Fluid Volume 4
        X118 = "i", #X118 = Fluid Code 5
        X119 = "d", #X119 = Annual Fluid Volume 5
        X134 = "i", #X134 = Fluid Code 6
        X135 = "d", #X135 = Annual Fluid Volume 6
        X150 = "i", #V150 = Fluid Code 7
        X151 = "d", #V151 = Annual Fluid Volume 7
        X166 = "i", #X166 = Fluid Code 8
        X167 = "d" #X167 = Annual Fluid Volume 8
        )
    )

#2.2 Drop the data we don't want, rename the first two columns, then add the new fluid columns. The order of these columns is specifically the same as the order of the fluid codes in the vector (FluidVector) below.
proddata <- proddata %>%
  select(X2,X3,X54,X55,X70,X71,X86,X87,X102,X103,X118,X119,X134,X135,X150,X151,X166,X167) %>% #These are the columns that we want
  rename(WellIdentifier=X2, FluidYear=X3) %>%  #Rename the ones that we will ultimately keep
  mutate(GasProd=-99.99, WaterProd=-99.99, LiquidGasProd=-99.99, OilProd=-99.99, PropaneProd=-99.99, ButaneProd=-99.99) #Make new numeric columns with distinct null values

#2.3 Organize the fluid data which is currently scattered through 8 columns
  #The fluids we are interested have the following Fluid Codes
    #2 = Gas
    #6 = Water
    #16 = Liquid Petroleum Gas
    #51 = Crude oil/bitumen
    #53 = Propane
    #54 = Butane

#2.3.1 Set up the fluid vector for incrementing through the fluids
FluidVector <- c(2,6,16,51,53,54)
NumFluids <- length(FluidVector)
PlaceVector <- c(1:NumFluids)

#2.3.2 For loop for each pair of production columns (each Fluid Code/Annual Fluid Volume pair)
for (ProdColumn in seq(3,17, by=2)){
  
  #2.3.3 For loop for each fluid type in the search vector (Fluid Vector), we will increment through the PlaceVector (1:NumFluids) but use that to go through each fluids
    for (i in PlaceVector) {
        
        print(paste("Prod Column=",ProdColumn,". FluidNumber=",FluidVector[i],sep=""))
        
        #2.3.4 We are going to use which statements to grab just the fluids we want and put them in the correct recently added column (e.g., GasProd, WaterProd)
        FluidObservations <- which(proddata[,ProdColumn]==FluidVector[i])
        
        #Use the vector to copy those values into the new fluid volumes
        proddata[FluidObservations,18+i] <- proddata[FluidObservations,ProdColumn+1]
        
    } #increment to the next produced fluid
} #increment to the next column

print("Production data sorted")

#2.4 Keep only the data that we really want (WellID, production year, production volumes for all fluids)
proddata <- proddata %>%
  select(WellIdentifier,FluidYear,GasProd,WaterProd,LiquidGasProd,OilProd,PropaneProd,ButaneProd)
proddata[proddata==-99.99]<-NA #Remove pesky -99.99 null values.

#2.5 Create prod_total by joining the control file (filtered up above - prodcontrol_filter) to the proddata_filtered
prod_total <- left_join(proddata, prodcontrol, by="WellIdentifier")

#2.6 Reformat the Well Identifier to a normal UWI
prod_total <- prod_total %>%
  mutate(WellIdentifier=paste(substr(WellIdentifier,11,12),"/",substr(WellIdentifier,9,10),"-",substr(WellIdentifier,7,8),"-",substr(WellIdentifier,1,3),"-",substr(WellIdentifier,5,6),"W",substr(WellIdentifier,4,4),"/",substr(WellIdentifier,13,13),sep="")) %>%
  rename(UWI = WellIdentifier)

#3.0 Format information regarding horizontal wells (List_of_Horizontal_Wells_data.csv)
horizontal_well <- read.csv("~/R_data/ProductionMapping/Data/RawData/WellLicense/List_of_Horizontal_Wells_data.csv")
horizontal_well <- horizontal_well %>%
  select(UWI=Well.Uwi.Formatted) %>%
  mutate(Horizontal=1)

#3.1 Join horizontal well information to 
prod_total <- left_join(x=prod_total, y=horizontal_well)

#4.0 Borehole locations. This is the AER "WellLocations_Bottom.csv" but I have converted/added UTM locations to it through a spreadsheet https://giscrack.com/download-excel-template-convert-geographic-coordinates-utm/ - this might have to be changed...
welllocations <- read.csv("~/R_data/ProductionMapping/Data/RawData/WellLocation/WellLocations_Bottom.csv",stringsAsFactors = FALSE)

#4.1 Rename WellIdentifier to UWI, drop columns we don't need
welllocations <- welllocations %>% 
  select(UWI, BH_Long, BH_Lat, BH_Easting, BH_Northing)

#4.2 Join location to total production 
prod_total <- left_join(x=prod_total, y=welllocations)

#5.0 drop all unnesssary data and values
rm(horizontal_well, prodcontrol, proddata, welllocations, FluidObservations, FluidVector, i, NumFluids, PlaceVector,ProdColumn)

This chunk takes the “prod_total” dataframe and creates a dataset specific to a single interval (e.g., Viking) or pool (Viking A). This is done by using a PoolSearchTerm and grepl filter on the PoolName column. To select the PoolSearchTerm look at unique values within the prod_total\(PoolName: unique(prod_total\)PoolName).

Inputs: pool_prod - the total production for tall of Alberta (df) PoolSearchTerm - the name of the interval or pool that we want to filter the data for (string)

Outputs: pool_prod - a dataframe of the production data (by year) pool_prod_vis - a dataframe that includes the

#INPUTS
#This is a string term that can be used below to subset the prod_total (e.g., "SAWTOOTH" or "SAWTOOTH WWW")
PoolSearchTerm <- "VIKING"


#--------------------------------------------------------------------------------
#Filter prod_total by a partial name (e.g., "SAWTOOTH" or "SAWTOOTH WWW")
pool_prod <- prod_total %>% 
  filter(grepl(PoolSearchTerm,PoolName)) %>%
  group_by(UWI) %>%
  mutate(seqyear=1:length(FluidYear)) %>%
  ungroup()

#Create visualization dataframe for mapping
pool_prod_vis <- pool_prod %>% 
  group_by(UWI) %>% 
  summarize(YearMin = min(FluidYear), 
            YearMax=max(FluidYear), 
            OilProd=sum(OilProd), 
            GasProd=sum(GasProd), 
            LiquidGasProd=sum(LiquidGasProd),
            BH_Long=min(BH_Long), 
            BH_Lat=min(BH_Lat)) %>% 
  mutate(Rad_oil=((OilProd-min(OilProd, na.rm=TRUE)))/(max(OilProd, na.rm=TRUE)-min(OilProd, na.rm=TRUE))*1000,
         Rad_oil = ifelse(is.na(Rad_oil), min(Rad_oil, na.rm=TRUE)/2+1,Rad_oil),
         Rad_gas=((GasProd-min(GasProd, na.rm=TRUE)))/(max(GasProd, na.rm=TRUE)-min(GasProd, na.rm=TRUE))*1000,
         Rad_gas = ifelse(is.na(Rad_gas), min(Rad_gas, na.rm=TRUE)/2,Rad_gas),
         Rad_lgas=((LiquidGasProd-min(LiquidGasProd, na.rm=TRUE)))/(max(LiquidGasProd, na.rm=TRUE)-min(LiquidGasProd, na.rm=TRUE))*1000,
         Rad_lgas = ifelse(is.na(Rad_lgas), min(Rad_lgas, na.rm=TRUE)/2,Rad_lgas)
         )

This section creates a leaflet map out of “pool_prod_vis” dataframe. Note the “radius=” term is default pointing a radius value for oil production there is also “Rad_gas” or “Rad_lgas”

leaflet() %>% 
  addTiles() %>% 
  addCircles(data=pool_prod_vis, 
             lng=~BH_Long, 
             lat=~BH_Lat, 
             radius=~Rad_oil, 
             opacity = 0.5, 
             popup=paste("UWI:",pool_prod_vis$UWI,"<br/>","Produced from:", pool_prod_vis$YearMin, " to ", pool_prod_vis$YearMax, "<br/>", "Oil produced: ", pool_prod_vis$OilProd, "m^3", "<br/>", "Gas produced: ", pool_prod_vis$GasProd, "1000 m^3", "<br/>", "Liquid gas produced: ", pool_prod_vis$LiquidGasProd, "m^3"))
LS0tDQp0aXRsZTogIlByb2R1Y3Rpb24gbWFwcGluZyB1c2luZyB1bnN1cGVydmlzZWQgY2xhc3NpZmljYXRpb24iDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpUaW1lIHNlcmllcyBwcm9kdWN0aW9uIGlzIGEgc3RhcGxlIGRhdGEgdHlwZSBpbiB0aGUgb2lsIGFuZCBnYXMgaW5kdXN0cnkuIEdlb2xvZ2lzdCBhbmQgZW5naW5lZXJzIHVzZSB0aGVzZSB0aW1lIHNlcmllcyBkYXRhIHRoYXQgcmVsYXRlIHRoZSB2b2x1bWVzIG9mIGZsdWlkcyBwcm9kdWNlZCBvdmVyIHRpbWUgdG8gcmVzZXJ2b2lyIHBlcmZvcm1hbmNlIG9yIHJlc2Vydm9pciBjaGFyYWN0ZXJpc3RpY3MgKGUuZy4sIHByaW1hcnkgb3Igc2Vjb25kYXJ5IHBvcm9zdGl5KS4gSG93ZXZlciwgdGltZSBzZXJpZXMgZGF0YSBpcyBkaWZmaWN1bHQgdG8gZGlzcGxheSBpbiBhIDJEIG1hcC4gVGhpcyBwcm9qZWN0IHdhcyBhbiBhdHRlbXB0IHRvIHVzZSB1bnN1cGVydmlzZWQgY2xhc3NpZmljYXRpb24gb2YgdGhlIHByb2R1Y3Rpb24gY3VydmVzIHRocm91Z2ggZHluYW1pYyB0aW1lIHdhcnBpbmcgdG8gY2x1c3Rlci9jbGFzc2lmeSB0aGVtIGFuZCBtb3JlIGVhc2lseSBtYXAgcHJvZHVjdGlvbi4NCg0KVGhpcyBub3RlYm9vayBjb250YWlucyBhIHNlcmllcyBvZiBzY3JpcHRzIHVzZWQgdG8gdGlkeS9jbGVhbiBwcm9kdWN0aW9uIGRhdGEgZm9yIEFsYmVydGEsIGZpbHRlciBkYXRhIGZvciBzcGVjaWZpYyBmb3JtYXRpb25zL2ZpZWxkcywgdmlzdWFsaXplIHByb2R1Y3Rpb24gY3VydmVzLCBhbmQgdGhlbiBjbHVzdGVyL2NsYXNzaWZ5IHByb2R1Y3Rpb24gY3VydmVzLiBJbiB0aGUgZnV0dXJlIHRoaXMgY291bGQgYmUgdHVybmVkIGludG8gYSBzaW5nbGUgc2NyaXB0IG9mIGZ1bmN0aW9ucyBvciBhIFNoaW55QXBwLiBGb3Igbm93IGl0IHdpbGwgbGl2ZSBoZXJlLCBsaWtlIHRoaXMuIA0KDQoNCkxpYnJhcmllcyB1c2VkIGZvciB0aGlzIHNjcmlwdCBhcmUNCmBgYHtyfQ0KI0RhdGEgaGFuZGxpbmcNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoUkFOTikNCg0KI1RpbWUgc2VyaWVzIHN0dWZmDQpsaWJyYXJ5KGR0d2NsdXN0KQ0KDQojUGxvdHRpbmcgYW5kIGdlb3NwYXRpYWwgc3R1ZmYNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KGxlYWZsZXQpDQpgYGANCg0KDQpUaGUgZmlyc3QgY2h1bmsgYmVsb3cgdGFrZXMgbWFueSByZWxhdGVkIGZpbGVzIGFuZCBwcm9jZXNzZXMgdGhlbSBhIHNpbmdsZSB0aWRpZWQgZmlsZSBmb3IgYW5hbHlzaXMuIFRoaXMgdGFrZXMgaW5wdXQgLmNzdiBpbiB0aGVpciByYXcgZm9ybSBhbmQgbWFuaXB1bGF0ZSB0aGVtIGluIG9uZSBzY3JpcHQgdG8gY3JlYXRlIHRoZSBmaW5hbCBkYXRhIHRoYXQgd2lsbCBiZSB1c2VkIGluIHRoZSBkYXRhIGFuYWx5c2lzLg0KDQpJbnB1dHM6DQogIENPTlRST0wuVFhUIHdpbGwgYmUgZmlsdGVyZWQgdG8gdGhlIGtleSBjb2x1bW5zIA0KICBQUkRISVNULlRYVCB3aWxsIGJlIHJlZm9ybWF0dGVkIGludG8gYSByZWFzb25hYmxlIGZvcm1hdCB0aGF0IGNhbiBiZSBoYW5kbGVkLiBBY2Nlc3NlZCBhdDogaHR0cDovL3d3dzEuYWVyLmNhL1Byb2R1Y3RDYXRhbG9ndWUvMjcxLmh0bWwNCiAgTGlzdF9vZl9Ib3Jpem9udGFsX1dlbGxzX2RhdGEuY3N2IC0gaW5mb3JtYXRpb24gcmVnYXJkaW5nIGhvcml6b250YWxseSBkcmlsbGVkIHdlbGxzLiBBY2Nlc3NlZCBhdDogaHR0cHM6Ly93d3cuYWVyLmNhL3Byb3ZpZGluZy1pbmZvcm1hdGlvbi9kYXRhLWFuZC1yZXBvcnRzL2FjdGl2aXR5LWFuZC1kYXRhL2hvcml6b250YWwtd2VsbC1saXN0DQogIFdlbGwgYm90dG9tIGhvbGUgbG9jYXRpb24uIEFjY2Vzc2VkIGF0OiBodHRwczovL3d3dy5hZXIuY2EvcHJvdmlkaW5nLWluZm9ybWF0aW9uL2RhdGEtYW5kLXJlcG9ydHMvc3RhdGlzdGljYWwtcmVwb3J0cy9zdDM3IC4gVGhlIHRleHQgdmVyaW9uIG9mIHRoZSBmaWxlIGlzIG1vc3QgdXNlZnVsIGJ1dCBjb3VsZCBiZSBjb252ZXJ0ZWQgZnJvbSBzaGFwZWZpbGUgdG8gLmNzdg0KDQpPdXRwdXRzOg0KICBwcm9kX3RvdGFsIC0gYSB0aWRpZWQgZGF0YWZyYW1lIChxdWl0ZSBsYXJnZS4uLndhdGNoIG91dCkgdGhhdCBjb250YWlucyB0aGUgdm9sdW1lIG9mIHByb2R1Y2VkIGZsdWlkcy4gSXQgaXMgYSBsb25nLWZvcm1hdCBkYXRhZnJhbWUgdGhhdCBjb250YWlucyBpbmRpdmlkdWFsIHJvd3MgZm9yIGVhY2ggeWVhciBmb3IgZWFjaCB3ZWxsLiAgDQoNCk5vdGVzOg0KSW5mb3JtYXRpb24gcmVnYXJkaW5nIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIENPTlRST0wuVFhUIGFuZCBQUkRISVNULlRYVCBmaWxlcyBjYW4gYmUgZm91bmQgaW4gdGhlIGZvbGxvd2luZyB3b3JkIGRvY3VtZW50LiBodHRwczovL3N0YXRpYy5hZXIuY2EvcHJkLzIwMjAtMDkvd2VsbC1wcm9kdWN0aW9uLWRhdGEtYWxsLWFsYmVydGEtbGF5b3V0LWZpbGUuZG9jeCBJdCBpcyBhIHdpbGQgZm9ybWF0Li4uDQpgYGB7cn0NCiMxLjEgSW1wb3J0IHRoZSBwcm9kdWN0aW9uIHdlbGwgY29udHJvbCBkb2N1bWVudCBmcm9tIHRoZSBDT05UUk9MLlRYVA0KcHJvZGNvbnRyb2wgPC0gcmVhZC5kZWxpbSgifi9SX2RhdGEvUHJvZHVjdGlvbk1hcHBpbmcvRGF0YS9SYXdEYXRhL1Byb2R1Y3Rpb24vQ09OVFJPTC5UWFQiLCBoZWFkZXI9RkFMU0UsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCg0KIzEuMiBTZWxlY3QgYW5kIHJlbmFtZSBkZXNpcmVkIGNvbHVtbnMsIHVzZSByZWdleCBnc3ViIHRvIHJlbW92ZSBzcGVjaWFsIGNoYXJhY3RlcnMgYW5kIHNwYWNlcyANCnByb2Rjb250cm9sIDwtIHByb2Rjb250cm9sICU+JSANCiAgc2VsZWN0KFdlbGxJZGVudGlmaWVyPVYyLCBXZWxsTmFtZT1WMywgUG9vbENvZGU9VjEyLCBQb29sTmFtZT1WMTMpDQpwcm9kY29udHJvbCRQb29sTmFtZSA8LSBnc3ViKCJbXkEtWmEtejAtOSw7Ll8tXSIsIiIscHJvZGNvbnRyb2wkUG9vbE5hbWUpDQoNCiMyLjAgRGVhbGluZyB3aXRoIHRoZSBwcm9kdWN0aW9uIGhpc3RvcnkNCiMyLjEgSW1wb3J0IHRoZSByYXcgcHJvZHVjdGlvbiBoaXN0b3J5IG9mIEFsYmVydGEgZnJvbSB0aGUgYmlnIC50eHQgYW5kIHNldCB1cCBkYXRhIHR5cGVzIG9mIHRoZSBjb2x1bW5zIHdlIHdhbnQuIA0KcHJvZGRhdGEgPC0gcmVhZF90c3YoIn4vUl9kYXRhL1Byb2R1Y3Rpb25NYXBwaW5nL0RhdGEvUmF3RGF0YS9Qcm9kdWN0aW9uL1BSREhJU1QuVFhUIiwNCgljb2xfbmFtZXM9RkFMU0UsDQoJcHJvZ3Jlc3M9VFJVRSwNCgljb2xfdHlwZXMgPSBjb2xzKA0KCQlYMiA9ICJjIiwgI1gyID0gV2VsbElkZW50aWZpZXIgKHJlbmFtZSB0byBXZWxsSWRlbnRpZmllcikNCgkJWDMgPSAiaSIsICNYMyA9IEZsdWlkIFllYXIgKHJlbmFtZSB0byBGbHVpZFllYXIpDQoJCVg1NCA9ICJpIiwgI1g1NCA9IEZsdWlkIENvZGUgMQ0KCQlYNTUgPSAiZCIsICNYNTUgPSBBbm51YWwgRmx1aWQgVm9sdW1lIDENCgkJWDcwID0gImkiLCAjWDcwID0gRmx1aWQgQ29kZSAyDQoJCVg3MSA9ICJkIiwgI1g3MSA9IEFubnVhbCBGbHVpZCBWb2x1bWUgMg0KCQlYODYgPSAiaSIsICNYODYgPSBGbHVpZCBDb2RlIDMNCgkJWDg3ID0gImQiLCAjWDg3ID0gQW5udWFsIEZsdWlkIFZvbHVtZSAzDQoJCVgxMDIgPSAiaSIsICNYMTAyID0gRmx1aWQgQ29kZSA0DQoJCVgxMDMgPSAiZCIsICNYMTAzID0gQW5udWFsIEZsdWlkIFZvbHVtZSA0DQoJCVgxMTggPSAiaSIsICNYMTE4ID0gRmx1aWQgQ29kZSA1DQoJCVgxMTkgPSAiZCIsICNYMTE5ID0gQW5udWFsIEZsdWlkIFZvbHVtZSA1DQoJCVgxMzQgPSAiaSIsICNYMTM0ID0gRmx1aWQgQ29kZSA2DQoJCVgxMzUgPSAiZCIsICNYMTM1ID0gQW5udWFsIEZsdWlkIFZvbHVtZSA2DQoJCVgxNTAgPSAiaSIsICNWMTUwID0gRmx1aWQgQ29kZSA3DQoJCVgxNTEgPSAiZCIsICNWMTUxID0gQW5udWFsIEZsdWlkIFZvbHVtZSA3DQoJCVgxNjYgPSAiaSIsICNYMTY2ID0gRmx1aWQgQ29kZSA4DQoJCVgxNjcgPSAiZCIgI1gxNjcgPSBBbm51YWwgRmx1aWQgVm9sdW1lIDgNCgkJKQ0KCSkNCg0KIzIuMiBEcm9wIHRoZSBkYXRhIHdlIGRvbid0IHdhbnQsIHJlbmFtZSB0aGUgZmlyc3QgdHdvIGNvbHVtbnMsIHRoZW4gYWRkIHRoZSBuZXcgZmx1aWQgY29sdW1ucy4gVGhlIG9yZGVyIG9mIHRoZXNlIGNvbHVtbnMgaXMgc3BlY2lmaWNhbGx5IHRoZSBzYW1lIGFzIHRoZSBvcmRlciBvZiB0aGUgZmx1aWQgY29kZXMgaW4gdGhlIHZlY3RvciAoRmx1aWRWZWN0b3IpIGJlbG93Lg0KcHJvZGRhdGEgPC0gcHJvZGRhdGEgJT4lDQogIHNlbGVjdChYMixYMyxYNTQsWDU1LFg3MCxYNzEsWDg2LFg4NyxYMTAyLFgxMDMsWDExOCxYMTE5LFgxMzQsWDEzNSxYMTUwLFgxNTEsWDE2NixYMTY3KSAlPiUgI1RoZXNlIGFyZSB0aGUgY29sdW1ucyB0aGF0IHdlIHdhbnQNCiAgcmVuYW1lKFdlbGxJZGVudGlmaWVyPVgyLCBGbHVpZFllYXI9WDMpICU+JSAgI1JlbmFtZSB0aGUgb25lcyB0aGF0IHdlIHdpbGwgdWx0aW1hdGVseSBrZWVwDQogIG11dGF0ZShHYXNQcm9kPS05OS45OSwgV2F0ZXJQcm9kPS05OS45OSwgTGlxdWlkR2FzUHJvZD0tOTkuOTksIE9pbFByb2Q9LTk5Ljk5LCBQcm9wYW5lUHJvZD0tOTkuOTksIEJ1dGFuZVByb2Q9LTk5Ljk5KSAjTWFrZSBuZXcgbnVtZXJpYyBjb2x1bW5zIHdpdGggZGlzdGluY3QgbnVsbCB2YWx1ZXMNCg0KIzIuMyBPcmdhbml6ZSB0aGUgZmx1aWQgZGF0YSB3aGljaCBpcyBjdXJyZW50bHkgc2NhdHRlcmVkIHRocm91Z2ggOCBjb2x1bW5zDQogICNUaGUgZmx1aWRzIHdlIGFyZSBpbnRlcmVzdGVkIGhhdmUgdGhlIGZvbGxvd2luZyBGbHVpZCBDb2Rlcw0KICAJIzIgPSBHYXMNCiAgCSM2ID0gV2F0ZXINCiAgCSMxNiA9IExpcXVpZCBQZXRyb2xldW0gR2FzDQogIAkjNTEgPSBDcnVkZSBvaWwvYml0dW1lbg0KICAJIzUzID0gUHJvcGFuZQ0KICAJIzU0ID0gQnV0YW5lDQoNCiMyLjMuMSBTZXQgdXAgdGhlIGZsdWlkIHZlY3RvciBmb3IgaW5jcmVtZW50aW5nIHRocm91Z2ggdGhlIGZsdWlkcw0KRmx1aWRWZWN0b3IgPC0gYygyLDYsMTYsNTEsNTMsNTQpDQpOdW1GbHVpZHMgPC0gbGVuZ3RoKEZsdWlkVmVjdG9yKQ0KUGxhY2VWZWN0b3IgPC0gYygxOk51bUZsdWlkcykNCg0KIzIuMy4yIEZvciBsb29wIGZvciBlYWNoIHBhaXIgb2YgcHJvZHVjdGlvbiBjb2x1bW5zIChlYWNoIEZsdWlkIENvZGUvQW5udWFsIEZsdWlkIFZvbHVtZSBwYWlyKQ0KZm9yIChQcm9kQ29sdW1uIGluIHNlcSgzLDE3LCBieT0yKSl7DQogIA0KICAjMi4zLjMgRm9yIGxvb3AgZm9yIGVhY2ggZmx1aWQgdHlwZSBpbiB0aGUgc2VhcmNoIHZlY3RvciAoRmx1aWQgVmVjdG9yKSwgd2Ugd2lsbCBpbmNyZW1lbnQgdGhyb3VnaCB0aGUgUGxhY2VWZWN0b3IgKDE6TnVtRmx1aWRzKSBidXQgdXNlIHRoYXQgdG8gZ28gdGhyb3VnaCBlYWNoIGZsdWlkcw0KCWZvciAoaSBpbiBQbGFjZVZlY3Rvcikgew0KCQkNCgkJcHJpbnQocGFzdGUoIlByb2QgQ29sdW1uPSIsUHJvZENvbHVtbiwiLiBGbHVpZE51bWJlcj0iLEZsdWlkVmVjdG9yW2ldLHNlcD0iIikpDQoJCQ0KCQkjMi4zLjQgV2UgYXJlIGdvaW5nIHRvIHVzZSB3aGljaCBzdGF0ZW1lbnRzIHRvIGdyYWIganVzdCB0aGUgZmx1aWRzIHdlIHdhbnQgYW5kIHB1dCB0aGVtIGluIHRoZSBjb3JyZWN0IHJlY2VudGx5IGFkZGVkIGNvbHVtbiAoZS5nLiwgR2FzUHJvZCwgV2F0ZXJQcm9kKQ0KCQlGbHVpZE9ic2VydmF0aW9ucyA8LSB3aGljaChwcm9kZGF0YVssUHJvZENvbHVtbl09PUZsdWlkVmVjdG9yW2ldKQ0KCQkNCgkJI1VzZSB0aGUgdmVjdG9yIHRvIGNvcHkgdGhvc2UgdmFsdWVzIGludG8gdGhlIG5ldyBmbHVpZCB2b2x1bWVzDQoJCXByb2RkYXRhW0ZsdWlkT2JzZXJ2YXRpb25zLDE4K2ldIDwtIHByb2RkYXRhW0ZsdWlkT2JzZXJ2YXRpb25zLFByb2RDb2x1bW4rMV0NCgkJDQoJfSAjaW5jcmVtZW50IHRvIHRoZSBuZXh0IHByb2R1Y2VkIGZsdWlkDQp9ICNpbmNyZW1lbnQgdG8gdGhlIG5leHQgY29sdW1uDQoNCnByaW50KCJQcm9kdWN0aW9uIGRhdGEgc29ydGVkIikNCg0KIzIuNCBLZWVwIG9ubHkgdGhlIGRhdGEgdGhhdCB3ZSByZWFsbHkgd2FudCAoV2VsbElELCBwcm9kdWN0aW9uIHllYXIsIHByb2R1Y3Rpb24gdm9sdW1lcyBmb3IgYWxsIGZsdWlkcykNCnByb2RkYXRhIDwtIHByb2RkYXRhICU+JQ0KICBzZWxlY3QoV2VsbElkZW50aWZpZXIsRmx1aWRZZWFyLEdhc1Byb2QsV2F0ZXJQcm9kLExpcXVpZEdhc1Byb2QsT2lsUHJvZCxQcm9wYW5lUHJvZCxCdXRhbmVQcm9kKQ0KcHJvZGRhdGFbcHJvZGRhdGE9PS05OS45OV08LU5BICNSZW1vdmUgcGVza3kgLTk5Ljk5IG51bGwgdmFsdWVzLg0KDQojMi41IENyZWF0ZSBwcm9kX3RvdGFsIGJ5IGpvaW5pbmcgdGhlIGNvbnRyb2wgZmlsZSAoZmlsdGVyZWQgdXAgYWJvdmUgLSBwcm9kY29udHJvbF9maWx0ZXIpIHRvIHRoZSBwcm9kZGF0YV9maWx0ZXJlZA0KcHJvZF90b3RhbCA8LSBsZWZ0X2pvaW4ocHJvZGRhdGEsIHByb2Rjb250cm9sLCBieT0iV2VsbElkZW50aWZpZXIiKQ0KDQojMi42IFJlZm9ybWF0IHRoZSBXZWxsIElkZW50aWZpZXIgdG8gYSBub3JtYWwgVVdJDQpwcm9kX3RvdGFsIDwtIHByb2RfdG90YWwgJT4lDQogIG11dGF0ZShXZWxsSWRlbnRpZmllcj1wYXN0ZShzdWJzdHIoV2VsbElkZW50aWZpZXIsMTEsMTIpLCIvIixzdWJzdHIoV2VsbElkZW50aWZpZXIsOSwxMCksIi0iLHN1YnN0cihXZWxsSWRlbnRpZmllciw3LDgpLCItIixzdWJzdHIoV2VsbElkZW50aWZpZXIsMSwzKSwiLSIsc3Vic3RyKFdlbGxJZGVudGlmaWVyLDUsNiksIlciLHN1YnN0cihXZWxsSWRlbnRpZmllciw0LDQpLCIvIixzdWJzdHIoV2VsbElkZW50aWZpZXIsMTMsMTMpLHNlcD0iIikpICU+JQ0KICByZW5hbWUoVVdJID0gV2VsbElkZW50aWZpZXIpDQoNCiMzLjAgRm9ybWF0IGluZm9ybWF0aW9uIHJlZ2FyZGluZyBob3Jpem9udGFsIHdlbGxzIChMaXN0X29mX0hvcml6b250YWxfV2VsbHNfZGF0YS5jc3YpDQpob3Jpem9udGFsX3dlbGwgPC0gcmVhZC5jc3YoIn4vUl9kYXRhL1Byb2R1Y3Rpb25NYXBwaW5nL0RhdGEvUmF3RGF0YS9XZWxsTGljZW5zZS9MaXN0X29mX0hvcml6b250YWxfV2VsbHNfZGF0YS5jc3YiKQ0KaG9yaXpvbnRhbF93ZWxsIDwtIGhvcml6b250YWxfd2VsbCAlPiUNCiAgc2VsZWN0KFVXST1XZWxsLlV3aS5Gb3JtYXR0ZWQpICU+JQ0KICBtdXRhdGUoSG9yaXpvbnRhbD0xKQ0KDQojMy4xIEpvaW4gaG9yaXpvbnRhbCB3ZWxsIGluZm9ybWF0aW9uIHRvIA0KcHJvZF90b3RhbCA8LSBsZWZ0X2pvaW4oeD1wcm9kX3RvdGFsLCB5PWhvcml6b250YWxfd2VsbCkNCg0KIzQuMCBCb3JlaG9sZSBsb2NhdGlvbnMuIFRoaXMgaXMgdGhlIEFFUiAiV2VsbExvY2F0aW9uc19Cb3R0b20uY3N2IiBidXQgSSBoYXZlIGNvbnZlcnRlZC9hZGRlZCBVVE0gbG9jYXRpb25zIHRvIGl0IHRocm91Z2ggYSBzcHJlYWRzaGVldCBodHRwczovL2dpc2NyYWNrLmNvbS9kb3dubG9hZC1leGNlbC10ZW1wbGF0ZS1jb252ZXJ0LWdlb2dyYXBoaWMtY29vcmRpbmF0ZXMtdXRtLyAtIHRoaXMgbWlnaHQgaGF2ZSB0byBiZSBjaGFuZ2VkLi4uDQp3ZWxsbG9jYXRpb25zIDwtIHJlYWQuY3N2KCJ+L1JfZGF0YS9Qcm9kdWN0aW9uTWFwcGluZy9EYXRhL1Jhd0RhdGEvV2VsbExvY2F0aW9uL1dlbGxMb2NhdGlvbnNfQm90dG9tLmNzdiIsc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KDQojNC4xIFJlbmFtZSBXZWxsSWRlbnRpZmllciB0byBVV0ksIGRyb3AgY29sdW1ucyB3ZSBkb24ndCBuZWVkDQp3ZWxsbG9jYXRpb25zIDwtIHdlbGxsb2NhdGlvbnMgJT4lIA0KICBzZWxlY3QoVVdJLCBCSF9Mb25nLCBCSF9MYXQsIEJIX0Vhc3RpbmcsIEJIX05vcnRoaW5nKQ0KDQojNC4yIEpvaW4gbG9jYXRpb24gdG8gdG90YWwgcHJvZHVjdGlvbiANCnByb2RfdG90YWwgPC0gbGVmdF9qb2luKHg9cHJvZF90b3RhbCwgeT13ZWxsbG9jYXRpb25zKQ0KDQojNS4wIGRyb3AgYWxsIHVubmVzc3NhcnkgZGF0YSBhbmQgdmFsdWVzDQpybShob3Jpem9udGFsX3dlbGwsIHByb2Rjb250cm9sLCBwcm9kZGF0YSwgd2VsbGxvY2F0aW9ucywgRmx1aWRPYnNlcnZhdGlvbnMsIEZsdWlkVmVjdG9yLCBpLCBOdW1GbHVpZHMsIFBsYWNlVmVjdG9yLFByb2RDb2x1bW4pDQoNCmBgYA0KDQpUaGlzIGNodW5rIHRha2VzIHRoZSAicHJvZF90b3RhbCIgZGF0YWZyYW1lIGFuZCBjcmVhdGVzIGEgZGF0YXNldCBzcGVjaWZpYyB0byBhIHNpbmdsZSBpbnRlcnZhbCAoZS5nLiwgVmlraW5nKSBvciBwb29sIChWaWtpbmcgQSkuIFRoaXMgaXMgZG9uZSBieSB1c2luZyBhIFBvb2xTZWFyY2hUZXJtIGFuZCBncmVwbCBmaWx0ZXIgb24gdGhlIFBvb2xOYW1lIGNvbHVtbi4gVG8gc2VsZWN0IHRoZSBQb29sU2VhcmNoVGVybSBsb29rIGF0IHVuaXF1ZSB2YWx1ZXMgd2l0aGluIHRoZSBwcm9kX3RvdGFsJFBvb2xOYW1lOiB1bmlxdWUocHJvZF90b3RhbCRQb29sTmFtZSkuDQoNCklucHV0czoNCiAgcG9vbF9wcm9kIC0gdGhlIHRvdGFsIHByb2R1Y3Rpb24gZm9yIHRhbGwgb2YgQWxiZXJ0YSAoZGYpDQogIFBvb2xTZWFyY2hUZXJtIC0gdGhlIG5hbWUgb2YgdGhlIGludGVydmFsIG9yIHBvb2wgdGhhdCB3ZSB3YW50IHRvIGZpbHRlciB0aGUgZGF0YSBmb3IgKHN0cmluZykNCiAgDQpPdXRwdXRzOg0KICBwb29sX3Byb2QgLSBhIGRhdGFmcmFtZSBvZiB0aGUgcHJvZHVjdGlvbiBkYXRhIChieSB5ZWFyKQ0KICBwb29sX3Byb2RfdmlzIC0gYSBkYXRhZnJhbWUgdGhhdCBpbmNsdWRlcyB0aGUgDQogIA0KYGBge3J9DQojSU5QVVRTDQojVGhpcyBpcyBhIHN0cmluZyB0ZXJtIHRoYXQgY2FuIGJlIHVzZWQgYmVsb3cgdG8gc3Vic2V0IHRoZSBwcm9kX3RvdGFsIChlLmcuLCAiU0FXVE9PVEgiIG9yICJTQVdUT09USCBXV1ciKQ0KUG9vbFNlYXJjaFRlcm0gPC0gIlZJS0lORyINCg0KDQojLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiNGaWx0ZXIgcHJvZF90b3RhbCBieSBhIHBhcnRpYWwgbmFtZSAoZS5nLiwgIlNBV1RPT1RIIiBvciAiU0FXVE9PVEggV1dXIikNCnBvb2xfcHJvZCA8LSBwcm9kX3RvdGFsICU+JSANCiAgZmlsdGVyKGdyZXBsKFBvb2xTZWFyY2hUZXJtLFBvb2xOYW1lKSkgJT4lDQogIGdyb3VwX2J5KFVXSSkgJT4lDQogIG11dGF0ZShzZXF5ZWFyPTE6bGVuZ3RoKEZsdWlkWWVhcikpICU+JQ0KICB1bmdyb3VwKCkNCg0KI0NyZWF0ZSB2aXN1YWxpemF0aW9uIGRhdGFmcmFtZSBmb3IgbWFwcGluZw0KcG9vbF9wcm9kX3ZpcyA8LSBwb29sX3Byb2QgJT4lIA0KICBncm91cF9ieShVV0kpICU+JSANCiAgc3VtbWFyaXplKFllYXJNaW4gPSBtaW4oRmx1aWRZZWFyKSwgDQogICAgICAgICAgICBZZWFyTWF4PW1heChGbHVpZFllYXIpLCANCiAgICAgICAgICAgIE9pbFByb2Q9c3VtKE9pbFByb2QpLCANCiAgICAgICAgICAgIEdhc1Byb2Q9c3VtKEdhc1Byb2QpLCANCiAgICAgICAgICAgIExpcXVpZEdhc1Byb2Q9c3VtKExpcXVpZEdhc1Byb2QpLA0KICAgICAgICAgICAgQkhfTG9uZz1taW4oQkhfTG9uZyksIA0KICAgICAgICAgICAgQkhfTGF0PW1pbihCSF9MYXQpKSAlPiUgDQogIG11dGF0ZShSYWRfb2lsPSgoT2lsUHJvZC1taW4oT2lsUHJvZCwgbmEucm09VFJVRSkpKS8obWF4KE9pbFByb2QsIG5hLnJtPVRSVUUpLW1pbihPaWxQcm9kLCBuYS5ybT1UUlVFKSkqMTAwMCwNCiAgICAgICAgIFJhZF9vaWwgPSBpZmVsc2UoaXMubmEoUmFkX29pbCksIG1pbihSYWRfb2lsLCBuYS5ybT1UUlVFKS8yKzEsUmFkX29pbCksDQogICAgICAgICBSYWRfZ2FzPSgoR2FzUHJvZC1taW4oR2FzUHJvZCwgbmEucm09VFJVRSkpKS8obWF4KEdhc1Byb2QsIG5hLnJtPVRSVUUpLW1pbihHYXNQcm9kLCBuYS5ybT1UUlVFKSkqMTAwMCwNCiAgICAgICAgIFJhZF9nYXMgPSBpZmVsc2UoaXMubmEoUmFkX2dhcyksIG1pbihSYWRfZ2FzLCBuYS5ybT1UUlVFKS8yLFJhZF9nYXMpLA0KICAgICAgICAgUmFkX2xnYXM9KChMaXF1aWRHYXNQcm9kLW1pbihMaXF1aWRHYXNQcm9kLCBuYS5ybT1UUlVFKSkpLyhtYXgoTGlxdWlkR2FzUHJvZCwgbmEucm09VFJVRSktbWluKExpcXVpZEdhc1Byb2QsIG5hLnJtPVRSVUUpKSoxMDAwLA0KICAgICAgICAgUmFkX2xnYXMgPSBpZmVsc2UoaXMubmEoUmFkX2xnYXMpLCBtaW4oUmFkX2xnYXMsIG5hLnJtPVRSVUUpLzIsUmFkX2xnYXMpDQogICAgICAgICApDQoNCmBgYA0KDQpUaGlzIHNlY3Rpb24gY3JlYXRlcyBhIGxlYWZsZXQgbWFwIG91dCBvZiAicG9vbF9wcm9kX3ZpcyIgZGF0YWZyYW1lLiBOb3RlIHRoZSAicmFkaXVzPSIgdGVybSBpcyBkZWZhdWx0IHBvaW50aW5nIGEgcmFkaXVzIHZhbHVlIGZvciBvaWwgcHJvZHVjdGlvbiB0aGVyZSBpcyBhbHNvICJSYWRfZ2FzIiBvciAiUmFkX2xnYXMiDQpgYGB7cn0NCmxlYWZsZXQoKSAlPiUgDQogIGFkZFRpbGVzKCkgJT4lIA0KICBhZGRDaXJjbGVzKGRhdGE9cG9vbF9wcm9kX3ZpcywgDQogICAgICAgICAgICAgbG5nPX5CSF9Mb25nLCANCiAgICAgICAgICAgICBsYXQ9fkJIX0xhdCwgDQogICAgICAgICAgICAgcmFkaXVzPX5SYWRfb2lsLCANCiAgICAgICAgICAgICBvcGFjaXR5ID0gMC41LCANCiAgICAgICAgICAgICBwb3B1cD1wYXN0ZSgiVVdJOiIscG9vbF9wcm9kX3ZpcyRVV0ksIjxici8+IiwiUHJvZHVjZWQgZnJvbToiLCBwb29sX3Byb2RfdmlzJFllYXJNaW4sICIgdG8gIiwgcG9vbF9wcm9kX3ZpcyRZZWFyTWF4LCAiPGJyLz4iLCAiT2lsIHByb2R1Y2VkOiAiLCBwb29sX3Byb2RfdmlzJE9pbFByb2QsICJtXjMiLCAiPGJyLz4iLCAiR2FzIHByb2R1Y2VkOiAiLCBwb29sX3Byb2RfdmlzJEdhc1Byb2QsICIxMDAwIG1eMyIsICI8YnIvPiIsICJMaXF1aWQgZ2FzIHByb2R1Y2VkOiAiLCBwb29sX3Byb2RfdmlzJExpcXVpZEdhc1Byb2QsICJtXjMiKSkNCg0K