CC3

## Preparando los datos
gradesData<-read.csv("cc3train.csv")
## En base a la nota final indicar si aprobo
gradesData$aprobado <- ifelse(gradesData$notaTotal >= 61, "aprobado", "reprobado")
gradesData$aprobadoNum <- ifelse(gradesData$notaTotal >= 61, 1, 0)

Los datos con los que se estara trabajando son las notas de los alumnos de Ciencias de la Computacion 3 de la cual soy catedratico en la seccion nocturna. Para contar con mayor cantidad de datos se estara usando las secciones B y C para determinar los modelos (cc3train.csv), por ser las mas numerosas. La seccion A que es la menor se usara para probar los modelos (cc3test.csv).

En los datasets se cuenta con las notas totales para cada alumno segun cada tipo de asignacion, y las notas de cada uno de los cinco proyectos realizados.

Agregamos una columna para visualizar como texto si el alumno aprobo, y una en la que vemos un 1 para aprobado y un 0 para reprobado, esto se usara al momento de hacer los modelos.

Alumnos aprobados

La tabla siguiente resume la cantidad de alumnos que aprobaron y reprobaron. Utilicemos los datos para determinar cuales son las asignaciones mas importantes, y cuales podrian ser las posibles causas por las que los alumnos reprueban.

table(gradesData$aprobado, dnn = c("Notas finales"))
Notas finales
 aprobado reprobado 
       37        10 
prop.table(table(gradesData$aprobado, dnn = c("Porcentaje")))
Porcentaje
 aprobado reprobado 
 0.787234  0.212766 

En las secciones B y C aprobaron 37 alumnos que corresponden al 78% aproximadamente.

hist(gradesData$notaTotal)

summary(gradesData$notaTotal)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   2.00   65.50   73.00   70.77   87.00  100.00 

La media fue 73 puntos. El primer cuartil se encuentra en 65.50 puntos, esto quiere decir que aproximadamente una cuarta parte de los estudiantes reprobo, pues se encuentra por debajo de esta nota. Este dato es importante, pues al probar algun modelo lineal consideraremos como reprobados a los que queden debajo de ese primer cuartil.

Modelos lineales

Obtengamos un modelo que incluya todas las categorias de asignacion.

linealCompleto <- lm(data = gradesData, formula = aprobadoNum ~ asistencia + cuestionarios + discusiones + examenes + laboratorios + otros + proyectos + tareas)

summary(linealCompleto)

Call:
lm(formula = aprobadoNum ~ asistencia + cuestionarios + discusiones + 
    examenes + laboratorios + otros + proyectos + tareas, data = gradesData)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.65292 -0.10390 -0.00104  0.15664  0.34138 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)   -0.160405   0.181957  -0.882   0.3836    
asistencia     0.090715   0.110962   0.818   0.4187    
cuestionarios  0.002343   0.024119   0.097   0.9231    
discusiones    0.025438   0.009672   2.630   0.0123 *  
examenes       0.020929   0.013244   1.580   0.1223    
laboratorios  -0.015373   0.015935  -0.965   0.3408    
otros          0.013077   0.011166   1.171   0.2488    
proyectos      0.031029   0.006283   4.938 1.61e-05 ***
tareas        -0.024382   0.019592  -1.244   0.2209    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.2335 on 38 degrees of freedom
Multiple R-squared:  0.7368,    Adjusted R-squared:  0.6814 
F-statistic:  13.3 on 8 and 38 DF,  p-value: 6.29e-09

Aqui se puede determinar que solo la categoria de Proyectos tiene una alta significancia sobre el aprobar la clase, sin embargo su valor de R-cuadrado aun es bajo siendo de solo 0.7368.

Comprobemos realizando un modelo que solo incluya Proyectos, y Discusiones que tambien tiene algo de significancia.

linealSimplificado <- lm(data = gradesData, formula = aprobadoNum ~ proyectos + discusiones)

summary(linealSimplificado)

Call:
lm(formula = aprobadoNum ~ proyectos + discusiones, data = gradesData)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.72177 -0.12904  0.01825  0.13965  0.34152 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 0.024696   0.083102   0.297   0.7677    
proyectos   0.032172   0.003856   8.344  1.3e-10 ***
discusiones 0.026902   0.008232   3.268   0.0021 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.2325 on 44 degrees of freedom
Multiple R-squared:  0.6978,    Adjusted R-squared:  0.6841 
F-statistic: 50.81 on 2 and 44 DF,  p-value: 3.674e-12

El valor de R-cuadrado disminuyo, ahora es de solo 0.6978.

Creamos tambien un modelo que incluye cada proyecto (en lugar de la nota total de estos) y las discusiones.

linealProyectos <- lm(data = gradesData, formula = aprobadoNum ~ proyecto1_1 + proyecto1_2 + proyecto2 + proyecto3 + proyecto4 + discusiones)

summary(linealProyectos)

Call:
lm(formula = aprobadoNum ~ proyecto1_1 + proyecto1_2 + proyecto2 + 
    proyecto3 + proyecto4 + discusiones, data = gradesData)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.66760 -0.07326  0.00183  0.15177  0.41880 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)  0.0479513  0.0848801   0.565  0.57528    
proyecto1_1 -0.0007499  0.0197091  -0.038  0.96984    
proyecto1_2  0.0066184  0.0435634   0.152  0.88001    
proyecto2    0.0543070  0.0117655   4.616 3.99e-05 ***
proyecto3    0.0309012  0.0148012   2.088  0.04323 *  
proyecto4    0.0422641  0.0164011   2.577  0.01376 *  
discusiones  0.0253065  0.0085719   2.952  0.00526 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.2295 on 40 degrees of freedom
Multiple R-squared:  0.7325,    Adjusted R-squared:  0.6923 
F-statistic: 18.25 on 6 and 40 DF,  p-value: 4.531e-10

De este modelo determinamos que su R-cuadrado es de 0.7325, y que el proyecto con mayor significancia es el 2. Los proyectos 3 y 4 tienen menor significancia que las discusiones en este modelo. El R-cuadrado volvio a aumentar respecto al modelo anterior.

El proyecto 1 tuvo un comportamiento particular.

p1 <- gradesData %>%
  select(proyecto1_1, proyecto1_2) %>%
  count(proyecto1_1, proyecto1_2)

arrange(p1, -n)

Las dos notas mas comunes fueron 6.00+3.00 (nota completa) y 0.0+0.0 (no entrego nada).

Eliminamos ambas fases del proyecto 1 y obtenemos el siguiente modelo.

linealProyectosSimplificado <- lm(data = gradesData, formula = aprobadoNum ~ proyecto2 + proyecto3 + proyecto4 + discusiones)

summary(linealProyectosSimplificado)

Call:
lm(formula = aprobadoNum ~ proyecto2 + proyecto3 + proyecto4 + 
    discusiones, data = gradesData)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.67315 -0.07582  0.00333  0.15385  0.42630 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 0.050750   0.079246   0.640  0.52539    
proyecto2   0.055126   0.009582   5.753 8.99e-07 ***
proyecto3   0.032054   0.011320   2.832  0.00708 ** 
proyecto4   0.042048   0.015125   2.780  0.00810 ** 
discusiones 0.025073   0.008220   3.050  0.00395 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.224 on 42 degrees of freedom
Multiple R-squared:  0.7323,    Adjusted R-squared:  0.7068 
F-statistic: 28.72 on 4 and 42 DF,  p-value: 1.567e-11

El valor de R-cuadrado es de 0.7323, casi no hubo cambio respecto al modelo anterior. La significancia del proyecto 3 y 4 aumento, ahora es similar a la de las discusiones.

Prediccion

Antes de probar los modelos, obtenemos cuantos alumnos aprobaron realmente en la seccion A, par poder comparar.

## Preparando los datos
testData<-read.csv("cc3test.csv")
## En base a la nota final indicar si aprobo
testData$aprobado <- ifelse(testData$notaTotal >= 61, "aprobado", "reprobado")
testData$aprobadoNum <- ifelse(testData$notaTotal >= 61, 1, 0)
## Obtener los datos reales
table(testData$aprobado, dnn = c("Notas finales"))
Notas finales
 aprobado reprobado 
       13         5 

Trece alumnos aprobaron.

Probamos el primero modelo. El primer cuartil se encuentra en 0.56, asi que usamos ese valor para indicar que predicciones se vuelven 0 y cuales 1.

testData<-read.csv("cc3test.csv")
prediccion0 <- predict(object = linealCompleto, newdata = testData)
summary(prediccion0)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.2861  0.5627  0.7475  0.6856  0.8443  1.0660 
hist(prediccion0)

prediccion0 <- ifelse(prediccion0 > 0.56, 1, 0)
table(prediccion0)
prediccion0
 0  1 
 4 14 

Aprobaron 14 alumnos, uno mas de los que aprobaron realmente.

En el segundo modelo elegimos el limite de la misma manera, segun el primer cuartil que ahora es 0.60.

testData<-read.csv("cc3test.csv")
prediccion1 <- predict(object = linealSimplificado, newdata = testData)
summary(prediccion1)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.2660  0.6031  0.7565  0.7079  0.8757  1.0690 
hist(prediccion1)

prediccion1 <- ifelse(prediccion1 > 0.60, 1, 0)
table(prediccion1)
prediccion1
 0  1 
 5 13 

Trece alumnos aprobaron segun la prediccion, se acerto.

En el tercer modelo el limite sera de 0.61.

testData<-read.csv("cc3test.csv")
prediccion2 <- predict(object = linealProyectos, newdata = testData)
summary(prediccion2)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.2177  0.6152  0.8829  0.7721  1.0050  1.1070 
hist(prediccion2)

prediccion2 <- ifelse(prediccion2 > 0.61, 1, 0)
table(prediccion2)
prediccion2
 0  1 
 4 14 

Perdimos algo de precision, de nuevo volvieron a ganar 14 alumnos.

De nuevo el limite es de 0.61 para el cuarto modelo.

testData<-read.csv("cc3test.csv")
prediccion3 <- predict(object = linealProyectosSimplificado, newdata = testData)
summary(prediccion3)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.2162  0.6145  0.8944  0.7785  1.0120  1.1230 
hist(prediccion3)

prediccion3 <- ifelse(prediccion3 > 0.61, 1, 0)
table(prediccion3)
prediccion3
 0  1 
 5 13 

Volvimos a obtener un resultado de 13 alumnos aprobados.

Conclusion sobre los modelos

En este caso los dos modelos que dieron el resultado exacto se obtuvieron a partir de eliminar las variables con menos significancia de un modelo anterior.

LS0tDQp0aXRsZTogIkNDMyINCmF1dGhvcjogIkx1aXMgSmltZW5leiINCmRhdGU6ICIxMiBkZSBkaWNpZW1icmUgZGUgMjAxNiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KDQojIyBQcmVwYXJhbmRvIGxhcyBsaWJyZXJpYXMNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KIyMjIENDMw0KDQpgYGB7ciBkYXRhLCBpbmNsdWRlPVRSVUV9DQojIyBQcmVwYXJhbmRvIGxvcyBkYXRvcw0KZ3JhZGVzRGF0YTwtcmVhZC5jc3YoImNjM3RyYWluLmNzdiIpDQojIyBFbiBiYXNlIGEgbGEgbm90YSBmaW5hbCBpbmRpY2FyIHNpIGFwcm9ibw0KZ3JhZGVzRGF0YSRhcHJvYmFkbyA8LSBpZmVsc2UoZ3JhZGVzRGF0YSRub3RhVG90YWwgPj0gNjEsICJhcHJvYmFkbyIsICJyZXByb2JhZG8iKQ0KZ3JhZGVzRGF0YSRhcHJvYmFkb051bSA8LSBpZmVsc2UoZ3JhZGVzRGF0YSRub3RhVG90YWwgPj0gNjEsIDEsIDApDQpgYGANCkxvcyBkYXRvcyBjb24gbG9zIHF1ZSBzZSBlc3RhcmEgdHJhYmFqYW5kbyBzb24gbGFzIG5vdGFzIGRlIGxvcyBhbHVtbm9zIGRlIENpZW5jaWFzIGRlIGxhIENvbXB1dGFjaW9uIDMgZGUgbGEgY3VhbCBzb3kgY2F0ZWRyYXRpY28gZW4gbGEgc2VjY2lvbiBub2N0dXJuYS4gUGFyYSBjb250YXIgY29uIG1heW9yIGNhbnRpZGFkIGRlIGRhdG9zIHNlIGVzdGFyYSB1c2FuZG8gbGFzIHNlY2Npb25lcyBCIHkgQyBwYXJhIGRldGVybWluYXIgbG9zIG1vZGVsb3MgKGNjM3RyYWluLmNzdiksIHBvciBzZXIgbGFzIG1hcyBudW1lcm9zYXMuIExhIHNlY2Npb24gQSBxdWUgZXMgbGEgbWVub3Igc2UgdXNhcmEgcGFyYSBwcm9iYXIgbG9zIG1vZGVsb3MgKGNjM3Rlc3QuY3N2KS4NCg0KRW4gbG9zIGRhdGFzZXRzIHNlIGN1ZW50YSBjb24gbGFzIG5vdGFzIHRvdGFsZXMgcGFyYSBjYWRhIGFsdW1ubyBzZWd1biBjYWRhIHRpcG8gZGUgYXNpZ25hY2lvbiwgeSBsYXMgbm90YXMgZGUgY2FkYSB1bm8gZGUgbG9zIGNpbmNvIHByb3llY3RvcyByZWFsaXphZG9zLg0KDQpBZ3JlZ2Ftb3MgdW5hIGNvbHVtbmEgcGFyYSB2aXN1YWxpemFyIGNvbW8gdGV4dG8gc2kgZWwgYWx1bW5vIGFwcm9ibywgeSB1bmEgZW4gbGEgcXVlIHZlbW9zIHVuIDEgcGFyYSBhcHJvYmFkbyB5IHVuIDAgcGFyYSByZXByb2JhZG8sIGVzdG8gc2UgdXNhcmEgYWwgbW9tZW50byBkZSBoYWNlciBsb3MgbW9kZWxvcy4NCg0KIyMjIEFsdW1ub3MgYXByb2JhZG9zDQoNCkxhIHRhYmxhIHNpZ3VpZW50ZSByZXN1bWUgbGEgY2FudGlkYWQgZGUgYWx1bW5vcyBxdWUgYXByb2Jhcm9uIHkgcmVwcm9iYXJvbi4gVXRpbGljZW1vcyBsb3MgZGF0b3MgcGFyYSBkZXRlcm1pbmFyIGN1YWxlcyBzb24gbGFzIGFzaWduYWNpb25lcyBtYXMgaW1wb3J0YW50ZXMsIHkgY3VhbGVzIHBvZHJpYW4gc2VyIGxhcyBwb3NpYmxlcyBjYXVzYXMgcG9yIGxhcyBxdWUgbG9zIGFsdW1ub3MgcmVwcnVlYmFuLg0KDQpgYGB7ciBhbHVtbm9zQXByb2JhZG9zfQ0KdGFibGUoZ3JhZGVzRGF0YSRhcHJvYmFkbywgZG5uID0gYygiTm90YXMgZmluYWxlcyIpKQ0KcHJvcC50YWJsZSh0YWJsZShncmFkZXNEYXRhJGFwcm9iYWRvLCBkbm4gPSBjKCJQb3JjZW50YWplIikpKQ0KYGBgDQpFbiBsYXMgc2VjY2lvbmVzIEIgeSBDIGFwcm9iYXJvbiAzNyBhbHVtbm9zIHF1ZSBjb3JyZXNwb25kZW4gYWwgNzglIGFwcm94aW1hZGFtZW50ZS4NCg0KYGBge3IgaGlzdH0NCmhpc3QoZ3JhZGVzRGF0YSRub3RhVG90YWwpDQoNCnN1bW1hcnkoZ3JhZGVzRGF0YSRub3RhVG90YWwpDQpgYGANCkxhIG1lZGlhIGZ1ZSA3MyBwdW50b3MuIEVsIHByaW1lciBjdWFydGlsIHNlIGVuY3VlbnRyYSBlbiA2NS41MCBwdW50b3MsIGVzdG8gcXVpZXJlIGRlY2lyIHF1ZSBhcHJveGltYWRhbWVudGUgdW5hIGN1YXJ0YSBwYXJ0ZSBkZSBsb3MgZXN0dWRpYW50ZXMgcmVwcm9ibywgcHVlcyBzZSBlbmN1ZW50cmEgcG9yIGRlYmFqbyBkZSBlc3RhIG5vdGEuIEVzdGUgZGF0byBlcyBpbXBvcnRhbnRlLCBwdWVzIGFsIHByb2JhciBhbGd1biBtb2RlbG8gbGluZWFsIGNvbnNpZGVyYXJlbW9zIGNvbW8gcmVwcm9iYWRvcyBhIGxvcyBxdWUgcXVlZGVuIGRlYmFqbyBkZSBlc2UgcHJpbWVyIGN1YXJ0aWwuDQoNCiMjIyBNb2RlbG9zIGxpbmVhbGVzDQoNCk9idGVuZ2Ftb3MgdW4gbW9kZWxvIHF1ZSBpbmNsdXlhIHRvZGFzIGxhcyBjYXRlZ29yaWFzIGRlIGFzaWduYWNpb24uDQpgYGB7ciBtb2RlbG9MaW5lYWxDb21wbGV0b30NCmxpbmVhbENvbXBsZXRvIDwtIGxtKGRhdGEgPSBncmFkZXNEYXRhLCBmb3JtdWxhID0gYXByb2JhZG9OdW0gfiBhc2lzdGVuY2lhICsgY3Vlc3Rpb25hcmlvcyArIGRpc2N1c2lvbmVzICsgZXhhbWVuZXMgKyBsYWJvcmF0b3Jpb3MgKyBvdHJvcyArIHByb3llY3RvcyArIHRhcmVhcykNCg0Kc3VtbWFyeShsaW5lYWxDb21wbGV0bykNCmBgYA0KQXF1aSBzZSBwdWVkZSBkZXRlcm1pbmFyIHF1ZSBzb2xvIGxhIGNhdGVnb3JpYSBkZSBQcm95ZWN0b3MgdGllbmUgdW5hIGFsdGEgc2lnbmlmaWNhbmNpYSBzb2JyZSBlbCBhcHJvYmFyIGxhIGNsYXNlLCBzaW4gZW1iYXJnbyBzdSB2YWxvciBkZSBSLWN1YWRyYWRvIGF1biBlcyBiYWpvIHNpZW5kbyBkZSBzb2xvIDAuNzM2OC4NCg0KQ29tcHJvYmVtb3MgcmVhbGl6YW5kbyB1biBtb2RlbG8gcXVlIHNvbG8gaW5jbHV5YSBQcm95ZWN0b3MsIHkgRGlzY3VzaW9uZXMgcXVlIHRhbWJpZW4gdGllbmUgYWxnbyBkZSBzaWduaWZpY2FuY2lhLg0KYGBge3IgbW9kZWxvTGluZWFsU2ltcGxpZmljYWRvfQ0KbGluZWFsU2ltcGxpZmljYWRvIDwtIGxtKGRhdGEgPSBncmFkZXNEYXRhLCBmb3JtdWxhID0gYXByb2JhZG9OdW0gfiBwcm95ZWN0b3MgKyBkaXNjdXNpb25lcykNCg0Kc3VtbWFyeShsaW5lYWxTaW1wbGlmaWNhZG8pDQpgYGANCkVsIHZhbG9yIGRlIFItY3VhZHJhZG8gZGlzbWludXlvLCBhaG9yYSBlcyBkZSBzb2xvIDAuNjk3OC4NCg0KQ3JlYW1vcyB0YW1iaWVuIHVuIG1vZGVsbyBxdWUgaW5jbHV5ZSBjYWRhIHByb3llY3RvIChlbiBsdWdhciBkZSBsYSBub3RhIHRvdGFsIGRlIGVzdG9zKSB5IGxhcyBkaXNjdXNpb25lcy4NCmBgYHtyIG1vZGVsb0xpbmVhbFByb3llY3Rvc30NCmxpbmVhbFByb3llY3RvcyA8LSBsbShkYXRhID0gZ3JhZGVzRGF0YSwgZm9ybXVsYSA9IGFwcm9iYWRvTnVtIH4gcHJveWVjdG8xXzEgKyBwcm95ZWN0bzFfMiArIHByb3llY3RvMiArIHByb3llY3RvMyArIHByb3llY3RvNCArIGRpc2N1c2lvbmVzKQ0KDQpzdW1tYXJ5KGxpbmVhbFByb3llY3RvcykNCmBgYA0KRGUgZXN0ZSBtb2RlbG8gZGV0ZXJtaW5hbW9zIHF1ZSBzdSBSLWN1YWRyYWRvIGVzIGRlIDAuNzMyNSwgeSBxdWUgZWwgcHJveWVjdG8gY29uIG1heW9yIHNpZ25pZmljYW5jaWEgZXMgZWwgMi4gTG9zIHByb3llY3RvcyAzIHkgNCB0aWVuZW4gbWVub3Igc2lnbmlmaWNhbmNpYSBxdWUgbGFzIGRpc2N1c2lvbmVzIGVuIGVzdGUgbW9kZWxvLiBFbCBSLWN1YWRyYWRvIHZvbHZpbyBhIGF1bWVudGFyIHJlc3BlY3RvIGFsIG1vZGVsbyBhbnRlcmlvci4NCg0KRWwgcHJveWVjdG8gMSB0dXZvIHVuIGNvbXBvcnRhbWllbnRvIHBhcnRpY3VsYXIuDQpgYGB7cn0NCnAxIDwtIGdyYWRlc0RhdGEgJT4lDQogIHNlbGVjdChwcm95ZWN0bzFfMSwgcHJveWVjdG8xXzIpICU+JQ0KICBjb3VudChwcm95ZWN0bzFfMSwgcHJveWVjdG8xXzIpDQoNCmFycmFuZ2UocDEsIC1uKQ0KYGBgDQpMYXMgZG9zIG5vdGFzIG1hcyBjb211bmVzIGZ1ZXJvbiA2LjAwKzMuMDAgKG5vdGEgY29tcGxldGEpIHkgMC4wKzAuMCAobm8gZW50cmVnbyBuYWRhKS4gDQoNCkVsaW1pbmFtb3MgYW1iYXMgZmFzZXMgZGVsIHByb3llY3RvIDEgeSBvYnRlbmVtb3MgZWwgc2lndWllbnRlIG1vZGVsby4NCmBgYHtyIG1vZGVsb0xpbmVhbFByb3llY3Rvc1NpbXBsaWZpY2Fkb30NCmxpbmVhbFByb3llY3Rvc1NpbXBsaWZpY2FkbyA8LSBsbShkYXRhID0gZ3JhZGVzRGF0YSwgZm9ybXVsYSA9IGFwcm9iYWRvTnVtIH4gcHJveWVjdG8yICsgcHJveWVjdG8zICsgcHJveWVjdG80ICsgZGlzY3VzaW9uZXMpDQoNCnN1bW1hcnkobGluZWFsUHJveWVjdG9zU2ltcGxpZmljYWRvKQ0KYGBgDQpFbCB2YWxvciBkZSBSLWN1YWRyYWRvIGVzIGRlIDAuNzMyMywgY2FzaSBubyBodWJvIGNhbWJpbyByZXNwZWN0byBhbCBtb2RlbG8gYW50ZXJpb3IuIExhIHNpZ25pZmljYW5jaWEgZGVsIHByb3llY3RvIDMgeSA0IGF1bWVudG8sIGFob3JhIGVzIHNpbWlsYXIgYSBsYSBkZSBsYXMgZGlzY3VzaW9uZXMuDQoNCiMjIyBQcmVkaWNjaW9uDQpBbnRlcyBkZSBwcm9iYXIgbG9zIG1vZGVsb3MsIG9idGVuZW1vcyBjdWFudG9zIGFsdW1ub3MgYXByb2Jhcm9uIHJlYWxtZW50ZSBlbiBsYSBzZWNjaW9uIEEsIHBhciBwb2RlciBjb21wYXJhci4NCmBgYHtyIHRlc3RSZWFsfQ0KIyMgUHJlcGFyYW5kbyBsb3MgZGF0b3MNCnRlc3REYXRhPC1yZWFkLmNzdigiY2MzdGVzdC5jc3YiKQ0KIyMgRW4gYmFzZSBhIGxhIG5vdGEgZmluYWwgaW5kaWNhciBzaSBhcHJvYm8NCnRlc3REYXRhJGFwcm9iYWRvIDwtIGlmZWxzZSh0ZXN0RGF0YSRub3RhVG90YWwgPj0gNjEsICJhcHJvYmFkbyIsICJyZXByb2JhZG8iKQ0KdGVzdERhdGEkYXByb2JhZG9OdW0gPC0gaWZlbHNlKHRlc3REYXRhJG5vdGFUb3RhbCA+PSA2MSwgMSwgMCkNCiMjIE9idGVuZXIgbG9zIGRhdG9zIHJlYWxlcw0KdGFibGUodGVzdERhdGEkYXByb2JhZG8sIGRubiA9IGMoIk5vdGFzIGZpbmFsZXMiKSkNCmBgYA0KVHJlY2UgYWx1bW5vcyBhcHJvYmFyb24uDQoNClByb2JhbW9zIGVsIHByaW1lcm8gbW9kZWxvLiBFbCBwcmltZXIgY3VhcnRpbCBzZSBlbmN1ZW50cmEgZW4gMC41NiwgYXNpIHF1ZSB1c2Ftb3MgZXNlIHZhbG9yIHBhcmEgaW5kaWNhciBxdWUgcHJlZGljY2lvbmVzIHNlIHZ1ZWx2ZW4gMCB5IGN1YWxlcyAxLg0KYGBge3IgdGVzdFByZWRpY2Npb24wfQ0KdGVzdERhdGE8LXJlYWQuY3N2KCJjYzN0ZXN0LmNzdiIpDQpwcmVkaWNjaW9uMCA8LSBwcmVkaWN0KG9iamVjdCA9IGxpbmVhbENvbXBsZXRvLCBuZXdkYXRhID0gdGVzdERhdGEpDQpzdW1tYXJ5KHByZWRpY2Npb24wKQ0KaGlzdChwcmVkaWNjaW9uMCkNCg0KcHJlZGljY2lvbjAgPC0gaWZlbHNlKHByZWRpY2Npb24wID4gMC41NiwgMSwgMCkNCnRhYmxlKHByZWRpY2Npb24wKQ0KYGBgDQpBcHJvYmFyb24gMTQgYWx1bW5vcywgdW5vIG1hcyBkZSBsb3MgcXVlIGFwcm9iYXJvbiByZWFsbWVudGUuDQoNCkVuIGVsIHNlZ3VuZG8gbW9kZWxvIGVsZWdpbW9zIGVsIGxpbWl0ZSBkZSBsYSBtaXNtYSBtYW5lcmEsIHNlZ3VuIGVsIHByaW1lciBjdWFydGlsIHF1ZSBhaG9yYSBlcyAwLjYwLg0KYGBge3IgdGVzdFByZWRpY2Npb24xfQ0KdGVzdERhdGE8LXJlYWQuY3N2KCJjYzN0ZXN0LmNzdiIpDQpwcmVkaWNjaW9uMSA8LSBwcmVkaWN0KG9iamVjdCA9IGxpbmVhbFNpbXBsaWZpY2FkbywgbmV3ZGF0YSA9IHRlc3REYXRhKQ0Kc3VtbWFyeShwcmVkaWNjaW9uMSkNCmhpc3QocHJlZGljY2lvbjEpDQoNCnByZWRpY2Npb24xIDwtIGlmZWxzZShwcmVkaWNjaW9uMSA+IDAuNjAsIDEsIDApDQp0YWJsZShwcmVkaWNjaW9uMSkNCmBgYA0KVHJlY2UgYWx1bW5vcyBhcHJvYmFyb24gc2VndW4gbGEgcHJlZGljY2lvbiwgc2UgYWNlcnRvLg0KDQpFbiBlbCB0ZXJjZXIgbW9kZWxvIGVsIGxpbWl0ZSBzZXJhIGRlIDAuNjEuDQpgYGB7ciB0ZXN0UHJlZGljY2lvbjJ9DQp0ZXN0RGF0YTwtcmVhZC5jc3YoImNjM3Rlc3QuY3N2IikNCnByZWRpY2Npb24yIDwtIHByZWRpY3Qob2JqZWN0ID0gbGluZWFsUHJveWVjdG9zLCBuZXdkYXRhID0gdGVzdERhdGEpDQpzdW1tYXJ5KHByZWRpY2Npb24yKQ0KaGlzdChwcmVkaWNjaW9uMikNCg0KcHJlZGljY2lvbjIgPC0gaWZlbHNlKHByZWRpY2Npb24yID4gMC42MSwgMSwgMCkNCnRhYmxlKHByZWRpY2Npb24yKQ0KYGBgDQpQZXJkaW1vcyBhbGdvIGRlIHByZWNpc2lvbiwgZGUgbnVldm8gdm9sdmllcm9uIGEgZ2FuYXIgMTQgYWx1bW5vcy4NCg0KRGUgbnVldm8gZWwgbGltaXRlIGVzIGRlIDAuNjEgcGFyYSBlbCBjdWFydG8gbW9kZWxvLg0KYGBge3IgdGVzdFByZWRpY2Npb24zfQ0KdGVzdERhdGE8LXJlYWQuY3N2KCJjYzN0ZXN0LmNzdiIpDQpwcmVkaWNjaW9uMyA8LSBwcmVkaWN0KG9iamVjdCA9IGxpbmVhbFByb3llY3Rvc1NpbXBsaWZpY2FkbywgbmV3ZGF0YSA9IHRlc3REYXRhKQ0Kc3VtbWFyeShwcmVkaWNjaW9uMykNCmhpc3QocHJlZGljY2lvbjMpDQoNCnByZWRpY2Npb24zIDwtIGlmZWxzZShwcmVkaWNjaW9uMyA+IDAuNjEsIDEsIDApDQp0YWJsZShwcmVkaWNjaW9uMykNCmBgYA0KVm9sdmltb3MgYSBvYnRlbmVyIHVuIHJlc3VsdGFkbyBkZSAxMyBhbHVtbm9zIGFwcm9iYWRvcy4NCg0KIyMjIENvbmNsdXNpb24gc29icmUgbG9zIG1vZGVsb3MNCkVuIGVzdGUgY2FzbyBsb3MgZG9zIG1vZGVsb3MgcXVlIGRpZXJvbiBlbCByZXN1bHRhZG8gZXhhY3RvIHNlIG9idHV2aWVyb24gYSBwYXJ0aXIgZGUgZWxpbWluYXIgbGFzIHZhcmlhYmxlcyBjb24gbWVub3Mgc2lnbmlmaWNhbmNpYSBkZSB1biBtb2RlbG8gYW50ZXJpb3Iu