Exploratory Analysis

This notebook uses the Solar Power Generation Data containing data of two solar power plant, where each plant has a power generation dataset and a sensor readings dataset.

The objective of this exercise is to use various data visualization techniques to explore the power generation and sensor readings of two solar power plants and their differences. The practice goals include data wrangling using data.table package and plotting against time/date/datetime.

Reference notebooks:
* Desc. Analytics of Solar Panels with R and Plotly
* Ensemble learning lib: MLens (Python)
* How to manage a solar power plant (Python)
* Solar Power Machine Learning I (Python)
* Solar_power_plant_analysis (Python)

Load libraries

library(janitor)
library(ggpubr)
library(lubridate)
library(tidyverse)
library(data.table)
library(PerformanceAnalytics)
library(corrplot)
view(data$p1_gen)

Import data

# load all data
load_p1_gen <- function() {
    data <- fread("Plant_1_Generation_Data.csv")
    return(data)
}
load_p1_weather <- function() {
    data <- fread("Plant_1_Weather_Sensor_Data.csv")
    return(data)
}
load_p2_gen <- function() {
    data <- fread("Plant_2_Generation_Data.csv")
    return(data)
}
load_p2_weather <- function() {
    data <- fread("Plant_2_Weather_Sensor_Data.csv")
    return(data)
}


# load data in a list 
load <- function() {
    data <- list()
    data$p1_gen <- load_p1_gen()
    data$p1_weather <- load_p1_weather()
    data$p2_gen <- load_p2_gen()
    data$p2_weather <- load_p2_weather()
    return(data)
}
data <- load()

Data cleaning

# clean names 
names(data$p1_gen) <- tolower(names(data$p1_gen))
names(data$p1_weather) <- tolower(names(data$p1_weather))
names(data$p2_gen) <- tolower(names(data$p2_gen))
names(data$p2_weather) <- tolower(names(data$p2_weather))

# parse datetime and factors 
clean_p1_gen <- function(data) {
    data[, date_time := dmy_hm(date_time)]
    data[, plant_id := as.factor(plant_id)]
    data[, source_key := as.factor(source_key)]
}

# parse datetime and factors 
clean_data <- function(data) {
    data[, date_time := as_datetime(date_time)]
    data[, plant_id := as.factor(plant_id)]
    data[, source_key := as.factor(source_key)]
}

# clean all data
clean <- function(data) {
    data$p1_gen <- clean_p1_gen(data$p1_gen)
    data$p1_weather <- clean_data(data$p1_weather)
    data$p2_gen <- clean_data(data$p2_gen)
    data$p2_weather <- clean_data(data$p2_weather)
    return(data)
}

clean_data = clean(data)
summarise = function(data){lapply(data,summary)}
summarise(clean_data)
$p1_gen
   date_time                      plant_id               source_key       dc_power        ac_power        daily_yield    total_yield     
 Min.   :2020-05-15 00:00:00   4135001:68778   bvBOhCH3iADSZry: 3155   Min.   :    0   Min.   :   0.00   Min.   :   0   Min.   :6183645  
 1st Qu.:2020-05-24 00:45:00                   1BY6WEcLGh8j5v7: 3154   1st Qu.:    0   1st Qu.:   0.00   1st Qu.:   0   1st Qu.:6512003  
 Median :2020-06-01 14:30:00                   7JYdWkrLSPkdwr4: 3133   Median :  429   Median :  41.49   Median :2659   Median :7146685  
 Mean   :2020-06-01 08:02:49                   VHMLBKoKgIrUVDU: 3133   Mean   : 3147   Mean   : 307.80   Mean   :3296   Mean   :6978712  
 3rd Qu.:2020-06-09 20:00:00                   ih0vzX44oOqAx2f: 3130   3rd Qu.: 6367   3rd Qu.: 623.62   3rd Qu.:6274   3rd Qu.:7268706  
 Max.   :2020-06-17 23:45:00                   ZnxXDlPa8U1GXgE: 3130   Max.   :14471   Max.   :1410.95   Max.   :9163   Max.   :7846821  
                                               (Other)        :49943                                                                     

$p1_weather
   date_time                      plant_id              source_key   ambient_temperature module_temperature  irradiation     
 Min.   :2020-05-15 00:00:00   4135001:3182   HmiyD2TTLFNqkNe:3182   Min.   :20.40       Min.   :18.14      Min.   :0.00000  
 1st Qu.:2020-05-23 22:48:45                                         1st Qu.:22.71       1st Qu.:21.09      1st Qu.:0.00000  
 Median :2020-06-01 09:52:30                                         Median :24.61       Median :24.62      Median :0.02465  
 Mean   :2020-06-01 05:52:22                                         Mean   :25.53       Mean   :31.09      Mean   :0.22831  
 3rd Qu.:2020-06-09 16:56:15                                         3rd Qu.:27.92       3rd Qu.:41.31      3rd Qu.:0.44959  
 Max.   :2020-06-17 23:45:00                                         Max.   :35.25       Max.   :65.55      Max.   :1.22165  

$p2_gen
   date_time                      plant_id               source_key       dc_power         ac_power       daily_yield    
 Min.   :2020-05-15 00:00:00   4136001:67698   81aHJ1q11NBPMrL: 3259   Min.   :   0.0   Min.   :   0.0   Min.   :   0.0  
 1st Qu.:2020-05-23 21:00:00                   9kRcWv60rDACzjR: 3259   1st Qu.:   0.0   1st Qu.:   0.0   1st Qu.: 272.8  
 Median :2020-06-01 23:00:00                   LlT2YUhhzqhg5Sw: 3259   Median :   0.0   Median :   0.0   Median :2911.0  
 Mean   :2020-06-01 10:44:33                   LYwnQax7tkwH5Cb: 3259   Mean   : 246.7   Mean   : 241.3   Mean   :3294.9  
 3rd Qu.:2020-06-09 23:30:00                   oZZkBaNadn6DNKz: 3259   3rd Qu.: 446.6   3rd Qu.: 438.2   3rd Qu.:5534.0  
 Max.   :2020-06-17 23:45:00                   PeE6FRyGXUgsRhN: 3259   Max.   :1420.9   Max.   :1385.4   Max.   :9873.0  
                                               (Other)        :48144                                                     
  total_yield       
 Min.   :0.000e+00  
 1st Qu.:1.996e+07  
 Median :2.826e+08  
 Mean   :6.589e+08  
 3rd Qu.:1.348e+09  
 Max.   :2.248e+09  
                    

$p2_weather
   date_time                      plant_id              source_key   ambient_temperature module_temperature  irradiation     
 Min.   :2020-05-15 00:00:00   4136001:3259   iq8k7ZNt4Mwm3w0:3259   Min.   :20.94       Min.   :20.27      Min.   :0.00000  
 1st Qu.:2020-05-23 12:07:30                                         1st Qu.:24.60       1st Qu.:23.72      1st Qu.:0.00000  
 Median :2020-06-01 00:00:00                                         Median :26.98       Median :27.53      Median :0.01904  
 Mean   :2020-06-01 00:04:35                                         Mean   :28.07       Mean   :32.77      Mean   :0.23274  
 3rd Qu.:2020-06-09 12:07:30                                         3rd Qu.:31.06       3rd Qu.:40.48      3rd Qu.:0.43872  
 Max.   :2020-06-17 23:45:00                                         Max.   :39.18       Max.   :66.64      Max.   :1.09877  
# drop singular variables
drop_sv <- function(data) {
    data$p1_gen[, plant_id := NULL]
    data$p1_weather[, plant_id := NULL][, source_key := NULL]
    data$p2_gen[, plant_id := NULL]
    data$p2_weather[, plant_id := NULL][, source_key := NULL]
    return(data)
}

data2 = drop_sv(clean_data)

Pairplot

pairplot <- function(data2) {
  data2[, date_time := NULL]
  chart.Correlation(data2[,-1], histogram=TRUE, method=c("spearman"))
}

pairplot(data2$p1_gen)

Distribution

# function for generation distribution
plt_gen_dist <- function(data) {
    x <- list(
      title = "Value"
    )
    y <- list(
      title = "Count"
    )

    ac_power <- ggplot(data, aes(x = ac_power)) + geom_histogram(alpha=0.7, fill="#457b9d")
    dc_power <- ggplot(data, aes(x = dc_power)) + geom_histogram(alpha=0.7, fill="#457b9d")
    daily_yield <- ggplot(data, aes(x = daily_yield)) + geom_histogram(alpha=0.7, fill="#457b9d")
    total_yield <- ggplot(data, aes(x =  total_yield)) + geom_histogram(alpha=0.7, fill="#457b9d")

    ggarrange(ac_power, dc_power, daily_yield, total_yield, nrow = 2, ncol= 2)
}

#function for weather distribution
plt_wx_dist <- function(data) {
    x <- list(
      title = "Value"
    )
    y <- list(
      title = "Number of occurences"
    )

    ambient <- ggplot(data, aes(x= ambient_temperature)) + geom_histogram(alpha=0.7, fill="#faa307") 
    module <- ggplot(data, aes(x= module_temperature)) + geom_histogram(alpha=0.7, fill="#faa307")
    irradiation <- ggplot(data, aes(x= irradiation)) + geom_histogram(alpha=0.7, fill="#faa307")

    ggarrange(ambient,  irradiation, module, nrow=2, ncol=2)
}

Plant 1

fig1a = plt_gen_dist(data2$p1_gen)
annotate_figure(fig1a, top = text_grob("Plant 1: Power generation", size = 12))

fig1b = plt_wx_dist(data2$p1_weather)
annotate_figure(fig1b, top = text_grob("Plant 1: Sensor readings ", size = 12))

Plant 2

fig2a = plt_gen_dist(data2$p2_gen)
annotate_figure(fig2a, top = text_grob("Plant 2: Solar power generation", size = 12))

fig2b = plt_wx_dist(data2$p2_weather)
annotate_figure(fig2b, top = text_grob("Plant 2: Sensor readings ", size = 12))

Daily summed yield

# function
get_daily_summed_yield <- function(data) {
    data[, day := date(date_time)]
    data[, .(daily_yield_sum = sum(daily_yield)), by = day]  
}

plt_daily_yield <- function(data) {
    x <- list(
      title = "Day"
    )
    y <- list(
      title = "Summed daily yield"
    )
    plot <- ggplot(data, aes(x=day,y=daily_yield_sum)) + geom_point() + geom_smooth(method=lm, se=FALSE)
    plot
}

daily_summed_yield_p1 <- get_daily_summed_yield(data2$p1_gen)
daily_summed_yield_p2 <- get_daily_summed_yield(data2$p2_gen)
fig3a = plt_daily_yield(daily_summed_yield_p1) + labs(title="Plant 1: Daily summed yield")
fig3b = plt_daily_yield(daily_summed_yield_p2) + labs(title="Plant 2: Daily summed yield")
ggarrange(fig3a,fig3b, ncol=2, nrow=1)

Data preparation

# plant 1
# reduced_p1_gen 
reduced_p1_gen = data2$p1_gen
reduced_p1_gen2 = reduced_p1_gen[,lapply(.SD, sum, na.rm=TRUE), by=list(date_time), .SDcols=c("dc_power","ac_power","daily_yield","total_yield")] 

reduced_p1_gen2[,date:=date(date_time)]
reduced_p1_gen2[,time:=as.ITime(date_time)]
reduced_p1_gen2$time = as.POSIXct(strptime(reduced_p1_gen2$time, format="%H:%M:%S"))

# merge reduced_p1_gen and p1_wx
p1_wx= data2$p1_weather

setkey(p1_wx,date_time)
setkey(reduced_p1_gen2,date_time)
p1= p1_wx[reduced_p1_gen2, nomatch=0]
dim(p1)
[1] 3157   10
# plant 2
# merge plant 2 data
reduced_p2_gen = data2$p2_gen
reduced_p2_gen2 = reduced_p2_gen[,lapply(.SD, sum, na.rm=TRUE), by=list(date_time), .SDcols=c("dc_power","ac_power","daily_yield","total_yield")]

reduced_p2_gen2[,date:=date(date_time)]
reduced_p2_gen2[,time:=as.ITime(date_time)]
reduced_p2_gen2$time = as.POSIXct(strptime(reduced_p2_gen2$time, format="%H:%M:%S"))

# merge p2 gen with p2 wx 
p2_wx= data2$p2_weather
setkey(p2_wx,date_time)
setkey(reduced_p2_gen2,date_time)
p2= p2_wx[reduced_p2_gen2, nomatch=0]
dim(p2)
[1] 3259   10

Plant 1

P1: dc power

# dc_power (time)
xlabel= c("00:00:00","06:00:00","12:00:00","18:00:00")
dc1a = ggplot(p1, aes(x=time, y=dc_power)) + geom_point(size=0.2, color="#457b9d",alpha=0.7) + stat_summary(aes(y=dc_power,group=1), fun.y=mean, color="red",geom="line",group=1) + scale_x_datetime(date_labels="%H:%S")
# dc_power (daily)
dc1b = ggplot(p1, aes(x=date, y=dc_power)) + geom_col(fill="#457b9d") + theme(axis.text.x=element_text(angle=45))
ggarrange(dc1a, dc1b, labels = c("a", "b"), ncol=2, nrow=1)

  • DC power
    • plant 1 produces power from ~06.00 to ~18.00
    • maximum power on May 25 2020

P1: daily yield

# daily_yield
dy1a =ggplot(p1, aes(x=time, y=daily_yield)) + geom_point(size=0.2, color="#457b9d",alpha=0.5) + stat_summary(aes(y=daily_yield,group=1), fun.y=mean, color="red",geom="line",group=1) + scale_x_datetime(date_labels="%H:%S")
# daily_yield facet
dy1b = ggplot(p1, aes(x=time, y=daily_yield)) + geom_point(size=0.2) + facet_wrap(~date) + scale_y_continuous(breaks=c(0, 100000, 200000)) + theme(axis.text.x=element_blank()) 
ggarrange(dy1a,dy1b,labels = c("a", "b"), nrow=1, ncol=2)

# boxplot
dy1c = ggplot(p1, aes(x=factor(date),y=daily_yield)) + geom_boxplot() + theme_bw() + theme(axis.text.x=element_text(angle=90)) + labs(x="date")
# barplot
dy1d = ggplot(p1, aes(x=factor(date),y=daily_yield)) + geom_col(fill="#457b9d") + theme_bw() + theme(axis.text.x=element_text(angle=90)) + labs(x="date")
ggarrange(dy1c,dy1d, labels = c("c", "d"),nrow=1, ncol=2 )

  • Daily yield
    • daily yield decreases after 18.00.
    • there are missing data on some dates for example, 2020-05-20.
    • daily yield changes daily, and there are no outliers observed.
    • the sum of daily yield changes daily.

P1: ambient temperature

# ambient temp (time)
at1a = ggplot(p1, aes(x=time, y=ambient_temperature)) + geom_point(size=0.2, color="#457b9d",alpha=0.5) + stat_summary(aes(y=ambient_temperature,group=1), fun.y=mean, color="red",geom="line",group=1) + scale_x_datetime(date_labels="%H:%S")
# boxplot
at1b = ggplot(p1, aes(x=factor(date),y=ambient_temperature)) + geom_boxplot() + theme_bw() + theme(axis.text.x=element_text(angle=90)) + labs(x="date", y="temperature (°C)")
# lineplots
dat = p1[,.(mean_at=mean(ambient_temperature)), .(date)]
at1c = ggplot(dat, aes(x=date, y=mean_at)) + geom_line(color="#457b9d") + labs(y="mean_ambient_temperature (°C)")

cols= c('mean_at')
dat[,(paste0(cols, "_pctChange")) := lapply(.SD, function(col){ 
      (col-shift(col,1,type = "lag"))/shift(col,1,type = "lag")
  }), .SDcols=cols]
at1d = ggplot(dat, aes(x=date, y=mean_at_pctChange)) + geom_line(color="#faa307") + scale_y_continuous(labels=scales::percent) 

ggarrange(at1a,at1b, labels = c("a", "b"), ncol=2, nrow=1)

ggarrange(at1c,at1d, labels = c("c", "d"),ncol=2, nrow=1)

  • Ambient temperature
    • the ambient temperature of records in May is higher than June.
    • the range of ambient temperature percentage change is larger in May than June.
# time series plot
# sesonality 7 days
ts_at = ts(dat$mean_at, frequency = 7)
stl_at = stl(ts_at, "periodic")
plot(stl_at)

P1: module temperature

mt1a = ggplot(p1, aes(x=time, y=module_temperature)) + geom_point(size=0.2, color="#457b9d",alpha=0.7) + stat_summary(aes(y=module_temperature,group=1), fun.y=mean, color="red",geom="line",group=1) + scale_x_datetime(date_labels="%H:%S")
# boxplot
mt1b = ggplot(p1, aes(x=factor(date),y=module_temperature)) + geom_boxplot() + theme_bw() + theme(axis.text.x=element_text(angle=90)) + labs(x="date", y="temperature (°C)")
# lineplots
dmt = p1[,.(mean_mt=mean(module_temperature)), .(date)]
mt1c = ggplot(dmt, aes(x=date, y=mean_mt)) + geom_line(color="#457b9d") + labs(y="mean_ambient_temperature (°C)")

cols= c('mean_mt')
dmt[,(paste0(cols, "_pctChange")) := lapply(.SD, function(col){ 
      (col-shift(col,1,type = "lag"))/shift(col,1,type = "lag")
  }), .SDcols=cols]
mt1d = ggplot(dmt, aes(x=date, y=mean_mt_pctChange)) + geom_line(color="#faa307") + scale_y_continuous(labels=scales::percent) 

ggarrange(mt1a,mt1b,labels = c("a", "b"), ncol=2, nrow=1)

ggarrange(mt1c,mt1d, labels = c("c", "d"),ncol=2, nrow=1)

  • there are four dates with outliers

P1: irradiation

# plot
ir1a = ggplot(p1, aes(x=time, y=irradiation)) + geom_point(size=0.2, color="#457b9d",alpha=0.7) + stat_summary(aes(y=irradiation,group=1), fun.y=mean, color="red",geom="line",group=1) + scale_x_continuous(breaks=c(0,21600,43200,64800), labels=xlabel)
# boxplot
ir1b = ggplot(p1, aes(x=factor(date),y=irradiation)) + geom_boxplot() + theme_bw() + theme(axis.text.x=element_text(angle=90)) + labs(x="date")
# line plot
irr = p1[,.(sum_irr=sum(irradiation)), .(date)]
ir1c = ggplot(irr, aes(x=date, y=sum_irr)) + geom_line(color="#457b9d")

ggarrange(ir1b,                                                 
          ggarrange(ir1a, ir1c, ncol = 2), nrow = 2 
          ) 

P1: spearman correlation

colnames(p1)
 [1] "date_time"           "ambient_temperature" "module_temperature"  "irradiation"         "dc_power"            "ac_power"           
 [7] "daily_yield"         "total_yield"         "date"                "time"                "delta_temperature"  
# delta temperature
p1$delta_temperature = abs(p1$ambient_temperature-p1$module_temperature)
summary(p1$delta_temperature)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.0023  1.5742  2.4745  7.4173 13.2152 35.2430 
# correlation
p1_c = p1[,-c(1,9,10)]
chart.Correlation(p1_c, histogram=TRUE, method=c("spearman"))

  • daily yield and total yield are not correlated with other features
# correlation heatmap without daily_yield and total_yield
# function
cors <- function(df) {
 M <- Hmisc::rcorr(as.matrix(df),type=c("spearman")) 
 Mdf <- map(M, ~data.frame(.x)) 
 return(Mdf) }

formatted_cors <- function(df){
 cors(df) %>%
 map(~rownames_to_column(.x, var="measure1")) %>%
 map(~pivot_longer(.x, -measure1, "measure2")) %>% 
 bind_rows(.id = "id") %>%
 pivot_wider(names_from = id, values_from = value) %>%
 mutate(sig_p = ifelse(P < .05, T, F), p_if_sig = ifelse(P <.05, P, NA), r_if_sig = ifelse(P <.05, r, NA)) }

# plot
p1_c = p1[,-c(1,7,8,9,10)]

formatted_cors(p1_c) %>% 
 ggplot(aes(measure1, measure2, fill=r, label=round(r_if_sig,3))) +
 geom_tile() + 
 labs(x = NULL, y = NULL, fill = "Spearman's\nCorrelation", title="Plant 1: Correlations", subtitle="without daily_yield and total_yield") + 
 scale_fill_gradient2(mid="#e0fbfc",low="#ee6c4d",high="#293241", limits=c(0,1)) +
 geom_text(color="white") +
 scale_x_discrete(expand=c(0,0)) + 
 scale_y_discrete(expand=c(0,0)) + 
 theme(axis.text.x=element_text(angle=90))

P1: reg plots

# reg plot
p1a = ggscatter(p1, x="dc_power",y="ac_power", add="reg.line", color="#8F3931FF",alpha=0.5) + theme_minimal()
p1b =ggscatter(p1, x="ambient_temperature",y="dc_power", add="reg.line", color="#767676FF",alpha=0.5) + theme_minimal()
p1c=ggscatter(p1, x="module_temperature",y="dc_power", add="reg.line", color="#FFA319FF",alpha=0.5) + theme_minimal()
p1d =ggscatter(p1, x="irradiation",y="dc_power", add="reg.line", color="#58593FFF",alpha=0.5) + theme_minimal()
p1e =ggscatter(p1, x="delta_temperature",y="dc_power", add="reg.line", color="#155F83FF",alpha=0.5) + theme_minimal()
p1f =ggscatter(p1, x="delta_temperature",y="irradiation", add="reg.line", color="#C16622FF",alpha=0.5) + theme_minimal()

ggarrange(p1a, p1b, p1c, p1d, p1e, p1f, labels= c("a","b","c","d","e","f"),ncol=3, nrow=2)

  • Plant 1 Reg plots
      1. inverters convert dc power to ac power linearly.
      1. dc power increases non linearly with ambient temperature.
      1. some linearity between dc power production and module temperature.
      1. dc power increases with irradiation.
      1. dc power is influenced by delta temperature.
      1. some linearity between irradiation and delta temperature.
  • Plant 1 summary
    • yield (daily_yield and total_yield) is not correlated to ac/dc power, temperature and irradiation.
    • transfer function between ac and dc power is linear.
    • dc power is influenced by ambient temperature, module temperature, irradiation and heat transfer between air and module.
    • all (n=22) inverters of Plant 1 lost around 90% of the dc power during conversion.

Plant 1 vs. Plant 2


# dc power (daily)
pp1= ggplot(data=reduced_p1_gen2) + geom_col(aes(x=date, y=dc_power, fill='plant 1')) + geom_col(data=reduced_p2_gen2, aes(x=date, y=dc_power,fill='plant 2')) + scale_fill_manual(values=c("#457b9d","#faa307")) + labs(fill="", title= "DC power (daily)") + theme(title =element_text(size=9))

# dc power (time)
pp2 = ggplot(data=reduced_p1_gen2) + geom_point(aes(x=time, y=dc_power, color='plant 1'),size=0.3,alpha=0.6) + geom_point(data=reduced_p2_gen2, aes(x=time, y=dc_power,color='plant 2'), size=0.3,alpha=0.9) + scale_color_manual(values=c("#457b9d","#faa307")) + labs(fill="", title="AC power (time)") + scale_x_datetime(date_label="%H:%M:%S")+ theme(title =element_text(size=9))

# ac power (daily)
pp3= ggplot(data=reduced_p1_gen2) + geom_col(aes(x=date, y=ac_power, fill='plant 1')) + geom_col(data=reduced_p2_gen2, aes(x=date, y=ac_power,fill='plant 2')) + scale_fill_manual(values=c("#457b9d","#faa307")) + labs(fill="", title= "AC power (daily)")+ theme(title =element_text(size=9))

# ac power (time)
pp4 = ggplot(data=reduced_p1_gen2) + geom_point(aes(x=time, y=ac_power, color='plant 1'),size=0.3,alpha=0.6) + geom_point(data=reduced_p2_gen2, aes(x=time, y=ac_power,color='plant 2'), size=0.3,alpha=0.9) + scale_color_manual(values=c("#457b9d","#faa307")) + labs(fill="", title="AC power (time)") + scale_x_datetime(date_label="%H:%M:%S")+ theme(title =element_text(size=9))


# daily yield (sum for each date)
reduced_p1_dyd = reduced_p1_gen2[,lapply(.SD, sum, na.rm=TRUE), by=list(date), .SDcols=c("daily_yield")]
reduced_p2_dyd = reduced_p2_gen2[,lapply(.SD, sum, na.rm=TRUE), by=list(date), .SDcols=c("daily_yield")]
pp5 = ggplot(data=reduced_p1_dyd) + geom_col(aes(x=date, y=daily_yield, fill='plant 1')) + geom_col(data=reduced_p2_dyd, aes(x=date, y=daily_yield,fill='plant 2')) + scale_fill_manual(values=c("#457b9d","#faa307")) + labs(fill="", title= "Daily yield (date)")+ theme(title =element_text(size=9))

# average total yield 
reduced_p1_aty = reduced_p1_gen2[,lapply(.SD, mean, na.rm=TRUE), by=list(date), .SDcols=c("total_yield")]
reduced_p2_aty = reduced_p2_gen2[,lapply(.SD, mean, na.rm=TRUE), by=list(date), .SDcols=c("total_yield")]
pp6 = ggplot(data=reduced_p2_aty) + geom_col(aes(x=date, y=total_yield, fill='plant 2')) + geom_col(data=reduced_p1_aty, aes(x=date, y=total_yield,fill='plant 1')) + scale_fill_manual(values=c("#457b9d","#faa307")) + labs(fill="", title= "Average total yield")+ theme(title =element_text(size=9))

ggarrange(pp1, pp2, pp3, pp4, pp5, pp6, labels= c("a","b","c","d","e","f"),ncol=3, nrow=2, common.legend = TRUE, legend = "top")

  • Plant 1 and Plant 2 generation
    • Plant 1 produced around 6 times more dc power than Plant 2.
    • Plant 1 produces more ac power than Plant 2.
    • Both plants produced similar daily yield (for each date).
    • Large difference between Plant 1 and Plant 2 average total yield (for each date).
p1_wx_ir= p1_wx[,time:=as.ITime(date_time)] 
p1_wx_ir$time = as.POSIXct(strptime(p1_wx_ir$time, format="%H:%M:%S"))
p2_wx= data2$p2_weather
p2_wx_ir= p2_wx[,time:=as.ITime(date_time)]
p2_wx_ir$time = as.POSIXct(strptime(p2_wx_ir$time, format="%H:%M:%S"))

# irradiation
irp1 = ggplot(data=p1_wx_ir) + geom_point(aes(x=time, y=irradiation, color='plant 1'),size=0.3,alpha=0.6) + geom_point(data=p2_wx_ir, aes(x=time, y=irradiation,color='plant 2'), size=0.3,alpha=0.9) + scale_color_manual(values=c("#457b9d","#faa307")) + labs(fill="", title="Irradiation (time)") + scale_x_datetime(date_label="%H:%M:%S")+ theme(title =element_text(size=9))

# temperature: ambient and module
temp_p1 = ggplot(data=p1_wx_ir) + geom_point(aes(x=time, y=ambient_temperature, color='Ambient'),size=0.3,alpha=0.7) + geom_point(data=p1_wx_ir, aes(x=time, y=module_temperature,color='Module'), size=0.3,alpha=0.7) + scale_color_manual(values=c("#9c6644","#00509d")) + labs(title="Plant 1",color="Temperature") + scale_x_datetime(date_label="%H:%M:%S")+ theme(title =element_text(size=9))
temp_p2 =  ggplot(data=p2_wx_ir) + geom_point(aes(x=time, y=ambient_temperature, color='Ambient'),size=0.3,alpha=0.7) + geom_point(data=p2_wx_ir, aes(x=time, y=module_temperature,color='Module'), size=0.3,alpha=0.7) + scale_color_manual(values=c("#9c6644","#00509d")) + labs(title="Plant 2",color="Temperature") + scale_x_datetime(date_label="%H:%M:%S") + theme(title =element_text(size=9))

ggarrange(irp1,                                                 
          ggarrange(temp_p1, temp_p2, ncol = 2), nrow = 2 
          ) 

  • Plant 1 and Plant 2 sensor
    • both plants have similar irradiation by time
    • both plants have similar temperature (ambient and module) by time
# daily yield by source key
da1 = data$p1_gen
dap1= ggplot(da1, aes(x=source_key,y=daily_yield)) + geom_boxplot() + coord_flip() + labs(title="Plant 1") + theme(title =element_text(size=9))

da2 = data$p2_gen
dap2 =ggplot(da2, aes(x=source_key,y=daily_yield)) + geom_boxplot() + coord_flip() + labs(title="Plant 2") + theme(title =element_text(size=9))

dap = ggarrange(dap1,dap2, ncol=2, nrow=1)
annotate_figure(dap, top = text_grob("Daily yield by source key", size = 12))

  • Plant 1 and Plant 2 source keys
    • both plants have 22 source keys each.
    • There are more differences in the median daily yield (datetime) between source keys in Plant 2 than in Plant 1.

Plant 2

P2: new variables

# new variables
p2$delta_temperature = abs(p2$ambient_temperature-p2$module_temperature)
p2 = within(p2, diff_daily_yield <- c(NA,diff(daily_yield)))
p2 = within(p2, diff_total_yield <- c(NA,diff(total_yield)))
p2 = within(p2, diff_ambient_temperature <- c(NA,diff(ambient_temperature)))
p2 = within(p2, diff_module_temperature <- c(NA,diff(module_temperature)))
p2 = within(p2, diff_ac_power <- c(NA,diff(ac_power)))
head(p2)

P2: spearman correlation

# get spearman correlation
p2c = p2[,-c(1,9,10)]
corr_mat=cor(p2c, use="complete.obs", method="spearman") #create Spearman correlation matrix

# p.mat function
cor.mtest <- function(mat, ...) {
    mat <- as.matrix(mat)
    n <- ncol(mat)
    p.mat<- matrix(NA, n, n)
    diag(p.mat) <- 0
    for (i in 1:(n - 1)) {
        for (j in (i + 1):n) {
            tmp <- cor.test(mat[, i], mat[, j], ...)
            p.mat[i, j] <- p.mat[j, i] <- tmp$p.value
        }
    }
  colnames(p.mat) <- rownames(p.mat) <- colnames(mat)
  p.mat
}
# get p.mat
p.mat <- cor.mtest(p2c, method="s",use="complete.obs")

col <- colorRampPalette(c("#BB4444", "#EE9988", "#FFFFFF", "#77AADD", "#4477AA"))

cor.mtest <- function(mat, ...) {
    mat <- as.matrix(mat)
    n <- ncol(mat)
    p.mat<- matrix(NA, n, n)
    diag(p.mat) <- 0
    for (i in 1:(n - 1)) {
        for (j in (i + 1):n) {
            tmp <- cor.test(mat[, i], mat[, j], ...)
            p.mat[i, j] <- p.mat[j, i] <- tmp$p.value
        }
    }
  colnames(p.mat) <- rownames(p.mat) <- colnames(mat)
  p.mat
}
# plot
corrplot(corr_mat, method="color", col=col(200),  
         type="upper", order="hclust", 
         addCoef.col = "black", # Add coefficient of correlation
         tl.col="black", tl.srt=90, #Text label color and rotation
         # Combine with significance
         p.mat = p.mat, sig.level = 0.01, insig = "blank", 
         # hide correlation coefficient on the principal diagonal
         diag=FALSE, number.cex=.6, tl.cex=.6
         )

  • Plant 2’s total yield is negatively correlated to all features, except for daily yield.

P2: reg plots

# reg plots
p2a = ggscatter(p2, x="dc_power",y="ac_power", add="reg.line", color="#8F3931FF",alpha=0.6, size=1) + theme_bw()
p2b = ggscatter(p2, x="ac_power",y="diff_daily_yield", add="reg.line", color="#767676FF",alpha=0.6,size=1) + theme_bw()
p2c = ggscatter(p2, x="irradiation",y="diff_daily_yield", add="reg.line", color="#FFA319FF",alpha=0.6, size=1) + theme_bw()
p2d = ggscatter(p2, x="module_temperature",y="diff_daily_yield", add="reg.line", color="#58593FFF",alpha=0.6, size=1) + theme_bw()
p2e =ggscatter(p2, x="delta_temperature",y="diff_daily_yield", add="reg.line", color="#155F83FF",alpha=0.6, size=1) + theme_bw()
p2f = ggscatter(p2, x="diff_daily_yield",y="diff_total_yield", add="reg.line", color="#C16622FF",alpha=0.6, size=1) + theme_bw()
p2g = ggscatter(p2, x="diff_module_temperature",y="diff_ac_power", add="reg.line", color="#350E20FF",alpha=0.6, size=1) + theme_bw() + labs(x="diff_module_temp")

ggarrange(p2a, p2b, p2c, p2d, p2e, p2f, p2g, labels= c("a","b","c","d","e","f","g"),ncol=3, nrow=3)

  • Plant 2 reg plots
    • inverter lost 0% of the power as dc power = ac power.
    • diff_daily_yield (next minus previous) is:
      • positive when ac power > 20,000 KW.
      • positive or negative with the variation of irradiation
      • negative when the module temperature is below 30°C, and PV panel product the energy if temperature is around 35°C.
      • negative when delta temperature is < 5°C, daily yield decreases every 15 minutes if the difference in module and ambient temperature is < 5°C.
    • there is more diff_ac_power when the diff_module_temp is between -5°C and 5°C.
  • Summary
    • Plant 1 produces 6 times more DC power than plant 2 and loses 90% of it when converting to AC power.
    • No losses in Plant 2 when converting DC to AC power.
    • AC power output and daily yield are similar for both plants.
    • There is a large difference between Plant 1 and Plant 2 average total yield; Plant 2 average total yield is higher than Plant 1.
    • Daily yield decreases when the delta temperature is < 5°C
LS0tCnRpdGxlOiAiU29sYXIgUG93ZXIgUGxhbnRzIgpkYXRlOiAiMjAyMS0wMSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgRXhwbG9yYXRvcnkgQW5hbHlzaXMKClRoaXMgbm90ZWJvb2sgdXNlcyB0aGUgW1NvbGFyIFBvd2VyIEdlbmVyYXRpb24gRGF0YV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9hbmlrYW5uYWwvc29sYXItcG93ZXItZ2VuZXJhdGlvbi1kYXRhKSBjb250YWluaW5nIGRhdGEgb2YgdHdvIHNvbGFyIHBvd2VyIHBsYW50LCB3aGVyZSBlYWNoIHBsYW50IGhhcyBhIHBvd2VyIGdlbmVyYXRpb24gZGF0YXNldCBhbmQgYSBzZW5zb3IgcmVhZGluZ3MgZGF0YXNldC4gCgpUaGUgb2JqZWN0aXZlIG9mIHRoaXMgZXhlcmNpc2UgaXMgdG8gdXNlIHZhcmlvdXMgZGF0YSB2aXN1YWxpemF0aW9uIHRlY2huaXF1ZXMgdG8gZXhwbG9yZSB0aGUgcG93ZXIgZ2VuZXJhdGlvbiBhbmQgc2Vuc29yIHJlYWRpbmdzIG9mIHR3byBzb2xhciBwb3dlciBwbGFudHMgYW5kIHRoZWlyIGRpZmZlcmVuY2VzLiBUaGUgcHJhY3RpY2UgZ29hbHMgaW5jbHVkZSBkYXRhIHdyYW5nbGluZyB1c2luZyBkYXRhLnRhYmxlIHBhY2thZ2UgYW5kIHBsb3R0aW5nIGFnYWluc3QgdGltZS9kYXRlL2RhdGV0aW1lLiAKClJlZmVyZW5jZSBub3RlYm9va3M6ICAKICAqIFtEZXNjLiBBbmFseXRpY3Mgb2YgU29sYXIgUGFuZWxzIHdpdGggUiBhbmQgUGxvdGx5XShodHRwczovL3d3dy5rYWdnbGUuY29tL2VzcHJlc3NvZG9wcGlvL2Rlc2MtYW5hbHl0aWNzLW9mLXNvbGFyLXBhbmVscy13aXRoLXItYW5kLXBsb3RseSkgICAKICAqIFtFbnNlbWJsZSBsZWFybmluZyBsaWI6IE1MZW5zIChQeXRob24pIF0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS92aXB1bGdvdGU0L2Vuc2VtYmxlLWxlYXJuaW5nLWxpYi1tbGVucy05OS1hY2N1cmFjeSkgICAgIAogICogW0hvdyB0byBtYW5hZ2UgYSBzb2xhciBwb3dlciBwbGFudCAoUHl0aG9uKV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS92aXJvc2t5L2hvdy10by1tYW5hZ2UtYS1zb2xhci1wb3dlci1wbGFudCkgICAgIAogICogW1NvbGFyIFBvd2VyIE1hY2hpbmUgTGVhcm5pbmcgSSAoUHl0aG9uKV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9lc3ByZXNzb2RvcHBpby9kZXNjLWFuYWx5dGljcy1vZi1zb2xhci1wYW5lbHMtd2l0aC1yLWFuZC1wbG90bHkpICAgICAKICAqIFtTb2xhcl9wb3dlcl9wbGFudF9hbmFseXNpcyAoUHl0aG9uKV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9zcmludXRpL3NvbGFyLXBvd2VyLXBsYW50LWFuYWx5c2lzKSAgICAgCgoKIyMjIExvYWQgbGlicmFyaWVzCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoamFuaXRvcikKbGlicmFyeShnZ3B1YnIpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KFBlcmZvcm1hbmNlQW5hbHl0aWNzKQpsaWJyYXJ5KGNvcnJwbG90KQpgYGAKCmBgYHtyfQp2aWV3KGRhdGEkcDFfZ2VuKQpgYGAKCgojIyMgSW1wb3J0IGRhdGEKYGBge3J9CiMgbG9hZCBhbGwgZGF0YQpsb2FkX3AxX2dlbiA8LSBmdW5jdGlvbigpIHsKICAgIGRhdGEgPC0gZnJlYWQoIlBsYW50XzFfR2VuZXJhdGlvbl9EYXRhLmNzdiIpCiAgICByZXR1cm4oZGF0YSkKfQpsb2FkX3AxX3dlYXRoZXIgPC0gZnVuY3Rpb24oKSB7CiAgICBkYXRhIDwtIGZyZWFkKCJQbGFudF8xX1dlYXRoZXJfU2Vuc29yX0RhdGEuY3N2IikKICAgIHJldHVybihkYXRhKQp9CmxvYWRfcDJfZ2VuIDwtIGZ1bmN0aW9uKCkgewogICAgZGF0YSA8LSBmcmVhZCgiUGxhbnRfMl9HZW5lcmF0aW9uX0RhdGEuY3N2IikKICAgIHJldHVybihkYXRhKQp9CmxvYWRfcDJfd2VhdGhlciA8LSBmdW5jdGlvbigpIHsKICAgIGRhdGEgPC0gZnJlYWQoIlBsYW50XzJfV2VhdGhlcl9TZW5zb3JfRGF0YS5jc3YiKQogICAgcmV0dXJuKGRhdGEpCn0KCgojIGxvYWQgZGF0YSBpbiBhIGxpc3QgCmxvYWQgPC0gZnVuY3Rpb24oKSB7CiAgICBkYXRhIDwtIGxpc3QoKQogICAgZGF0YSRwMV9nZW4gPC0gbG9hZF9wMV9nZW4oKQogICAgZGF0YSRwMV93ZWF0aGVyIDwtIGxvYWRfcDFfd2VhdGhlcigpCiAgICBkYXRhJHAyX2dlbiA8LSBsb2FkX3AyX2dlbigpCiAgICBkYXRhJHAyX3dlYXRoZXIgPC0gbG9hZF9wMl93ZWF0aGVyKCkKICAgIHJldHVybihkYXRhKQp9CmRhdGEgPC0gbG9hZCgpCmBgYAoKCiMjIyBEYXRhIGNsZWFuaW5nCmBgYHtyfQojIGNsZWFuIG5hbWVzIApuYW1lcyhkYXRhJHAxX2dlbikgPC0gdG9sb3dlcihuYW1lcyhkYXRhJHAxX2dlbikpCm5hbWVzKGRhdGEkcDFfd2VhdGhlcikgPC0gdG9sb3dlcihuYW1lcyhkYXRhJHAxX3dlYXRoZXIpKQpuYW1lcyhkYXRhJHAyX2dlbikgPC0gdG9sb3dlcihuYW1lcyhkYXRhJHAyX2dlbikpCm5hbWVzKGRhdGEkcDJfd2VhdGhlcikgPC0gdG9sb3dlcihuYW1lcyhkYXRhJHAyX3dlYXRoZXIpKQoKIyBwYXJzZSBkYXRldGltZSBhbmQgZmFjdG9ycyAKY2xlYW5fcDFfZ2VuIDwtIGZ1bmN0aW9uKGRhdGEpIHsKICAgIGRhdGFbLCBkYXRlX3RpbWUgOj0gZG15X2htKGRhdGVfdGltZSldCiAgICBkYXRhWywgcGxhbnRfaWQgOj0gYXMuZmFjdG9yKHBsYW50X2lkKV0KICAgIGRhdGFbLCBzb3VyY2Vfa2V5IDo9IGFzLmZhY3Rvcihzb3VyY2Vfa2V5KV0KfQoKIyBwYXJzZSBkYXRldGltZSBhbmQgZmFjdG9ycyAKY2xlYW5fZGF0YSA8LSBmdW5jdGlvbihkYXRhKSB7CiAgICBkYXRhWywgZGF0ZV90aW1lIDo9IGFzX2RhdGV0aW1lKGRhdGVfdGltZSldCiAgICBkYXRhWywgcGxhbnRfaWQgOj0gYXMuZmFjdG9yKHBsYW50X2lkKV0KICAgIGRhdGFbLCBzb3VyY2Vfa2V5IDo9IGFzLmZhY3Rvcihzb3VyY2Vfa2V5KV0KfQoKIyBjbGVhbiBhbGwgZGF0YQpjbGVhbiA8LSBmdW5jdGlvbihkYXRhKSB7CiAgICBkYXRhJHAxX2dlbiA8LSBjbGVhbl9wMV9nZW4oZGF0YSRwMV9nZW4pCiAgICBkYXRhJHAxX3dlYXRoZXIgPC0gY2xlYW5fZGF0YShkYXRhJHAxX3dlYXRoZXIpCiAgICBkYXRhJHAyX2dlbiA8LSBjbGVhbl9kYXRhKGRhdGEkcDJfZ2VuKQogICAgZGF0YSRwMl93ZWF0aGVyIDwtIGNsZWFuX2RhdGEoZGF0YSRwMl93ZWF0aGVyKQogICAgcmV0dXJuKGRhdGEpCn0KCmNsZWFuX2RhdGEgPSBjbGVhbihkYXRhKQpgYGAKCmBgYHtyfQpzdW1tYXJpc2UgPSBmdW5jdGlvbihkYXRhKXtsYXBwbHkoZGF0YSxzdW1tYXJ5KX0Kc3VtbWFyaXNlKGNsZWFuX2RhdGEpCmBgYAoKYGBge3J9CiMgZHJvcCBzaW5ndWxhciB2YXJpYWJsZXMKZHJvcF9zdiA8LSBmdW5jdGlvbihkYXRhKSB7CiAgICBkYXRhJHAxX2dlblssIHBsYW50X2lkIDo9IE5VTExdCiAgICBkYXRhJHAxX3dlYXRoZXJbLCBwbGFudF9pZCA6PSBOVUxMXVssIHNvdXJjZV9rZXkgOj0gTlVMTF0KICAgIGRhdGEkcDJfZ2VuWywgcGxhbnRfaWQgOj0gTlVMTF0KICAgIGRhdGEkcDJfd2VhdGhlclssIHBsYW50X2lkIDo9IE5VTExdWywgc291cmNlX2tleSA6PSBOVUxMXQogICAgcmV0dXJuKGRhdGEpCn0KCmRhdGEyID0gZHJvcF9zdihjbGVhbl9kYXRhKQpgYGAKCgojIyMgUGFpcnBsb3QKYGBge3IsIHdhcm5pbmc9RkFMU0V9CnBhaXJwbG90IDwtIGZ1bmN0aW9uKGRhdGEyKSB7CiAgZGF0YTJbLCBkYXRlX3RpbWUgOj0gTlVMTF0KICBjaGFydC5Db3JyZWxhdGlvbihkYXRhMlssLTFdLCBoaXN0b2dyYW09VFJVRSwgbWV0aG9kPWMoInNwZWFybWFuIikpCn0KCnBhaXJwbG90KGRhdGEyJHAxX2dlbikKYGBgCgojIyMgRGlzdHJpYnV0aW9uCmBgYHtyfQojIGZ1bmN0aW9uIGZvciBnZW5lcmF0aW9uIGRpc3RyaWJ1dGlvbgpwbHRfZ2VuX2Rpc3QgPC0gZnVuY3Rpb24oZGF0YSkgewogICAgeCA8LSBsaXN0KAogICAgICB0aXRsZSA9ICJWYWx1ZSIKICAgICkKICAgIHkgPC0gbGlzdCgKICAgICAgdGl0bGUgPSAiQ291bnQiCiAgICApCgogICAgYWNfcG93ZXIgPC0gZ2dwbG90KGRhdGEsIGFlcyh4ID0gYWNfcG93ZXIpKSArIGdlb21faGlzdG9ncmFtKGFscGhhPTAuNywgZmlsbD0iIzQ1N2I5ZCIpCiAgICBkY19wb3dlciA8LSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSBkY19wb3dlcikpICsgZ2VvbV9oaXN0b2dyYW0oYWxwaGE9MC43LCBmaWxsPSIjNDU3YjlkIikKICAgIGRhaWx5X3lpZWxkIDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IGRhaWx5X3lpZWxkKSkgKyBnZW9tX2hpc3RvZ3JhbShhbHBoYT0wLjcsIGZpbGw9IiM0NTdiOWQiKQogICAgdG90YWxfeWllbGQgPC0gZ2dwbG90KGRhdGEsIGFlcyh4ID0gIHRvdGFsX3lpZWxkKSkgKyBnZW9tX2hpc3RvZ3JhbShhbHBoYT0wLjcsIGZpbGw9IiM0NTdiOWQiKQoKICAgIGdnYXJyYW5nZShhY19wb3dlciwgZGNfcG93ZXIsIGRhaWx5X3lpZWxkLCB0b3RhbF95aWVsZCwgbnJvdyA9IDIsIG5jb2w9IDIpCn0KCiNmdW5jdGlvbiBmb3Igd2VhdGhlciBkaXN0cmlidXRpb24KcGx0X3d4X2Rpc3QgPC0gZnVuY3Rpb24oZGF0YSkgewogICAgeCA8LSBsaXN0KAogICAgICB0aXRsZSA9ICJWYWx1ZSIKICAgICkKICAgIHkgPC0gbGlzdCgKICAgICAgdGl0bGUgPSAiTnVtYmVyIG9mIG9jY3VyZW5jZXMiCiAgICApCgogICAgYW1iaWVudCA8LSBnZ3Bsb3QoZGF0YSwgYWVzKHg9IGFtYmllbnRfdGVtcGVyYXR1cmUpKSArIGdlb21faGlzdG9ncmFtKGFscGhhPTAuNywgZmlsbD0iI2ZhYTMwNyIpIAogICAgbW9kdWxlIDwtIGdncGxvdChkYXRhLCBhZXMoeD0gbW9kdWxlX3RlbXBlcmF0dXJlKSkgKyBnZW9tX2hpc3RvZ3JhbShhbHBoYT0wLjcsIGZpbGw9IiNmYWEzMDciKQogICAgaXJyYWRpYXRpb24gPC0gZ2dwbG90KGRhdGEsIGFlcyh4PSBpcnJhZGlhdGlvbikpICsgZ2VvbV9oaXN0b2dyYW0oYWxwaGE9MC43LCBmaWxsPSIjZmFhMzA3IikKCiAgICBnZ2FycmFuZ2UoYW1iaWVudCwgIGlycmFkaWF0aW9uLCBtb2R1bGUsIG5yb3c9MiwgbmNvbD0yKQp9CmBgYAoKIyMjIyBQbGFudCAxCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpmaWcxYSA9IHBsdF9nZW5fZGlzdChkYXRhMiRwMV9nZW4pCmFubm90YXRlX2ZpZ3VyZShmaWcxYSwgdG9wID0gdGV4dF9ncm9iKCJQbGFudCAxOiBQb3dlciBnZW5lcmF0aW9uIiwgc2l6ZSA9IDEyKSkKZmlnMWIgPSBwbHRfd3hfZGlzdChkYXRhMiRwMV93ZWF0aGVyKQphbm5vdGF0ZV9maWd1cmUoZmlnMWIsIHRvcCA9IHRleHRfZ3JvYigiUGxhbnQgMTogU2Vuc29yIHJlYWRpbmdzICIsIHNpemUgPSAxMikpCmBgYAoKIyMjIyBQbGFudCAyCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpmaWcyYSA9IHBsdF9nZW5fZGlzdChkYXRhMiRwMl9nZW4pCmFubm90YXRlX2ZpZ3VyZShmaWcyYSwgdG9wID0gdGV4dF9ncm9iKCJQbGFudCAyOiBQb3dlciBnZW5lcmF0aW9uIiwgc2l6ZSA9IDEyKSkKZmlnMmIgPSBwbHRfd3hfZGlzdChkYXRhMiRwMl93ZWF0aGVyKQphbm5vdGF0ZV9maWd1cmUoZmlnMmIsIHRvcCA9IHRleHRfZ3JvYigiUGxhbnQgMjogU2Vuc29yIHJlYWRpbmdzICIsIHNpemUgPSAxMikpCmBgYAoKIyMjIERhaWx5IHN1bW1lZCB5aWVsZApgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyBmdW5jdGlvbgpnZXRfZGFpbHlfc3VtbWVkX3lpZWxkIDwtIGZ1bmN0aW9uKGRhdGEpIHsKICAgIGRhdGFbLCBkYXkgOj0gZGF0ZShkYXRlX3RpbWUpXQogICAgZGF0YVssIC4oZGFpbHlfeWllbGRfc3VtID0gc3VtKGRhaWx5X3lpZWxkKSksIGJ5ID0gZGF5XSAgCn0KCnBsdF9kYWlseV95aWVsZCA8LSBmdW5jdGlvbihkYXRhKSB7CiAgICB4IDwtIGxpc3QoCiAgICAgIHRpdGxlID0gIkRheSIKICAgICkKICAgIHkgPC0gbGlzdCgKICAgICAgdGl0bGUgPSAiU3VtbWVkIGRhaWx5IHlpZWxkIgogICAgKQogICAgcGxvdCA8LSBnZ3Bsb3QoZGF0YSwgYWVzKHg9ZGF5LHk9ZGFpbHlfeWllbGRfc3VtKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX3Ntb290aChtZXRob2Q9bG0sIHNlPUZBTFNFKQogICAgcGxvdAp9CgpkYWlseV9zdW1tZWRfeWllbGRfcDEgPC0gZ2V0X2RhaWx5X3N1bW1lZF95aWVsZChkYXRhMiRwMV9nZW4pCmRhaWx5X3N1bW1lZF95aWVsZF9wMiA8LSBnZXRfZGFpbHlfc3VtbWVkX3lpZWxkKGRhdGEyJHAyX2dlbikKZmlnM2EgPSBwbHRfZGFpbHlfeWllbGQoZGFpbHlfc3VtbWVkX3lpZWxkX3AxKSArIGxhYnModGl0bGU9IlBsYW50IDE6IERhaWx5IHN1bW1lZCB5aWVsZCIpCmZpZzNiID0gcGx0X2RhaWx5X3lpZWxkKGRhaWx5X3N1bW1lZF95aWVsZF9wMikgKyBsYWJzKHRpdGxlPSJQbGFudCAyOiBEYWlseSBzdW1tZWQgeWllbGQiKQpnZ2FycmFuZ2UoZmlnM2EsZmlnM2IsIG5jb2w9MiwgbnJvdz0xKQpgYGAKCgojIyMgRGF0YSBwcmVwYXJhdGlvbgpgYGB7cn0KIyBwbGFudCAxCiMgcmVkdWNlZF9wMV9nZW4gCnJlZHVjZWRfcDFfZ2VuID0gZGF0YTIkcDFfZ2VuCnJlZHVjZWRfcDFfZ2VuMiA9IHJlZHVjZWRfcDFfZ2VuWyxsYXBwbHkoLlNELCBzdW0sIG5hLnJtPVRSVUUpLCBieT1saXN0KGRhdGVfdGltZSksIC5TRGNvbHM9YygiZGNfcG93ZXIiLCJhY19wb3dlciIsImRhaWx5X3lpZWxkIiwidG90YWxfeWllbGQiKV0gCgpyZWR1Y2VkX3AxX2dlbjJbLGRhdGU6PWRhdGUoZGF0ZV90aW1lKV0KcmVkdWNlZF9wMV9nZW4yWyx0aW1lOj1hcy5JVGltZShkYXRlX3RpbWUpXQpyZWR1Y2VkX3AxX2dlbjIkdGltZSA9IGFzLlBPU0lYY3Qoc3RycHRpbWUocmVkdWNlZF9wMV9nZW4yJHRpbWUsIGZvcm1hdD0iJUg6JU06JVMiKSkKCiMgbWVyZ2UgcmVkdWNlZF9wMV9nZW4gYW5kIHAxX3d4CnAxX3d4PSBkYXRhMiRwMV93ZWF0aGVyCgpzZXRrZXkocDFfd3gsZGF0ZV90aW1lKQpzZXRrZXkocmVkdWNlZF9wMV9nZW4yLGRhdGVfdGltZSkKcDE9IHAxX3d4W3JlZHVjZWRfcDFfZ2VuMiwgbm9tYXRjaD0wXQpkaW0ocDEpCmBgYAoKYGBge3J9CiMgcGxhbnQgMgojIG1lcmdlIHBsYW50IDIgZGF0YQpyZWR1Y2VkX3AyX2dlbiA9IGRhdGEyJHAyX2dlbgpyZWR1Y2VkX3AyX2dlbjIgPSByZWR1Y2VkX3AyX2dlblssbGFwcGx5KC5TRCwgc3VtLCBuYS5ybT1UUlVFKSwgYnk9bGlzdChkYXRlX3RpbWUpLCAuU0Rjb2xzPWMoImRjX3Bvd2VyIiwiYWNfcG93ZXIiLCJkYWlseV95aWVsZCIsInRvdGFsX3lpZWxkIildCgpyZWR1Y2VkX3AyX2dlbjJbLGRhdGU6PWRhdGUoZGF0ZV90aW1lKV0KcmVkdWNlZF9wMl9nZW4yWyx0aW1lOj1hcy5JVGltZShkYXRlX3RpbWUpXQpyZWR1Y2VkX3AyX2dlbjIkdGltZSA9IGFzLlBPU0lYY3Qoc3RycHRpbWUocmVkdWNlZF9wMl9nZW4yJHRpbWUsIGZvcm1hdD0iJUg6JU06JVMiKSkKCiMgbWVyZ2UgcDIgZ2VuIHdpdGggcDIgd3ggCnAyX3d4PSBkYXRhMiRwMl93ZWF0aGVyCnNldGtleShwMl93eCxkYXRlX3RpbWUpCnNldGtleShyZWR1Y2VkX3AyX2dlbjIsZGF0ZV90aW1lKQpwMj0gcDJfd3hbcmVkdWNlZF9wMl9nZW4yLCBub21hdGNoPTBdCmRpbShwMikKYGBgCgojIyMgUGxhbnQgMQojIyMjIFAxOiBkYyBwb3dlcgpgYGB7ciwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjUsIGZpZy53aWR0aD01fQojIGRjX3Bvd2VyICh0aW1lKQp4bGFiZWw9IGMoIjAwOjAwOjAwIiwiMDY6MDA6MDAiLCIxMjowMDowMCIsIjE4OjAwOjAwIikKZGMxYSA9IGdncGxvdChwMSwgYWVzKHg9dGltZSwgeT1kY19wb3dlcikpICsgZ2VvbV9wb2ludChzaXplPTAuMiwgY29sb3I9IiM0NTdiOWQiLGFscGhhPTAuNykgKyBzdGF0X3N1bW1hcnkoYWVzKHk9ZGNfcG93ZXIsZ3JvdXA9MSksIGZ1bi55PW1lYW4sIGNvbG9yPSJyZWQiLGdlb209ImxpbmUiLGdyb3VwPTEpICsgc2NhbGVfeF9kYXRldGltZShkYXRlX2xhYmVscz0iJUg6JVMiKQojIGRjX3Bvd2VyIChkYWlseSkKZGMxYiA9IGdncGxvdChwMSwgYWVzKHg9ZGF0ZSwgeT1kY19wb3dlcikpICsgZ2VvbV9jb2woZmlsbD0iIzQ1N2I5ZCIpICsgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTQ1KSkKZ2dhcnJhbmdlKGRjMWEsIGRjMWIsIGxhYmVscyA9IGMoImEiLCAiYiIpLCBuY29sPTIsIG5yb3c9MSkKYGBgCiogREMgcG93ZXIKICArIHBsYW50IDEgcHJvZHVjZXMgcG93ZXIgZnJvbSB+MDYuMDAgdG8gfjE4LjAwICAgCiAgKyBtYXhpbXVtIHBvd2VyIG9uIE1heSAyNSAyMDIwCgoKIyMjIyBQMTogZGFpbHkgeWllbGQKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi41LCBmaWcud2lkdGg9NX0KIyBkYWlseV95aWVsZApkeTFhID1nZ3Bsb3QocDEsIGFlcyh4PXRpbWUsIHk9ZGFpbHlfeWllbGQpKSArIGdlb21fcG9pbnQoc2l6ZT0wLjIsIGNvbG9yPSIjNDU3YjlkIixhbHBoYT0wLjUpICsgc3RhdF9zdW1tYXJ5KGFlcyh5PWRhaWx5X3lpZWxkLGdyb3VwPTEpLCBmdW4ueT1tZWFuLCBjb2xvcj0icmVkIixnZW9tPSJsaW5lIixncm91cD0xKSArIHNjYWxlX3hfZGF0ZXRpbWUoZGF0ZV9sYWJlbHM9IiVIOiVTIikKIyBkYWlseV95aWVsZCBmYWNldApkeTFiID0gZ2dwbG90KHAxLCBhZXMoeD10aW1lLCB5PWRhaWx5X3lpZWxkKSkgKyBnZW9tX3BvaW50KHNpemU9MC4yKSArIGZhY2V0X3dyYXAofmRhdGUpICsgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1jKDAsIDEwMDAwMCwgMjAwMDAwKSkgKyB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCkpIApnZ2FycmFuZ2UoZHkxYSxkeTFiLGxhYmVscyA9IGMoImEiLCAiYiIpLCBucm93PTEsIG5jb2w9MikKYGBgCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjUsIGZpZy53aWR0aD01fQojIGJveHBsb3QKZHkxYyA9IGdncGxvdChwMSwgYWVzKHg9ZmFjdG9yKGRhdGUpLHk9ZGFpbHlfeWllbGQpKSArIGdlb21fYm94cGxvdCgpICsgdGhlbWVfYncoKSArIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT05MCkpICsgbGFicyh4PSJkYXRlIikKIyBiYXJwbG90CmR5MWQgPSBnZ3Bsb3QocDEsIGFlcyh4PWZhY3RvcihkYXRlKSx5PWRhaWx5X3lpZWxkKSkgKyBnZW9tX2NvbChmaWxsPSIjNDU3YjlkIikgKyB0aGVtZV9idygpICsgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTkwKSkgKyBsYWJzKHg9ImRhdGUiKQpnZ2FycmFuZ2UoZHkxYyxkeTFkLCBsYWJlbHMgPSBjKCJjIiwgImQiKSxucm93PTEsIG5jb2w9MiApCmBgYAoqIERhaWx5IHlpZWxkCiAgKyBkYWlseSB5aWVsZCBkZWNyZWFzZXMgYWZ0ZXIgMTguMDAuICAgCiAgKyB0aGVyZSBhcmUgbWlzc2luZyBkYXRhIG9uIHNvbWUgZGF0ZXMgZm9yIGV4YW1wbGUsIDIwMjAtMDUtMjAuIAogICsgZGFpbHkgeWllbGQgY2hhbmdlcyBkYWlseSwgYW5kIHRoZXJlIGFyZSBubyBvdXRsaWVycyBvYnNlcnZlZC4gCiAgKyB0aGUgc3VtIG9mIGRhaWx5IHlpZWxkIGNoYW5nZXMgZGFpbHkuCgojIyMjIFAxOiBhbWJpZW50IHRlbXBlcmF0dXJlIApgYGB7ciwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjUsIGZpZy53aWR0aD01fQojIGFtYmllbnQgdGVtcCAodGltZSkKYXQxYSA9IGdncGxvdChwMSwgYWVzKHg9dGltZSwgeT1hbWJpZW50X3RlbXBlcmF0dXJlKSkgKyBnZW9tX3BvaW50KHNpemU9MC4yLCBjb2xvcj0iIzQ1N2I5ZCIsYWxwaGE9MC41KSArIHN0YXRfc3VtbWFyeShhZXMoeT1hbWJpZW50X3RlbXBlcmF0dXJlLGdyb3VwPTEpLCBmdW4ueT1tZWFuLCBjb2xvcj0icmVkIixnZW9tPSJsaW5lIixncm91cD0xKSArIHNjYWxlX3hfZGF0ZXRpbWUoZGF0ZV9sYWJlbHM9IiVIOiVTIikKIyBib3hwbG90CmF0MWIgPSBnZ3Bsb3QocDEsIGFlcyh4PWZhY3RvcihkYXRlKSx5PWFtYmllbnRfdGVtcGVyYXR1cmUpKSArIGdlb21fYm94cGxvdCgpICsgdGhlbWVfYncoKSArIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT05MCkpICsgbGFicyh4PSJkYXRlIiwgeT0idGVtcGVyYXR1cmUgKMKwQykiKQojIGxpbmVwbG90cwpkYXQgPSBwMVssLihtZWFuX2F0PW1lYW4oYW1iaWVudF90ZW1wZXJhdHVyZSkpLCAuKGRhdGUpXQphdDFjID0gZ2dwbG90KGRhdCwgYWVzKHg9ZGF0ZSwgeT1tZWFuX2F0KSkgKyBnZW9tX2xpbmUoY29sb3I9IiM0NTdiOWQiKSArIGxhYnMoeT0ibWVhbl9hbWJpZW50X3RlbXBlcmF0dXJlICjCsEMpIikKCmNvbHM9IGMoJ21lYW5fYXQnKQpkYXRbLChwYXN0ZTAoY29scywgIl9wY3RDaGFuZ2UiKSkgOj0gbGFwcGx5KC5TRCwgZnVuY3Rpb24oY29sKXsgCiAgICAgIChjb2wtc2hpZnQoY29sLDEsdHlwZSA9ICJsYWciKSkvc2hpZnQoY29sLDEsdHlwZSA9ICJsYWciKQogIH0pLCAuU0Rjb2xzPWNvbHNdCmF0MWQgPSBnZ3Bsb3QoZGF0LCBhZXMoeD1kYXRlLCB5PW1lYW5fYXRfcGN0Q2hhbmdlKSkgKyBnZW9tX2xpbmUoY29sb3I9IiNmYWEzMDciKSArIHNjYWxlX3lfY29udGludW91cyhsYWJlbHM9c2NhbGVzOjpwZXJjZW50KSAKCmdnYXJyYW5nZShhdDFhLGF0MWIsIGxhYmVscyA9IGMoImEiLCAiYiIpLCBuY29sPTIsIG5yb3c9MSkKZ2dhcnJhbmdlKGF0MWMsYXQxZCwgbGFiZWxzID0gYygiYyIsICJkIiksbmNvbD0yLCBucm93PTEpCmBgYAoKKiBBbWJpZW50IHRlbXBlcmF0dXJlCiAgKyB0aGUgYW1iaWVudCB0ZW1wZXJhdHVyZSBvZiByZWNvcmRzIGluIE1heSBpcyBoaWdoZXIgdGhhbiBKdW5lLiAKICArIHRoZSByYW5nZSBvZiBhbWJpZW50IHRlbXBlcmF0dXJlIHBlcmNlbnRhZ2UgY2hhbmdlIGlzIGxhcmdlciBpbiBNYXkgdGhhbiBKdW5lLiAKCgpgYGB7cn0KIyB0aW1lIHNlcmllcyBwbG90CiMgc2Vzb25hbGl0eSA3IGRheXMKdHNfYXQgPSB0cyhkYXQkbWVhbl9hdCwgZnJlcXVlbmN5ID0gNykKc3RsX2F0ID0gc3RsKHRzX2F0LCAicGVyaW9kaWMiKQpwbG90KHN0bF9hdCkKYGBgCgogCgojIyMjIFAxOiBtb2R1bGUgdGVtcGVyYXR1cmUKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi41LCBmaWcud2lkdGg9NX0KbXQxYSA9IGdncGxvdChwMSwgYWVzKHg9dGltZSwgeT1tb2R1bGVfdGVtcGVyYXR1cmUpKSArIGdlb21fcG9pbnQoc2l6ZT0wLjIsIGNvbG9yPSIjNDU3YjlkIixhbHBoYT0wLjcpICsgc3RhdF9zdW1tYXJ5KGFlcyh5PW1vZHVsZV90ZW1wZXJhdHVyZSxncm91cD0xKSwgZnVuLnk9bWVhbiwgY29sb3I9InJlZCIsZ2VvbT0ibGluZSIsZ3JvdXA9MSkgKyBzY2FsZV94X2RhdGV0aW1lKGRhdGVfbGFiZWxzPSIlSDolUyIpCiMgYm94cGxvdAptdDFiID0gZ2dwbG90KHAxLCBhZXMoeD1mYWN0b3IoZGF0ZSkseT1tb2R1bGVfdGVtcGVyYXR1cmUpKSArIGdlb21fYm94cGxvdCgpICsgdGhlbWVfYncoKSArIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT05MCkpICsgbGFicyh4PSJkYXRlIiwgeT0idGVtcGVyYXR1cmUgKMKwQykiKQojIGxpbmVwbG90cwpkbXQgPSBwMVssLihtZWFuX210PW1lYW4obW9kdWxlX3RlbXBlcmF0dXJlKSksIC4oZGF0ZSldCm10MWMgPSBnZ3Bsb3QoZG10LCBhZXMoeD1kYXRlLCB5PW1lYW5fbXQpKSArIGdlb21fbGluZShjb2xvcj0iIzQ1N2I5ZCIpICsgbGFicyh5PSJtZWFuX2FtYmllbnRfdGVtcGVyYXR1cmUgKMKwQykiKQoKY29scz0gYygnbWVhbl9tdCcpCmRtdFssKHBhc3RlMChjb2xzLCAiX3BjdENoYW5nZSIpKSA6PSBsYXBwbHkoLlNELCBmdW5jdGlvbihjb2wpeyAKICAgICAgKGNvbC1zaGlmdChjb2wsMSx0eXBlID0gImxhZyIpKS9zaGlmdChjb2wsMSx0eXBlID0gImxhZyIpCiAgfSksIC5TRGNvbHM9Y29sc10KbXQxZCA9IGdncGxvdChkbXQsIGFlcyh4PWRhdGUsIHk9bWVhbl9tdF9wY3RDaGFuZ2UpKSArIGdlb21fbGluZShjb2xvcj0iI2ZhYTMwNyIpICsgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OnBlcmNlbnQpIAoKZ2dhcnJhbmdlKG10MWEsbXQxYixsYWJlbHMgPSBjKCJhIiwgImIiKSwgbmNvbD0yLCBucm93PTEpCmdnYXJyYW5nZShtdDFjLG10MWQsIGxhYmVscyA9IGMoImMiLCAiZCIpLG5jb2w9MiwgbnJvdz0xKQpgYGAKCiogdGhlcmUgYXJlIGZvdXIgZGF0ZXMgd2l0aCBvdXRsaWVycwoKIyMjIyBQMTogaXJyYWRpYXRpb24gCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQojIHBsb3QKaXIxYSA9IGdncGxvdChwMSwgYWVzKHg9dGltZSwgeT1pcnJhZGlhdGlvbikpICsgZ2VvbV9wb2ludChzaXplPTAuMiwgY29sb3I9IiM0NTdiOWQiLGFscGhhPTAuNykgKyBzdGF0X3N1bW1hcnkoYWVzKHk9aXJyYWRpYXRpb24sZ3JvdXA9MSksIGZ1bi55PW1lYW4sIGNvbG9yPSJyZWQiLGdlb209ImxpbmUiLGdyb3VwPTEpICsgc2NhbGVfeF9kYXRldGltZShkYXRlX2xhYmVscz0iJUg6JVMiKQojIGJveHBsb3QKaXIxYiA9IGdncGxvdChwMSwgYWVzKHg9ZmFjdG9yKGRhdGUpLHk9aXJyYWRpYXRpb24pKSArIGdlb21fYm94cGxvdCgpICsgdGhlbWVfYncoKSArIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT05MCkpICsgbGFicyh4PSJkYXRlIikKIyBsaW5lIHBsb3QKaXJyID0gcDFbLC4oc3VtX2lycj1zdW0oaXJyYWRpYXRpb24pKSwgLihkYXRlKV0KaXIxYyA9IGdncGxvdChpcnIsIGFlcyh4PWRhdGUsIHk9c3VtX2lycikpICsgZ2VvbV9saW5lKGNvbG9yPSIjNDU3YjlkIikKCmdnYXJyYW5nZShpcjFiLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgIGdnYXJyYW5nZShpcjFhLCBpcjFjLCBuY29sID0gMiksIG5yb3cgPSAyIAogICAgICAgICAgKSAKYGBgCgoKIyMjIyBQMTogc3BlYXJtYW4gY29ycmVsYXRpb24KCmBgYHtyfQpjb2xuYW1lcyhwMSkKYGBgCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KIyBkZWx0YSB0ZW1wZXJhdHVyZQpwMSRkZWx0YV90ZW1wZXJhdHVyZSA9IGFicyhwMSRhbWJpZW50X3RlbXBlcmF0dXJlLXAxJG1vZHVsZV90ZW1wZXJhdHVyZSkKc3VtbWFyeShwMSRkZWx0YV90ZW1wZXJhdHVyZSkKCiMgY29ycmVsYXRpb24KcDFfYyA9IHAxWywtYygxLDksMTApXQpjaGFydC5Db3JyZWxhdGlvbihwMV9jLCBoaXN0b2dyYW09VFJVRSwgbWV0aG9kPWMoInNwZWFybWFuIikpCmBgYAoKKiBkYWlseSB5aWVsZCBhbmQgdG90YWwgeWllbGQgYXJlIG5vdCBjb3JyZWxhdGVkIHdpdGggb3RoZXIgZmVhdHVyZXMKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQojIGNvcnJlbGF0aW9uIGhlYXRtYXAgd2l0aG91dCBkYWlseV95aWVsZCBhbmQgdG90YWxfeWllbGQKIyBmdW5jdGlvbgpjb3JzIDwtIGZ1bmN0aW9uKGRmKSB7CiBNIDwtIEhtaXNjOjpyY29ycihhcy5tYXRyaXgoZGYpLHR5cGU9Yygic3BlYXJtYW4iKSkgCiBNZGYgPC0gbWFwKE0sIH5kYXRhLmZyYW1lKC54KSkgCiByZXR1cm4oTWRmKSB9Cgpmb3JtYXR0ZWRfY29ycyA8LSBmdW5jdGlvbihkZil7CiBjb3JzKGRmKSAlPiUKIG1hcCh+cm93bmFtZXNfdG9fY29sdW1uKC54LCB2YXI9Im1lYXN1cmUxIikpICU+JQogbWFwKH5waXZvdF9sb25nZXIoLngsIC1tZWFzdXJlMSwgIm1lYXN1cmUyIikpICU+JSAKIGJpbmRfcm93cyguaWQgPSAiaWQiKSAlPiUKIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBpZCwgdmFsdWVzX2Zyb20gPSB2YWx1ZSkgJT4lCiBtdXRhdGUoc2lnX3AgPSBpZmVsc2UoUCA8IC4wNSwgVCwgRiksIHBfaWZfc2lnID0gaWZlbHNlKFAgPC4wNSwgUCwgTkEpLCByX2lmX3NpZyA9IGlmZWxzZShQIDwuMDUsIHIsIE5BKSkgfQoKIyBwbG90CnAxX2MgPSBwMVssLWMoMSw3LDgsOSwxMCldCgpmb3JtYXR0ZWRfY29ycyhwMV9jKSAlPiUgCiBnZ3Bsb3QoYWVzKG1lYXN1cmUxLCBtZWFzdXJlMiwgZmlsbD1yLCBsYWJlbD1yb3VuZChyX2lmX3NpZywzKSkpICsKIGdlb21fdGlsZSgpICsgCiBsYWJzKHggPSBOVUxMLCB5ID0gTlVMTCwgZmlsbCA9ICJTcGVhcm1hbidzXG5Db3JyZWxhdGlvbiIsIHRpdGxlPSJQbGFudCAxOiBDb3JyZWxhdGlvbnMiLCBzdWJ0aXRsZT0id2l0aG91dCBkYWlseV95aWVsZCBhbmQgdG90YWxfeWllbGQiKSArIAogc2NhbGVfZmlsbF9ncmFkaWVudDIobWlkPSIjZTBmYmZjIixsb3c9IiNlZTZjNGQiLGhpZ2g9IiMyOTMyNDEiLCBsaW1pdHM9YygwLDEpKSArCiBnZW9tX3RleHQoY29sb3I9IndoaXRlIikgKwogc2NhbGVfeF9kaXNjcmV0ZShleHBhbmQ9YygwLDApKSArIAogc2NhbGVfeV9kaXNjcmV0ZShleHBhbmQ9YygwLDApKSArIAogdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTkwKSkKYGBgCgojIyMjIFAxOiByZWcgcGxvdHMKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGZpZy5oZWlnaHQ9My41LCBmaWcud2lkdGg9NX0KIyByZWcgcGxvdApwMWEgPSBnZ3NjYXR0ZXIocDEsIHg9ImRjX3Bvd2VyIix5PSJhY19wb3dlciIsIGFkZD0icmVnLmxpbmUiLCBjb2xvcj0iIzhGMzkzMUZGIixhbHBoYT0wLjUpICsgdGhlbWVfbWluaW1hbCgpCnAxYiA9Z2dzY2F0dGVyKHAxLCB4PSJhbWJpZW50X3RlbXBlcmF0dXJlIix5PSJkY19wb3dlciIsIGFkZD0icmVnLmxpbmUiLCBjb2xvcj0iIzc2NzY3NkZGIixhbHBoYT0wLjUpICsgdGhlbWVfbWluaW1hbCgpCnAxYz1nZ3NjYXR0ZXIocDEsIHg9Im1vZHVsZV90ZW1wZXJhdHVyZSIseT0iZGNfcG93ZXIiLCBhZGQ9InJlZy5saW5lIiwgY29sb3I9IiNGRkEzMTlGRiIsYWxwaGE9MC41KSArIHRoZW1lX21pbmltYWwoKQpwMWQgPWdnc2NhdHRlcihwMSwgeD0iaXJyYWRpYXRpb24iLHk9ImRjX3Bvd2VyIiwgYWRkPSJyZWcubGluZSIsIGNvbG9yPSIjNTg1OTNGRkYiLGFscGhhPTAuNSkgKyB0aGVtZV9taW5pbWFsKCkKcDFlID1nZ3NjYXR0ZXIocDEsIHg9ImRlbHRhX3RlbXBlcmF0dXJlIix5PSJkY19wb3dlciIsIGFkZD0icmVnLmxpbmUiLCBjb2xvcj0iIzE1NUY4M0ZGIixhbHBoYT0wLjUpICsgdGhlbWVfbWluaW1hbCgpCnAxZiA9Z2dzY2F0dGVyKHAxLCB4PSJkZWx0YV90ZW1wZXJhdHVyZSIseT0iaXJyYWRpYXRpb24iLCBhZGQ9InJlZy5saW5lIiwgY29sb3I9IiNDMTY2MjJGRiIsYWxwaGE9MC41KSArIHRoZW1lX21pbmltYWwoKQoKZ2dhcnJhbmdlKHAxYSwgcDFiLCBwMWMsIHAxZCwgcDFlLCBwMWYsIGxhYmVscz0gYygiYSIsImIiLCJjIiwiZCIsImUiLCJmIiksbmNvbD0zLCBucm93PTIpCmBgYAoqIFBsYW50IDEgUmVnIHBsb3RzCiAgKyAoYSkgaW52ZXJ0ZXJzIGNvbnZlcnQgZGMgcG93ZXIgdG8gYWMgcG93ZXIgbGluZWFybHkuICAKICArIChiKSBkYyBwb3dlciBpbmNyZWFzZXMgbm9uIGxpbmVhcmx5IHdpdGggYW1iaWVudCB0ZW1wZXJhdHVyZS4gIAogICsgKGMpIHNvbWUgbGluZWFyaXR5IGJldHdlZW4gZGMgcG93ZXIgcHJvZHVjdGlvbiBhbmQgbW9kdWxlIHRlbXBlcmF0dXJlLiAgCiAgKyAoZCkgZGMgcG93ZXIgaW5jcmVhc2VzIHdpdGggaXJyYWRpYXRpb24uICAKICArIChlKSBkYyBwb3dlciBpcyBpbmZsdWVuY2VkIGJ5IGRlbHRhIHRlbXBlcmF0dXJlLiAgICAKICArIChmKSBzb21lIGxpbmVhcml0eSBiZXR3ZWVuIGlycmFkaWF0aW9uIGFuZCBkZWx0YSB0ZW1wZXJhdHVyZS4gICAgICAKCiogUGxhbnQgMSBzdW1tYXJ5CiAgKyB5aWVsZCAoZGFpbHlfeWllbGQgYW5kIHRvdGFsX3lpZWxkKSBpcyBub3QgY29ycmVsYXRlZCB0byBhYy9kYyBwb3dlciwgdGVtcGVyYXR1cmUgYW5kIGlycmFkaWF0aW9uLiAgIAogICsgdHJhbnNmZXIgZnVuY3Rpb24gYmV0d2VlbiBhYyBhbmQgZGMgcG93ZXIgaXMgbGluZWFyLiAgICAgCiAgKyBkYyBwb3dlciBpcyBpbmZsdWVuY2VkIGJ5IGFtYmllbnQgdGVtcGVyYXR1cmUsIG1vZHVsZSB0ZW1wZXJhdHVyZSwgaXJyYWRpYXRpb24gYW5kIGhlYXQgdHJhbnNmZXIgYmV0d2VlbiBhaXIgYW5kIG1vZHVsZS4gICAgIAogICsgYWxsIChuPTIyKSBpbnZlcnRlcnMgb2YgUGxhbnQgMSBsb3N0IGFyb3VuZCA5MCUgb2YgdGhlIGRjIHBvd2VyIGR1cmluZyBjb252ZXJzaW9uLiAKCgojIyMgUGxhbnQgMSB2cy4gUGxhbnQgMgoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGZpZy5oZWlnaHQ9My41LCBmaWcud2lkdGg9NX0KCiMgZGMgcG93ZXIgKGRhaWx5KQpwcDE9IGdncGxvdChkYXRhPXJlZHVjZWRfcDFfZ2VuMikgKyBnZW9tX2NvbChhZXMoeD1kYXRlLCB5PWRjX3Bvd2VyLCBmaWxsPSdwbGFudCAxJykpICsgZ2VvbV9jb2woZGF0YT1yZWR1Y2VkX3AyX2dlbjIsIGFlcyh4PWRhdGUsIHk9ZGNfcG93ZXIsZmlsbD0ncGxhbnQgMicpKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjNDU3YjlkIiwiI2ZhYTMwNyIpKSArIGxhYnMoZmlsbD0iIiwgdGl0bGU9ICJEQyBwb3dlciAoZGFpbHkpIikgKyB0aGVtZSh0aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9OSkpCgojIGRjIHBvd2VyICh0aW1lKQpwcDIgPSBnZ3Bsb3QoZGF0YT1yZWR1Y2VkX3AxX2dlbjIpICsgZ2VvbV9wb2ludChhZXMoeD10aW1lLCB5PWRjX3Bvd2VyLCBjb2xvcj0ncGxhbnQgMScpLHNpemU9MC4zLGFscGhhPTAuNikgKyBnZW9tX3BvaW50KGRhdGE9cmVkdWNlZF9wMl9nZW4yLCBhZXMoeD10aW1lLCB5PWRjX3Bvd2VyLGNvbG9yPSdwbGFudCAyJyksIHNpemU9MC4zLGFscGhhPTAuOSkgKyBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoIiM0NTdiOWQiLCIjZmFhMzA3IikpICsgbGFicyhmaWxsPSIiLCB0aXRsZT0iQUMgcG93ZXIgKHRpbWUpIikgKyBzY2FsZV94X2RhdGV0aW1lKGRhdGVfbGFiZWw9IiVIOiVNOiVTIikrIHRoZW1lKHRpdGxlID1lbGVtZW50X3RleHQoc2l6ZT05KSkKCiMgYWMgcG93ZXIgKGRhaWx5KQpwcDM9IGdncGxvdChkYXRhPXJlZHVjZWRfcDFfZ2VuMikgKyBnZW9tX2NvbChhZXMoeD1kYXRlLCB5PWFjX3Bvd2VyLCBmaWxsPSdwbGFudCAxJykpICsgZ2VvbV9jb2woZGF0YT1yZWR1Y2VkX3AyX2dlbjIsIGFlcyh4PWRhdGUsIHk9YWNfcG93ZXIsZmlsbD0ncGxhbnQgMicpKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjNDU3YjlkIiwiI2ZhYTMwNyIpKSArIGxhYnMoZmlsbD0iIiwgdGl0bGU9ICJBQyBwb3dlciAoZGFpbHkpIikrIHRoZW1lKHRpdGxlID1lbGVtZW50X3RleHQoc2l6ZT05KSkKCiMgYWMgcG93ZXIgKHRpbWUpCnBwNCA9IGdncGxvdChkYXRhPXJlZHVjZWRfcDFfZ2VuMikgKyBnZW9tX3BvaW50KGFlcyh4PXRpbWUsIHk9YWNfcG93ZXIsIGNvbG9yPSdwbGFudCAxJyksc2l6ZT0wLjMsYWxwaGE9MC42KSArIGdlb21fcG9pbnQoZGF0YT1yZWR1Y2VkX3AyX2dlbjIsIGFlcyh4PXRpbWUsIHk9YWNfcG93ZXIsY29sb3I9J3BsYW50IDInKSwgc2l6ZT0wLjMsYWxwaGE9MC45KSArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzQ1N2I5ZCIsIiNmYWEzMDciKSkgKyBsYWJzKGZpbGw9IiIsIHRpdGxlPSJBQyBwb3dlciAodGltZSkiKSArIHNjYWxlX3hfZGF0ZXRpbWUoZGF0ZV9sYWJlbD0iJUg6JU06JVMiKSsgdGhlbWUodGl0bGUgPWVsZW1lbnRfdGV4dChzaXplPTkpKQoKCiMgZGFpbHkgeWllbGQgKHN1bSBmb3IgZWFjaCBkYXRlKQpyZWR1Y2VkX3AxX2R5ZCA9IHJlZHVjZWRfcDFfZ2VuMlssbGFwcGx5KC5TRCwgc3VtLCBuYS5ybT1UUlVFKSwgYnk9bGlzdChkYXRlKSwgLlNEY29scz1jKCJkYWlseV95aWVsZCIpXQpyZWR1Y2VkX3AyX2R5ZCA9IHJlZHVjZWRfcDJfZ2VuMlssbGFwcGx5KC5TRCwgc3VtLCBuYS5ybT1UUlVFKSwgYnk9bGlzdChkYXRlKSwgLlNEY29scz1jKCJkYWlseV95aWVsZCIpXQpwcDUgPSBnZ3Bsb3QoZGF0YT1yZWR1Y2VkX3AxX2R5ZCkgKyBnZW9tX2NvbChhZXMoeD1kYXRlLCB5PWRhaWx5X3lpZWxkLCBmaWxsPSdwbGFudCAxJykpICsgZ2VvbV9jb2woZGF0YT1yZWR1Y2VkX3AyX2R5ZCwgYWVzKHg9ZGF0ZSwgeT1kYWlseV95aWVsZCxmaWxsPSdwbGFudCAyJykpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiM0NTdiOWQiLCIjZmFhMzA3IikpICsgbGFicyhmaWxsPSIiLCB0aXRsZT0gIkRhaWx5IHlpZWxkIChkYXRlKSIpKyB0aGVtZSh0aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9OSkpCgojIGF2ZXJhZ2UgdG90YWwgeWllbGQgCnJlZHVjZWRfcDFfYXR5ID0gcmVkdWNlZF9wMV9nZW4yWyxsYXBwbHkoLlNELCBtZWFuLCBuYS5ybT1UUlVFKSwgYnk9bGlzdChkYXRlKSwgLlNEY29scz1jKCJ0b3RhbF95aWVsZCIpXQpyZWR1Y2VkX3AyX2F0eSA9IHJlZHVjZWRfcDJfZ2VuMlssbGFwcGx5KC5TRCwgbWVhbiwgbmEucm09VFJVRSksIGJ5PWxpc3QoZGF0ZSksIC5TRGNvbHM9YygidG90YWxfeWllbGQiKV0KcHA2ID0gZ2dwbG90KGRhdGE9cmVkdWNlZF9wMl9hdHkpICsgZ2VvbV9jb2woYWVzKHg9ZGF0ZSwgeT10b3RhbF95aWVsZCwgZmlsbD0ncGxhbnQgMicpKSArIGdlb21fY29sKGRhdGE9cmVkdWNlZF9wMV9hdHksIGFlcyh4PWRhdGUsIHk9dG90YWxfeWllbGQsZmlsbD0ncGxhbnQgMScpKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjNDU3YjlkIiwiI2ZhYTMwNyIpKSArIGxhYnMoZmlsbD0iIiwgdGl0bGU9ICJBdmVyYWdlIHRvdGFsIHlpZWxkIikrIHRoZW1lKHRpdGxlID1lbGVtZW50X3RleHQoc2l6ZT05KSkKCmdnYXJyYW5nZShwcDEsIHBwMiwgcHAzLCBwcDQsIHBwNSwgcHA2LCBsYWJlbHM9IGMoImEiLCJiIiwiYyIsImQiLCJlIiwiZiIpLG5jb2w9MywgbnJvdz0yLCBjb21tb24ubGVnZW5kID0gVFJVRSwgbGVnZW5kID0gInRvcCIpCmBgYAoKKiBQbGFudCAxIGFuZCBQbGFudCAyIGdlbmVyYXRpb24KICArIFBsYW50IDEgcHJvZHVjZWQgYXJvdW5kIDYgdGltZXMgbW9yZSBkYyBwb3dlciB0aGFuIFBsYW50IDIuICAKICArIFBsYW50IDEgcHJvZHVjZXMgbW9yZSBhYyBwb3dlciB0aGFuIFBsYW50IDIuICAKICArIEJvdGggcGxhbnRzIHByb2R1Y2VkIHNpbWlsYXIgZGFpbHkgeWllbGQgKGZvciBlYWNoIGRhdGUpLiAgCiAgKyBMYXJnZSBkaWZmZXJlbmNlIGJldHdlZW4gUGxhbnQgMSBhbmQgUGxhbnQgMiBhdmVyYWdlIHRvdGFsIHlpZWxkIChmb3IgZWFjaCBkYXRlKS4gCgoKYGBge3J9CnAxX3d4X2lyPSBwMV93eFssdGltZTo9YXMuSVRpbWUoZGF0ZV90aW1lKV0gCnAxX3d4X2lyJHRpbWUgPSBhcy5QT1NJWGN0KHN0cnB0aW1lKHAxX3d4X2lyJHRpbWUsIGZvcm1hdD0iJUg6JU06JVMiKSkKcDJfd3g9IGRhdGEyJHAyX3dlYXRoZXIKcDJfd3hfaXI9IHAyX3d4Wyx0aW1lOj1hcy5JVGltZShkYXRlX3RpbWUpXQpwMl93eF9pciR0aW1lID0gYXMuUE9TSVhjdChzdHJwdGltZShwMl93eF9pciR0aW1lLCBmb3JtYXQ9IiVIOiVNOiVTIikpCgojIGlycmFkaWF0aW9uCmlycDEgPSBnZ3Bsb3QoZGF0YT1wMV93eF9pcikgKyBnZW9tX3BvaW50KGFlcyh4PXRpbWUsIHk9aXJyYWRpYXRpb24sIGNvbG9yPSdwbGFudCAxJyksc2l6ZT0wLjMsYWxwaGE9MC42KSArIGdlb21fcG9pbnQoZGF0YT1wMl93eF9pciwgYWVzKHg9dGltZSwgeT1pcnJhZGlhdGlvbixjb2xvcj0ncGxhbnQgMicpLCBzaXplPTAuMyxhbHBoYT0wLjkpICsgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCIjNDU3YjlkIiwiI2ZhYTMwNyIpKSArIGxhYnMoZmlsbD0iIiwgdGl0bGU9IklycmFkaWF0aW9uICh0aW1lKSIpICsgc2NhbGVfeF9kYXRldGltZShkYXRlX2xhYmVsPSIlSDolTTolUyIpKyB0aGVtZSh0aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9OSkpCgojIHRlbXBlcmF0dXJlOiBhbWJpZW50IGFuZCBtb2R1bGUKdGVtcF9wMSA9IGdncGxvdChkYXRhPXAxX3d4X2lyKSArIGdlb21fcG9pbnQoYWVzKHg9dGltZSwgeT1hbWJpZW50X3RlbXBlcmF0dXJlLCBjb2xvcj0nQW1iaWVudCcpLHNpemU9MC4zLGFscGhhPTAuNykgKyBnZW9tX3BvaW50KGRhdGE9cDFfd3hfaXIsIGFlcyh4PXRpbWUsIHk9bW9kdWxlX3RlbXBlcmF0dXJlLGNvbG9yPSdNb2R1bGUnKSwgc2l6ZT0wLjMsYWxwaGE9MC43KSArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzljNjY0NCIsIiMwMDUwOWQiKSkgKyBsYWJzKHRpdGxlPSJQbGFudCAxIixjb2xvcj0iVGVtcGVyYXR1cmUiKSArIHNjYWxlX3hfZGF0ZXRpbWUoZGF0ZV9sYWJlbD0iJUg6JU06JVMiKSsgdGhlbWUodGl0bGUgPWVsZW1lbnRfdGV4dChzaXplPTkpKQp0ZW1wX3AyID0gIGdncGxvdChkYXRhPXAyX3d4X2lyKSArIGdlb21fcG9pbnQoYWVzKHg9dGltZSwgeT1hbWJpZW50X3RlbXBlcmF0dXJlLCBjb2xvcj0nQW1iaWVudCcpLHNpemU9MC4zLGFscGhhPTAuNykgKyBnZW9tX3BvaW50KGRhdGE9cDJfd3hfaXIsIGFlcyh4PXRpbWUsIHk9bW9kdWxlX3RlbXBlcmF0dXJlLGNvbG9yPSdNb2R1bGUnKSwgc2l6ZT0wLjMsYWxwaGE9MC43KSArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzljNjY0NCIsIiMwMDUwOWQiKSkgKyBsYWJzKHRpdGxlPSJQbGFudCAyIixjb2xvcj0iVGVtcGVyYXR1cmUiKSArIHNjYWxlX3hfZGF0ZXRpbWUoZGF0ZV9sYWJlbD0iJUg6JU06JVMiKSArIHRoZW1lKHRpdGxlID1lbGVtZW50X3RleHQoc2l6ZT05KSkKCmdnYXJyYW5nZShpcnAxLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgIGdnYXJyYW5nZSh0ZW1wX3AxLCB0ZW1wX3AyLCBuY29sID0gMiksIG5yb3cgPSAyIAogICAgICAgICAgKSAKYGBgCgoqIFBsYW50IDEgYW5kIFBsYW50IDIgc2Vuc29yCiAgKyBib3RoIHBsYW50cyBoYXZlIHNpbWlsYXIgaXJyYWRpYXRpb24gYnkgdGltZQogICsgYm90aCBwbGFudHMgaGF2ZSBzaW1pbGFyIHRlbXBlcmF0dXJlIChhbWJpZW50IGFuZCBtb2R1bGUpIGJ5IHRpbWUKCmBgYHtyfQojIGRhaWx5IHlpZWxkIGJ5IHNvdXJjZSBrZXkKZGExID0gZGF0YSRwMV9nZW4KZGFwMT0gZ2dwbG90KGRhMSwgYWVzKHg9c291cmNlX2tleSx5PWRhaWx5X3lpZWxkKSkgKyBnZW9tX2JveHBsb3QoKSArIGNvb3JkX2ZsaXAoKSArIGxhYnModGl0bGU9IlBsYW50IDEiKSArIHRoZW1lKHRpdGxlID1lbGVtZW50X3RleHQoc2l6ZT05KSkKCmRhMiA9IGRhdGEkcDJfZ2VuCmRhcDIgPWdncGxvdChkYTIsIGFlcyh4PXNvdXJjZV9rZXkseT1kYWlseV95aWVsZCkpICsgZ2VvbV9ib3hwbG90KCkgKyBjb29yZF9mbGlwKCkgKyBsYWJzKHRpdGxlPSJQbGFudCAyIikgKyB0aGVtZSh0aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9OSkpCgpkYXAgPSBnZ2FycmFuZ2UoZGFwMSxkYXAyLCBuY29sPTIsIG5yb3c9MSkKYW5ub3RhdGVfZmlndXJlKGRhcCwgdG9wID0gdGV4dF9ncm9iKCJEYWlseSB5aWVsZCBieSBzb3VyY2Uga2V5Iiwgc2l6ZSA9IDEyKSkKYGBgCgoqIFBsYW50IDEgYW5kIFBsYW50IDIgc291cmNlIGtleXMgIAogICsgYm90aCBwbGFudHMgaGF2ZSAyMiBzb3VyY2Uga2V5cyBlYWNoLiAgIAogICsgVGhlcmUgYXJlIG1vcmUgZGlmZmVyZW5jZXMgaW4gdGhlIG1lZGlhbiBkYWlseSB5aWVsZCAoZGF0ZXRpbWUpIGJldHdlZW4gc291cmNlIGtleXMgaW4gUGxhbnQgMiB0aGFuIGluIFBsYW50IDEuICAKCgojIyMgUGxhbnQgMgojIyMjIFAyOiBuZXcgdmFyaWFibGVzCmBgYHtyfQojIG5ldyB2YXJpYWJsZXMKcDIkZGVsdGFfdGVtcGVyYXR1cmUgPSBhYnMocDIkYW1iaWVudF90ZW1wZXJhdHVyZS1wMiRtb2R1bGVfdGVtcGVyYXR1cmUpCnAyID0gd2l0aGluKHAyLCBkaWZmX2RhaWx5X3lpZWxkIDwtIGMoTkEsZGlmZihkYWlseV95aWVsZCkpKQpwMiA9IHdpdGhpbihwMiwgZGlmZl90b3RhbF95aWVsZCA8LSBjKE5BLGRpZmYodG90YWxfeWllbGQpKSkKcDIgPSB3aXRoaW4ocDIsIGRpZmZfYW1iaWVudF90ZW1wZXJhdHVyZSA8LSBjKE5BLGRpZmYoYW1iaWVudF90ZW1wZXJhdHVyZSkpKQpwMiA9IHdpdGhpbihwMiwgZGlmZl9tb2R1bGVfdGVtcGVyYXR1cmUgPC0gYyhOQSxkaWZmKG1vZHVsZV90ZW1wZXJhdHVyZSkpKQpwMiA9IHdpdGhpbihwMiwgZGlmZl9hY19wb3dlciA8LSBjKE5BLGRpZmYoYWNfcG93ZXIpKSkKaGVhZChwMikKYGBgCgoKIyMjIyBQMjogc3BlYXJtYW4gY29ycmVsYXRpb24KYGBge3IsIHdhcm5pbmc9RkFMU0V9CiMgZ2V0IHNwZWFybWFuIGNvcnJlbGF0aW9uCnAyYyA9IHAyWywtYygxLDksMTApXQpjb3JyX21hdD1jb3IocDJjLCB1c2U9ImNvbXBsZXRlLm9icyIsIG1ldGhvZD0ic3BlYXJtYW4iKSAjY3JlYXRlIFNwZWFybWFuIGNvcnJlbGF0aW9uIG1hdHJpeAoKIyBwLm1hdCBmdW5jdGlvbgpjb3IubXRlc3QgPC0gZnVuY3Rpb24obWF0LCAuLi4pIHsKICAgIG1hdCA8LSBhcy5tYXRyaXgobWF0KQogICAgbiA8LSBuY29sKG1hdCkKICAgIHAubWF0PC0gbWF0cml4KE5BLCBuLCBuKQogICAgZGlhZyhwLm1hdCkgPC0gMAogICAgZm9yIChpIGluIDE6KG4gLSAxKSkgewogICAgICAgIGZvciAoaiBpbiAoaSArIDEpOm4pIHsKICAgICAgICAgICAgdG1wIDwtIGNvci50ZXN0KG1hdFssIGldLCBtYXRbLCBqXSwgLi4uKQogICAgICAgICAgICBwLm1hdFtpLCBqXSA8LSBwLm1hdFtqLCBpXSA8LSB0bXAkcC52YWx1ZQogICAgICAgIH0KICAgIH0KICBjb2xuYW1lcyhwLm1hdCkgPC0gcm93bmFtZXMocC5tYXQpIDwtIGNvbG5hbWVzKG1hdCkKICBwLm1hdAp9CiMgZ2V0IHAubWF0CnAubWF0IDwtIGNvci5tdGVzdChwMmMsIG1ldGhvZD0icyIsdXNlPSJjb21wbGV0ZS5vYnMiKQoKY29sIDwtIGNvbG9yUmFtcFBhbGV0dGUoYygiI0JCNDQ0NCIsICIjRUU5OTg4IiwgIiNGRkZGRkYiLCAiIzc3QUFERCIsICIjNDQ3N0FBIikpCgpjb3IubXRlc3QgPC0gZnVuY3Rpb24obWF0LCAuLi4pIHsKICAgIG1hdCA8LSBhcy5tYXRyaXgobWF0KQogICAgbiA8LSBuY29sKG1hdCkKICAgIHAubWF0PC0gbWF0cml4KE5BLCBuLCBuKQogICAgZGlhZyhwLm1hdCkgPC0gMAogICAgZm9yIChpIGluIDE6KG4gLSAxKSkgewogICAgICAgIGZvciAoaiBpbiAoaSArIDEpOm4pIHsKICAgICAgICAgICAgdG1wIDwtIGNvci50ZXN0KG1hdFssIGldLCBtYXRbLCBqXSwgLi4uKQogICAgICAgICAgICBwLm1hdFtpLCBqXSA8LSBwLm1hdFtqLCBpXSA8LSB0bXAkcC52YWx1ZQogICAgICAgIH0KICAgIH0KICBjb2xuYW1lcyhwLm1hdCkgPC0gcm93bmFtZXMocC5tYXQpIDwtIGNvbG5hbWVzKG1hdCkKICBwLm1hdAp9CmBgYAoKYGBge3J9CiMgcGxvdApjb3JycGxvdChjb3JyX21hdCwgbWV0aG9kPSJjb2xvciIsIGNvbD1jb2woMjAwKSwgIAogICAgICAgICB0eXBlPSJ1cHBlciIsIG9yZGVyPSJoY2x1c3QiLCAKICAgICAgICAgYWRkQ29lZi5jb2wgPSAiYmxhY2siLCAjIEFkZCBjb2VmZmljaWVudCBvZiBjb3JyZWxhdGlvbgogICAgICAgICB0bC5jb2w9ImJsYWNrIiwgdGwuc3J0PTkwLCAjVGV4dCBsYWJlbCBjb2xvciBhbmQgcm90YXRpb24KICAgICAgICAgIyBDb21iaW5lIHdpdGggc2lnbmlmaWNhbmNlCiAgICAgICAgIHAubWF0ID0gcC5tYXQsIHNpZy5sZXZlbCA9IDAuMDEsIGluc2lnID0gImJsYW5rIiwgCiAgICAgICAgICMgaGlkZSBjb3JyZWxhdGlvbiBjb2VmZmljaWVudCBvbiB0aGUgcHJpbmNpcGFsIGRpYWdvbmFsCiAgICAgICAgIGRpYWc9RkFMU0UsIG51bWJlci5jZXg9LjYsIHRsLmNleD0uNgogICAgICAgICApCmBgYAoKKiBQbGFudCAyJ3MgdG90YWwgeWllbGQgaXMgbmVnYXRpdmVseSBjb3JyZWxhdGVkIHRvIGFsbCBmZWF0dXJlcywgZXhjZXB0IGZvciBkYWlseSB5aWVsZC4gCgojIyMjIFAyOiByZWcgcGxvdHMKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGZpZy5oZWlnaHQ9My41LCBmaWcud2lkdGg9NX0KIyByZWcgcGxvdHMKcDJhID0gZ2dzY2F0dGVyKHAyLCB4PSJkY19wb3dlciIseT0iYWNfcG93ZXIiLCBhZGQ9InJlZy5saW5lIiwgY29sb3I9IiM4RjM5MzFGRiIsYWxwaGE9MC42LCBzaXplPTEpICsgdGhlbWVfYncoKQpwMmIgPSBnZ3NjYXR0ZXIocDIsIHg9ImFjX3Bvd2VyIix5PSJkaWZmX2RhaWx5X3lpZWxkIiwgYWRkPSJyZWcubGluZSIsIGNvbG9yPSIjNzY3Njc2RkYiLGFscGhhPTAuNixzaXplPTEpICsgdGhlbWVfYncoKQpwMmMgPSBnZ3NjYXR0ZXIocDIsIHg9ImlycmFkaWF0aW9uIix5PSJkaWZmX2RhaWx5X3lpZWxkIiwgYWRkPSJyZWcubGluZSIsIGNvbG9yPSIjRkZBMzE5RkYiLGFscGhhPTAuNiwgc2l6ZT0xKSArIHRoZW1lX2J3KCkKcDJkID0gZ2dzY2F0dGVyKHAyLCB4PSJtb2R1bGVfdGVtcGVyYXR1cmUiLHk9ImRpZmZfZGFpbHlfeWllbGQiLCBhZGQ9InJlZy5saW5lIiwgY29sb3I9IiM1ODU5M0ZGRiIsYWxwaGE9MC42LCBzaXplPTEpICsgdGhlbWVfYncoKQpwMmUgPWdnc2NhdHRlcihwMiwgeD0iZGVsdGFfdGVtcGVyYXR1cmUiLHk9ImRpZmZfZGFpbHlfeWllbGQiLCBhZGQ9InJlZy5saW5lIiwgY29sb3I9IiMxNTVGODNGRiIsYWxwaGE9MC42LCBzaXplPTEpICsgdGhlbWVfYncoKQpwMmYgPSBnZ3NjYXR0ZXIocDIsIHg9ImRpZmZfZGFpbHlfeWllbGQiLHk9ImRpZmZfdG90YWxfeWllbGQiLCBhZGQ9InJlZy5saW5lIiwgY29sb3I9IiNDMTY2MjJGRiIsYWxwaGE9MC42LCBzaXplPTEpICsgdGhlbWVfYncoKQpwMmcgPSBnZ3NjYXR0ZXIocDIsIHg9ImRpZmZfbW9kdWxlX3RlbXBlcmF0dXJlIix5PSJkaWZmX2FjX3Bvd2VyIiwgYWRkPSJyZWcubGluZSIsIGNvbG9yPSIjMzUwRTIwRkYiLGFscGhhPTAuNiwgc2l6ZT0xKSArIHRoZW1lX2J3KCkgKyBsYWJzKHg9ImRpZmZfbW9kdWxlX3RlbXAiKQoKZ2dhcnJhbmdlKHAyYSwgcDJiLCBwMmMsIHAyZCwgcDJlLCBwMmYsIHAyZywgbGFiZWxzPSBjKCJhIiwiYiIsImMiLCJkIiwiZSIsImYiLCJnIiksbmNvbD0zLCBucm93PTMpCgpgYGAKCiogUGxhbnQgMiByZWcgcGxvdHMKICArIGludmVydGVyIGxvc3QgMCUgb2YgdGhlIHBvd2VyIGFzIGRjIHBvd2VyID0gYWMgcG93ZXIuICAKICArIGRpZmZfZGFpbHlfeWllbGQgKG5leHQgbWludXMgcHJldmlvdXMpIGlzOiAgIAogICAgKyBwb3NpdGl2ZSB3aGVuIGFjIHBvd2VyID4gMjAsMDAwIEtXLiAgCiAgICArIHBvc2l0aXZlIG9yIG5lZ2F0aXZlIHdpdGggdGhlIHZhcmlhdGlvbiBvZiBpcnJhZGlhdGlvbiAgICAgCiAgICArIG5lZ2F0aXZlIHdoZW4gdGhlIG1vZHVsZSB0ZW1wZXJhdHVyZSBpcyBiZWxvdyAzMMKwQywgYW5kIFBWIHBhbmVsIHByb2R1Y3QgdGhlIGVuZXJneSBpZiB0ZW1wZXJhdHVyZSBpcyBhcm91bmQgMzXCsEMuICAgCiAgICArIG5lZ2F0aXZlIHdoZW4gZGVsdGEgdGVtcGVyYXR1cmUgaXMgPCA1wrBDLCBkYWlseSB5aWVsZCBkZWNyZWFzZXMgZXZlcnkgMTUgbWludXRlcyBpZiB0aGUgZGlmZmVyZW5jZSBpbiBtb2R1bGUgYW5kIGFtYmllbnQgdGVtcGVyYXR1cmUgaXMgPCA1wrBDLiAgIAogICsgdGhlcmUgaXMgbW9yZSBkaWZmX2FjX3Bvd2VyIHdoZW4gdGhlIGRpZmZfbW9kdWxlX3RlbXAgaXMgYmV0d2VlbiAtNcKwQyBhbmQgNcKwQy4gICAKICAKKiBTdW1tYXJ5CiAgKyBQbGFudCAxIHByb2R1Y2VzIDYgdGltZXMgbW9yZSBEQyBwb3dlciB0aGFuIHBsYW50IDIgYW5kIGxvc2VzIDkwJSBvZiBpdCB3aGVuIGNvbnZlcnRpbmcgdG8gQUMgcG93ZXIuCiAgKyBObyBsb3NzZXMgaW4gUGxhbnQgMiB3aGVuIGNvbnZlcnRpbmcgREMgdG8gQUMgcG93ZXIuICAKICArIEFDIHBvd2VyIG91dHB1dCBhbmQgZGFpbHkgeWllbGQgYXJlIHNpbWlsYXIgZm9yIGJvdGggcGxhbnRzLiAgCiAgKyBUaGVyZSBpcyBhIGxhcmdlIGRpZmZlcmVuY2UgYmV0d2VlbiBQbGFudCAxIGFuZCBQbGFudCAyIGF2ZXJhZ2UgdG90YWwgeWllbGQ7IFBsYW50IDIgYXZlcmFnZSB0b3RhbCB5aWVsZCBpcyBoaWdoZXIgdGhhbiBQbGFudCAxLiAgCiAgKyBEYWlseSB5aWVsZCBkZWNyZWFzZXMgd2hlbiB0aGUgZGVsdGEgdGVtcGVyYXR1cmUgaXMgPCA1wrBDCiAgCg==