Glimpse of Data:

data<-read.csv(file="WIND_VXE_2013.csv", header=TRUE)
#data$PCTimeStamp= dmy_hm( as.character(data$PCTimeStamp)) 
head(data)
# cat("The Data is of:",max(data$PCTimeStamp,na.rm = TRUE)-min(data$PCTimeStamp,na.rm = TRUE),"Days","Date From:",min(data$PCTimeStamp,na.rm = TRUE),"To:",max(data$PCTimeStamp,na.rm = TRUE))
# min(data$PCTimeStamp,na.rm = TRUE)
# max(data$PCTimeStamp,na.rm = TRUE)-min(data$PCTimeStamp,na.rm = TRUE)
# cat("The Data is of:",max(data$PCTimeStamp,na.rm = TRUE)-min(data$PCTimeStamp,na.rm = TRUE),"Days From\n")
# min(data$PCTimeStamp,na.rm = TRUE)
# assign new column names to be more succint (avoid spaces, use _ or . to seperate words)
new.names<-c("date_time","T1_Possible_Power","T2_Possible_Power","T3_Possible_Power","T4_Possible_Power","T5_Possible_Power","T6_Possible_Power","T7_Possible_Power","T1_Total_Active_Power","T2_Total_Active_Power","T3_Total_Active_Power","T4_Total_Active_Power","T5_Total_Active_Power","T6_Total_Active_Power","T7_Total_Active_Power","mean_wind_mps", "min_wind_mps", "max_wind_mps", "cum_energy_delivered_kwh")
# make sure the new names lineup properly with the ones they're replacing...
cbind(names(data), new.names)
                                                     new.names                 
 [1,] "PCTimeStamp"                                  "date_time"               
 [2,] "WTG01_Grid.Production.PossiblePower.Avg...1." "T1_Possible_Power"       
 [3,] "WTG02_Grid.Production.PossiblePower.Avg...2." "T2_Possible_Power"       
 [4,] "WTG03_Grid.Production.PossiblePower.Avg...3." "T3_Possible_Power"       
 [5,] "WTG04_Grid.Production.PossiblePower.Avg...4." "T4_Possible_Power"       
 [6,] "WTG05_Grid.Production.PossiblePower.Avg...5." "T5_Possible_Power"       
 [7,] "WTG06_Grid.Production.PossiblePower.Avg...6." "T6_Possible_Power"       
 [8,] "WTG07_Grid.Production.PossiblePower.Avg...7." "T7_Possible_Power"       
 [9,] "WTG01_Total.Active.power..8."                 "T1_Total_Active_Power"   
[10,] "WTG02_Total.Active.power..9."                 "T2_Total_Active_Power"   
[11,] "WTG03_Total.Active.power..10."                "T3_Total_Active_Power"   
[12,] "WTG04_Total.Active.power..11."                "T4_Total_Active_Power"   
[13,] "WTG05_Total.Active.power..12."                "T5_Total_Active_Power"   
[14,] "WTG06_Total.Active.power..13."                "T6_Total_Active_Power"   
[15,] "WTG07_Total.Active.power..14."                "T7_Total_Active_Power"   
[16,] "MET_Avg..Wind.speed.1..15."                   "mean_wind_mps"           
[17,] "MET_Min..Wind.speed.1..16."                   "min_wind_mps"            
[18,] "MET_Max..Wind.speed.1..17."                   "max_wind_mps"            
[19,] "GRID1_KWH_DEL"                                "cum_energy_delivered_kwh"
# if yes, replace column names with new names
names(data)<-new.names

Descriptive Statistic:

No of Data Type Available in Data

**VARIABLES in Data:** 
Main data: 19 Columns 52560 Observation 
------------------------------------------------------------------------------------------------------ 
DATA TYPE: Numerical 18 Observation: 52560 
Numerical Colums are:
 T1_Possible_Power T2_Possible_Power T3_Possible_Power T4_Possible_Power T5_Possible_Power T6_Possible_Power T7_Possible_Power T1_Total_Active_Power T2_Total_Active_Power T3_Total_Active_Power T4_Total_Active_Power T5_Total_Active_Power T6_Total_Active_Power T7_Total_Active_Power mean_wind_mps min_wind_mps max_wind_mps cum_energy_delivered_kwh 
------------------------------------------------------------------------------------------------------ 
DATA TYPE: Categorical data Observation: 
Categorical Colums are: 
------------------------------------------------------------------------------------------------------

Missing value : Before Pre-processing

missmap(data)

cat("\n ***********NA VALUE COLUMN WISE*********** \n")

 ***********NA VALUE COLUMN WISE*********** 
sort(colSums((is.na(data))),decreasing = TRUE)
       T3_Possible_Power    T3_Total_Active_Power        T1_Possible_Power    T1_Total_Active_Power 
                     927                      927                      725                      725 
       T2_Possible_Power        T7_Possible_Power    T2_Total_Active_Power    T7_Total_Active_Power 
                     710                      710                      710                      710 
       T5_Possible_Power    T5_Total_Active_Power        T4_Possible_Power    T4_Total_Active_Power 
                     685                      685                      654                      654 
       T6_Possible_Power    T6_Total_Active_Power cum_energy_delivered_kwh            mean_wind_mps 
                     652                      652                       59                        3 
            min_wind_mps             max_wind_mps                date_time 
                       3                        3                        0 
cat("\n ----------------------------------------------------------\n")

 ----------------------------------------------------------
summary(data)
          date_time     T1_Possible_Power T2_Possible_Power T3_Possible_Power T4_Possible_Power T5_Possible_Power
 01-01-13 00:00:    1   Min.   : -3.0     Min.   : -3.0     Min.   : -2.0     Min.   : -3.0     Min.   : -3.0    
 01-01-13 00:10:    1   1st Qu.:191.0     1st Qu.:204.0     1st Qu.:214.0     1st Qu.:222.0     1st Qu.:192.0    
 01-01-13 00:20:    1   Median :518.0     Median :530.0     Median :552.0     Median :598.0     Median :553.0    
 01-01-13 00:30:    1   Mean   :475.9     Mean   :483.5     Mean   :496.8     Mean   :517.6     Mean   :495.3    
 01-01-13 00:40:    1   3rd Qu.:772.0     3rd Qu.:774.0     3rd Qu.:792.0     3rd Qu.:819.0     3rd Qu.:805.0    
 01-01-13 00:50:    1   Max.   :850.0     Max.   :850.0     Max.   :850.0     Max.   :850.0     Max.   :850.0    
 (Other)       :52554   NA's   :725       NA's   :710       NA's   :927       NA's   :654       NA's   :685      
 T6_Possible_Power T7_Possible_Power T1_Total_Active_Power T2_Total_Active_Power T3_Total_Active_Power
 Min.   : -2.0     Min.   : -6       Min.   :3109970       Min.   : 609852       Min.   :3254759      
 1st Qu.:206.0     1st Qu.:180       1st Qu.:3895622       1st Qu.:1391641       1st Qu.:4066341      
 Median :537.0     Median :497       Median :4744043       Median :2341906       Median :5022455      
 Mean   :489.3     Mean   :472       Mean   :4608851       Mean   :2189450       Mean   :4870726      
 3rd Qu.:786.0     3rd Qu.:785       3rd Qu.:5262894       3rd Qu.:2894952       3rd Qu.:5586471      
 Max.   :850.0     Max.   :850       Max.   :6045048       Max.   :3690817       Max.   :6413840      
 NA's   :652       NA's   :710       NA's   :725           NA's   :710           NA's   :927          
 T4_Total_Active_Power T5_Total_Active_Power T6_Total_Active_Power T7_Total_Active_Power mean_wind_mps    min_wind_mps 
 Min.   :3341303       Min.   :3230186       Min.   :3264175       Min.   :3136754       Min.   : 0.00   Min.   : 0.0  
 1st Qu.:4168935       1st Qu.:4023712       1st Qu.:4085929       1st Qu.:3919523       1st Qu.: 5.60   1st Qu.: 3.6  
 Median :5159420       Median :4986563       Median :5057922       Median :4875312       Median : 8.50   Median : 6.0  
 Mean   :5003720       Mean   :4827587       Mean   :4903693       Mean   :4712335       Mean   : 8.08   Mean   : 5.6  
 3rd Qu.:5736883       3rd Qu.:5531891       3rd Qu.:5624685       3rd Qu.:5410488       3rd Qu.:10.90   3rd Qu.: 7.8  
 Max.   :6575858       Max.   :6326853       Max.   :6451012       Max.   :6193951       Max.   :18.80   Max.   :15.8  
 NA's   :654           NA's   :685           NA's   :652           NA's   :710           NA's   :3       NA's   :3     
  max_wind_mps   cum_energy_delivered_kwh
 Min.   : 0.00   Min.   :     78         
 1st Qu.: 7.60   1st Qu.:2741038         
 Median :11.10   Median :5152314         
 Mean   :10.55   Mean   :5052061         
 3rd Qu.:13.90   3rd Qu.:7152592         
 Max.   :31.50   Max.   :9999699         
 NA's   :3       NA's   :59              

Subset Data type

# subset data by type
Cumulative<-subset(data, select=c(1,19))
Possible<-subset(data, select=1:8)
Active<-subset(data, select=c(1,9:15))
Wind<-subset(data, select=c(1,16:18))
# select data to use
dat<-data # keep all the data
# similarly, you could select all the data besides Active Power 
# dat<-merge(Possible, Cumulative, Wind) # keep all but Active Power

Preview the raw data

dim(dat)
[1] 52560    19
str(dat)
'data.frame':   52560 obs. of  19 variables:
 $ date_time               : Factor w/ 52560 levels "01-01-13 00:00",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ T1_Possible_Power       : int  817 732 764 773 689 735 782 814 730 727 ...
 $ T2_Possible_Power       : int  805 790 774 769 690 753 818 796 735 773 ...
 $ T3_Possible_Power       : int  786 763 793 759 711 808 736 789 736 768 ...
 $ T4_Possible_Power       : int  809 809 821 813 800 830 822 835 805 832 ...
 $ T5_Possible_Power       : int  755 771 736 627 749 832 713 747 780 797 ...
 $ T6_Possible_Power       : int  745 758 668 717 749 757 638 696 716 749 ...
 $ T7_Possible_Power       : int  743 811 656 752 723 797 654 736 683 799 ...
 $ T1_Total_Active_Power   : int  3109970 3110050 3110130 3110211 3110288 3110366 3110447 3110527 3110604 3110682 ...
 $ T2_Total_Active_Power   : int  609852 609933 610014 610095 610176 610257 610338 610419 610499 610579 ...
 $ T3_Total_Active_Power   : int  3254759 3254839 3254920 3255002 3255083 3255164 3255244 3255325 3255406 3255487 ...
 $ T4_Total_Active_Power   : int  3341303 3341384 3341465 3341546 3341628 3341709 3341790 3341870 3341952 3342033 ...
 $ T5_Total_Active_Power   : int  3230186 3230266 3230346 3230421 3230501 3230582 3230662 3230742 3230823 3230904 ...
 $ T6_Total_Active_Power   : int  3264175 3264255 3264336 3264417 3264499 3264579 3264660 3264740 3264821 3264902 ...
 $ T7_Total_Active_Power   : int  3136754 3136835 3136914 3136994 3137075 3137156 3137234 3137314 3137394 3137474 ...
 $ mean_wind_mps           : num  11.3 12 11.6 11.8 11.2 11.1 12 11.3 11.9 11.2 ...
 $ min_wind_mps            : num  8.1 9.1 7.1 9.4 8.1 7.1 9 6.9 8.6 7.9 ...
 $ max_wind_mps            : num  14.5 15.4 16.7 14.2 14.3 14.4 14.8 14.5 15.5 14.8 ...
 $ cum_energy_delivered_kwh: int  2510065 2510615 2511165 2511714 2512265 2512815 2513365 2513915 2514465 2515015 ...
summary(dat)
          date_time     T1_Possible_Power T2_Possible_Power T3_Possible_Power T4_Possible_Power T5_Possible_Power
 01-01-13 00:00:    1   Min.   : -3.0     Min.   : -3.0     Min.   : -2.0     Min.   : -3.0     Min.   : -3.0    
 01-01-13 00:10:    1   1st Qu.:191.0     1st Qu.:204.0     1st Qu.:214.0     1st Qu.:222.0     1st Qu.:192.0    
 01-01-13 00:20:    1   Median :518.0     Median :530.0     Median :552.0     Median :598.0     Median :553.0    
 01-01-13 00:30:    1   Mean   :475.9     Mean   :483.5     Mean   :496.8     Mean   :517.6     Mean   :495.3    
 01-01-13 00:40:    1   3rd Qu.:772.0     3rd Qu.:774.0     3rd Qu.:792.0     3rd Qu.:819.0     3rd Qu.:805.0    
 01-01-13 00:50:    1   Max.   :850.0     Max.   :850.0     Max.   :850.0     Max.   :850.0     Max.   :850.0    
 (Other)       :52554   NA's   :725       NA's   :710       NA's   :927       NA's   :654       NA's   :685      
 T6_Possible_Power T7_Possible_Power T1_Total_Active_Power T2_Total_Active_Power T3_Total_Active_Power
 Min.   : -2.0     Min.   : -6       Min.   :3109970       Min.   : 609852       Min.   :3254759      
 1st Qu.:206.0     1st Qu.:180       1st Qu.:3895622       1st Qu.:1391641       1st Qu.:4066341      
 Median :537.0     Median :497       Median :4744043       Median :2341906       Median :5022455      
 Mean   :489.3     Mean   :472       Mean   :4608851       Mean   :2189450       Mean   :4870726      
 3rd Qu.:786.0     3rd Qu.:785       3rd Qu.:5262894       3rd Qu.:2894952       3rd Qu.:5586471      
 Max.   :850.0     Max.   :850       Max.   :6045048       Max.   :3690817       Max.   :6413840      
 NA's   :652       NA's   :710       NA's   :725           NA's   :710           NA's   :927          
 T4_Total_Active_Power T5_Total_Active_Power T6_Total_Active_Power T7_Total_Active_Power mean_wind_mps    min_wind_mps 
 Min.   :3341303       Min.   :3230186       Min.   :3264175       Min.   :3136754       Min.   : 0.00   Min.   : 0.0  
 1st Qu.:4168935       1st Qu.:4023712       1st Qu.:4085929       1st Qu.:3919523       1st Qu.: 5.60   1st Qu.: 3.6  
 Median :5159420       Median :4986563       Median :5057922       Median :4875312       Median : 8.50   Median : 6.0  
 Mean   :5003720       Mean   :4827587       Mean   :4903693       Mean   :4712335       Mean   : 8.08   Mean   : 5.6  
 3rd Qu.:5736883       3rd Qu.:5531891       3rd Qu.:5624685       3rd Qu.:5410488       3rd Qu.:10.90   3rd Qu.: 7.8  
 Max.   :6575858       Max.   :6326853       Max.   :6451012       Max.   :6193951       Max.   :18.80   Max.   :15.8  
 NA's   :654           NA's   :685           NA's   :652           NA's   :710           NA's   :3       NA's   :3     
  max_wind_mps   cum_energy_delivered_kwh
 Min.   : 0.00   Min.   :     78         
 1st Qu.: 7.60   1st Qu.:2741038         
 Median :11.10   Median :5152314         
 Mean   :10.55   Mean   :5052061         
 3rd Qu.:13.90   3rd Qu.:7152592         
 Max.   :31.50   Max.   :9999699         
 NA's   :3       NA's   :59              

Checking for the Missing Value:

check<-function(df){
  # count NA (missing values)
  NAs<-sum(is.na(df))
  print(paste("Missing Values:", NAs))
  
  # count incomplete records (rows containing missing values)
  ok<-complete.cases(df)
  print(paste("Incomplete Records:", sum(! ok)))
  
  # Show incomplete records (if less than 100 NAs). 
  if(NAs > 0 & NAs <= 100) print( df[which(! complete.cases(df)), ] )
  
  # If more than 100, show column-wise distribution of NAs.
  if (NAs > 100) hist(which(is.na(df), arr.ind=TRUE)[,2], xlab="Column", freq=TRUE, breaks=1:dim(df)[2], main="Column-wise distribution of missing values")
  }
removed<-function(nrow, nrow1){
  print(paste("number of records REMOVED:", nrow-nrow1, sep=" "))
  print(paste("number of records REMAINING:", nrow1, sep=" "))
}

Now lets Check our Function:

[1] "Missing Values: 10194"
[1] "Incomplete Records: 1377"

Remove missing values

Here we apply the na.omit function (native) and the removed function we just wrote (user defined)

nrow<-dim(dat)[1] # record the dimensions of the data (before removing anything!)
dat<-na.omit(dat) # omit rows containing missing values
nrow1<-dim(dat)[1] # record the new dimensions of the data (after removing NAs)
removed(nrow, nrow1) # check how many records have been removed
[1] "number of records REMOVED: 1377"
[1] "number of records REMAINING: 51183"

Compute energy sentout and energy benchmarks

# compute energy sentout in each timeblock (10min dt) usingz the cumulative energy counter
n<-length(dat$cum_energy_delivered_kwh)#51183
a<-dat$cum_energy_delivered_kwh[1:n-1]#
b<-dat$cum_energy_delivered_kwh[2:n]
diff<-b-a# This is (2nd Row - 1st Row)
dat$energy_sentout_10min_kwh<-c(diff,0) # cannot compute difference for last timestep, set to zero.
# compute kinetic energy in the wind at each windspeed
# Wind Power = (1/2)*rho*area*(velocity)^3 = [kg/m^3]*[m^2]*[m/s]^3 = [kg*m^2/s^3] = [kg*m^2/s^2][1/s] = [Newton-meter]/[second] = [Joules/second] = [Watts]
rho=1.225 # density of wind/Air (kg/m^3)
area=2174 # sweep area of wind turbines (m^2)
turbines=7 # number of turbines
c<-(1/2)*rho*area
dat$wind_power_kw<-c*(dat$mean_wind_mps)^3*turbines/1000 # kW avg power
dat$wind_energy_10min_kwh<-c*(dat$mean_wind_mps)^3*turbines/(1000*6) # kWh in 10 min
# compute betz limit
betz.coef<- 16/27
dat$betz_limit_10min_kwh<-dat$wind_energy_10min_kwh*betz.coef
# compute turbine efficiency
dat$turbine_eff<-dat$energy_sentout_10min_kwh/dat$wind_energy_10min_kwh
# compute total Possible Power
uncurtailed_power<-apply(X=dat[,2:8], MARGIN=1, FUN=sum)
dat$uncurtailed_10min_kwh<-(uncurtailed_power)/6
# compute curtailment
dat$curtailment_10min_kwh<-dat$uncurtailed-dat$energy_sentout_10min_kwh
# Check for NA
check(dat)
[1] "Missing Values: 179"
[1] "Incomplete Records: 179"

# NaN arise when we compute turbine efficiency with zero in the numerator and denominator (due to windspeed = 0 & energy_sentout = 0). Set these to zero.
nan<-which(dat$turbine_eff == "NaN")
# length(nan) # same as number of NAs
# head(dat[nan, ]) # look at the rows containing "NaN"
dat$turbine_eff[nan]<-0
# Likewise, Inf arise when we compute turbine efficiency with zero in the denominator (due to windspeed = 0).  Set these to zero.
inf<-which(dat$turbine_eff == "Inf")
# length(inf) # not counted in NA count, beware!
# head(dat[inf, ]) # look at the rows containing "NaN"
dat$turbine_eff[inf]<-0
# check again
check(dat) # NAs have been removed.
[1] "Missing Values: 0"
[1] "Incomplete Records: 0"
# covert modified date_time character string to POSIX
dat$date_time <- as.POSIXlt(as.character(dat$date_time), format="%m-%d-%y %H:%M")
#dat$date_time= mdy_hm(as.character(dat$date_time)) 
dat$month <- cut(dat$date_time, breaks = "month")
dat$week <- cut(dat$date_time, breaks = "week")
dat$day <- cut(dat$date_time, breaks = "day")
dat$hour <- cut(dat$date_time, breaks = "hour")
# dummy<-strsplit(as.character(week), split=" ")
# week<-lapply(dummy, '[[', 1) # keep the date, drop the time
# dat$week <- week
# 
# dummy<-strsplit(as.character(day), split=" ")
# day<-lapply(dummy, '[[', 1) # keep the date, drop the time
# #dat$day<-as.factor(day)
# dat$day<-day
# 
# dummy<-strsplit(as.character(hour), split=" ")
# hour<-lapply(dummy, '[[', 2) # keep the time, drop the date
# dat$hour<-as.factor(hour)

    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
-9999000      235      417       24      585    47120 

# looks funny...
# number of records (rows) before filtering
nrow<-dim(dat)[1] 
# FILTER 1: remove negative energy values
dat<-subset(dat, dat$energy_sentout_10min_kwh >= 0) #
# number of records removed
nrow2<-dim(dat)[1]
removed(nrow, nrow2)
[1] "number of records REMOVED: 0"
[1] "number of records REMAINING: 51181"
# Filter positive energy values based on physical knowledge of the system. 
# Filters enumerated in order of increasing stringency:
# FILTER 2: remove energy values beyond what's possible given installed capacity
capacity<-850*7 # 850 KW rated capacity x 7 turbines
capacity_10min_kwh<-capacity*(10/60) # max energy sentout in 10 minutes
dat<-subset(dat, dat$energy_sentout_10min_kwh <= capacity_10min_kwh)
# # FILTER 3: remove statistical outliers: set quantiles to keep
# range<-quantile(dat$energy_sentout_10min_kwh, probs=c(0.01,0.99))
# dat<-subset(dat, dat$energy_sentout_10min_kwh > range[1])
# dat<-subset(dat, dat$energy_sentout_10min_kwh < range[2])
# # FILTER 4: remove energy values beyond what's possible given windspeed (e.g. kinetic energy contained in wind)
# dat<-subset(dat, dat$energy_sentout_10min_kwh <= dat$wind_energy_10min_kwh)
 
# # FILTER 5: remove energy values beyond what's possible given windspeed AND Betz limit (kinetic energy *16/27)
# dat<-subset(dat, dat$energy_sentout_10min_kwh <= dat$betz_limit_10min_kwh)
# number of records removed
nrow3<-dim(dat)[1]
removed(nrow2, nrow3)
[1] "number of records REMOVED: 39"
[1] "number of records REMAINING: 51142"
# check the quantiles and histogram again
summary(dat$energy_sentout_10min_kwh)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    0.0   235.0   417.0   411.3   585.0   964.0 
hist(dat$energy_sentout_10min_kwh)

# check for outliers in wind data
# quantiles
summary(dat$mean_wind_mps) 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   5.600   8.500   8.111  10.900  18.800 
summary(dat$min_wind_mps)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   3.700   6.000   5.624   7.800  15.800 
summary(dat$max_wind_mps)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   0.00    7.70   11.10   10.59   13.90   31.50 
# historgrams
hist(dat$mean_wind_mps, main="Histogram of Mean Windspeeds")

dat<-subset(dat, mean_wind_mps > 0.5)
wind<-subset(dat, select=c("week", "mean_wind_mps", "max_wind_mps", "min_wind_mps"))
wind<-ddply(wind, .(week), numcolwise(mean))
# plot wind vs time
test<-melt(wind, id.vars=("week"))
ggplot(test, aes(x=week, y=value, group=variable, colour=variable, linetype=variable)) +
  geom_line() +
  scale_y_continuous(name="Windspeed [m/s]") + 
  labs(title="Wind Timeseries") +
  theme_classic() +
  scale_x_discrete(breaks=levels(test$week)[seq(1,360, by=30)], labels=abbreviate)

energy<-subset(dat, select=c("mean_wind_mps", "energy_sentout_10min_kwh", "wind_energy_10min_kwh", "betz_limit_10min_kwh"))
energy<-melt(energy, id.vars=("mean_wind_mps"))
ggplot(energy, aes(x=mean_wind_mps, y=value, group=variable, colour=variable)) + 
  geom_point() +
  scale_y_continuous(name="KWh in 10min", limit=c(0, max(dat$energy_sentout_10min_kwh))) + 
  scale_x_continuous(name="Windspeed (mps)") + 
  labs(title="Empirical Power Curve with Betz Limit and Theoretical Wind Energy") +
  theme_classic()

dat<-subset(dat, dat$min_wind_mps > 3)
dat<-subset(dat, dat$max_wind_mps < 25)

write.csv(dat, file="clean_VXE_wind_speed.csv")
save(dat, file="clean_VXE_wind_speed.rsav")
filter<-which(dat$mean_wind_mps < 3 | dat$mean_wind_mps > 25)
dat$energy_sentout_10min_kwh[filter]<-0
# fit a distribution to the wind speed data
library(fitdistrplus)
package <U+393C><U+3E31>fitdistrplus<U+393C><U+3E32> was built under R version 3.3.3Loading required package: MASS
package <U+393C><U+3E31>MASS<U+393C><U+3E32> was built under R version 3.3.3
Attaching package: <U+393C><U+3E31>MASS<U+393C><U+3E32>

The following objects are masked from <U+393C><U+3E31>package:fma<U+393C><U+3E32>:

    cement, housing, petrol

The following object is masked from <U+393C><U+3E31>package:dplyr<U+393C><U+3E32>:

    select

Loading required package: survival
package <U+393C><U+3E31>survival<U+393C><U+3E32> was built under R version 3.3.3
descdist(dat$mean_wind_mps) # heuristic
summary statistics
------
min:  3.4   max:  18.8 
median:  9.5 
mean:  9.499968 
estimated sd:  2.705678 
estimated skewness:  0.1801147 
estimated kurtosis:  2.493589 

# based on heuristic, and knowledge of the system, fit a weibull distribution
weibull.fit<-fitdist(dat$mean_wind_mps, distr="weibull")
summary(weibull.fit)
Fitting of the distribution ' weibull ' by maximum likelihood 
Parameters : 
       estimate Std. Error
shape  3.865554 0.01475029
scale 10.505568 0.01425645
Loglikelihood:  -97840.72   AIC:  195685.4   BIC:  195702.7 
Correlation matrix:
          shape     scale
shape 1.0000000 0.3225372
scale 0.3225372 1.0000000
plot(weibull.fit, demp=TRUE)

# summation of energy sentout, divided by the number of hours, yields average power sentout.
avg.power<-sum(dat$energy_sentout_10min_kwh)/(dim(dat)[1]/6)
# summation of energy sentout, divided by the number of hours*capacity, yields capacity factor.
actual.cap.factor<-sum(dat$energy_sentout_10min_kwh)/(850*7*dim(dat)[1]/6)
uncurtailed.cap.factor<-sum(dat$uncurtailed_10min_kwh)/(850*7*dim(dat)[1]/6)
betz.cap.factor<-sum(dat$betz_limit_10min_kwh)/(850*7*dim(dat)[1]/6)
# display the results
data.frame(actual.cap.factor, uncurtailed.cap.factor, betz.cap.factor)
nrow<-dim(dat)[1] # number of complete records BEFORE filtering
# select the numeric or integer class columns
c<-sapply(dat, class) # class of each column
colIndex<-which(c=="numeric" | c=="integer") # column index of numeric or integer vectors
# compute column-wise quantiles
# apply(dat[,-1], 2, stats::quantile)
range<-apply(dat[,colIndex], 2, function(x) {quantile(x, probs=c(.01, .99)) } )
# get names of the columns that we want to filter on (all but the first column)
# names<-colnames(dat[,colIndex])
names<-names(colIndex) # identical to above
# iterate through each column, subsetting the ENTIRE DATA.FRAME by removing records (rows) associated with an outlier value in that column
for(i in 1:dim(range)[2]){
  n<-dim(dat)[1] # number of rows at start
  dat<-subset(dat, eval(parse(text=names[i])) > range[1,i])  # keep records above 1st percentile
  dat<-subset(dat, eval(parse(text=names[i])) < range[2,i])  # keep records less than 99 percentile
  p<-dim(dat)[1] # number of rows at end
  print(paste(n-p, "records dropped when filtering:", toupper(names[i]), sep=" "))
}
[1] "1422 records dropped when filtering: T1_POSSIBLE_POWER"
[1] "577 records dropped when filtering: T2_POSSIBLE_POWER"
[1] "660 records dropped when filtering: T3_POSSIBLE_POWER"
[1] "1370 records dropped when filtering: T4_POSSIBLE_POWER"
[1] "567 records dropped when filtering: T5_POSSIBLE_POWER"
[1] "210 records dropped when filtering: T6_POSSIBLE_POWER"
[1] "344 records dropped when filtering: T7_POSSIBLE_POWER"
[1] "658 records dropped when filtering: T1_TOTAL_ACTIVE_POWER"
[1] "0 records dropped when filtering: T2_TOTAL_ACTIVE_POWER"
[1] "0 records dropped when filtering: T3_TOTAL_ACTIVE_POWER"
[1] "0 records dropped when filtering: T4_TOTAL_ACTIVE_POWER"
[1] "0 records dropped when filtering: T5_TOTAL_ACTIVE_POWER"
[1] "0 records dropped when filtering: T6_TOTAL_ACTIVE_POWER"
[1] "0 records dropped when filtering: T7_TOTAL_ACTIVE_POWER"
[1] "152 records dropped when filtering: MEAN_WIND_MPS"
[1] "348 records dropped when filtering: MIN_WIND_MPS"
[1] "101 records dropped when filtering: MAX_WIND_MPS"
[1] "732 records dropped when filtering: CUM_ENERGY_DELIVERED_KWH"
[1] "186 records dropped when filtering: ENERGY_SENTOUT_10MIN_KWH"
[1] "0 records dropped when filtering: WIND_POWER_KW"
[1] "0 records dropped when filtering: WIND_ENERGY_10MIN_KWH"
[1] "0 records dropped when filtering: BETZ_LIMIT_10MIN_KWH"
[1] "372 records dropped when filtering: TURBINE_EFF"
[1] "0 records dropped when filtering: UNCURTAILED_10MIN_KWH"
[1] "471 records dropped when filtering: CURTAILMENT_10MIN_KWH"
# number of records removed
nrow2<-dim(dat)[1]
removed(nrow, nrow2)
[1] "number of records REMOVED: 8170"
[1] "number of records REMAINING: 32378"
# based on heuristic, and knowledge of the system, fit a weibull distribution
weibull.fit.2<-fitdist(dat$mean_wind_mps, distr="weibull")
summary(weibull.fit.2)
Fitting of the distribution ' weibull ' by maximum likelihood 
Parameters : 
      estimate Std. Error
shape 4.676697 0.02018640
scale 9.959792 0.01249698
Loglikelihood:  -71206.92   AIC:  142417.8   BIC:  142434.6 
Correlation matrix:
          shape     scale
shape 1.0000000 0.3210392
scale 0.3210392 1.0000000
plot(dat$mean_wind_mps, dat$uncurtailed_10min_kwh, col="red", cex=0.2, pch=20, main="Curtailed and Uncurtailed Wind Energy Production\nSao Vicente, Cape Verde (2013)", xlab="Windspeed (mps)", ylab="Energy (kWh/10min)")
points(dat$mean_wind_mps, dat$energy_sentout_10min_kwh, col="green", cex=0.2, pch=20)
legend("topleft", legend=c("Energy Possible (Uncurtailed)", "Energy Sentout (Curtailed)"), col=c("red", "green"), pch=20)

# load the manufacturers power curve
MPC<-read.table(file="v52-850KW-power-curve.csv", header=TRUE, strip.white=TRUE, sep=",")
MPC$design_sentout_10min_kwh<-MPC$power_kW*7/6 # kW x (7 turbines) x (1/6 hour)
MPC<-subset(MPC, windspeed_mps<25)
# plot the manufacturers power curve
plot(x=MPC$windspeed_mps, y=MPC$design_sentout_10min_kwh, type="p", pch=4, main="Manufacturers Power Curve\n with Empirical Overlay", xlab="Windspeed (mps)", ylab="Energy (kWh/10min)", xlim=range(dat$mean_wind_mps))
points(y=dat$energy_sentout_10min_kwh, x=dat$mean_wind_mps, col="green", cex=0.1, pch=20)
points(y=dat$uncurtailed_10min_kwh, x=dat$mean_wind_mps, col="red", cex=0.1, pch=20)
legend("bottomright", col=c("black", "green", "red"), pch=c(4, 20, 20), legend=c("Manufacturers Power Curve", "Energy Sentout (Curtailed)","Energy Possible (Uncurtailed)") )

# For each observation, supply the energy sentout (kWh), find the closest matching value on the power curve (kW), query the corresponding wind speed from the power curve (mps). This becomes the corrected windspeed. Apply this whenever the measured windspeed is less than the windspeed dictated by the power curve. This shifts all points that were left of the power curve, onto the power curve.
# matching vector must be in ascending order
MPC<-MPC[ order(MPC$power_kW, decreasing=FALSE), ]
# pass energy sentout (kWh), return row index of closest matching value on power curve (kW)
power.match<-findInterval(x=dat$energy_sentout_10min_kwh, vec=MPC$power_kW, all.inside=TRUE, rightmost.closed=TRUE) 
# read-off corresponding wind speed from the power curve (mps)
corrected.windspeed<-MPC[power.match, c("windspeed_mps")]
# # visually inspect matched values with values matched against --> Looks good!
# plot(x=MPC[power.match, c("windspeed_mps")], y=MPC[power.match, c("power_kW")], col="red", cex=3, xlab="windspeed_mps", ylab="power_kW")
# points(x=MPC$windspeed_mps, y=MPC$power_kW, col="black", cex=0.1)
# create a new column called "corrected_wind_mps"
dat$corrected_wind_mps<-corrected.windspeed
# if the observed mean windspeed is greater than the windspeed dictated by the power curve, that's OK --> keep the recorded mean windspeed. 
keep<-which(dat$mean_wind_mps >= dat$corrected_wind_mps)
dat$corrected_wind_mps[keep]<-dat$mean_wind_mps[keep]
# We do this because it is possible that less power was produced at a given windspeed than dictated by the power curve, due to curtailment.
# however it is *not* possible to produce more power at a given windspeed than dictated by the power curve. Hence we correct those values, only.
fitw<-fitdist(dat$mean_wind_mps, "weibull")
summary(fitw)
Fitting of the distribution ' weibull ' by maximum likelihood 
Parameters : 
      estimate Std. Error
shape 4.676697 0.02018640
scale 9.959792 0.01249698
Loglikelihood:  -71206.92   AIC:  142417.8   BIC:  142434.6 
Correlation matrix:
          shape     scale
shape 1.0000000 0.3210392
scale 0.3210392 1.0000000
denscomp(fitw, addlegend=FALSE, main="Histogram and Weibull Fit for Uncorrected Windspeeds")

fitw<-fitdist(dat$corrected_wind_mps, "weibull")
summary(fitw)
Fitting of the distribution ' weibull ' by maximum likelihood 
Parameters : 
       estimate Std. Error
shape  3.977839 0.01378950
scale 10.427128 0.01542095
Loglikelihood:  -74324.82   AIC:  148653.6   BIC:  148670.4 
Correlation matrix:
          shape     scale
shape 1.0000000 0.3278074
scale 0.3278074 1.0000000
denscomp(fitw, addlegend=FALSE, main="Histogram and Weibull Fit for Corrected Windspeeds")

plot(x=MPC$windspeed_mps, y=MPC$design_sentout_10min_kwh, type="p", pch=4, main="Manufacturers Power Curve\n with Corrected Empirical Overlay", xlab="Windspeed (mps)", ylab="Energy (kWh/10min)", xlim=c(0,20))
points(y=dat$energy_sentout_10min_kwh, x=dat$corrected_wind_mps, col="green", cex=0.1, pch=20)
points(y=dat$uncurtailed_10min_kwh, x=dat$corrected_wind_mps, col="red", cex=0.1, pch=20)
legend("bottomright", col=c("black", "green", "red"), pch=c(4, 20, 20), legend=c("Manufacturers Power Curve", "Curtailed Power Curve (with windspeed correction)","Uncurtailed Power Curve (with windspeed correction)") )

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnJtKGxpc3Q9bHMoKSkNCnNldHdkKCJEOi8vUHJvZHVjdCBGYWN0b3J5Ly8yNSBQb3dlciBDdXJ2ZSBBbmFseXNpcy8vIikNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkoZHBseXIpO2xpYnJhcnkoZGF0YS50YWJsZSk7bGlicmFyeShBbWVsaWEpDQpgYGANCg0KDQpHbGltcHNlIG9mIERhdGE6ICANCmBgYHtyfQ0KZGF0YTwtcmVhZC5jc3YoZmlsZT0iV0lORF9WWEVfMjAxMy5jc3YiLCBoZWFkZXI9VFJVRSkNCiNkYXRhJFBDVGltZVN0YW1wPSBkbXlfaG0oIGFzLmNoYXJhY3RlcihkYXRhJFBDVGltZVN0YW1wKSkgDQpoZWFkKGRhdGEpDQpgYGANCg0KYGBge3J9DQojIGNhdCgiVGhlIERhdGEgaXMgb2Y6IixtYXgoZGF0YSRQQ1RpbWVTdGFtcCxuYS5ybSA9IFRSVUUpLW1pbihkYXRhJFBDVGltZVN0YW1wLG5hLnJtID0gVFJVRSksIkRheXMiLCJEYXRlIEZyb206IixtaW4oZGF0YSRQQ1RpbWVTdGFtcCxuYS5ybSA9IFRSVUUpLCJUbzoiLG1heChkYXRhJFBDVGltZVN0YW1wLG5hLnJtID0gVFJVRSkpDQojIG1pbihkYXRhJFBDVGltZVN0YW1wLG5hLnJtID0gVFJVRSkNCiMgbWF4KGRhdGEkUENUaW1lU3RhbXAsbmEucm0gPSBUUlVFKS1taW4oZGF0YSRQQ1RpbWVTdGFtcCxuYS5ybSA9IFRSVUUpDQoNCiMgY2F0KCJUaGUgRGF0YSBpcyBvZjoiLG1heChkYXRhJFBDVGltZVN0YW1wLG5hLnJtID0gVFJVRSktbWluKGRhdGEkUENUaW1lU3RhbXAsbmEucm0gPSBUUlVFKSwiRGF5cyBGcm9tXG4iKQ0KIyBtaW4oZGF0YSRQQ1RpbWVTdGFtcCxuYS5ybSA9IFRSVUUpDQpgYGANCg0KYGBge3J9DQojIGFzc2lnbiBuZXcgY29sdW1uIG5hbWVzIHRvIGJlIG1vcmUgc3VjY2ludCAoYXZvaWQgc3BhY2VzLCB1c2UgXyBvciAuIHRvIHNlcGVyYXRlIHdvcmRzKQ0KbmV3Lm5hbWVzPC1jKCJkYXRlX3RpbWUiLCJUMV9Qb3NzaWJsZV9Qb3dlciIsIlQyX1Bvc3NpYmxlX1Bvd2VyIiwiVDNfUG9zc2libGVfUG93ZXIiLCJUNF9Qb3NzaWJsZV9Qb3dlciIsIlQ1X1Bvc3NpYmxlX1Bvd2VyIiwiVDZfUG9zc2libGVfUG93ZXIiLCJUN19Qb3NzaWJsZV9Qb3dlciIsIlQxX1RvdGFsX0FjdGl2ZV9Qb3dlciIsIlQyX1RvdGFsX0FjdGl2ZV9Qb3dlciIsIlQzX1RvdGFsX0FjdGl2ZV9Qb3dlciIsIlQ0X1RvdGFsX0FjdGl2ZV9Qb3dlciIsIlQ1X1RvdGFsX0FjdGl2ZV9Qb3dlciIsIlQ2X1RvdGFsX0FjdGl2ZV9Qb3dlciIsIlQ3X1RvdGFsX0FjdGl2ZV9Qb3dlciIsIm1lYW5fd2luZF9tcHMiLCAibWluX3dpbmRfbXBzIiwgIm1heF93aW5kX21wcyIsICJjdW1fZW5lcmd5X2RlbGl2ZXJlZF9rd2giKQ0KIyBtYWtlIHN1cmUgdGhlIG5ldyBuYW1lcyBsaW5ldXAgcHJvcGVybHkgd2l0aCB0aGUgb25lcyB0aGV5J3JlIHJlcGxhY2luZy4uLg0KY2JpbmQobmFtZXMoZGF0YSksIG5ldy5uYW1lcykNCiMgaWYgeWVzLCByZXBsYWNlIGNvbHVtbiBuYW1lcyB3aXRoIG5ldyBuYW1lcw0KbmFtZXMoZGF0YSk8LW5ldy5uYW1lcw0KYGBgDQoNCiMjRGVzY3JpcHRpdmUgU3RhdGlzdGljOiAgDQoNCg0KDQpObyBvZiBEYXRhIFR5cGUgQXZhaWxhYmxlIGluIERhdGENCg0KYGBge3IsIGVjaG89RkFMU0V9DQojIEZvciBOdW1lcmljYWwgVmFyaWFibGVzDQpudW1zIDwtIHNhcHBseShkYXRhLCBpcy5udW1lcmljKQ0KbnVtX2RmIDwtIGRhdGFbLGMobnVtcyldDQoNCiMgRm9yIGNhdGVnb3JpY2FsIFZhcmlhYmxlcw0KY2F0IDwtIHNhcHBseShkYXRhLCBpcy5mYWN0b3IpDQpjYXRfZGYgPC0gZGF0YVssYyhjYXQpXQ0KDQpjYXQgKCIqKlZBUklBQkxFUyBpbiBEYXRhOioqIiwiXG5NYWluIGRhdGE6IixkaW0oZGF0YSlbMl0gLCJDb2x1bW5zIixkaW0oZGF0YSlbMV0sIk9ic2VydmF0aW9uIiwNCiAgICAgIlxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIiwNCiAgICAgIlxuREFUQSBUWVBFOiBOdW1lcmljYWwiLGRpbShudW1fZGYpWzJdICwiT2JzZXJ2YXRpb246IixkaW0obnVtX2RmKVsxXSwiXG5OdW1lcmljYWwgQ29sdW1zIGFyZTpcbiIsbmFtZXMobnVtX2RmKSwNCiAgICAgIlxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIiwNCiAgICAgICJcbkRBVEEgVFlQRTogQ2F0ZWdvcmljYWwgZGF0YSIsZGltKGNhdF9kZilbMl0gLCJPYnNlcnZhdGlvbjoiLGRpbShjYXRfZGYpWzFdLCJcbkNhdGVnb3JpY2FsIENvbHVtcyBhcmU6IixuYW1lcyhjYXRfZGYpLA0KICAgICAgIlxuLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIikNCg0KYGBgDQoNCg0KTWlzc2luZyB2YWx1ZSAgOiBCZWZvcmUgUHJlLXByb2Nlc3NpbmcNCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMX0NCm1pc3NtYXAoZGF0YSkNCmNhdCgiXG4gKioqKioqKioqKipOQSBWQUxVRSBDT0xVTU4gV0lTRSoqKioqKioqKioqIFxuIikNCnNvcnQoY29sU3VtcygoaXMubmEoZGF0YSkpKSxkZWNyZWFzaW5nID0gVFJVRSkNCmNhdCgiXG4gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuIikNCnN1bW1hcnkoZGF0YSkNCmBgYA0KDQpTdWJzZXQgRGF0YSB0eXBlDQpgYGB7cn0NCiMgc3Vic2V0IGRhdGEgYnkgdHlwZQ0KQ3VtdWxhdGl2ZTwtc3Vic2V0KGRhdGEsIHNlbGVjdD1jKDEsMTkpKQ0KUG9zc2libGU8LXN1YnNldChkYXRhLCBzZWxlY3Q9MTo4KQ0KQWN0aXZlPC1zdWJzZXQoZGF0YSwgc2VsZWN0PWMoMSw5OjE1KSkNCldpbmQ8LXN1YnNldChkYXRhLCBzZWxlY3Q9YygxLDE2OjE4KSkNCg0KIyBzZWxlY3QgZGF0YSB0byB1c2UNCmRhdDwtZGF0YSAjIGtlZXAgYWxsIHRoZSBkYXRhDQoNCiMgc2ltaWxhcmx5LCB5b3UgY291bGQgc2VsZWN0IGFsbCB0aGUgZGF0YSBiZXNpZGVzIEFjdGl2ZSBQb3dlciANCiMgZGF0PC1tZXJnZShQb3NzaWJsZSwgQ3VtdWxhdGl2ZSwgV2luZCkgIyBrZWVwIGFsbCBidXQgQWN0aXZlIFBvd2VyDQpgYGANCg0KUHJldmlldyB0aGUgcmF3IGRhdGENCg0KYGBge3J9DQpkaW0oZGF0KQ0Kc3RyKGRhdCkNCnN1bW1hcnkoZGF0KQ0KYGBgDQoNCg0KDQpDaGVja2luZyBmb3IgdGhlIE1pc3NpbmcgVmFsdWU6ICANCmBgYHtyfQ0KY2hlY2s8LWZ1bmN0aW9uKGRmKXsNCiAgIyBjb3VudCBOQSAobWlzc2luZyB2YWx1ZXMpDQogIE5Bczwtc3VtKGlzLm5hKGRmKSkNCiAgcHJpbnQocGFzdGUoIk1pc3NpbmcgVmFsdWVzOiIsIE5BcykpDQogIA0KICAjIGNvdW50IGluY29tcGxldGUgcmVjb3JkcyAocm93cyBjb250YWluaW5nIG1pc3NpbmcgdmFsdWVzKQ0KICBvazwtY29tcGxldGUuY2FzZXMoZGYpDQogIHByaW50KHBhc3RlKCJJbmNvbXBsZXRlIFJlY29yZHM6Iiwgc3VtKCEgb2spKSkNCiAgDQogICMgU2hvdyBpbmNvbXBsZXRlIHJlY29yZHMgKGlmIGxlc3MgdGhhbiAxMDAgTkFzKS4gDQogIGlmKE5BcyA+IDAgJiBOQXMgPD0gMTAwKSBwcmludCggZGZbd2hpY2goISBjb21wbGV0ZS5jYXNlcyhkZikpLCBdICkNCiAgDQogICMgSWYgbW9yZSB0aGFuIDEwMCwgc2hvdyBjb2x1bW4td2lzZSBkaXN0cmlidXRpb24gb2YgTkFzLg0KICBpZiAoTkFzID4gMTAwKSBoaXN0KHdoaWNoKGlzLm5hKGRmKSwgYXJyLmluZD1UUlVFKVssMl0sIHhsYWI9IkNvbHVtbiIsIGZyZXE9VFJVRSwgYnJlYWtzPTE6ZGltKGRmKVsyXSwgbWFpbj0iQ29sdW1uLXdpc2UgZGlzdHJpYnV0aW9uIG9mIG1pc3NpbmcgdmFsdWVzIikNCiAgfQ0KYGBgDQoNCg0KYGBge3J9DQpyZW1vdmVkPC1mdW5jdGlvbihucm93LCBucm93MSl7DQogIHByaW50KHBhc3RlKCJudW1iZXIgb2YgcmVjb3JkcyBSRU1PVkVEOiIsIG5yb3ctbnJvdzEsIHNlcD0iICIpKQ0KICBwcmludChwYXN0ZSgibnVtYmVyIG9mIHJlY29yZHMgUkVNQUlOSU5HOiIsIG5yb3cxLCBzZXA9IiAiKSkNCn0NCmBgYA0KDQoNCk5vdyBsZXRzIENoZWNrIG91ciBGdW5jdGlvbjogIA0KYGBge3IsIGVjaG89RkFMU0UsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTExLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KY2hlY2soZGF0KSAgIyBOQXMgcHJlc2VudC4gQ29sdW1uLXdpc2UgZGlzdHJpYnV0aW9uIGlzIHJlbGF0aXZlbHkgdW5pZm9ybSAoYmVzaWRlcyB3aW5kIHdpdGggcmVsYXRpdmVseSBmZXcpDQpgYGANCg0KUmVtb3ZlIG1pc3NpbmcgdmFsdWVzDQoNCkhlcmUgd2UgYXBwbHkgdGhlIG5hLm9taXQgZnVuY3Rpb24gKG5hdGl2ZSkgYW5kIHRoZSByZW1vdmVkIGZ1bmN0aW9uIHdlIGp1c3Qgd3JvdGUgKHVzZXIgZGVmaW5lZCkgIA0KYGBge3J9DQpucm93PC1kaW0oZGF0KVsxXSAjIHJlY29yZCB0aGUgZGltZW5zaW9ucyBvZiB0aGUgZGF0YSAoYmVmb3JlIHJlbW92aW5nIGFueXRoaW5nISkNCmRhdDwtbmEub21pdChkYXQpICMgb21pdCByb3dzIGNvbnRhaW5pbmcgbWlzc2luZyB2YWx1ZXMNCm5yb3cxPC1kaW0oZGF0KVsxXSAjIHJlY29yZCB0aGUgbmV3IGRpbWVuc2lvbnMgb2YgdGhlIGRhdGEgKGFmdGVyIHJlbW92aW5nIE5BcykNCnJlbW92ZWQobnJvdywgbnJvdzEpICMgY2hlY2sgaG93IG1hbnkgcmVjb3JkcyBoYXZlIGJlZW4gcmVtb3ZlZA0KYGBgDQoNCg0KQ29tcHV0ZSBlbmVyZ3kgc2VudG91dCBhbmQgZW5lcmd5IGJlbmNobWFya3MgIA0KDQotICBlbmVyZ3kgc2VudG91dCBpbiAxMCBtaW4gKEtXaCkgIA0KLSAga2luZXRpYyBlbmVyZ3kgYXZhaWxhYmxlIGFzIGEgZnVuY3Rpb24gb2Ygb2JzZXJ2ZWQgd2luZHNwZWVkcyAgDQotICBCZXR6IGxpbWl0ICANCi0gIHR1cmJpbmUgZWZmaWNpZW5jeSAgDQoNCmBgYHtyfQ0KIyBjb21wdXRlIGVuZXJneSBzZW50b3V0IGluIGVhY2ggdGltZWJsb2NrICgxMG1pbiBkdCkgdXNpbmd6IHRoZSBjdW11bGF0aXZlIGVuZXJneSBjb3VudGVyDQpuPC1sZW5ndGgoZGF0JGN1bV9lbmVyZ3lfZGVsaXZlcmVkX2t3aCkjNTExODMNCmE8LWRhdCRjdW1fZW5lcmd5X2RlbGl2ZXJlZF9rd2hbMTpuLTFdIw0KYjwtZGF0JGN1bV9lbmVyZ3lfZGVsaXZlcmVkX2t3aFsyOm5dDQpkaWZmPC1iLWEjIFRoaXMgaXMgKDJuZCBSb3cgLSAxc3QgUm93KQ0KZGF0JGVuZXJneV9zZW50b3V0XzEwbWluX2t3aDwtYyhkaWZmLDApICMgY2Fubm90IGNvbXB1dGUgZGlmZmVyZW5jZSBmb3IgbGFzdCB0aW1lc3RlcCwgc2V0IHRvIHplcm8uDQoNCiMgY29tcHV0ZSBraW5ldGljIGVuZXJneSBpbiB0aGUgd2luZCBhdCBlYWNoIHdpbmRzcGVlZA0KIyBXaW5kIFBvd2VyID0gKDEvMikqcmhvKmFyZWEqKHZlbG9jaXR5KV4zID0gW2tnL21eM10qW21eMl0qW20vc11eMyA9IFtrZyptXjIvc14zXSA9IFtrZyptXjIvc14yXVsxL3NdID0gW05ld3Rvbi1tZXRlcl0vW3NlY29uZF0gPSBbSm91bGVzL3NlY29uZF0gPSBbV2F0dHNdDQpyaG89MS4yMjUgIyBkZW5zaXR5IG9mIHdpbmQvQWlyIChrZy9tXjMpDQphcmVhPTIxNzQgIyBzd2VlcCBhcmVhIG9mIHdpbmQgdHVyYmluZXMgKG1eMikNCnR1cmJpbmVzPTcgIyBudW1iZXIgb2YgdHVyYmluZXMNCmM8LSgxLzIpKnJobyphcmVhDQpkYXQkd2luZF9wb3dlcl9rdzwtYyooZGF0JG1lYW5fd2luZF9tcHMpXjMqdHVyYmluZXMvMTAwMCAjIGtXIGF2ZyBwb3dlcg0KZGF0JHdpbmRfZW5lcmd5XzEwbWluX2t3aDwtYyooZGF0JG1lYW5fd2luZF9tcHMpXjMqdHVyYmluZXMvKDEwMDAqNikgIyBrV2ggaW4gMTAgbWluDQoNCiMgY29tcHV0ZSBiZXR6IGxpbWl0DQpiZXR6LmNvZWY8LSAxNi8yNw0KZGF0JGJldHpfbGltaXRfMTBtaW5fa3doPC1kYXQkd2luZF9lbmVyZ3lfMTBtaW5fa3doKmJldHouY29lZg0KDQojIGNvbXB1dGUgdHVyYmluZSBlZmZpY2llbmN5DQpkYXQkdHVyYmluZV9lZmY8LWRhdCRlbmVyZ3lfc2VudG91dF8xMG1pbl9rd2gvZGF0JHdpbmRfZW5lcmd5XzEwbWluX2t3aA0KDQojIGNvbXB1dGUgdG90YWwgUG9zc2libGUgUG93ZXINCnVuY3VydGFpbGVkX3Bvd2VyPC1hcHBseShYPWRhdFssMjo4XSwgTUFSR0lOPTEsIEZVTj1zdW0pDQpkYXQkdW5jdXJ0YWlsZWRfMTBtaW5fa3doPC0odW5jdXJ0YWlsZWRfcG93ZXIpLzYNCg0KIyBjb21wdXRlIGN1cnRhaWxtZW50DQpkYXQkY3VydGFpbG1lbnRfMTBtaW5fa3doPC1kYXQkdW5jdXJ0YWlsZWQtZGF0JGVuZXJneV9zZW50b3V0XzEwbWluX2t3aA0KYGBgDQoNCg0KDQpgYGB7ciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTF9DQojIENoZWNrIGZvciBOQQ0KY2hlY2soZGF0KQ0KYGBgDQoNCg0KYGBge3J9DQojIE5hTiBhcmlzZSB3aGVuIHdlIGNvbXB1dGUgdHVyYmluZSBlZmZpY2llbmN5IHdpdGggemVybyBpbiB0aGUgbnVtZXJhdG9yIGFuZCBkZW5vbWluYXRvciAoZHVlIHRvIHdpbmRzcGVlZCA9IDAgJiBlbmVyZ3lfc2VudG91dCA9IDApLiBTZXQgdGhlc2UgdG8gemVyby4NCm5hbjwtd2hpY2goZGF0JHR1cmJpbmVfZWZmID09ICJOYU4iKQ0KIyBsZW5ndGgobmFuKSAjIHNhbWUgYXMgbnVtYmVyIG9mIE5Bcw0KIyBoZWFkKGRhdFtuYW4sIF0pICMgbG9vayBhdCB0aGUgcm93cyBjb250YWluaW5nICJOYU4iDQpkYXQkdHVyYmluZV9lZmZbbmFuXTwtMA0KDQojIExpa2V3aXNlLCBJbmYgYXJpc2Ugd2hlbiB3ZSBjb21wdXRlIHR1cmJpbmUgZWZmaWNpZW5jeSB3aXRoIHplcm8gaW4gdGhlIGRlbm9taW5hdG9yIChkdWUgdG8gd2luZHNwZWVkID0gMCkuICBTZXQgdGhlc2UgdG8gemVyby4NCmluZjwtd2hpY2goZGF0JHR1cmJpbmVfZWZmID09ICJJbmYiKQ0KIyBsZW5ndGgoaW5mKSAjIG5vdCBjb3VudGVkIGluIE5BIGNvdW50LCBiZXdhcmUhDQojIGhlYWQoZGF0W2luZiwgXSkgIyBsb29rIGF0IHRoZSByb3dzIGNvbnRhaW5pbmcgIk5hTiINCmRhdCR0dXJiaW5lX2VmZltpbmZdPC0wDQoNCiMgY2hlY2sgYWdhaW4NCmNoZWNrKGRhdCkgIyBOQXMgaGF2ZSBiZWVuIHJlbW92ZWQuDQpgYGANCg0KDQoNCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBUbyBjb252ZXJ0IGEgY2hhcmFjdGVyIHJlcHJlc2VudGF0aW9uIG9mIGRhdGUtdGltZSBpbnRvIGEgUE9TSVggb2JqZWN0LCBlYWNoIGVudHJ5IG11c3QgY29uZm9ybSB0byBhIHN0YW5kYXJkIGZvcm1hdC4NCiMgSW4gdGhlIHJhdyBkYXRhLCBtaWRuaWdodCB2YWx1ZXMgYXJlIG1pc3NpbmcgdGhlIGhvdXIgYW5kIG1pbnV0ZS4gRml4IHRoZXNlOg0KZ2V0PC13aGljaChpcy5uYShhcy5QT1NJWGx0KGRhdCRkYXRlX3RpbWUsZm9ybWF0PSIlbS8lZC8leSAlSDolTSIpKSkgIyByZXR1cm4gcm93IGluZGV4IG9mIGRhdGVfdGltZSB0aGF0IGNhbm5vdCBiZSBjb2VyY2VkIHRvIFBPU0lYIG9iamVjdA0KZGF0JGRhdGVfdGltZVtnZXRdPC1wYXN0ZShkYXQkZGF0ZV90aW1lW2dldF0sICIwMDowMCIsIHNlcD0iICIpICMgYW1tZW5kIHRob3NlIHdpdGggdGhlIG1pc3NpbmcgaG91cjp0aW1lIGluZm8uDQoNCiMgY2hlY2sgaWYgbW9kaWZpZWQgZGF0ZV90aW1lIGNoYXJhY3RlciBzdHJpbmcgY2FuIGJlIGNvZXJjZWQgdG8gUE9TSVggd2l0aG91dCBnZW5lcmF0aW5nIE5BIHZhbHVlcy4NCnN1bShpcy5uYShhcy5QT1NJWGx0KGRhdCRkYXRlX3RpbWUsIGZvcm1hdD0iJW0vJWQvJXkgJUg6JU0iKSkpICMgemVybyBOQXMNCmBgYA0KDQoNCg0KYGBge3J9DQojIGNvdmVydCBtb2RpZmllZCBkYXRlX3RpbWUgY2hhcmFjdGVyIHN0cmluZyB0byBQT1NJWA0KZGF0JGRhdGVfdGltZSA8LSBhcy5QT1NJWGx0KGFzLmNoYXJhY3RlcihkYXQkZGF0ZV90aW1lKSwgZm9ybWF0PSIlbS0lZC0leSAlSDolTSIpDQojZGF0JGRhdGVfdGltZT0gbWR5X2htKGFzLmNoYXJhY3RlcihkYXQkZGF0ZV90aW1lKSkgDQpkYXQkbW9udGggPC0gY3V0KGRhdCRkYXRlX3RpbWUsIGJyZWFrcyA9ICJtb250aCIpDQpkYXQkd2VlayA8LSBjdXQoZGF0JGRhdGVfdGltZSwgYnJlYWtzID0gIndlZWsiKQ0KZGF0JGRheSA8LSBjdXQoZGF0JGRhdGVfdGltZSwgYnJlYWtzID0gImRheSIpDQpkYXQkaG91ciA8LSBjdXQoZGF0JGRhdGVfdGltZSwgYnJlYWtzID0gImhvdXIiKQ0KDQojIGR1bW15PC1zdHJzcGxpdChhcy5jaGFyYWN0ZXIod2VlayksIHNwbGl0PSIgIikNCiMgd2VlazwtbGFwcGx5KGR1bW15LCAnW1snLCAxKSAjIGtlZXAgdGhlIGRhdGUsIGRyb3AgdGhlIHRpbWUNCiMgZGF0JHdlZWsgPC0gd2Vlaw0KIyANCiMgZHVtbXk8LXN0cnNwbGl0KGFzLmNoYXJhY3RlcihkYXkpLCBzcGxpdD0iICIpDQojIGRheTwtbGFwcGx5KGR1bW15LCAnW1snLCAxKSAjIGtlZXAgdGhlIGRhdGUsIGRyb3AgdGhlIHRpbWUNCiMgI2RhdCRkYXk8LWFzLmZhY3RvcihkYXkpDQojIGRhdCRkYXk8LWRheQ0KIyANCiMgZHVtbXk8LXN0cnNwbGl0KGFzLmNoYXJhY3Rlcihob3VyKSwgc3BsaXQ9IiAiKQ0KIyBob3VyPC1sYXBwbHkoZHVtbXksICdbWycsIDIpICMga2VlcCB0aGUgdGltZSwgZHJvcCB0aGUgZGF0ZQ0KIyBkYXQkaG91cjwtYXMuZmFjdG9yKGhvdXIpDQpgYGANCg0KDQoNCmBgYHtyLCBlY2hvPUZBTFNFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgZW5lcmd5IGRhdGENCmVuZXJneTwtc3Vic2V0KGRhdCwgc2VsZWN0PWMoImRheSIsICJlbmVyZ3lfc2VudG91dF8xMG1pbl9rd2giLCAid2luZF9lbmVyZ3lfMTBtaW5fa3doIiwgImJldHpfbGltaXRfMTBtaW5fa3doIiwgInVuY3VydGFpbGVkXzEwbWluX2t3aCIsICJjdXJ0YWlsbWVudF8xMG1pbl9rd2giKSkNCmxpYnJhcnkocGx5cikNCmVuZXJneTwtZGRwbHkoZW5lcmd5LCAuKGRheSksIG51bWNvbHdpc2Uoc3VtKSkNCg0KIyBwbG90IGVuZXJneSB2cyB0aW1lDQp0ZXN0PC1tZWx0KGVuZXJneSwgaWQudmFycz0oImRheSIpKQ0KZ2dwbG90KHRlc3QsIGFlcyh4PWRheSwgeT12YWx1ZS8xMF4zLCBncm91cD12YXJpYWJsZSwgY29sb3VyPXZhcmlhYmxlLCBsaW5ldHlwZT12YXJpYWJsZSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZT0iTVdoIHBlciBkYXkiKSArIA0KICBsYWJzKHRpdGxlPSJFbmVyZ3kgVGltZXNlcmllcyIpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShicmVha3M9dGVzdCRkYXlbc2VxKDEsIDM2MCwgYnk9NjApXSwgbGFiZWxzPWFiYnJldmlhdGUpDQpgYGANCg0KYGBge3IsIGVjaG89RkFMU0UsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTExLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBmYWNldCB3cmFwIGJ5IG1vbnRoDQplbmVyZ3k8LXN1YnNldChkYXQsIHNlbGVjdD1jKCJkYXkiLCJ3ZWVrIiwgIm1vbnRoIiwgImVuZXJneV9zZW50b3V0XzEwbWluX2t3aCIsICJ3aW5kX2VuZXJneV8xMG1pbl9rd2giLCAiYmV0el9saW1pdF8xMG1pbl9rd2giLCAidW5jdXJ0YWlsZWRfMTBtaW5fa3doIiwgImN1cnRhaWxtZW50XzEwbWluX2t3aCIpKQ0KZW5lcmd5PC1kZHBseShlbmVyZ3ksIC4oZGF5LCB3ZWVrLCBtb250aCksIG51bWNvbHdpc2Uoc3VtKSkNCg0KIyBwbG90IGVuZXJneSB2cyB0aW1lDQp0ZXN0PC1tZWx0KGVuZXJneSwgaWQudmFycz1jKCJkYXkiLCAid2VlayIsICJtb250aCIpKQ0KbGV2ZWxzKHRlc3QkbW9udGgpIDwtIG1vbnRoLm5hbWVbMToxMl0NCg0KZ2dwbG90KHRlc3QsIGFlcyh4PWRheSwgeT12YWx1ZS8xMF4zLCBncm91cD12YXJpYWJsZSwgY29sb3VyPXZhcmlhYmxlLCBsaW5ldHlwZT12YXJpYWJsZSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBmYWNldF93cmFwKH5tb250aCwgc2NhbGVzPSJmcmVlIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZT0iTVdoIHBlciBkYXkiKSArIA0KICBsYWJzKHRpdGxlPSJNb250aHdpc2UgRW5lcmd5IFRpbWVzZXJpZXMiKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHNjYWxlX3hfZGlzY3JldGUoYnJlYWtzPU5VTEwpDQpgYGANCg0KYGBge3IsIGVjaG89RkFMU0UsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTExLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBjaGVjayBmb3Igb3V0bGllcnMgaW4gdGhlIGVuZXJneSBkYXRhDQpzdW1tYXJ5KGRhdCRlbmVyZ3lfc2VudG91dF8xMG1pbl9rd2gpICMgcXVhbnRpbGVzDQpoaXN0KGRhdCRlbmVyZ3lfc2VudG91dF8xMG1pbl9rd2gpICAgICMgaGlzdG9ncmFtDQpgYGANCg0KYGBge3J9DQojIGxvb2tzIGZ1bm55Li4uDQoNCiMgbnVtYmVyIG9mIHJlY29yZHMgKHJvd3MpIGJlZm9yZSBmaWx0ZXJpbmcNCm5yb3c8LWRpbShkYXQpWzFdIA0KDQojIEZJTFRFUiAxOiByZW1vdmUgbmVnYXRpdmUgZW5lcmd5IHZhbHVlcw0KZGF0PC1zdWJzZXQoZGF0LCBkYXQkZW5lcmd5X3NlbnRvdXRfMTBtaW5fa3doID49IDApICMNCg0KIyBudW1iZXIgb2YgcmVjb3JkcyByZW1vdmVkDQpucm93MjwtZGltKGRhdClbMV0NCnJlbW92ZWQobnJvdywgbnJvdzIpDQpgYGANCg0KYGBge3J9DQojIEZpbHRlciBwb3NpdGl2ZSBlbmVyZ3kgdmFsdWVzIGJhc2VkIG9uIHBoeXNpY2FsIGtub3dsZWRnZSBvZiB0aGUgc3lzdGVtLiANCiMgRmlsdGVycyBlbnVtZXJhdGVkIGluIG9yZGVyIG9mIGluY3JlYXNpbmcgc3RyaW5nZW5jeToNCiMgRklMVEVSIDI6IHJlbW92ZSBlbmVyZ3kgdmFsdWVzIGJleW9uZCB3aGF0J3MgcG9zc2libGUgZ2l2ZW4gaW5zdGFsbGVkIGNhcGFjaXR5DQpjYXBhY2l0eTwtODUwKjcgIyA4NTAgS1cgcmF0ZWQgY2FwYWNpdHkgeCA3IHR1cmJpbmVzDQpjYXBhY2l0eV8xMG1pbl9rd2g8LWNhcGFjaXR5KigxMC82MCkgIyBtYXggZW5lcmd5IHNlbnRvdXQgaW4gMTAgbWludXRlcw0KZGF0PC1zdWJzZXQoZGF0LCBkYXQkZW5lcmd5X3NlbnRvdXRfMTBtaW5fa3doIDw9IGNhcGFjaXR5XzEwbWluX2t3aCkNCg0KIyAjIEZJTFRFUiAzOiByZW1vdmUgc3RhdGlzdGljYWwgb3V0bGllcnM6IHNldCBxdWFudGlsZXMgdG8ga2VlcA0KIyByYW5nZTwtcXVhbnRpbGUoZGF0JGVuZXJneV9zZW50b3V0XzEwbWluX2t3aCwgcHJvYnM9YygwLjAxLDAuOTkpKQ0KIyBkYXQ8LXN1YnNldChkYXQsIGRhdCRlbmVyZ3lfc2VudG91dF8xMG1pbl9rd2ggPiByYW5nZVsxXSkNCiMgZGF0PC1zdWJzZXQoZGF0LCBkYXQkZW5lcmd5X3NlbnRvdXRfMTBtaW5fa3doIDwgcmFuZ2VbMl0pDQoNCiMgIyBGSUxURVIgNDogcmVtb3ZlIGVuZXJneSB2YWx1ZXMgYmV5b25kIHdoYXQncyBwb3NzaWJsZSBnaXZlbiB3aW5kc3BlZWQgKGUuZy4ga2luZXRpYyBlbmVyZ3kgY29udGFpbmVkIGluIHdpbmQpDQojIGRhdDwtc3Vic2V0KGRhdCwgZGF0JGVuZXJneV9zZW50b3V0XzEwbWluX2t3aCA8PSBkYXQkd2luZF9lbmVyZ3lfMTBtaW5fa3doKQ0KIA0KIyAjIEZJTFRFUiA1OiByZW1vdmUgZW5lcmd5IHZhbHVlcyBiZXlvbmQgd2hhdCdzIHBvc3NpYmxlIGdpdmVuIHdpbmRzcGVlZCBBTkQgQmV0eiBsaW1pdCAoa2luZXRpYyBlbmVyZ3kgKjE2LzI3KQ0KIyBkYXQ8LXN1YnNldChkYXQsIGRhdCRlbmVyZ3lfc2VudG91dF8xMG1pbl9rd2ggPD0gZGF0JGJldHpfbGltaXRfMTBtaW5fa3doKQ0KDQojIG51bWJlciBvZiByZWNvcmRzIHJlbW92ZWQNCm5yb3czPC1kaW0oZGF0KVsxXQ0KcmVtb3ZlZChucm93MiwgbnJvdzMpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTExLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBjaGVjayB0aGUgcXVhbnRpbGVzIGFuZCBoaXN0b2dyYW0gYWdhaW4NCnN1bW1hcnkoZGF0JGVuZXJneV9zZW50b3V0XzEwbWluX2t3aCkNCmhpc3QoZGF0JGVuZXJneV9zZW50b3V0XzEwbWluX2t3aCkNCmBgYA0KDQoNCmBgYHtyLCBlY2hvPUZBTFNFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgVG90YWwgZW5lcmd5LCBwZXIgd2Vlaw0KZW5lcmd5PC1zdWJzZXQoZGF0LCBzZWxlY3Q9Yygid2VlayIsICJlbmVyZ3lfc2VudG91dF8xMG1pbl9rd2giLCAidW5jdXJ0YWlsZWRfMTBtaW5fa3doIiwgImN1cnRhaWxtZW50XzEwbWluX2t3aCIsICJiZXR6X2xpbWl0XzEwbWluX2t3aCIpKQ0KZW5lcmd5PC1kZHBseShlbmVyZ3ksIC4od2VlayksIG51bWNvbHdpc2Uoc3VtKSkNCg0KIyBwbG90IGVuZXJneSB2cyB0aW1lDQp0ZXN0PC1tZWx0KGVuZXJneSwgaWQudmFycz0oIndlZWsiKSkNCmdncGxvdCh0ZXN0LCBhZXMoeD13ZWVrLCB5PXZhbHVlLzEwXjMsIGdyb3VwPXZhcmlhYmxlLCBjb2xvdXI9dmFyaWFibGUsIGxpbmV0eXBlPXZhcmlhYmxlKSkgKw0KICBnZW9tX2xpbmUoKSArDQogIHNjYWxlX3lfY29udGludW91cyhuYW1lPSJNV2ggcGVyIHdlZWsiKSArIA0KICBsYWJzKHRpdGxlPSJFbmVyZ3kgVGltZXNlcmllcyIpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShicmVha3M9bGV2ZWxzKHRlc3Qkd2Vlaylbc2VxKDEsNTIsIGJ5PTgpXSwgbGFiZWxzPWFiYnJldmlhdGUpDQpgYGANCg0KDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgY2hlY2sgZm9yIG91dGxpZXJzIGluIHdpbmQgZGF0YQ0KIyBxdWFudGlsZXMNCnN1bW1hcnkoZGF0JG1lYW5fd2luZF9tcHMpIA0Kc3VtbWFyeShkYXQkbWluX3dpbmRfbXBzKQ0Kc3VtbWFyeShkYXQkbWF4X3dpbmRfbXBzKQ0KIyBoaXN0b3JncmFtcw0KaGlzdChkYXQkbWVhbl93aW5kX21wcywgbWFpbj0iSGlzdG9ncmFtIG9mIE1lYW4gV2luZHNwZWVkcyIpDQpoaXN0KGRhdCRtaW5fd2luZF9tcHMsIG1haW49Ikhpc3RvZ3JhbSBvZiBNaW4gV2luZHNwZWVkcyIpDQpoaXN0KGRhdCRtYXhfd2luZF9tcHMsIG1haW49Ikhpc3RvZ3JhbSBvZiBNYXggV2luZHNwZWVkcyIpDQpgYGANCg0KDQoNCmBgYHtyfQ0KZGF0PC1zdWJzZXQoZGF0LCBtZWFuX3dpbmRfbXBzID4gMC41KQ0KYGBgDQoNCg0KDQpgYGB7ciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp3aW5kPC1zdWJzZXQoZGF0LCBzZWxlY3Q9Yygid2VlayIsICJtZWFuX3dpbmRfbXBzIiwgIm1heF93aW5kX21wcyIsICJtaW5fd2luZF9tcHMiKSkNCndpbmQ8LWRkcGx5KHdpbmQsIC4od2VlayksIG51bWNvbHdpc2UobWVhbikpDQoNCiMgcGxvdCB3aW5kIHZzIHRpbWUNCnRlc3Q8LW1lbHQod2luZCwgaWQudmFycz0oIndlZWsiKSkNCmdncGxvdCh0ZXN0LCBhZXMoeD13ZWVrLCB5PXZhbHVlLCBncm91cD12YXJpYWJsZSwgY29sb3VyPXZhcmlhYmxlLCBsaW5ldHlwZT12YXJpYWJsZSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZT0iV2luZHNwZWVkIFttL3NdIikgKyANCiAgbGFicyh0aXRsZT0iV2luZCBUaW1lc2VyaWVzIikgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGJyZWFrcz1sZXZlbHModGVzdCR3ZWVrKVtzZXEoMSwzNjAsIGJ5PTMwKV0sIGxhYmVscz1hYmJyZXZpYXRlKQ0KYGBgDQoNCg0KDQpgYGB7ciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQplbmVyZ3k8LXN1YnNldChkYXQsIHNlbGVjdD1jKCJtZWFuX3dpbmRfbXBzIiwgImVuZXJneV9zZW50b3V0XzEwbWluX2t3aCIsICJ3aW5kX2VuZXJneV8xMG1pbl9rd2giLCAiYmV0el9saW1pdF8xMG1pbl9rd2giKSkNCmVuZXJneTwtbWVsdChlbmVyZ3ksIGlkLnZhcnM9KCJtZWFuX3dpbmRfbXBzIikpDQpnZ3Bsb3QoZW5lcmd5LCBhZXMoeD1tZWFuX3dpbmRfbXBzLCB5PXZhbHVlLCBncm91cD12YXJpYWJsZSwgY29sb3VyPXZhcmlhYmxlKSkgKyANCiAgZ2VvbV9wb2ludCgpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWU9IktXaCBpbiAxMG1pbiIsIGxpbWl0PWMoMCwgbWF4KGRhdCRlbmVyZ3lfc2VudG91dF8xMG1pbl9rd2gpKSkgKyANCiAgc2NhbGVfeF9jb250aW51b3VzKG5hbWU9IldpbmRzcGVlZCAobXBzKSIpICsgDQogIGxhYnModGl0bGU9IkVtcGlyaWNhbCBQb3dlciBDdXJ2ZSB3aXRoIEJldHogTGltaXQgYW5kIFRoZW9yZXRpY2FsIFdpbmQgRW5lcmd5IikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQoNCg0KYGBge3J9DQpkYXQ8LXN1YnNldChkYXQsIGRhdCRtaW5fd2luZF9tcHMgPiAzKQ0KZGF0PC1zdWJzZXQoZGF0LCBkYXQkbWF4X3dpbmRfbXBzIDwgMjUpDQpgYGANCg0KDQpgYGB7ciwgZWNobz1GQUxTRSwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQplbmVyZ3k8LXN1YnNldChkYXQsIHNlbGVjdD1jKCJtZWFuX3dpbmRfbXBzIiwgImVuZXJneV9zZW50b3V0XzEwbWluX2t3aCIsICJ3aW5kX2VuZXJneV8xMG1pbl9rd2giLCAiYmV0el9saW1pdF8xMG1pbl9rd2giKSkNCmVuZXJneTwtbWVsdChlbmVyZ3ksIGlkLnZhcnM9KCJtZWFuX3dpbmRfbXBzIikpDQpnZ3Bsb3QoZW5lcmd5LCBhZXMoeD1tZWFuX3dpbmRfbXBzLCB5PXZhbHVlLCBncm91cD12YXJpYWJsZSwgY29sb3VyPXZhcmlhYmxlKSkgKyANCiAgZ2VvbV9wb2ludCgpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWU9IktXaCBpbiAxMG1pbiIsIGxpbWl0PWMoMCwgbWF4KGRhdCRlbmVyZ3lfc2VudG91dF8xMG1pbl9rd2gpKSkgKyANCiAgc2NhbGVfeF9jb250aW51b3VzKG5hbWU9IldpbmRzcGVlZCAobXBzKSIpICsgDQogIGxhYnModGl0bGU9IkVtcGlyaWNhbCBQb3dlciBDdXJ2ZSB3aXRoIEJldHogTGltaXQgYW5kIFRoZW9yZXRpY2FsIFdpbmQgRW5lcmd5IikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQoNCmBgYHtyfQ0Kd3JpdGUuY3N2KGRhdCwgZmlsZT0iY2xlYW5fVlhFX3dpbmRfc3BlZWQuY3N2IikNCnNhdmUoZGF0LCBmaWxlPSJjbGVhbl9WWEVfd2luZF9zcGVlZC5yc2F2IikNCmBgYA0KDQoNCg0KYGBge3J9DQpmaWx0ZXI8LXdoaWNoKGRhdCRtZWFuX3dpbmRfbXBzIDwgMyB8IGRhdCRtZWFuX3dpbmRfbXBzID4gMjUpDQpkYXQkZW5lcmd5X3NlbnRvdXRfMTBtaW5fa3doW2ZpbHRlcl08LTANCmBgYA0KDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgZml0IGEgZGlzdHJpYnV0aW9uIHRvIHRoZSB3aW5kIHNwZWVkIGRhdGENCmxpYnJhcnkoZml0ZGlzdHJwbHVzKQ0KZGVzY2Rpc3QoZGF0JG1lYW5fd2luZF9tcHMpICMgaGV1cmlzdGljDQpgYGANCg0KDQoNCmBgYHtyfQ0KIyBiYXNlZCBvbiBoZXVyaXN0aWMsIGFuZCBrbm93bGVkZ2Ugb2YgdGhlIHN5c3RlbSwgZml0IGEgd2VpYnVsbCBkaXN0cmlidXRpb24NCndlaWJ1bGwuZml0PC1maXRkaXN0KGRhdCRtZWFuX3dpbmRfbXBzLCBkaXN0cj0id2VpYnVsbCIpDQpzdW1tYXJ5KHdlaWJ1bGwuZml0KQ0KYGBgDQoNCg0KYGBge3J9DQpwbG90KHdlaWJ1bGwuZml0LCBkZW1wPVRSVUUpDQpgYGANCg0KDQoNCmBgYHtyfQ0KIyBzdW1tYXRpb24gb2YgZW5lcmd5IHNlbnRvdXQsIGRpdmlkZWQgYnkgdGhlIG51bWJlciBvZiBob3VycywgeWllbGRzIGF2ZXJhZ2UgcG93ZXIgc2VudG91dC4NCmF2Zy5wb3dlcjwtc3VtKGRhdCRlbmVyZ3lfc2VudG91dF8xMG1pbl9rd2gpLyhkaW0oZGF0KVsxXS82KQ0KDQojIHN1bW1hdGlvbiBvZiBlbmVyZ3kgc2VudG91dCwgZGl2aWRlZCBieSB0aGUgbnVtYmVyIG9mIGhvdXJzKmNhcGFjaXR5LCB5aWVsZHMgY2FwYWNpdHkgZmFjdG9yLg0KYWN0dWFsLmNhcC5mYWN0b3I8LXN1bShkYXQkZW5lcmd5X3NlbnRvdXRfMTBtaW5fa3doKS8oODUwKjcqZGltKGRhdClbMV0vNikNCnVuY3VydGFpbGVkLmNhcC5mYWN0b3I8LXN1bShkYXQkdW5jdXJ0YWlsZWRfMTBtaW5fa3doKS8oODUwKjcqZGltKGRhdClbMV0vNikNCmJldHouY2FwLmZhY3Rvcjwtc3VtKGRhdCRiZXR6X2xpbWl0XzEwbWluX2t3aCkvKDg1MCo3KmRpbShkYXQpWzFdLzYpDQoNCiMgZGlzcGxheSB0aGUgcmVzdWx0cw0KZGF0YS5mcmFtZShhY3R1YWwuY2FwLmZhY3RvciwgdW5jdXJ0YWlsZWQuY2FwLmZhY3RvciwgYmV0ei5jYXAuZmFjdG9yKQ0KYGBgDQoNCmBgYHtyfQ0KbnJvdzwtZGltKGRhdClbMV0gIyBudW1iZXIgb2YgY29tcGxldGUgcmVjb3JkcyBCRUZPUkUgZmlsdGVyaW5nDQoNCiMgc2VsZWN0IHRoZSBudW1lcmljIG9yIGludGVnZXIgY2xhc3MgY29sdW1ucw0KYzwtc2FwcGx5KGRhdCwgY2xhc3MpICMgY2xhc3Mgb2YgZWFjaCBjb2x1bW4NCmNvbEluZGV4PC13aGljaChjPT0ibnVtZXJpYyIgfCBjPT0iaW50ZWdlciIpICMgY29sdW1uIGluZGV4IG9mIG51bWVyaWMgb3IgaW50ZWdlciB2ZWN0b3JzDQoNCiMgY29tcHV0ZSBjb2x1bW4td2lzZSBxdWFudGlsZXMNCiMgYXBwbHkoZGF0WywtMV0sIDIsIHN0YXRzOjpxdWFudGlsZSkNCnJhbmdlPC1hcHBseShkYXRbLGNvbEluZGV4XSwgMiwgZnVuY3Rpb24oeCkge3F1YW50aWxlKHgsIHByb2JzPWMoLjAxLCAuOTkpKSB9ICkNCg0KIyBnZXQgbmFtZXMgb2YgdGhlIGNvbHVtbnMgdGhhdCB3ZSB3YW50IHRvIGZpbHRlciBvbiAoYWxsIGJ1dCB0aGUgZmlyc3QgY29sdW1uKQ0KIyBuYW1lczwtY29sbmFtZXMoZGF0Wyxjb2xJbmRleF0pDQpuYW1lczwtbmFtZXMoY29sSW5kZXgpICMgaWRlbnRpY2FsIHRvIGFib3ZlDQoNCiMgaXRlcmF0ZSB0aHJvdWdoIGVhY2ggY29sdW1uLCBzdWJzZXR0aW5nIHRoZSBFTlRJUkUgREFUQS5GUkFNRSBieSByZW1vdmluZyByZWNvcmRzIChyb3dzKSBhc3NvY2lhdGVkIHdpdGggYW4gb3V0bGllciB2YWx1ZSBpbiB0aGF0IGNvbHVtbg0KZm9yKGkgaW4gMTpkaW0ocmFuZ2UpWzJdKXsNCiAgbjwtZGltKGRhdClbMV0gIyBudW1iZXIgb2Ygcm93cyBhdCBzdGFydA0KICBkYXQ8LXN1YnNldChkYXQsIGV2YWwocGFyc2UodGV4dD1uYW1lc1tpXSkpID4gcmFuZ2VbMSxpXSkgICMga2VlcCByZWNvcmRzIGFib3ZlIDFzdCBwZXJjZW50aWxlDQogIGRhdDwtc3Vic2V0KGRhdCwgZXZhbChwYXJzZSh0ZXh0PW5hbWVzW2ldKSkgPCByYW5nZVsyLGldKSAgIyBrZWVwIHJlY29yZHMgbGVzcyB0aGFuIDk5IHBlcmNlbnRpbGUNCiAgcDwtZGltKGRhdClbMV0gIyBudW1iZXIgb2Ygcm93cyBhdCBlbmQNCiAgcHJpbnQocGFzdGUobi1wLCAicmVjb3JkcyBkcm9wcGVkIHdoZW4gZmlsdGVyaW5nOiIsIHRvdXBwZXIobmFtZXNbaV0pLCBzZXA9IiAiKSkNCn0NCmBgYA0KDQoNCmBgYHtyfQ0KIyBudW1iZXIgb2YgcmVjb3JkcyByZW1vdmVkDQpucm93MjwtZGltKGRhdClbMV0NCnJlbW92ZWQobnJvdywgbnJvdzIpDQpgYGANCg0KDQpgYGB7cn0NCiMgYmFzZWQgb24gaGV1cmlzdGljLCBhbmQga25vd2xlZGdlIG9mIHRoZSBzeXN0ZW0sIGZpdCBhIHdlaWJ1bGwgZGlzdHJpYnV0aW9uDQp3ZWlidWxsLmZpdC4yPC1maXRkaXN0KGRhdCRtZWFuX3dpbmRfbXBzLCBkaXN0cj0id2VpYnVsbCIpDQpzdW1tYXJ5KHdlaWJ1bGwuZml0LjIpDQpgYGANCg0KDQoNCmBgYHtyfQ0KcGxvdChkYXQkbWVhbl93aW5kX21wcywgZGF0JHVuY3VydGFpbGVkXzEwbWluX2t3aCwgY29sPSJyZWQiLCBjZXg9MC4yLCBwY2g9MjAsIG1haW49IkN1cnRhaWxlZCBhbmQgVW5jdXJ0YWlsZWQgV2luZCBFbmVyZ3kgUHJvZHVjdGlvblxuU2FvIFZpY2VudGUsIENhcGUgVmVyZGUgKDIwMTMpIiwgeGxhYj0iV2luZHNwZWVkIChtcHMpIiwgeWxhYj0iRW5lcmd5IChrV2gvMTBtaW4pIikNCnBvaW50cyhkYXQkbWVhbl93aW5kX21wcywgZGF0JGVuZXJneV9zZW50b3V0XzEwbWluX2t3aCwgY29sPSJncmVlbiIsIGNleD0wLjIsIHBjaD0yMCkNCmxlZ2VuZCgidG9wbGVmdCIsIGxlZ2VuZD1jKCJFbmVyZ3kgUG9zc2libGUgKFVuY3VydGFpbGVkKSIsICJFbmVyZ3kgU2VudG91dCAoQ3VydGFpbGVkKSIpLCBjb2w9YygicmVkIiwgImdyZWVuIiksIHBjaD0yMCkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBsb2FkIHRoZSBtYW51ZmFjdHVyZXJzIHBvd2VyIGN1cnZlDQpNUEM8LXJlYWQudGFibGUoZmlsZT0idjUyLTg1MEtXLXBvd2VyLWN1cnZlLmNzdiIsIGhlYWRlcj1UUlVFLCBzdHJpcC53aGl0ZT1UUlVFLCBzZXA9IiwiKQ0KTVBDJGRlc2lnbl9zZW50b3V0XzEwbWluX2t3aDwtTVBDJHBvd2VyX2tXKjcvNiAjIGtXIHggKDcgdHVyYmluZXMpIHggKDEvNiBob3VyKQ0KTVBDPC1zdWJzZXQoTVBDLCB3aW5kc3BlZWRfbXBzPDI1KQ0KDQojIHBsb3QgdGhlIG1hbnVmYWN0dXJlcnMgcG93ZXIgY3VydmUNCnBsb3QoeD1NUEMkd2luZHNwZWVkX21wcywgeT1NUEMkZGVzaWduX3NlbnRvdXRfMTBtaW5fa3doLCB0eXBlPSJwIiwgcGNoPTQsIG1haW49Ik1hbnVmYWN0dXJlcnMgUG93ZXIgQ3VydmVcbiB3aXRoIEVtcGlyaWNhbCBPdmVybGF5IiwgeGxhYj0iV2luZHNwZWVkIChtcHMpIiwgeWxhYj0iRW5lcmd5IChrV2gvMTBtaW4pIiwgeGxpbT1yYW5nZShkYXQkbWVhbl93aW5kX21wcykpDQpwb2ludHMoeT1kYXQkZW5lcmd5X3NlbnRvdXRfMTBtaW5fa3doLCB4PWRhdCRtZWFuX3dpbmRfbXBzLCBjb2w9ImdyZWVuIiwgY2V4PTAuMSwgcGNoPTIwKQ0KcG9pbnRzKHk9ZGF0JHVuY3VydGFpbGVkXzEwbWluX2t3aCwgeD1kYXQkbWVhbl93aW5kX21wcywgY29sPSJyZWQiLCBjZXg9MC4xLCBwY2g9MjApDQpsZWdlbmQoImJvdHRvbXJpZ2h0IiwgY29sPWMoImJsYWNrIiwgImdyZWVuIiwgInJlZCIpLCBwY2g9Yyg0LCAyMCwgMjApLCBsZWdlbmQ9YygiTWFudWZhY3R1cmVycyBQb3dlciBDdXJ2ZSIsICJFbmVyZ3kgU2VudG91dCAoQ3VydGFpbGVkKSIsIkVuZXJneSBQb3NzaWJsZSAoVW5jdXJ0YWlsZWQpIikgKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCiMgRm9yIGVhY2ggb2JzZXJ2YXRpb24sIHN1cHBseSB0aGUgZW5lcmd5IHNlbnRvdXQgKGtXaCksIGZpbmQgdGhlIGNsb3Nlc3QgbWF0Y2hpbmcgdmFsdWUgb24gdGhlIHBvd2VyIGN1cnZlIChrVyksIHF1ZXJ5IHRoZSBjb3JyZXNwb25kaW5nIHdpbmQgc3BlZWQgZnJvbSB0aGUgcG93ZXIgY3VydmUgKG1wcykuIFRoaXMgYmVjb21lcyB0aGUgY29ycmVjdGVkIHdpbmRzcGVlZC4gQXBwbHkgdGhpcyB3aGVuZXZlciB0aGUgbWVhc3VyZWQgd2luZHNwZWVkIGlzIGxlc3MgdGhhbiB0aGUgd2luZHNwZWVkIGRpY3RhdGVkIGJ5IHRoZSBwb3dlciBjdXJ2ZS4gVGhpcyBzaGlmdHMgYWxsIHBvaW50cyB0aGF0IHdlcmUgbGVmdCBvZiB0aGUgcG93ZXIgY3VydmUsIG9udG8gdGhlIHBvd2VyIGN1cnZlLg0KDQojIG1hdGNoaW5nIHZlY3RvciBtdXN0IGJlIGluIGFzY2VuZGluZyBvcmRlcg0KTVBDPC1NUENbIG9yZGVyKE1QQyRwb3dlcl9rVywgZGVjcmVhc2luZz1GQUxTRSksIF0NCg0KIyBwYXNzIGVuZXJneSBzZW50b3V0IChrV2gpLCByZXR1cm4gcm93IGluZGV4IG9mIGNsb3Nlc3QgbWF0Y2hpbmcgdmFsdWUgb24gcG93ZXIgY3VydmUgKGtXKQ0KcG93ZXIubWF0Y2g8LWZpbmRJbnRlcnZhbCh4PWRhdCRlbmVyZ3lfc2VudG91dF8xMG1pbl9rd2gsIHZlYz1NUEMkcG93ZXJfa1csIGFsbC5pbnNpZGU9VFJVRSwgcmlnaHRtb3N0LmNsb3NlZD1UUlVFKSANCg0KIyByZWFkLW9mZiBjb3JyZXNwb25kaW5nIHdpbmQgc3BlZWQgZnJvbSB0aGUgcG93ZXIgY3VydmUgKG1wcykNCmNvcnJlY3RlZC53aW5kc3BlZWQ8LU1QQ1twb3dlci5tYXRjaCwgYygid2luZHNwZWVkX21wcyIpXQ0KDQojICMgdmlzdWFsbHkgaW5zcGVjdCBtYXRjaGVkIHZhbHVlcyB3aXRoIHZhbHVlcyBtYXRjaGVkIGFnYWluc3QgLS0+IExvb2tzIGdvb2QhDQojIHBsb3QoeD1NUENbcG93ZXIubWF0Y2gsIGMoIndpbmRzcGVlZF9tcHMiKV0sIHk9TVBDW3Bvd2VyLm1hdGNoLCBjKCJwb3dlcl9rVyIpXSwgY29sPSJyZWQiLCBjZXg9MywgeGxhYj0id2luZHNwZWVkX21wcyIsIHlsYWI9InBvd2VyX2tXIikNCiMgcG9pbnRzKHg9TVBDJHdpbmRzcGVlZF9tcHMsIHk9TVBDJHBvd2VyX2tXLCBjb2w9ImJsYWNrIiwgY2V4PTAuMSkNCg0KIyBjcmVhdGUgYSBuZXcgY29sdW1uIGNhbGxlZCAiY29ycmVjdGVkX3dpbmRfbXBzIg0KZGF0JGNvcnJlY3RlZF93aW5kX21wczwtY29ycmVjdGVkLndpbmRzcGVlZA0KDQojIGlmIHRoZSBvYnNlcnZlZCBtZWFuIHdpbmRzcGVlZCBpcyBncmVhdGVyIHRoYW4gdGhlIHdpbmRzcGVlZCBkaWN0YXRlZCBieSB0aGUgcG93ZXIgY3VydmUsIHRoYXQncyBPSyAtLT4ga2VlcCB0aGUgcmVjb3JkZWQgbWVhbiB3aW5kc3BlZWQuIA0Ka2VlcDwtd2hpY2goZGF0JG1lYW5fd2luZF9tcHMgPj0gZGF0JGNvcnJlY3RlZF93aW5kX21wcykNCmRhdCRjb3JyZWN0ZWRfd2luZF9tcHNba2VlcF08LWRhdCRtZWFuX3dpbmRfbXBzW2tlZXBdDQojIFdlIGRvIHRoaXMgYmVjYXVzZSBpdCBpcyBwb3NzaWJsZSB0aGF0IGxlc3MgcG93ZXIgd2FzIHByb2R1Y2VkIGF0IGEgZ2l2ZW4gd2luZHNwZWVkIHRoYW4gZGljdGF0ZWQgYnkgdGhlIHBvd2VyIGN1cnZlLCBkdWUgdG8gY3VydGFpbG1lbnQuDQojIGhvd2V2ZXIgaXQgaXMgKm5vdCogcG9zc2libGUgdG8gcHJvZHVjZSBtb3JlIHBvd2VyIGF0IGEgZ2l2ZW4gd2luZHNwZWVkIHRoYW4gZGljdGF0ZWQgYnkgdGhlIHBvd2VyIGN1cnZlLiBIZW5jZSB3ZSBjb3JyZWN0IHRob3NlIHZhbHVlcywgb25seS4NCmBgYA0KDQoNCg0KYGBge3J9DQpmaXR3PC1maXRkaXN0KGRhdCRtZWFuX3dpbmRfbXBzLCAid2VpYnVsbCIpDQpzdW1tYXJ5KGZpdHcpDQpgYGANCg0KDQoNCmBgYHtyfQ0KZGVuc2NvbXAoZml0dywgYWRkbGVnZW5kPUZBTFNFLCBtYWluPSJIaXN0b2dyYW0gYW5kIFdlaWJ1bGwgRml0IGZvciBVbmNvcnJlY3RlZCBXaW5kc3BlZWRzIikNCmBgYA0KDQoNCg0KYGBge3J9DQpmaXR3PC1maXRkaXN0KGRhdCRjb3JyZWN0ZWRfd2luZF9tcHMsICJ3ZWlidWxsIikNCnN1bW1hcnkoZml0dykNCmBgYA0KDQpgYGB7cn0NCmRlbnNjb21wKGZpdHcsIGFkZGxlZ2VuZD1GQUxTRSwgbWFpbj0iSGlzdG9ncmFtIGFuZCBXZWlidWxsIEZpdCBmb3IgQ29ycmVjdGVkIFdpbmRzcGVlZHMiKQ0KYGBgDQoNCg0KYGBge3J9DQpwbG90KHg9TVBDJHdpbmRzcGVlZF9tcHMsIHk9TVBDJGRlc2lnbl9zZW50b3V0XzEwbWluX2t3aCwgdHlwZT0icCIsIHBjaD00LCBtYWluPSJNYW51ZmFjdHVyZXJzIFBvd2VyIEN1cnZlXG4gd2l0aCBDb3JyZWN0ZWQgRW1waXJpY2FsIE92ZXJsYXkiLCB4bGFiPSJXaW5kc3BlZWQgKG1wcykiLCB5bGFiPSJFbmVyZ3kgKGtXaC8xMG1pbikiLCB4bGltPWMoMCwyMCkpDQpwb2ludHMoeT1kYXQkZW5lcmd5X3NlbnRvdXRfMTBtaW5fa3doLCB4PWRhdCRjb3JyZWN0ZWRfd2luZF9tcHMsIGNvbD0iZ3JlZW4iLCBjZXg9MC4xLCBwY2g9MjApDQpwb2ludHMoeT1kYXQkdW5jdXJ0YWlsZWRfMTBtaW5fa3doLCB4PWRhdCRjb3JyZWN0ZWRfd2luZF9tcHMsIGNvbD0icmVkIiwgY2V4PTAuMSwgcGNoPTIwKQ0KbGVnZW5kKCJib3R0b21yaWdodCIsIGNvbD1jKCJibGFjayIsICJncmVlbiIsICJyZWQiKSwgcGNoPWMoNCwgMjAsIDIwKSwgbGVnZW5kPWMoIk1hbnVmYWN0dXJlcnMgUG93ZXIgQ3VydmUiLCAiQ3VydGFpbGVkIFBvd2VyIEN1cnZlICh3aXRoIHdpbmRzcGVlZCBjb3JyZWN0aW9uKSIsIlVuY3VydGFpbGVkIFBvd2VyIEN1cnZlICh3aXRoIHdpbmRzcGVlZCBjb3JyZWN0aW9uKSIpICkNCmBgYA0KDQo=