library(readr)
library(dplyr)
library(lubridate)
library(ggplot2)
library(plotly)
library(ggmap)

rad2deg <- function(rad) {(rad * 180) / (pi)}
deg2rad <- function(deg) {(deg * pi) / (180)}



caprino_hours<-read.csv("data/caprino_hours.csv.gz",header = F)
caprino_imu_gyros<-read.csv("data/caprino_imu_gyro.csv.gz", header=F)
caprino_imu_acc<-read.csv("data/caprino_imu_acc.csv.gz", header=F)
caprino_gps<-read.csv("data/caprino_gps.csv",header=F)

caprino_imu_acc %>% nrow()
caprino_imu_gyros %>% nrow()
caprino_gps %>% nrow()
caprino_hours %>% nrow()

Datos de los archivos

Construccion del Dataset

caprino_data<-cbind(caprino_imu_acc,caprino_imu_gyros %>% select(-V1))
caprino_data<-cbind(caprino_data[seq(2,nrow(caprino_data),2),] %>% head(nrow(caprino_gps)-1),  caprino_gps[2:nrow(caprino_gps),] ) 
names(caprino_data)<-c("imu_ts",
                       "accX",
                       "accY",
                       "accZ",
                       "gyroX",
                       "gyroY",
                       "gyroZ",
                       "gps_hour",
                       "gps_ts",
                       "lat",
                       "long",
                       "alt"
                        )
#caprino_data %>% mutate(diff=imu_ts-gps_ts) %>% select(diff)
caprino_data<-caprino_data %>% mutate(activity=ifelse(hms(gps_hour)>hms("8:56:00") & hms(gps_hour)<hms("10:38:00"),"Dco",
                                       
                                        ifelse(hms(gps_hour)>=hms("10:38:00") & hms(gps_hour)<hms("11:38:00"),"C",
                                        ifelse(hms(gps_hour)>=hms("11:38:00") & hms(gps_hour)<hms("12:05:00"),"P",
                                        ifelse(hms(gps_hour)>=hms("12:05:00") & hms(gps_hour)<hms("15:48:00"),"Dca",
                                        ifelse(hms(gps_hour)>=hms("17:30:00") & hms(gps_hour)<hms("20:33:00"),"P",
                                        ifelse(hms(gps_hour)>=hms("20:50:00") & hms(gps_hour)<hms("21:31:00"),"C",
                                        ifelse(hms(gps_hour)>=hms("21:33:00") & hms(gps_hour)<hms("21:50:00"),"Dco",NA)
                                        )))))
                                        )
                        
                        )
caprino_data<-caprino_data %>% mutate(id=cut(as.POSIXct(hms(caprino_data$gps_hour),origin="1960-01-01",tz="GMT"),breaks="1 min")) 

Numero de mediciones por tipo de actividad

NA hace referencia a aquellas mediciones que no fueron etiquetadas

caprino_data %>% group_by(activity) %>% summarise(n=n())

Grafica de la diferencia entre el tiempo de muestreo de la IMU y GPS.

  ggplot(caprino_data%>% mutate(diff=imu_ts - gps_ts))+
  geom_line(aes(y=diff,x=seq(1,nrow(caprino_data))),color='blue')+
  theme_bw()

Grafica del trayecto georeferenciado discriminado por actividad

Grafica del trayecto georeferenciado discriminado por actividad

Las zonas con menor densidad de puntos indican un menor lapso de tiempo en esa zona.

caprino_data   %>%
ggplot(aes(x=long,y=lat))+
  geom_point(aes(color=activity),alpha=0.01,size=0.8)+
  scale_color_brewer(palette="Spectral")+
  theme_bw()

caprino_data_altitude_group<-caprino_data %>% mutate(altitude_group = cut(alt, breaks = c(200,378,525,527,530,650,800,900))) 

ggplot(caprino_data_altitude_group,aes(x=long,y=lat))+
  stat_density2d(aes(fill=altitude_group,alpha=..level..), geom="polygon",  bins=1000) +
  theme_minimal()
  

Distribución de los resultados de los sensores en cada una de las actividades (sin outliers)

caprino_data_melted<-caprino_data %>% select(accX,accY,accZ,gyroX,gyroY,gyroZ,activity) %>% reshape2::melt() 
Using activity as id variables
  ggplot(caprino_data_melted)+
  geom_boxplot(aes(x=variable,color=variable,y=value),outlier.size=0.1,outlier.shape = NA)+
  scale_y_continuous(limits = c(-25,10))+
  theme_bw()+
theme(axis.text.x = element_text(angle = 45, hjust = 1))+
  facet_wrap(~activity) 

NA

Tomamos muestras aleatorias de 1 minuto de duracion para sobre las cuales despues querremos predecir el comportamiento

set.seed(21092019)
minute_slice<-as.vector(t(caprino_data %>% group_by(id) %>% summarise(n=n()) %>% select(id)))
trainidx<-sample(1:length(minute_slice),(length(minute_slice)*70)/100.0)
data_train<-caprino_data %>% filter( id %in% minute_slice[trainidx])
data_test<-caprino_data %>% filter( id %in% (minute_slice[-trainidx]))
data_test%>% group_by(id) %>% summarise(n=n())

Aplicamos Bag-of-features.

presupuesto

Consideramos cada muestra como un vector de 5 dimensiones. La idea es aplicar un algoritmo de clustering sobre estos vectores (accX,accY,accZ,GyroX,gyroY). Una vez encontrado los centroides, para cada muestra de 1 se construye un histograma de los vectores que han caido en cada uno de estos clusters. Este histograma es el que se utiliza para entregar el algoritmo.

La hipotesis de aplicar este metodo es que aquellas muestras que pertenecen a una misma actividad tendran valores similares. Los cuales han sido representado mediantes estos histogramas.

Aplicamos k-means con 12 centroides sobre el conjunto de entrenamiento.

cluster_results<-kmeans(x = data_train%>% select(accX,accY,accZ,gyroX,gyroY), centers = 24, nstart = 5)
did not converge in 10 iterationsQuick-TRANSfer stage steps exceeded maximum (= 8090550)Quick-TRANSfer stage steps exceeded maximum (= 8090550)Quick-TRANSfer stage steps exceeded maximum (= 8090550)Quick-TRANSfer stage steps exceeded maximum (= 8090550)
data_train_cluster<-cbind(data_train,cluster=cluster_results$cluster) %>% group_by(activity,cluster)
data_train_cluster %>% summarise(n=n()) %>%
  ggplot()+
  geom_col(aes(x=cluster,y=n),fill='skyblue')+
  facet_wrap(~activity)+
  theme_bw()

NA

Aplicamos k-means sobre el conjunto de test. Se usan los mismos centroides encontrados en training

cluster_results_test<-kmeans(x = data_test%>% select(accX,accY,accZ,gyroX,gyroY), centers = cluster_results$centers)
data_test_cluster<-cbind(data_test,cluster=cluster_results_test$cluster) %>% group_by(activity,cluster)
data_test_cluster %>% summarise(n=n()) %>%
  ggplot()+
  geom_col(aes(x=cluster,y=n),fill='orange')+
  facet_wrap(~activity)+
  theme_bw()

Preparando el dataset para aplicar un clasificador

Tomamos intervalos de 1m y creamos los atributos usando bag-of-features

sample_minute_slice<-minute_slice[sample(1:length(minute_slice),40)]
data_train_cluster %>% filter (id %in% sample_minute_slice)  %>% group_by(activity,id,cluster) %>% summarise(n=n()) %>%
  ggplot()+
  geom_col(aes(x=cluster,y=n,fill=activity))+
  facet_wrap(~id)+
  theme_bw()

data_train_cluster_labeled<-data_train_cluster   %>% group_by(id,cluster) %>% summarise(n=n()) %>% reshape2::dcast(id~cluster,fill = 0) %>% inner_join(data_train_cluster%>% ungroup() %>% select(id,activity)  %>% unique()  ,by="id") %>% filter(!is.na(activity))
Using n as value column: use value.var to override.
data_test_cluster_labeled<-data_test_cluster   %>% group_by(id,cluster) %>% summarise(n=n()) %>% reshape2::dcast(id~cluster,fill = 0) %>% inner_join(data_test_cluster%>% ungroup() %>% select(id,activity)  %>% unique() ,by="id") 
Using n as value column: use value.var to override.

Probamos Random Forest

Aplicamos 2x5 CV para elegir la mejor version del modelo

library(caret)
library(doMC)
registerDoMC(cores=6)
ctrl_fast <- trainControl(method="repeatedcv", 
                     repeats=2,
                     number=5, 
                     #summaryFunction=twoClassSummary,
                     verboseIter=F,
                     #preProcOptions = list(pcaComp = 10),
                     classProbs=TRUE,
                     allowParallel = T)  
nnGrid <-  expand.grid(decay = c(0), size=c(10,20,50,100,200,300)) 
svmGrid <-  expand.grid(sigma = c(0.001,0.00001), C=c(0.1,0.001,0.5)) 
kerasGrid <- expand.grid(
  size=c(20,50,2000,5000),
  lambda=c(0),
  batch_size=c(1024),
  decay=c(0),
  activation=c('tanh','sigmoid','relu'),
  lr=c(0.001), rho=c(0.9)
)
train_formula=formula("activity~.")
#ctrl_fast$sampling<-"smote"
rfFit <- caret::train(train_formula,
               data = data_train_cluster_labeled %>% select(-id),
               #metric="ROC",
               #preProcess=c("pca"),
               
               #method = "mlpWeightDecay",
               #method = "svmRadial",
               #method ="mlpKerasDecay",
               method = "rf",
               #tuneGrid=kerasGrid,
               tuneLength=11,
               verbose=0,
               #epochs=100,
               trControl = ctrl_fast)

modelos obtenidos

rfFit
Random Forest 

459 samples
 24 predictors
  4 classes: 'C', 'Dca', 'Dco', 'P' 

No pre-processing
Resampling: Cross-Validated (5 fold, repeated 2 times) 
Summary of sample sizes: 366, 368, 368, 368, 366, 368, ... 
Resampling results across tuning parameters:

  mtry  Accuracy   Kappa    
   2    0.7840887  0.7014212
   4    0.7951013  0.7175083
   6    0.7983624  0.7220231
   8    0.7907174  0.7116646
  10    0.7896777  0.7099344
  13    0.7830726  0.7005419
  15    0.7853416  0.7040351
  17    0.7831435  0.7006733
  19    0.7841949  0.7021027
  21    0.7831435  0.7010262
  24    0.7754748  0.6901564

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was mtry = 6.

Evaluacion sobre el conjunto de test

Matriz de Confusion

data_test_prediction<-predict(rfFit,data_test_cluster_labeled %>% select(-id))#,type="prob")
caret::confusionMatrix(data_test_prediction,as.factor(data_test_cluster_labeled$activity))
Confusion Matrix and Statistics

          Reference
Prediction  C Dca Dco  P
       C   25   0   1  1
       Dca  0  65   5  9
       Dco  0   1  27  4
       P    1   7   4 44

Overall Statistics
                                          
               Accuracy : 0.8299          
                 95% CI : (0.7695, 0.8799)
    No Information Rate : 0.3763          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.7605          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: C Class: Dca Class: Dco Class: P
Sensitivity            0.9615     0.8904     0.7297   0.7586
Specificity            0.9881     0.8843     0.9682   0.9118
Pos Pred Value         0.9259     0.8228     0.8438   0.7857
Neg Pred Value         0.9940     0.9304     0.9383   0.8986
Prevalence             0.1340     0.3763     0.1907   0.2990
Detection Rate         0.1289     0.3351     0.1392   0.2268
Detection Prevalence   0.1392     0.4072     0.1649   0.2887
Balanced Accuracy      0.9748     0.8874     0.8489   0.8352

CONCLUSIONES

LS0tCnRpdGxlOiAiRGF0YXNldCBHYW5hZG8gQ2FwcmlubyA3LzkvMTgiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKLS0tCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShnZ21hcCkKCnJhZDJkZWcgPC0gZnVuY3Rpb24ocmFkKSB7KHJhZCAqIDE4MCkgLyAocGkpfQpkZWcycmFkIDwtIGZ1bmN0aW9uKGRlZykgeyhkZWcgKiBwaSkgLyAoMTgwKX0KCgoKY2Fwcmlub19ob3VyczwtcmVhZC5jc3YoImRhdGEvY2Fwcmlub19ob3Vycy5jc3YuZ3oiLGhlYWRlciA9IEYpCmNhcHJpbm9faW11X2d5cm9zPC1yZWFkLmNzdigiZGF0YS9jYXByaW5vX2ltdV9neXJvLmNzdi5neiIsIGhlYWRlcj1GKQpjYXByaW5vX2ltdV9hY2M8LXJlYWQuY3N2KCJkYXRhL2NhcHJpbm9faW11X2FjYy5jc3YuZ3oiLCBoZWFkZXI9RikKY2Fwcmlub19ncHM8LXJlYWQuY3N2KCJkYXRhL2NhcHJpbm9fZ3BzLmNzdiIsaGVhZGVyPUYpCgpjYXByaW5vX2ltdV9hY2MgJT4lIG5yb3coKQpjYXByaW5vX2ltdV9neXJvcyAlPiUgbnJvdygpCmNhcHJpbm9fZ3BzICU+JSBucm93KCkKY2Fwcmlub19ob3VycyAlPiUgbnJvdygpCgoKYGBgCgoKIyMjIERhdG9zIGRlIGxvcyBhcmNoaXZvcyAKKiBjYXByaW5vX2ltdV9hY2MuY3N2LCA0IGNvbHVtbmFzOiB0aWVtcG8gVVRDIGVuIG1pY3Jvc2VndW5kb3MsCmFjZWxlcmFjacOzbiBYIGVuIG0vc8KyLCBhY2VsZXJhY2nDs24gWSBlbiBtL3PCsiwgYWNlbGVyYWNpw7NuLCBaIGVuIG0vc8KyLgpNdWVzdHJlYWRvIGEgMjAwIG11ZXN0cmFzIHBvciBzZWd1bmRvLgoKKiBjYXByaW5vX2ltdV9neXJvLmNzdiwgNCBjb2x1bW5hczogdGllbXBvIFVUQyBlbiBtaWNyb3NlZ3VuZG9zLAp2ZWxvY2lkYWQgYW5ndWxhciBYIGVuIHJhZC9zLCB2ZWxvY2lkYWQgYW5ndWxhciBZIGVuIHJhZC9zLCB2ZWxvY2lkYWQKYW5ndWxhciBaIGVuIHJhZC9zLiBNdWVzdHJlYWRvIGEgMjAwIG11ZXN0cmFzIHBvciBzZWd1bmRvLgoKKiBjYXByaW5vX2dwcy5jc3YsIDQgY29sdW1uYXM6IHRpZW1wbyBVVEMgZW4gbWljcm9zZWd1bmRvcywgbGF0aXR1ZCBlbgpyYWRpYW5lcywgbG9uZ2l0dWQgZW4gcmFkaWFuZXMsIGFsdGl0dWQgZW4gbWV0cm9zLiBNdWVzdHJlYWRvIGEgNQptdWVzdHJhcyBwb3Igc2VndW5kby4KCiogY2Fwcmlub19ob3Vycy5jc3Y6IHRpZW1wbyBlbiBob3JhcywgbWludXRvcyB5IHNlZ3VuZG9zLiBNdWVzdHJlYWRvIGEKNSBtdWVzdHJhcyBwb3Igc2VndW5kby4gTGEgcHJpbWVyYSBob3JhIGRlIGVzdGUgYXJjaGl2byBjb2luY2lkZSBjb24KZWwgcHJpbWVyIHZhbG9yIGRlIHRpZW1wbyBVVEMgZGVsIGFyY2hpdm8gY2Fwcmlub19ncHMuY3N2LgoKCgojIyBDb25zdHJ1Y2Npb24gZGVsIERhdGFzZXQKCmBgYHtyfQpjYXByaW5vX2RhdGE8LWNiaW5kKGNhcHJpbm9faW11X2FjYyxjYXByaW5vX2ltdV9neXJvcyAlPiUgc2VsZWN0KC1WMSkpCgpjYXByaW5vX2RhdGE8LWNiaW5kKGNhcHJpbm9fZGF0YVtzZXEoMixucm93KGNhcHJpbm9fZGF0YSksMiksXSAlPiUgaGVhZChucm93KGNhcHJpbm9fZ3BzKS0xKSwgIGNhcHJpbm9fZ3BzWzI6bnJvdyhjYXByaW5vX2dwcyksXSApIApuYW1lcyhjYXByaW5vX2RhdGEpPC1jKCJpbXVfdHMiLAogICAgICAgICAgICAgICAgICAgICAgICJhY2NYIiwKICAgICAgICAgICAgICAgICAgICAgICAiYWNjWSIsCiAgICAgICAgICAgICAgICAgICAgICAgImFjY1oiLAogICAgICAgICAgICAgICAgICAgICAgICJneXJvWCIsCiAgICAgICAgICAgICAgICAgICAgICAgImd5cm9ZIiwKICAgICAgICAgICAgICAgICAgICAgICAiZ3lyb1oiLAogICAgICAgICAgICAgICAgICAgICAgICJncHNfaG91ciIsCiAgICAgICAgICAgICAgICAgICAgICAgImdwc190cyIsCiAgICAgICAgICAgICAgICAgICAgICAgImxhdCIsCiAgICAgICAgICAgICAgICAgICAgICAgImxvbmciLAogICAgICAgICAgICAgICAgICAgICAgICJhbHQiCiAgICAgICAgICAgICAgICAgICAgICAgICkKI2NhcHJpbm9fZGF0YSAlPiUgbXV0YXRlKGRpZmY9aW11X3RzLWdwc190cykgJT4lIHNlbGVjdChkaWZmKQpjYXByaW5vX2RhdGE8LWNhcHJpbm9fZGF0YSAlPiUgbXV0YXRlKGFjdGl2aXR5PWlmZWxzZShobXMoZ3BzX2hvdXIpPmhtcygiODo1NjowMCIpICYgaG1zKGdwc19ob3VyKTxobXMoIjEwOjM4OjAwIiksIkRjbyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGhtcyhncHNfaG91cik+PWhtcygiMTA6Mzg6MDAiKSAmIGhtcyhncHNfaG91cik8aG1zKCIxMTozODowMCIpLCJDIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShobXMoZ3BzX2hvdXIpPj1obXMoIjExOjM4OjAwIikgJiBobXMoZ3BzX2hvdXIpPGhtcygiMTI6MDU6MDAiKSwiUCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoaG1zKGdwc19ob3VyKT49aG1zKCIxMjowNTowMCIpICYgaG1zKGdwc19ob3VyKTxobXMoIjE1OjQ4OjAwIiksIkRjYSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoaG1zKGdwc19ob3VyKT49aG1zKCIxNzozMDowMCIpICYgaG1zKGdwc19ob3VyKTxobXMoIjIwOjMzOjAwIiksIlAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGhtcyhncHNfaG91cik+PWhtcygiMjA6NTA6MDAiKSAmIGhtcyhncHNfaG91cik8aG1zKCIyMTozMTowMCIpLCJDIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShobXMoZ3BzX2hvdXIpPj1obXMoIjIxOjMzOjAwIikgJiBobXMoZ3BzX2hvdXIpPGhtcygiMjE6NTA6MDAiKSwiRGNvIixOQSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpKSkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICApCgpjYXByaW5vX2RhdGE8LWNhcHJpbm9fZGF0YSAlPiUgbXV0YXRlKGlkPWN1dChhcy5QT1NJWGN0KGhtcyhjYXByaW5vX2RhdGEkZ3BzX2hvdXIpLG9yaWdpbj0iMTk2MC0wMS0wMSIsdHo9IkdNVCIpLGJyZWFrcz0iMSBtaW4iKSkgCgpgYGAKICAKIyNOdW1lcm8gZGUgbWVkaWNpb25lcyBwb3IgdGlwbyBkZSBhY3RpdmlkYWQgIApOQSBoYWNlIHJlZmVyZW5jaWEgYSBhcXVlbGxhcyBtZWRpY2lvbmVzIHF1ZSBubyBmdWVyb24gZXRpcXVldGFkYXMKYGBge3J9CmNhcHJpbm9fZGF0YSAlPiUgZ3JvdXBfYnkoYWN0aXZpdHkpICU+JSBzdW1tYXJpc2Uobj1uKCkpCgpgYGAKCiMjIEdyYWZpY2EgZGUgbGEgZGlmZXJlbmNpYSBlbnRyZSBlbCB0aWVtcG8gZGUgbXVlc3RyZW8gZGUgbGEgSU1VIHkgR1BTLgpgYGB7cn0KICBnZ3Bsb3QoY2Fwcmlub19kYXRhJT4lIG11dGF0ZShkaWZmPWltdV90cyAtIGdwc190cykpKwogIGdlb21fbGluZShhZXMoeT1kaWZmLHg9c2VxKDEsbnJvdyhjYXByaW5vX2RhdGEpKSksY29sb3I9J2JsdWUnKSsKICB0aGVtZV9idygpCmBgYAoKIyMgR3JhZmljYSBkZWwgdHJheWVjdG8gZ2VvcmVmZXJlbmNpYWRvICBkaXNjcmltaW5hZG8gcG9yIGFjdGl2aWRhZCAKYGBge3IgZmlnLndpZHRoPTEyfQptemFfbWFwIDwtIGdldF9tYXAobG9jYXRpb24gPSBjKGxhdD0tMzIuMzQsIGxvbj0tNjcuOTA3NDcpLCB6b29tID0gMTQsIG1hcHR5cGUgPSAidGVycmFpbiIsIGNvbG9yID0gJ2J3JykKCmdnbWFwKG16YV9tYXApKyAKICAjc3RhdF9kZW5zaXR5MmQoYWVzKHk9IHJhZDJkZWcobGF0KSwgeD1yYWQyZGVnKGxvbmcpLGZpbGw9YWx0aXR1ZGVfZ3JvdXAsYWxwaGE9Li5sZXZlbC4uKSwgZ2VvbT0icG9seWdvbiIsICBiaW5zPTEwMDAsZGF0YT1jYXByaW5vX2RhdGFfYWx0aXR1ZGVfZ3JvdXApKwogCiAgZ2VvbV9wb2ludChhZXMoeT0gcmFkMmRlZyhsYXQpLCB4PXJhZDJkZWcobG9uZyksc2hhcGU9YWN0aXZpdHksY29sb3I9YWN0aXZpdHkpLHNpemU9MixkYXRhPWNhcHJpbm9fZGF0YSU+JSBzYW1wbGVfbigxMDAwMDApLGZpbGw9J2JsYWNrJykrCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IlNldDIiKQogIAoKI3FtcGxvdCh4ID1yYWQyZGVnKGxvbmcpLCB5PXJhZDJkZWcobGF0KSwgZGF0YSA9IGNhcHJpbm9fZGF0YSwgbWFwdHlwZSA9ICJ0b25lciIsIGNvbG9yID0gSSgicmVkIikpCgogIAogIAojbXphIDwtIGMobGVmdCA9IC02Ny45MjUsIGJvdHRvbSA9IC0zMi40MCwgcmlnaHQgPSAtNjcuOSwgdG9wID0tMzIuMjUpCiNtYXAgPC0gZ2V0X3N0YW1lbm1hcChiYm94PW16YSwgem9vbSA9IDE1LCBtYXB0eXBlID0gInRvbmVyLWxpdGUiLGNyb3AgPSBUKQojZ2dtYXAobWFwKSAgKwojICBnZW9tX3BvaW50KGFlcyh5PSByYWQyZGVnKGxhdCksIHg9cmFkMmRlZyhsb25nKSxzaGFwZT1hY3Rpdml0eSxjb2xvcj1hY3Rpdml0eSksc2l6ZT0yLGRhdGE9Y2Fwcmlub19kYXRhJT4lIHNhbXBsZV9uKDEwMDAwMCksZmlsbD0nYmxhY2snKSsKIyMgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTZXQyIikKIz9nZXRfc3RhbWVubWFwCiAgICAKYGBgCiMjIEdyYWZpY2EgZGVsIHRyYXllY3RvIGdlb3JlZmVyZW5jaWFkbyAgZGlzY3JpbWluYWRvIHBvciBhY3RpdmlkYWQKTGFzIHpvbmFzIGNvbiBtZW5vciBkZW5zaWRhZCBkZSBwdW50b3MgaW5kaWNhbiB1biBtZW5vciBsYXBzbyBkZSB0aWVtcG8gZW4gZXNhIHpvbmEuCmBgYHtyfQoKY2Fwcmlub19kYXRhICAgJT4lCmdncGxvdChhZXMoeD1sb25nLHk9bGF0KSkrCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9YWN0aXZpdHkpLGFscGhhPTAuMDEsc2l6ZT0wLjgpKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTcGVjdHJhbCIpKwogIHRoZW1lX2J3KCkKYGBgCgoKYGBge3IgZmlnLndpZHRoPTF9CmNhcHJpbm9fZGF0YV9hbHRpdHVkZV9ncm91cDwtY2Fwcmlub19kYXRhICU+JSBtdXRhdGUoYWx0aXR1ZGVfZ3JvdXAgPSBjdXQoYWx0LCBicmVha3MgPSBjKDIwMCwzNzgsNTI1LDUyNyw1MzAsNjUwLDgwMCw5MDApKSkgCgpnZ3Bsb3QoY2Fwcmlub19kYXRhX2FsdGl0dWRlX2dyb3VwLGFlcyh4PWxvbmcseT1sYXQpKSsKICBzdGF0X2RlbnNpdHkyZChhZXMoZmlsbD1hbHRpdHVkZV9ncm91cCxhbHBoYT0uLmxldmVsLi4pLCBnZW9tPSJwb2x5Z29uIiwgIGJpbnM9MTAwMCkgKwogIHRoZW1lX21pbmltYWwoKQogIApgYGAKCgojIyBEaXN0cmlidWNpw7NuIGRlIGxvcyByZXN1bHRhZG9zIGRlIGxvcyBzZW5zb3JlcyBlbiBjYWRhIHVuYSBkZSBsYXMgYWN0aXZpZGFkZXMgKHNpbiBvdXRsaWVycykKKiBTZSBvYnNlcnZhbiBkaWZlcmVuY2lhcyBlbiBsYSBkaXN0cmlidWNpb24gZGUgbGEgYWN0aXZpZGFkIFAgZW4gY29tcGFyYWNpb24gYSBsYXMgb3RyYXMgYWN0aXZpZGFkZXMuCiogTGFzIGFjdGl2aWRhZGVzICoqRGNhKiogeSAqKkRjbyoqIHByZXNlbnRhbiBzaW1pbHR1ZGVzIChsbyBjdWFsIGVzIGVzcGVyYWJsZSkuCiogTGEgYWN0aXZkYWQgKipQKiogcHJlc2VudGEgYWxndW5hcyBzaW1pbGl0dWRlcyBjb24gKipEY2EqKiB5ICoqRGNvKiosIGxvIHF1ZSBwdWVkZSBkaWZpY3VsdGFyIHN1IGRldGVjdGlvbi4KCmBgYHtyfQpjYXByaW5vX2RhdGFfbWVsdGVkPC1jYXByaW5vX2RhdGEgJT4lIHNlbGVjdChhY2NYLGFjY1ksYWNjWixneXJvWCxneXJvWSxneXJvWixhY3Rpdml0eSkgJT4lIHJlc2hhcGUyOjptZWx0KCkgCiAgZ2dwbG90KGNhcHJpbm9fZGF0YV9tZWx0ZWQpKwogIGdlb21fYm94cGxvdChhZXMoeD12YXJpYWJsZSxjb2xvcj12YXJpYWJsZSx5PXZhbHVlKSxvdXRsaWVyLnNpemU9MC4xLG91dGxpZXIuc2hhcGUgPSBOQSkrCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoLTI1LDEwKSkrCiAgdGhlbWVfYncoKSsKdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkrCiAgZmFjZXRfd3JhcCh+YWN0aXZpdHkpIAogIApgYGAKIyBUb21hbW9zIG11ZXN0cmFzIGFsZWF0b3JpYXMgZGUgMSBtaW51dG8gZGUgZHVyYWNpb24gcGFyYSBzb2JyZSBsYXMgY3VhbGVzIGRlc3B1ZXMgcXVlcnJlbW9zIHByZWRlY2lyIGVsIGNvbXBvcnRhbWllbnRvCgpgYGB7cn0Kc2V0LnNlZWQoMjEwOTIwMTkpCm1pbnV0ZV9zbGljZTwtYXMudmVjdG9yKHQoY2Fwcmlub19kYXRhICU+JSBncm91cF9ieShpZCkgJT4lIHN1bW1hcmlzZShuPW4oKSkgJT4lIHNlbGVjdChpZCkpKQp0cmFpbmlkeDwtc2FtcGxlKDE6bGVuZ3RoKG1pbnV0ZV9zbGljZSksKGxlbmd0aChtaW51dGVfc2xpY2UpKjcwKS8xMDAuMCkKZGF0YV90cmFpbjwtY2Fwcmlub19kYXRhICU+JSBmaWx0ZXIoIGlkICVpbiUgbWludXRlX3NsaWNlW3RyYWluaWR4XSkKZGF0YV90ZXN0PC1jYXByaW5vX2RhdGEgJT4lIGZpbHRlciggaWQgJWluJSAobWludXRlX3NsaWNlWy10cmFpbmlkeF0pKQpkYXRhX3Rlc3QlPiUgZ3JvdXBfYnkoaWQpICU+JSBzdW1tYXJpc2Uobj1uKCkpCmBgYAoKIyBBcGxpY2Ftb3MgQmFnLW9mLWZlYXR1cmVzLgojIyBwcmVzdXB1ZXN0bwoKIENvbnNpZGVyYW1vcyBjYWRhIG11ZXN0cmEgY29tbyB1biB2ZWN0b3IgZGUgNSBkaW1lbnNpb25lcy4gTGEgaWRlYSBlcyBhcGxpY2FyIHVuIGFsZ29yaXRtbyBkZSBjbHVzdGVyaW5nIHNvYnJlIGVzdG9zIHZlY3RvcmVzIChhY2NYLGFjY1ksYWNjWixHeXJvWCxneXJvWSkuIFVuYSB2ZXogZW5jb250cmFkbyBsb3MgY2VudHJvaWRlcywgcGFyYSBjYWRhIG11ZXN0cmEgZGUgMSBzZSBjb25zdHJ1eWUgdW4gaGlzdG9ncmFtYSBkZSBsb3MgdmVjdG9yZXMgcXVlIGhhbiBjYWlkbyBlbiBjYWRhIHVubyBkZSBlc3RvcyBjbHVzdGVycy4gRXN0ZSBoaXN0b2dyYW1hIGVzIGVsIHF1ZSBzZSB1dGlsaXphIHBhcmEgZW50cmVnYXIgZWwgYWxnb3JpdG1vLgoKTGEgaGlwb3Rlc2lzIGRlIGFwbGljYXIgZXN0ZSBtZXRvZG8gZXMgcXVlIGFxdWVsbGFzIG11ZXN0cmFzIHF1ZSBwZXJ0ZW5lY2VuIGEgdW5hIG1pc21hIGFjdGl2aWRhZCB0ZW5kcmFuIHZhbG9yZXMgc2ltaWxhcmVzLiBMb3MgY3VhbGVzIGhhbiBzaWRvIHJlcHJlc2VudGFkbyBtZWRpYW50ZXMgZXN0b3MgaGlzdG9ncmFtYXMuIAoKIyMgQXBsaWNhbW9zIGstbWVhbnMgY29uIDEyIGNlbnRyb2lkZXMgc29icmUgZWwgY29uanVudG8gZGUgZW50cmVuYW1pZW50by4KCmBgYHtyfQoKY2x1c3Rlcl9yZXN1bHRzPC1rbWVhbnMoeCA9IGRhdGFfdHJhaW4lPiUgc2VsZWN0KGFjY1gsYWNjWSxhY2NaLGd5cm9YLGd5cm9ZKSwgY2VudGVycyA9IDI0LCBuc3RhcnQgPSA1KQpkYXRhX3RyYWluX2NsdXN0ZXI8LWNiaW5kKGRhdGFfdHJhaW4sY2x1c3Rlcj1jbHVzdGVyX3Jlc3VsdHMkY2x1c3RlcikgJT4lIGdyb3VwX2J5KGFjdGl2aXR5LGNsdXN0ZXIpCmRhdGFfdHJhaW5fY2x1c3RlciAlPiUgc3VtbWFyaXNlKG49bigpKSAlPiUKICBnZ3Bsb3QoKSsKICBnZW9tX2NvbChhZXMoeD1jbHVzdGVyLHk9biksZmlsbD0nc2t5Ymx1ZScpKwogIGZhY2V0X3dyYXAofmFjdGl2aXR5KSsKICB0aGVtZV9idygpCiAgCmBgYAoKIyMgQXBsaWNhbW9zIGstbWVhbnMgc29icmUgZWwgY29uanVudG8gZGUgdGVzdC4gU2UgdXNhbiBsb3MgbWlzbW9zIGNlbnRyb2lkZXMgZW5jb250cmFkb3MgZW4gdHJhaW5pbmcKYGBge3J9CmNsdXN0ZXJfcmVzdWx0c190ZXN0PC1rbWVhbnMoeCA9IGRhdGFfdGVzdCU+JSBzZWxlY3QoYWNjWCxhY2NZLGFjY1osZ3lyb1gsZ3lyb1kpLCBjZW50ZXJzID0gY2x1c3Rlcl9yZXN1bHRzJGNlbnRlcnMpCmRhdGFfdGVzdF9jbHVzdGVyPC1jYmluZChkYXRhX3Rlc3QsY2x1c3Rlcj1jbHVzdGVyX3Jlc3VsdHNfdGVzdCRjbHVzdGVyKSAlPiUgZ3JvdXBfYnkoYWN0aXZpdHksY2x1c3RlcikKZGF0YV90ZXN0X2NsdXN0ZXIgJT4lIHN1bW1hcmlzZShuPW4oKSkgJT4lCiAgZ2dwbG90KCkrCiAgZ2VvbV9jb2woYWVzKHg9Y2x1c3Rlcix5PW4pLGZpbGw9J29yYW5nZScpKwogIGZhY2V0X3dyYXAofmFjdGl2aXR5KSsKICB0aGVtZV9idygpCgpgYGAKCiMjIFByZXBhcmFuZG8gZWwgZGF0YXNldCBwYXJhIGFwbGljYXIgdW4gY2xhc2lmaWNhZG9yClRvbWFtb3MgaW50ZXJ2YWxvcyBkZSAxbSB5IGNyZWFtb3MgbG9zIGF0cmlidXRvcyB1c2FuZG8gYmFnLW9mLWZlYXR1cmVzCmBgYHtyIGZpZy5oZWlnaHQ9MTJ9CgoKc2FtcGxlX21pbnV0ZV9zbGljZTwtbWludXRlX3NsaWNlW3NhbXBsZSgxOmxlbmd0aChtaW51dGVfc2xpY2UpLDQwKV0KZGF0YV90cmFpbl9jbHVzdGVyICU+JSBmaWx0ZXIgKGlkICVpbiUgc2FtcGxlX21pbnV0ZV9zbGljZSkgICU+JSBncm91cF9ieShhY3Rpdml0eSxpZCxjbHVzdGVyKSAlPiUgc3VtbWFyaXNlKG49bigpKSAlPiUKICBnZ3Bsb3QoKSsKICBnZW9tX2NvbChhZXMoeD1jbHVzdGVyLHk9bixmaWxsPWFjdGl2aXR5KSkrCiAgZmFjZXRfd3JhcCh+aWQpKwogIHRoZW1lX2J3KCkKCmBgYApgYGB7cn0KCmRhdGFfdHJhaW5fY2x1c3Rlcl9sYWJlbGVkPC1kYXRhX3RyYWluX2NsdXN0ZXIgICAlPiUgZ3JvdXBfYnkoaWQsY2x1c3RlcikgJT4lIHN1bW1hcmlzZShuPW4oKSkgJT4lIHJlc2hhcGUyOjpkY2FzdChpZH5jbHVzdGVyLGZpbGwgPSAwKSAlPiUgaW5uZXJfam9pbihkYXRhX3RyYWluX2NsdXN0ZXIlPiUgdW5ncm91cCgpICU+JSBzZWxlY3QoaWQsYWN0aXZpdHkpICAlPiUgdW5pcXVlKCkgICxieT0iaWQiKSAlPiUgZmlsdGVyKCFpcy5uYShhY3Rpdml0eSkpCgoKCmRhdGFfdGVzdF9jbHVzdGVyX2xhYmVsZWQ8LWRhdGFfdGVzdF9jbHVzdGVyICAgJT4lIGdyb3VwX2J5KGlkLGNsdXN0ZXIpICU+JSBzdW1tYXJpc2Uobj1uKCkpICU+JSByZXNoYXBlMjo6ZGNhc3QoaWR+Y2x1c3RlcixmaWxsID0gMCkgJT4lIGlubmVyX2pvaW4oZGF0YV90ZXN0X2NsdXN0ZXIlPiUgdW5ncm91cCgpICU+JSBzZWxlY3QoaWQsYWN0aXZpdHkpICAlPiUgdW5pcXVlKCkgLGJ5PSJpZCIpIApgYGAKIyMgUHJvYmFtb3MgUmFuZG9tIEZvcmVzdApBcGxpY2Ftb3MgMng1IENWIHBhcmEgZWxlZ2lyIGxhIG1lam9yIHZlcnNpb24gZGVsIG1vZGVsbwpgYGB7cn0KbGlicmFyeShjYXJldCkKbGlicmFyeShkb01DKQpyZWdpc3RlckRvTUMoY29yZXM9NikKY3RybF9mYXN0IDwtIHRyYWluQ29udHJvbChtZXRob2Q9InJlcGVhdGVkY3YiLCAKICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cz0yLAogICAgICAgICAgICAgICAgICAgICBudW1iZXI9NSwgCiAgICAgICAgICAgICAgICAgICAgICNzdW1tYXJ5RnVuY3Rpb249dHdvQ2xhc3NTdW1tYXJ5LAogICAgICAgICAgICAgICAgICAgICB2ZXJib3NlSXRlcj1GLAogICAgICAgICAgICAgICAgICAgICAjcHJlUHJvY09wdGlvbnMgPSBsaXN0KHBjYUNvbXAgPSAxMCksCiAgICAgICAgICAgICAgICAgICAgIGNsYXNzUHJvYnM9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgYWxsb3dQYXJhbGxlbCA9IFQpICAKCm5uR3JpZCA8LSAgZXhwYW5kLmdyaWQoZGVjYXkgPSBjKDApLCBzaXplPWMoMTAsMjAsNTAsMTAwLDIwMCwzMDApKSAKc3ZtR3JpZCA8LSAgZXhwYW5kLmdyaWQoc2lnbWEgPSBjKDAuMDAxLDAuMDAwMDEpLCBDPWMoMC4xLDAuMDAxLDAuNSkpIAoKa2VyYXNHcmlkIDwtIGV4cGFuZC5ncmlkKAogIHNpemU9YygyMCw1MCwyMDAwLDUwMDApLAogIGxhbWJkYT1jKDApLAogIGJhdGNoX3NpemU9YygxMDI0KSwKICBkZWNheT1jKDApLAogIGFjdGl2YXRpb249YygndGFuaCcsJ3NpZ21vaWQnLCdyZWx1JyksCiAgbHI9YygwLjAwMSksIHJobz1jKDAuOSkKKQp0cmFpbl9mb3JtdWxhPWZvcm11bGEoImFjdGl2aXR5fi4iKQojY3RybF9mYXN0JHNhbXBsaW5nPC0ic21vdGUiCnJmRml0IDwtIGNhcmV0Ojp0cmFpbih0cmFpbl9mb3JtdWxhLAogICAgICAgICAgICAgICBkYXRhID0gZGF0YV90cmFpbl9jbHVzdGVyX2xhYmVsZWQgJT4lIHNlbGVjdCgtaWQpLAogICAgICAgICAgICAgICAjbWV0cmljPSJST0MiLAogICAgICAgICAgICAgICAjcHJlUHJvY2Vzcz1jKCJwY2EiKSwKICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICNtZXRob2QgPSAibWxwV2VpZ2h0RGVjYXkiLAogICAgICAgICAgICAgICAjbWV0aG9kID0gInN2bVJhZGlhbCIsCiAgICAgICAgICAgICAgICNtZXRob2QgPSJtbHBLZXJhc0RlY2F5IiwKICAgICAgICAgICAgICAgbWV0aG9kID0gInJmIiwKICAgICAgICAgICAgICAgI3R1bmVHcmlkPWtlcmFzR3JpZCwKICAgICAgICAgICAgICAgdHVuZUxlbmd0aD0xMSwKICAgICAgICAgICAgICAgdmVyYm9zZT0wLAogICAgICAgICAgICAgICAjZXBvY2hzPTEwMCwKICAgICAgICAgICAgICAgdHJDb250cm9sID0gY3RybF9mYXN0KQoKYGBgCiMjIG1vZGVsb3Mgb2J0ZW5pZG9zCmBgYHtyfQpyZkZpdApgYGAKCiMjIEV2YWx1YWNpb24gc29icmUgZWwgY29uanVudG8gZGUgdGVzdApNYXRyaXogZGUgQ29uZnVzaW9uCmBgYHtyfQpkYXRhX3Rlc3RfcHJlZGljdGlvbjwtcHJlZGljdChyZkZpdCxkYXRhX3Rlc3RfY2x1c3Rlcl9sYWJlbGVkICU+JSBzZWxlY3QoLWlkKSkjLHR5cGU9InByb2IiKQpjYXJldDo6Y29uZnVzaW9uTWF0cml4KGRhdGFfdGVzdF9wcmVkaWN0aW9uLGFzLmZhY3RvcihkYXRhX3Rlc3RfY2x1c3Rlcl9sYWJlbGVkJGFjdGl2aXR5KSkKYGBgCgpgYGB7cn0KCnRhYmxlKGRhdGFfdGVzdF9wcmVkaWN0aW9uLGFzLmZhY3RvcihkYXRhX3Rlc3RfY2x1c3Rlcl9sYWJlbGVkJGFjdGl2aXR5KSkKCnJlc2hhcGUyOjptZWx0KHRhYmxlKGRhdGFfdGVzdF9wcmVkaWN0aW9uLGFzLmZhY3RvcihkYXRhX3Rlc3RfY2x1c3Rlcl9sYWJlbGVkJGFjdGl2aXR5KSkpICU+JQogIGdncGxvdChhZXMoeD1kYXRhX3Rlc3RfcHJlZGljdGlvbix5PVZhcjIpKSsKICBnZW9tX3RpbGUoYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXIgPSAid2hpdGUiKSArIAogICBnZW9tX3RleHQoYWVzKGxhYmVsID0gc3ByaW50ZigiJTEuMGYiLCB2YWx1ZSkpLCB2anVzdCA9IDEpKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gImJsdWUiLCBoaWdoID0gInJlZCIpKwogIHhsYWIoIiBQcmVkaWN0ZWQgQWN0aXZpdHkgIikreWxhYigiIEFjdHVhbCBBY3Rpdml0eSIpKwogIHRoZW1lX2J3KCkrIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgojQ09OQ0xVU0lPTkVTCg==