Example data: 634 older adults (age 80-100) self-reporting on 7 items
assessing the Instrumental Activities of Daily Living (IADL) as
follows:
- Housework (cleaning and laundry): 1=64%
- Bedmaking: 1=84%
- Cooking: 1=77%
- Everyday shopping: 1=66%
- Getting to places outside of walking distance: 1=65%
- Handling banking and other business: 1=73%
- Using the telephone 1=94%
Two versions of a response format were available:
Binary -> 0 = “needs help”, 1 = “does not need help”
Categorical -> 0 = “can’t do it”, 1=”big problems”, 2=”some
problems”, 3=”no problems”
Higher scores indicate greater function. We will look at each
response format in turn.
Package Installation and Loading
if (require(lavaan) == FALSE){
install.packages("lavaan")
}
library(lavaan)
if (require(ggplot2) == FALSE){
install.packages("ggplot2")
}
library(ggplot2)
Data Import into R
The data are in a text file named adl.dat orignally used
in Mplus (so no column names were included at the top of the file). The
file contains more items than we will use, so we select only items above
from the whole file.
#read in data file (Mplus file format so having to label columns)
adlData = read.table(file = "adl.dat", header = FALSE, na.strings = ".", col.names = c("case", paste0("dpa", 1:14), paste0("dia", 1:7), paste0("cpa", 1:14), paste0("cia", 1:7)))
#select Situations items and PersonID variables
iadlDataInit = adlData[c(paste0("cia", 1:7))]
#remove cases with all items missing
removeCases = which(apply(X = iadlDataInit, MARGIN = 1, FUN = function (x){ length(which(is.na(x)))}) == 7)
iadlData = iadlDataInit[-removeCases,]
Graded Response Model (2PL-ish version) using WLSMV probit
model
grm2Psyntax = "
IADL =~ cia1 + cia2 + cia3 + cia4 + cia5 + cia6 + cia7
"
grm2Pestimates = cfa(model = grm2Psyntax, data = iadlData, ordered = c("cia1", "cia2", "cia3", "cia4", "cia5", "cia6", "cia7"), std.lv = TRUE, parameterization="theta")
summary(grm2Pestimates, fit.measures = TRUE, rsquare = TRUE, standardized = TRUE)
Parameter estimates under WLSMV Theta Parameterization (Probit) for
the 2PL version of polytomous responses
Factor loadings:
Latent Variables: Estimate Std.Err z-value P(>|z|) Std.lv Std.all
IADL =~
cia1 3.450 0.301 11.470 0.000 3.450 0.960 cia2 3.620 0.507 7.139 0.000
3.620 0.964 cia3 3.026 0.292 10.361 0.000 3.026 0.949 cia4 3.310 0.300
11.030 0.000 3.310 0.957 cia5 2.360 0.190 12.412 0.000 2.360 0.921 cia6
1.943 0.182 10.680 0.000 1.943 0.889 cia7 1.044 0.135 7.724 0.000 1.044
0.722
Thresholds:
Estimate Std.Err z-value P(>|z|) Std.lv Std.all
cia1|t1 -4.946 0.385 -12.838 0.000 -4.946 -1.377
cia1|t2 -3.636 0.324 -11.233 0.000 -3.636 -1.012
cia1|t3 -0.900 0.219 -4.111 0.000 -0.900 -0.251
cia2|t1 -5.502 0.658 -8.356 0.000 -5.502 -1.465
cia2|t2 -4.797 0.605 -7.928 0.000 -4.797 -1.277
cia2|t3 -2.973 0.463 -6.421 0.000 -2.973 -0.792
cia3|t1 -4.426 0.362 -12.226 0.000 -4.426 -1.389
cia3|t2 -3.702 0.331 -11.175 0.000 -3.702 -1.162
cia3|t3 -2.050 0.262 -7.821 0.000 -2.050 -0.643
cia4|t1 -4.490 0.355 -12.647 0.000 -4.490 -1.298
cia4|t2 -3.100 0.282 -10.977 0.000 -3.100 -0.897
cia4|t3 -1.098 0.225 -4.881 0.000 -1.098 -0.317
cia5|t1 -4.098 0.281 -14.607 0.000 -4.098 -1.599
cia5|t2 -2.195 0.190 -11.578 0.000 -2.195 -0.856
cia5|t3 -0.618 0.151 -4.089 0.000 -0.618 -0.241
cia6|t1 -3.605 0.255 -14.125 0.000 -3.605 -1.650
cia6|t2 -2.558 0.203 -12.628 0.000 -2.558 -1.171
cia6|t3 -1.531 0.171 -8.974 0.000 -1.531 -0.701
cia7|t1 -3.311 0.268 -12.345 0.000 -3.311 -2.291
cia7|t2 -2.695 0.199 -13.556 0.000 -2.695 -1.864
cia7|t3 -1.788 0.145 -12.304 0.000 -1.788 -1.237
Calculations
Logit = 1.7*probit, or Probit = Logit/1.7
IFA model: Probit(y=1) = –threshold + loading(Theta)
Threshold = expected probit of (y=0) for someone with Theta=0
When *-1, threshold \(\rightarrow\)
intercept: expected probit for (y=1) instead
Loading = regression of item probit on Theta
For 4-category responses, the sub-models look like this: Probit(y= 0
vs 123) = -threshold$1 + loading(Theta) Probit(y= 01 vs 23) =
-threshold$2 + loading(Theta) Probit y= 012 vs 3) = -threshold$3 +
loading(Theta)
IRT model: Probit(y) = a(theta – difficulty) a = discrimination
(rescaled slope) = loading b = difficulty (location on latent metric) =
threshold/loading
For 4-category responses, the sub-models look like this: $1 Probit(y=
0 vs 123) = a(theta – difficulty$1) $2 Probit(y= 01 vs 23) = a(theta –
difficulty$2) $3 Probit(y= 012 vs 3) = a(theta – difficulty$3)
Model Fit
LOCAL FIT VIA RAW RESIDUAL CORRELATIONS LEFTOVER POLYCHORIC
CORRELATION (HOW FAR OFF FROM DATA)
With raw residuals, the scale is that of correlation (different from
normalized)
resid(object = grm2Pestimates)
Item Plots
lavaanCatItemPlot = function(lavObject, varname, sds = 3){
output = inspect(object = lavObject, what = "est")
if (!varname %in% rownames(output$lambda)) stop(paste(varname, "not found in lavaan object"))
if (dim(output$lambda)[2]>1) stop("plots only given for one factor models")
itemloading = output$lambda[which(rownames(output$lambda) == varname),1]
itemthresholds = output$tau[grep(pattern = varname, x = rownames(output$tau))]
factorname = colnames(output$lambda)
factormean = output$alpha[which(rownames(output$alpha) == factorname)]
factorvar = output$psi[which(rownames(output$psi) == factorname)]
factormin = factormean - 3*sqrt(factorvar)
factormax = factormean + 3*sqrt(factorvar)
factorX = seq(factormin, factormax, .01)
itemloc = which(lavObject@Data@ov$name == varname)
itemlevels = unlist(strsplit(x = lavObject@Data@ov$lnam[itemloc], split = "\\|"))
if (length(itemthresholds)>1){
plotdata = NULL
plotdata2 = NULL
itemY = NULL
itemY2 = NULL
itemX = NULL
itemText = NULL
for (level in 1:length(itemthresholds)){
itemY = pnorm(q = -1*itemthresholds[level] + itemloading*factorX)
itemY2 = cbind(itemY2, pnorm(q = -1*itemthresholds[level] + itemloading*factorX))
itemText = paste0("P(", varname, " > ", itemlevels[level], ")")
itemText2 = paste0("P(", varname, " = ", itemlevels[level], ")")
plotdata = rbind(plotdata, data.frame(factor = factorX, prob = itemY, plot = itemText))
if (level == 1){
plotdata2 = data.frame(factor = factorX, plot = itemText2, prob = matrix(1, nrow = dim(itemY2)[1], ncol=1) - itemY2[,level])
} else if (level == length(itemthresholds)){
plotdata2 = rbind(plotdata2, data.frame(factor = factorX, plot = itemText2, prob = itemY2[,level-1] - itemY2[,level]))
plotdata2 = rbind(plotdata2, data.frame(factor = factorX, plot = paste0("P(", varname, " = ", itemlevels[level+1], ")"), prob = itemY2[,level]))
} else {
plotdata2 = rbind(plotdata2, data.frame(factor = factorX, plot = itemText2, prob = itemY2[,level-1] - itemY2[,level]))
}
}
names(plotdata) = c(factorname , "Probability", "Cumulative")
ggplot(data = plotdata, aes_string(x = factorname, y = "Probability", colour = "Cumulative")) + geom_line(size = 2)
names(plotdata2) = c(factorname, "Response", "Probability")
ggplot(data = plotdata2, aes_string(x = factorname, y = "Probability", colour = "Response")) + geom_line(size = 2)
} else {
itemY = pnorm(q = -1*itemthresholds[1] + itemloading*factorX)
itemText2 = paste0("P(", varname, " = ", itemlevels[1], ")")
plotdata = data.frame(factor = factorX, prob = itemY, plot = itemText2)
names(plotdata) = c(factorname , "Probability", "Response")
ggplot(data = plotdata, aes_string(x = factorname, y = "Probability", colour = "Response")) + geom_line(size = 2)
}
}
lavaanCatItemPlot(lavObject = grm2Pestimates, varname = "cia4", sds = 3)
Graded Response Model (1PL-ish version) using WLSMV probit
model
We can also set the item discriminations equal for a 1PL-like
model:
grm1Psyntax = "
IADL =~ a*cia1 + a*cia2 + a*cia3 + a*cia4 + a*cia5 + a*cia6 + a*cia7
"
grm1Pestimates = cfa(model = grm1Psyntax, data = iadlData, ordered = c("cia1", "cia2", "cia3", "cia4", "cia5", "cia6", "cia7"), std.lv = TRUE, parameterization="theta")
summary(grm1Pestimates, fit.measures = TRUE, rsquare = TRUE, standardized = TRUE)
Similarly, we can use the anova() function to compare
model fit:
anova(grm1Pestimates, grm2Pestimates)
LS0tCnRpdGxlOiAiUFNRRiA2MjQ5OiBFeGFtcGxlIDUgKElSVC9JRkEpIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIHdvcmRfZG9jdW1lbnQ6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKLS0tCgpFeGFtcGxlIGRhdGE6IDYzNCBvbGRlciBhZHVsdHMgKGFnZSA4MC0xMDApIHNlbGYtcmVwb3J0aW5nIG9uIDcgaXRlbXMgYXNzZXNzaW5nIHRoZSBJbnN0cnVtZW50YWwgQWN0aXZpdGllcyBvZiBEYWlseSBMaXZpbmcgKElBREwpIGFzIGZvbGxvd3M6CgoxLiAgSG91c2V3b3JrIChjbGVhbmluZyBhbmQgbGF1bmRyeSk6IDE9NjQlCjIuICBCZWRtYWtpbmc6IDE9ODQlCjMuICBDb29raW5nOiAxPTc3JQo0LiAgRXZlcnlkYXkgc2hvcHBpbmc6IDE9NjYlCjUuICBHZXR0aW5nIHRvIHBsYWNlcyBvdXRzaWRlIG9mIHdhbGtpbmcgZGlzdGFuY2U6IDE9NjUlCjYuICBIYW5kbGluZyBiYW5raW5nIGFuZCBvdGhlciBidXNpbmVzczogMT03MyVcCjcuICBVc2luZyB0aGUgdGVsZXBob25lIDE9OTQlCgpUd28gdmVyc2lvbnMgb2YgYSByZXNwb25zZSBmb3JtYXQgd2VyZSBhdmFpbGFibGU6CgpCaW5hcnkgLVw+IDAgPSDigJxuZWVkcyBoZWxw4oCdLCAxID0g4oCcZG9lcyBub3QgbmVlZCBoZWxw4oCdCgpDYXRlZ29yaWNhbCAtXD4gMCA9IOKAnGNhbuKAmXQgZG8gaXTigJ0sIDE94oCdYmlnIHByb2JsZW1z4oCdLCAyPeKAnXNvbWUgcHJvYmxlbXPigJ0sIDM94oCdbm8gcHJvYmxlbXPigJ0KCkhpZ2hlciBzY29yZXMgaW5kaWNhdGUgZ3JlYXRlciBmdW5jdGlvbi4gV2Ugd2lsbCBsb29rIGF0IGVhY2ggcmVzcG9uc2UgZm9ybWF0IGluIHR1cm4uCgojIyMgUGFja2FnZSBJbnN0YWxsYXRpb24gYW5kIExvYWRpbmcKCmBgYHtyIHNldHVwLCBpbmNsdWRlPVRSVUV9CmlmIChyZXF1aXJlKGxhdmFhbikgPT0gRkFMU0UpewogIGluc3RhbGwucGFja2FnZXMoImxhdmFhbiIpCn0KbGlicmFyeShsYXZhYW4pCgppZiAocmVxdWlyZShnZ3Bsb3QyKSA9PSBGQUxTRSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpCn0KbGlicmFyeShnZ3Bsb3QyKQpgYGAKCiMjIyBEYXRhIEltcG9ydCBpbnRvIFIKClRoZSBkYXRhIGFyZSBpbiBhIHRleHQgZmlsZSBuYW1lZCBgYWRsLmRhdGAgb3JpZ25hbGx5IHVzZWQgaW4gTXBsdXMgKHNvIG5vIGNvbHVtbiBuYW1lcyB3ZXJlIGluY2x1ZGVkIGF0IHRoZSB0b3Agb2YgdGhlIGZpbGUpLiBUaGUgZmlsZSBjb250YWlucyBtb3JlIGl0ZW1zIHRoYW4gd2Ugd2lsbCB1c2UsIHNvIHdlIHNlbGVjdCBvbmx5IGl0ZW1zIGFib3ZlIGZyb20gdGhlIHdob2xlIGZpbGUuCgpgYGB7ciBkYXRhLCBpbmNsdWRlPVRSVUV9CgojcmVhZCBpbiBkYXRhIGZpbGUgKE1wbHVzIGZpbGUgZm9ybWF0IHNvIGhhdmluZyB0byBsYWJlbCBjb2x1bW5zKQphZGxEYXRhID0gcmVhZC50YWJsZShmaWxlID0gImFkbC5kYXQiLCBoZWFkZXIgPSBGQUxTRSwgbmEuc3RyaW5ncyA9ICIuIiwgY29sLm5hbWVzID0gYygiY2FzZSIsIHBhc3RlMCgiZHBhIiwgMToxNCksIHBhc3RlMCgiZGlhIiwgMTo3KSwgcGFzdGUwKCJjcGEiLCAxOjE0KSwgcGFzdGUwKCJjaWEiLCAxOjcpKSkKCiNzZWxlY3QgU2l0dWF0aW9ucyBpdGVtcyBhbmQgUGVyc29uSUQgdmFyaWFibGVzCmlhZGxEYXRhSW5pdCA9IGFkbERhdGFbYyhwYXN0ZTAoImNpYSIsIDE6NykpXQoKI3JlbW92ZSBjYXNlcyB3aXRoIGFsbCBpdGVtcyBtaXNzaW5nCnJlbW92ZUNhc2VzID0gd2hpY2goYXBwbHkoWCA9IGlhZGxEYXRhSW5pdCwgTUFSR0lOID0gMSwgRlVOID0gZnVuY3Rpb24gKHgpeyBsZW5ndGgod2hpY2goaXMubmEoeCkpKX0pID09IDcpCgppYWRsRGF0YSA9IGlhZGxEYXRhSW5pdFstcmVtb3ZlQ2FzZXMsXQpgYGAKCiMjIyBHcmFkZWQgUmVzcG9uc2UgTW9kZWwgKDJQTC1pc2ggdmVyc2lvbikgdXNpbmcgV0xTTVYgcHJvYml0IG1vZGVsCgpgYGB7ciBncm0ycCwgaW5jbHVkZT1UUlVFfQpncm0yUHN5bnRheCA9ICIKSUFETCA9fiBjaWExICsgY2lhMiArIGNpYTMgKyBjaWE0ICsgY2lhNSArIGNpYTYgKyBjaWE3CiIKCmdybTJQZXN0aW1hdGVzID0gY2ZhKG1vZGVsID0gZ3JtMlBzeW50YXgsIGRhdGEgPSBpYWRsRGF0YSwgb3JkZXJlZCA9IGMoImNpYTEiLCAiY2lhMiIsICJjaWEzIiwgImNpYTQiLCAiY2lhNSIsICJjaWE2IiwgImNpYTciKSwgc3RkLmx2ID0gVFJVRSwgcGFyYW1ldGVyaXphdGlvbj0idGhldGEiKQpzdW1tYXJ5KGdybTJQZXN0aW1hdGVzLCBmaXQubWVhc3VyZXMgPSBUUlVFLCByc3F1YXJlID0gVFJVRSwgc3RhbmRhcmRpemVkID0gVFJVRSkKCmBgYAoKIyMjIyBQYXJhbWV0ZXIgZXN0aW1hdGVzIHVuZGVyIFdMU01WIFRoZXRhIFBhcmFtZXRlcml6YXRpb24gKFByb2JpdCkgZm9yIHRoZSAyUEwgdmVyc2lvbiBvZiBwb2x5dG9tb3VzIHJlc3BvbnNlcwoKIyMjIyMgRmFjdG9yIGxvYWRpbmdzOgoKTGF0ZW50IFZhcmlhYmxlczogRXN0aW1hdGUgU3RkLkVyciB6LXZhbHVlIFAoXD5cfHpcfCkgU3RkLmx2IFN0ZC5hbGwgSUFETCA9XH5cCmNpYTEgMy40NTAgMC4zMDEgMTEuNDcwIDAuMDAwIDMuNDUwIDAuOTYwIGNpYTIgMy42MjAgMC41MDcgNy4xMzkgMC4wMDAgMy42MjAgMC45NjQgY2lhMyAzLjAyNiAwLjI5MiAxMC4zNjEgMC4wMDAgMy4wMjYgMC45NDkgY2lhNCAzLjMxMCAwLjMwMCAxMS4wMzAgMC4wMDAgMy4zMTAgMC45NTcgY2lhNSAyLjM2MCAwLjE5MCAxMi40MTIgMC4wMDAgMi4zNjAgMC45MjEgY2lhNiAxLjk0MyAwLjE4MiAxMC42ODAgMC4wMDAgMS45NDMgMC44ODkgY2lhNyAxLjA0NCAwLjEzNSA3LjcyNCAwLjAwMCAxLjA0NCAwLjcyMgoKIyMjIyMgVGhyZXNob2xkczoKCmBgYCAgICAgICAgIAogICAgICAgICAgICAgICBFc3RpbWF0ZSAgU3RkLkVyciAgei12YWx1ZSAgUCg+fHp8KSAgIFN0ZC5sdiAgU3RkLmFsbApjaWExfHQxICAgICAgICAgIC00Ljk0NiAgICAwLjM4NSAgLTEyLjgzOCAgICAwLjAwMCAgIC00Ljk0NiAgIC0xLjM3NwpjaWExfHQyICAgICAgICAgIC0zLjYzNiAgICAwLjMyNCAgLTExLjIzMyAgICAwLjAwMCAgIC0zLjYzNiAgIC0xLjAxMgpjaWExfHQzICAgICAgICAgIC0wLjkwMCAgICAwLjIxOSAgIC00LjExMSAgICAwLjAwMCAgIC0wLjkwMCAgIC0wLjI1MQpjaWEyfHQxICAgICAgICAgIC01LjUwMiAgICAwLjY1OCAgIC04LjM1NiAgICAwLjAwMCAgIC01LjUwMiAgIC0xLjQ2NQpjaWEyfHQyICAgICAgICAgIC00Ljc5NyAgICAwLjYwNSAgIC03LjkyOCAgICAwLjAwMCAgIC00Ljc5NyAgIC0xLjI3NwpjaWEyfHQzICAgICAgICAgIC0yLjk3MyAgICAwLjQ2MyAgIC02LjQyMSAgICAwLjAwMCAgIC0yLjk3MyAgIC0wLjc5MgpjaWEzfHQxICAgICAgICAgIC00LjQyNiAgICAwLjM2MiAgLTEyLjIyNiAgICAwLjAwMCAgIC00LjQyNiAgIC0xLjM4OQpjaWEzfHQyICAgICAgICAgIC0zLjcwMiAgICAwLjMzMSAgLTExLjE3NSAgICAwLjAwMCAgIC0zLjcwMiAgIC0xLjE2MgpjaWEzfHQzICAgICAgICAgIC0yLjA1MCAgICAwLjI2MiAgIC03LjgyMSAgICAwLjAwMCAgIC0yLjA1MCAgIC0wLjY0MwpjaWE0fHQxICAgICAgICAgIC00LjQ5MCAgICAwLjM1NSAgLTEyLjY0NyAgICAwLjAwMCAgIC00LjQ5MCAgIC0xLjI5OApjaWE0fHQyICAgICAgICAgIC0zLjEwMCAgICAwLjI4MiAgLTEwLjk3NyAgICAwLjAwMCAgIC0zLjEwMCAgIC0wLjg5NwpjaWE0fHQzICAgICAgICAgIC0xLjA5OCAgICAwLjIyNSAgIC00Ljg4MSAgICAwLjAwMCAgIC0xLjA5OCAgIC0wLjMxNwpjaWE1fHQxICAgICAgICAgIC00LjA5OCAgICAwLjI4MSAgLTE0LjYwNyAgICAwLjAwMCAgIC00LjA5OCAgIC0xLjU5OQpjaWE1fHQyICAgICAgICAgIC0yLjE5NSAgICAwLjE5MCAgLTExLjU3OCAgICAwLjAwMCAgIC0yLjE5NSAgIC0wLjg1NgpjaWE1fHQzICAgICAgICAgIC0wLjYxOCAgICAwLjE1MSAgIC00LjA4OSAgICAwLjAwMCAgIC0wLjYxOCAgIC0wLjI0MQpjaWE2fHQxICAgICAgICAgIC0zLjYwNSAgICAwLjI1NSAgLTE0LjEyNSAgICAwLjAwMCAgIC0zLjYwNSAgIC0xLjY1MApjaWE2fHQyICAgICAgICAgIC0yLjU1OCAgICAwLjIwMyAgLTEyLjYyOCAgICAwLjAwMCAgIC0yLjU1OCAgIC0xLjE3MQpjaWE2fHQzICAgICAgICAgIC0xLjUzMSAgICAwLjE3MSAgIC04Ljk3NCAgICAwLjAwMCAgIC0xLjUzMSAgIC0wLjcwMQpjaWE3fHQxICAgICAgICAgIC0zLjMxMSAgICAwLjI2OCAgLTEyLjM0NSAgICAwLjAwMCAgIC0zLjMxMSAgIC0yLjI5MQpjaWE3fHQyICAgICAgICAgIC0yLjY5NSAgICAwLjE5OSAgLTEzLjU1NiAgICAwLjAwMCAgIC0yLjY5NSAgIC0xLjg2NApjaWE3fHQzICAgICAgICAgIC0xLjc4OCAgICAwLjE0NSAgLTEyLjMwNCAgICAwLjAwMCAgIC0xLjc4OCAgIC0xLjIzNwpgYGAKCiMjIyMgQ2FsY3VsYXRpb25zCgpMb2dpdCA9IDEuN1wqcHJvYml0LCBvciBQcm9iaXQgPSBMb2dpdC8xLjcKCklGQSBtb2RlbDogUHJvYml0KHk9MSkgPSDigJN0aHJlc2hvbGQgKyBsb2FkaW5nKFRoZXRhKQoKVGhyZXNob2xkID0gZXhwZWN0ZWQgcHJvYml0IG9mICh5PTApIGZvciBzb21lb25lIHdpdGggVGhldGE9MAoKV2hlbiBcKi0xLCB0aHJlc2hvbGQgJFxyaWdodGFycm93JCBpbnRlcmNlcHQ6IGV4cGVjdGVkIHByb2JpdCBmb3IgKHk9MSkgaW5zdGVhZAoKTG9hZGluZyA9IHJlZ3Jlc3Npb24gb2YgaXRlbSBwcm9iaXQgb24gVGhldGEKCkZvciA0LWNhdGVnb3J5IHJlc3BvbnNlcywgdGhlIHN1Yi1tb2RlbHMgbG9vayBsaWtlIHRoaXM6IFByb2JpdCh5PSAwIHZzIDEyMykgPSAtdGhyZXNob2xkXCQxICsgbG9hZGluZyhUaGV0YSkgUHJvYml0KHk9IDAxIHZzIDIzKSA9IC10aHJlc2hvbGRcJDIgKyBsb2FkaW5nKFRoZXRhKSBQcm9iaXQgeT0gMDEyIHZzIDMpID0gLXRocmVzaG9sZFwkMyArIGxvYWRpbmcoVGhldGEpCgpJUlQgbW9kZWw6IFByb2JpdCh5KSA9IGEodGhldGEg4oCTIGRpZmZpY3VsdHkpIGEgPSBkaXNjcmltaW5hdGlvbiAocmVzY2FsZWQgc2xvcGUpID0gbG9hZGluZyBiID0gZGlmZmljdWx0eSAobG9jYXRpb24gb24gbGF0ZW50IG1ldHJpYykgPSB0aHJlc2hvbGQvbG9hZGluZwoKRm9yIDQtY2F0ZWdvcnkgcmVzcG9uc2VzLCB0aGUgc3ViLW1vZGVscyBsb29rIGxpa2UgdGhpczogXCQxIFByb2JpdCh5PSAwIHZzIDEyMykgPSBhKHRoZXRhIOKAkyBkaWZmaWN1bHR5XCQxKSBcJDIgUHJvYml0KHk9IDAxIHZzIDIzKSA9IGEodGhldGEg4oCTIGRpZmZpY3VsdHlcJDIpIFwkMyBQcm9iaXQoeT0gMDEyIHZzIDMpID0gYSh0aGV0YSDigJMgZGlmZmljdWx0eVwkMykKCiMjIyMgTW9kZWwgRml0CgpMT0NBTCBGSVQgVklBIFJBVyBSRVNJRFVBTCBDT1JSRUxBVElPTlMgTEVGVE9WRVIgUE9MWUNIT1JJQyBDT1JSRUxBVElPTiAoSE9XIEZBUiBPRkYgRlJPTSBEQVRBKQoKV2l0aCByYXcgcmVzaWR1YWxzLCB0aGUgc2NhbGUgaXMgdGhhdCBvZiBjb3JyZWxhdGlvbiAoZGlmZmVyZW50IGZyb20gbm9ybWFsaXplZCkKCmBgYHtyIGdybTJwZml0LCBpbmNsdWRlPVRSVUV9CnJlc2lkKG9iamVjdCA9IGdybTJQZXN0aW1hdGVzKQpgYGAKCiMjIyMgSXRlbSBQbG90cwoKYGBge3IgaXRlbXBsb3QsIGluY2x1ZGU9VFJVRX0KbGF2YWFuQ2F0SXRlbVBsb3QgPSBmdW5jdGlvbihsYXZPYmplY3QsIHZhcm5hbWUsIHNkcyA9IDMpewogIG91dHB1dCA9IGluc3BlY3Qob2JqZWN0ID0gbGF2T2JqZWN0LCB3aGF0ID0gImVzdCIpCiAgaWYgKCF2YXJuYW1lICVpbiUgcm93bmFtZXMob3V0cHV0JGxhbWJkYSkpIHN0b3AocGFzdGUodmFybmFtZSwgIm5vdCBmb3VuZCBpbiBsYXZhYW4gb2JqZWN0IikpCiAgaWYgKGRpbShvdXRwdXQkbGFtYmRhKVsyXT4xKSBzdG9wKCJwbG90cyBvbmx5IGdpdmVuIGZvciBvbmUgZmFjdG9yIG1vZGVscyIpCiAgCiAgaXRlbWxvYWRpbmcgPSBvdXRwdXQkbGFtYmRhW3doaWNoKHJvd25hbWVzKG91dHB1dCRsYW1iZGEpID09IHZhcm5hbWUpLDFdCiAgaXRlbXRocmVzaG9sZHMgPSBvdXRwdXQkdGF1W2dyZXAocGF0dGVybiA9IHZhcm5hbWUsIHggPSByb3duYW1lcyhvdXRwdXQkdGF1KSldCiAgCiAgZmFjdG9ybmFtZSA9IGNvbG5hbWVzKG91dHB1dCRsYW1iZGEpCiAgZmFjdG9ybWVhbiA9IG91dHB1dCRhbHBoYVt3aGljaChyb3duYW1lcyhvdXRwdXQkYWxwaGEpID09IGZhY3Rvcm5hbWUpXQogIGZhY3RvcnZhciA9IG91dHB1dCRwc2lbd2hpY2gocm93bmFtZXMob3V0cHV0JHBzaSkgPT0gZmFjdG9ybmFtZSldCiAgCiAgZmFjdG9ybWluID0gZmFjdG9ybWVhbiAtIDMqc3FydChmYWN0b3J2YXIpCiAgZmFjdG9ybWF4ID0gZmFjdG9ybWVhbiArIDMqc3FydChmYWN0b3J2YXIpCiAgCiAgZmFjdG9yWCA9IHNlcShmYWN0b3JtaW4sIGZhY3Rvcm1heCwgLjAxKQogIGl0ZW1sb2MgPSB3aGljaChsYXZPYmplY3RARGF0YUBvdiRuYW1lID09IHZhcm5hbWUpICAgICAgCiAgaXRlbWxldmVscyA9IHVubGlzdChzdHJzcGxpdCh4ID0gbGF2T2JqZWN0QERhdGFAb3YkbG5hbVtpdGVtbG9jXSwgc3BsaXQgPSAiXFx8IikpICAKICBpZiAobGVuZ3RoKGl0ZW10aHJlc2hvbGRzKT4xKXsKCiAgICAKICAgIHBsb3RkYXRhID0gTlVMTAogICAgcGxvdGRhdGEyID0gTlVMTAogICAgaXRlbVkgPSBOVUxMCiAgICBpdGVtWTIgPSBOVUxMCiAgICBpdGVtWCA9IE5VTEwKICAgIGl0ZW1UZXh0ID0gTlVMTAogICAgZm9yIChsZXZlbCBpbiAxOmxlbmd0aChpdGVtdGhyZXNob2xkcykpewogICAgICAKICAgICAgaXRlbVkgPSBwbm9ybShxID0gLTEqaXRlbXRocmVzaG9sZHNbbGV2ZWxdICsgaXRlbWxvYWRpbmcqZmFjdG9yWCkKICAgICAgaXRlbVkyID0gY2JpbmQoaXRlbVkyLCBwbm9ybShxID0gLTEqaXRlbXRocmVzaG9sZHNbbGV2ZWxdICsgaXRlbWxvYWRpbmcqZmFjdG9yWCkpCiAgICAgIGl0ZW1UZXh0ID0gcGFzdGUwKCJQKCIsIHZhcm5hbWUsICIgPiAiLCBpdGVtbGV2ZWxzW2xldmVsXSwgIikiKQogICAgICBpdGVtVGV4dDIgPSBwYXN0ZTAoIlAoIiwgdmFybmFtZSwgIiA9ICIsIGl0ZW1sZXZlbHNbbGV2ZWxdLCAiKSIpCiAgICAgIHBsb3RkYXRhID0gcmJpbmQocGxvdGRhdGEsIGRhdGEuZnJhbWUoZmFjdG9yID0gZmFjdG9yWCwgcHJvYiA9IGl0ZW1ZLCBwbG90ID0gaXRlbVRleHQpKQogICAgICAKICAgICAgaWYgKGxldmVsID09IDEpewogICAgICAgIHBsb3RkYXRhMiA9IGRhdGEuZnJhbWUoZmFjdG9yID0gZmFjdG9yWCwgcGxvdCA9IGl0ZW1UZXh0MiwgcHJvYiA9IG1hdHJpeCgxLCBucm93ID0gZGltKGl0ZW1ZMilbMV0sIG5jb2w9MSkgLSBpdGVtWTJbLGxldmVsXSkKICAgICAgfSBlbHNlIGlmIChsZXZlbCA9PSBsZW5ndGgoaXRlbXRocmVzaG9sZHMpKXsKICAgICAgICBwbG90ZGF0YTIgPSByYmluZChwbG90ZGF0YTIsIGRhdGEuZnJhbWUoZmFjdG9yID0gZmFjdG9yWCwgcGxvdCA9IGl0ZW1UZXh0MiwgcHJvYiA9IGl0ZW1ZMlssbGV2ZWwtMV0gLSBpdGVtWTJbLGxldmVsXSkpCiAgICAgICAgcGxvdGRhdGEyID0gcmJpbmQocGxvdGRhdGEyLCBkYXRhLmZyYW1lKGZhY3RvciA9IGZhY3RvclgsIHBsb3QgPSBwYXN0ZTAoIlAoIiwgdmFybmFtZSwgIiA9ICIsIGl0ZW1sZXZlbHNbbGV2ZWwrMV0sICIpIiksIHByb2IgPSBpdGVtWTJbLGxldmVsXSkpICAgICAgICAgICAgICAgICAgCiAgICAgIH0gZWxzZSB7CiAgICAgICAgcGxvdGRhdGEyID0gcmJpbmQocGxvdGRhdGEyLCBkYXRhLmZyYW1lKGZhY3RvciA9IGZhY3RvclgsIHBsb3QgPSBpdGVtVGV4dDIsIHByb2IgPSBpdGVtWTJbLGxldmVsLTFdIC0gaXRlbVkyWyxsZXZlbF0pKQogICAgICB9CiAgICAgIAogICAgfQogICAgCiAgICBuYW1lcyhwbG90ZGF0YSkgPSBjKGZhY3Rvcm5hbWUgLCAiUHJvYmFiaWxpdHkiLCAiQ3VtdWxhdGl2ZSIpCiAgICBnZ3Bsb3QoZGF0YSA9IHBsb3RkYXRhLCBhZXNfc3RyaW5nKHggPSBmYWN0b3JuYW1lLCB5ID0gIlByb2JhYmlsaXR5IiwgY29sb3VyID0gIkN1bXVsYXRpdmUiKSkgKyBnZW9tX2xpbmUoc2l6ZSA9IDIpCiAgICAKICAgIG5hbWVzKHBsb3RkYXRhMikgPSBjKGZhY3Rvcm5hbWUsICJSZXNwb25zZSIsICJQcm9iYWJpbGl0eSIpCiAgICBnZ3Bsb3QoZGF0YSA9IHBsb3RkYXRhMiwgYWVzX3N0cmluZyh4ID0gZmFjdG9ybmFtZSwgeSA9ICJQcm9iYWJpbGl0eSIsIGNvbG91ciA9ICJSZXNwb25zZSIpKSArIGdlb21fbGluZShzaXplID0gMikKICB9IGVsc2UgewogICAgCiAgICBpdGVtWSA9IHBub3JtKHEgPSAtMSppdGVtdGhyZXNob2xkc1sxXSArIGl0ZW1sb2FkaW5nKmZhY3RvclgpCiAgICBpdGVtVGV4dDIgPSBwYXN0ZTAoIlAoIiwgdmFybmFtZSwgIiA9ICIsIGl0ZW1sZXZlbHNbMV0sICIpIikKICAgIHBsb3RkYXRhID0gZGF0YS5mcmFtZShmYWN0b3IgPSBmYWN0b3JYLCBwcm9iID0gaXRlbVksIHBsb3QgPSBpdGVtVGV4dDIpCiAgICAKICAgIG5hbWVzKHBsb3RkYXRhKSA9IGMoZmFjdG9ybmFtZSAsICJQcm9iYWJpbGl0eSIsICJSZXNwb25zZSIpCiAgICBnZ3Bsb3QoZGF0YSA9IHBsb3RkYXRhLCBhZXNfc3RyaW5nKHggPSBmYWN0b3JuYW1lLCB5ID0gIlByb2JhYmlsaXR5IiwgY29sb3VyID0gIlJlc3BvbnNlIikpICsgZ2VvbV9saW5lKHNpemUgPSAyKQogICAgCiAgfQp9CgpsYXZhYW5DYXRJdGVtUGxvdChsYXZPYmplY3QgPSBncm0yUGVzdGltYXRlcywgdmFybmFtZSA9ICJjaWE0Iiwgc2RzID0gMykKYGBgCgojIyMgR3JhZGVkIFJlc3BvbnNlIE1vZGVsICgxUEwtaXNoIHZlcnNpb24pIHVzaW5nIFdMU01WIHByb2JpdCBtb2RlbAoKV2UgY2FuIGFsc28gc2V0IHRoZSBpdGVtIGRpc2NyaW1pbmF0aW9ucyBlcXVhbCBmb3IgYSAxUEwtbGlrZSBtb2RlbDoKCmBgYHtyIGdybTFwLCBpbmNsdWRlPVRSVUV9CmdybTFQc3ludGF4ID0gIgpJQURMID1+IGEqY2lhMSArIGEqY2lhMiArIGEqY2lhMyArIGEqY2lhNCArIGEqY2lhNSArIGEqY2lhNiArIGEqY2lhNwoiCgpncm0xUGVzdGltYXRlcyA9IGNmYShtb2RlbCA9IGdybTFQc3ludGF4LCBkYXRhID0gaWFkbERhdGEsIG9yZGVyZWQgPSBjKCJjaWExIiwgImNpYTIiLCAiY2lhMyIsICJjaWE0IiwgImNpYTUiLCAiY2lhNiIsICJjaWE3IiksIHN0ZC5sdiA9IFRSVUUsIHBhcmFtZXRlcml6YXRpb249InRoZXRhIikKc3VtbWFyeShncm0xUGVzdGltYXRlcywgZml0Lm1lYXN1cmVzID0gVFJVRSwgcnNxdWFyZSA9IFRSVUUsIHN0YW5kYXJkaXplZCA9IFRSVUUpCgpgYGAKClNpbWlsYXJseSwgd2UgY2FuIHVzZSB0aGUgYGFub3ZhKClgIGZ1bmN0aW9uIHRvIGNvbXBhcmUgbW9kZWwgZml0OgoKYGBge3IgY29tcGFyZSwgaW5jbHVkZT1UUlVFfQphbm92YShncm0xUGVzdGltYXRlcywgZ3JtMlBlc3RpbWF0ZXMpCmBgYAo=