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
- inverters convert dc power to ac power linearly.
- dc power increases non linearly with ambient temperature.
- some linearity between dc power production and module temperature.
- dc power increases with irradiation.
- dc power is influenced by delta temperature.
- 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==