Preamble
Install
Libraries
#install.packages("remotes")
#remotes::install_github("DevPsyLab/petersenlab")
Load Libraries
library("tidyverse")
library("psych")
library("mice")
library("micemd")
library("miceadds")
library("mitml")
library("Amelia")
library("jomo")
library("parallel")
library("future")
library("furrr")
library("nlme")
library("MplusAutomation")
Data
data(Oxboys, package = "nlme")
Oxboys_addNA <- data.frame(Oxboys)
Oxboys_addNA <- Oxboys_addNA %>%
rename(id = Subject)
Oxboys_addNA$id <- as.integer(Oxboys_addNA$id)
Oxboys_addNA$Occasion <- as.integer(Oxboys_addNA$Occasion)
set.seed(52242)
Oxboys_addNA[sample(1:nrow(Oxboys_addNA), 25), "height"] <- NA
dataToImpute <- Oxboys_addNA
varsToImpute <- c("height")
Y <- varsToImpute
Types of
Missingness
Missing Completely at Random (MCAR)
the probability of being missing is the same for all cases
missingness is not related to variables in the model
(Conditionally) Missing at Random (MAR)
missingness is related to variables in the model, but once we
condition on the variables in the model, the missingness is
haphazard
the unobserved values do not play a role
Missing Not at Random (MNAR)
missingness is related to variables that are not in the model (i.e.,
for reasons that are unknown)
unobserved variables carry information about whether a case will
have missing data
Approaches to Handle
Missing Data
For MCAR/MAR
Missingness
Full Information Maximum Likelihood (FIML)
Multiple Imputation
Approaches to Multiple
Imputation
multiple imputation by joint modeling
assumes that the variables in follow a joint distribution, e.g.:
multivariate normal (i.e., multivariate normal imputation)
R
packages:
multiple imputation by chained equations
aka:
fully conditional specification multiple imputation
sequential regression multiple imputation
R
packages:
mice
predictive mean matching (?mice::mice.impute.pmm
) can
be useful to obtain bounded imputations for non-normally distributed
variables
Describe Missing
Data
describe(dataToImpute)
md.pattern(dataToImpute, rotate.names = TRUE)
id age Occasion height
209 1 1 1 1 0
25 1 1 1 0 1
0 0 0 25 25
Pre-Imputation
Setup
Specify Number of
Imputations
numImputations <- 5 # generally use 100 imputations; this example uses 5 for speed
Detect Cores
numCores <- parallel::detectCores() - 1
Multilevel Multiple
Imputation
Methods
mice
Types
Continuous
Outcomes
https://stefvanbuuren.name/fimd/sec-multioutcome.html#methods
(archived at https://perma.cc/8CDA-TS3K )
?mice::mice.impute.2l.norm
?mice::mice.impute.2l.pan
?mice::mice.impute.2l.lmer
?miceadds::mice.impute.2l.pmm
?miceadds::mice.impute.2l.contextual.pmm
?miceadds::mice.impute.2l.continuous
?micemd::mice.impute.2l.2stage.norm
?micemd::mice.impute.2l.2stage.pmm
?micemd::mice.impute.2l.glm.norm
?micemd::mice.impute.2l.jomo
Specifying the
Imputation Method
meth <- make.method(dataToImpute)
meth[1:length(meth)] <- ""
meth[Y] <- "2l.pmm" # specify the imputation method here; this can differ by outcome variable
Specifying the
Predictor Matrix
A predictor matrix is a matrix of values, where:
columns with non-zero values are predictors of the variable
specified in the given row
the diagonal of the predictor matrix should be zero because a
variable cannot predict itself
The values are:
NOT a predictor of the outcome: 0
cluster variable: -2
fixed effect of predictor: 1
fixed effect and random effect of predictor: 2
include cluster mean of predictor in addition to fixed effect of
predictor: 3
include cluster mean of predictor in addition to fixed effect and
random effect of predictor: 4
pred <- make.predictorMatrix(dataToImpute)
pred[1:nrow(pred), 1:ncol(pred)] <- 0
pred[Y, "id"] <- (-2) # cluster variable
pred[Y, "Occasion"] <- 1 # fixed effect predictor
pred[Y, "age"] <- 2 # random effect predictor
pred[Y, Y] <- 1 # fixed effect predictor
diag(pred) <- 0
pred
id age height Occasion
id 0 0 0 0
age 0 0 0 0
height -2 2 0 1
Occasion 0 0 0 0
Syntax
mi_mice <- mice(
as.data.frame(dataToImpute),
method = meth,
predictorMatrix = pred,
m = numImputations,
maxit = 5, # generally use 100 maximum iterations; this example uses 5 for speed
seed = 52242)
iter imp variable
1 1 height
1 2 height
1 3 height
1 4 height
1 5 height
2 1 height
2 2 height
2 3 height
2 4 height
2 5 height
3 1 height
3 2 height
3 3 height
3 4 height
3 5 height
4 1 height
4 2 height
4 3 height
4 4 height
4 5 height
5 1 height
5 2 height
5 3 height
5 4 height
5 5 height
jomo
level1Vars <- c("height")
level2Vars <- c("v3","v4")
clusterVars <- c("id")
fullyObservedCovariates <- c("age","Occasion")
set.seed(52242)
mi_jomo <- jomo(
Y = data.frame(dataToImpute[, level1Vars]),
#Y2 = data.frame(dataToImpute[, level2Vars]),
X = data.frame(dataToImpute[, fullyObservedCovariates]),
clus = data.frame(dataToImpute[, clusterVars]),
nimp = numImputations,
meth = "random"
)
Amelia
!
in the console output indicates that the current
estimated complete data covariance matrix is not invertible
*
in the console output indicates that the likelihood
has not monotonically increased in that step
boundVars <- c("height")
boundCols <- which(names(dataToImpute) %in% boundVars)
boundLower <- 100
boundUpper <- 200
varBounds <- cbind(boundCols, boundLower, boundUpper)
set.seed(52242)
mi_amelia <- amelia(
dataToImpute,
m = numImputations,
ts = "age",
cs = "id",
polytime = 1,
intercs = TRUE,
#ords = ordinalVars,
#bounds = varBounds,
parallel = "snow",
#ncpus = numCores,
empri = .01*nrow(dataToImpute)) # ridge prior for numerical stability
-- Imputation 1 --
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
21
-- Imputation 2 --
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
-- Imputation 3 --
1 2 3 4 5 6 7 8
-- Imputation 4 --
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
121 122 123
-- Imputation 5 --
1 2 3 4 5 6 7 8 9 10 11 12
Mplus
Save
Mplus
Data File
Save R
object as Mplus
data file:
prepareMplusData(dataToImpute, file.path("dataToImpute.dat"))
TITLE: Your title goes here
DATA: FILE = "dataToImpute.dat";
VARIABLE:
NAMES = id age height Occasion;
MISSING=.;
Mplus
Syntax for Multilevel Imputation
Mplus
syntax for multilevel imputation:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!! MPLUS SYNTAX LINES CANNOT EXCEED 90 CHARACTERS;
!!!!! VARIABLE NAMES AND PARAMETER LABELS CANNOT EXCEED 8 CHARACTERS EACH;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
TITLE: Model Title
DATA: FILE = "dataToImpute.dat";
VARIABLE:
NAMES = id age height Occasion;
MISSING = .;
USEVARIABLES ARE age height Occasion;
!CATEGORICAL ARE INSERT_NAMES_OF_CATEGORICAL_VARIABLES_HERE;
CLUSTER = id;
ANALYSIS:
TYPE = twolevel basic;
bseed = 52242;
PROCESSORS = 2;
DATA IMPUTATION:
IMPUTE = age(0-100) height Occasion; !put ' (c)' after categorical vars
NDATASETS = 100;
SAVE = imp*.dat
Putting a range of values after a variable (e.g., 0-100
)
sets the lower and upper bounds of the imputed values. This would save a
implist.dat
file that can be used to run the model on the
multiply imputed data, as shown here .
Imputation
Diagnostics
Logged Events
mi_mice$loggedEvents
NULL
Trace Plots
On convergence, the streams of the trace plots should intermingle and
be free of any trend (at the later iterations). Convergence is diagnosed
when the variance between different sequences is no larger than the
variance within each individual sequence.
plot(mi_mice, c("height"))
Density Plots
densityplot(mi_mice)
densityplot(mi_mice, ~ height)
Strip Plots
stripplot(mi_mice)
stripplot(mi_mice, height ~ .imp)
Post-Processing
Modify/Create New
Variables
mice
mi_mice_long <- complete(
mi_mice,
action = "long",
include = TRUE)
mi_mice_long$newVar <- mi_mice_long$age * mi_mice_long$height
jomo
mi_jomo <- mi_jomo %>%
rename(height = dataToImpute...level1Vars.)
mi_jomo$newVar <- mi_jomo$age * mi_jomo$height
Amelia
for(i in 1:length(mi_amelia$imputations)){
mi_amelia$imputations[[i]]$newVar <- mi_amelia$imputations[[i]]$age * mi_amelia$imputations[[i]]$height
}
Convert to
mids
object
mice
mi_mice_mids <- as.mids(mi_mice_long)
jomo
mi_jomo_mids <- miceadds::jomo2mids(mi_jomo)
Amelia
mi_amelia_mids <- miceadds::datlist2mids(mi_amelia$imputations)
Export for
Mplus
mids2mplus(mi_mice_mids, path = file.path("InsertFilePathHere", fsep = ""))
mids2mplus(mi_jomo_mids, path = file.path("InsertFilePathHere", fsep = ""))
mids2mplus(mi_amelia_mids, path = file.path("InsertFilePathHere", fsep = ""))
LS0tCnRpdGxlOiAiTXVsdGlwbGUgSW1wdXRhdGlvbiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZSA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyA9IFRSVUUsCiAgZXJyb3IgPSBUUlVFLAogIGNvbW1lbnQgPSAiIiwKICBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IikKYGBgCgojIFByZWFtYmxlCgojIyBJbnN0YWxsIExpYnJhcmllcwoKYGBge3J9CiNpbnN0YWxsLnBhY2thZ2VzKCJyZW1vdGVzIikKI3JlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJEZXZQc3lMYWIvcGV0ZXJzZW5sYWIiKQpgYGAKCiMjIExvYWQgTGlicmFyaWVzCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmxpYnJhcnkoInRpZHl2ZXJzZSIpCmxpYnJhcnkoInBzeWNoIikKbGlicmFyeSgibWljZSIpCmxpYnJhcnkoIm1pY2VtZCIpCmxpYnJhcnkoIm1pY2VhZGRzIikKbGlicmFyeSgibWl0bWwiKQpsaWJyYXJ5KCJBbWVsaWEiKQpsaWJyYXJ5KCJqb21vIikKbGlicmFyeSgicGFyYWxsZWwiKQpsaWJyYXJ5KCJmdXR1cmUiKQpsaWJyYXJ5KCJmdXJyciIpCmxpYnJhcnkoIm5sbWUiKQpsaWJyYXJ5KCJNcGx1c0F1dG9tYXRpb24iKQpgYGAKCiMjIERhdGEKCmBgYHtyfQpkYXRhKE94Ym95cywgcGFja2FnZSA9ICJubG1lIikKCk94Ym95c19hZGROQSA8LSBkYXRhLmZyYW1lKE94Ym95cykKT3hib3lzX2FkZE5BIDwtIE94Ym95c19hZGROQSAlPiUgCiAgcmVuYW1lKGlkID0gU3ViamVjdCkKCk94Ym95c19hZGROQSRpZCA8LSBhcy5pbnRlZ2VyKE94Ym95c19hZGROQSRpZCkKT3hib3lzX2FkZE5BJE9jY2FzaW9uIDwtIGFzLmludGVnZXIoT3hib3lzX2FkZE5BJE9jY2FzaW9uKQoKc2V0LnNlZWQoNTIyNDIpCk94Ym95c19hZGROQVtzYW1wbGUoMTpucm93KE94Ym95c19hZGROQSksIDI1KSwgImhlaWdodCJdIDwtIE5BCgpkYXRhVG9JbXB1dGUgPC0gT3hib3lzX2FkZE5BCnZhcnNUb0ltcHV0ZSA8LSBjKCJoZWlnaHQiKQpZIDwtIHZhcnNUb0ltcHV0ZQpgYGAKCiMgVHlwZXMgb2YgTWlzc2luZ25lc3MKCjEuIE1pc3NpbmcgQ29tcGxldGVseSBhdCBSYW5kb20gKE1DQVIpCiAgICAtIHRoZSBwcm9iYWJpbGl0eSBvZiBiZWluZyBtaXNzaW5nIGlzIHRoZSBzYW1lIGZvciBhbGwgY2FzZXMKICAgIC0gbWlzc2luZ25lc3MgaXMgbm90IHJlbGF0ZWQgdG8gdmFyaWFibGVzIGluIHRoZSBtb2RlbAoxLiAoQ29uZGl0aW9uYWxseSkgTWlzc2luZyBhdCBSYW5kb20gKE1BUikKICAgIC0gbWlzc2luZ25lc3MgaXMgcmVsYXRlZCB0byB2YXJpYWJsZXMgaW4gdGhlIG1vZGVsLCBidXQgb25jZSB3ZSBjb25kaXRpb24gb24gdGhlIHZhcmlhYmxlcyBpbiB0aGUgbW9kZWwsIHRoZSBtaXNzaW5nbmVzcyBpcyBoYXBoYXphcmQKICAgIC0gdGhlIHVub2JzZXJ2ZWQgdmFsdWVzIGRvIG5vdCBwbGF5IGEgcm9sZQoxLiBNaXNzaW5nIE5vdCBhdCBSYW5kb20gKE1OQVIpCiAgICAtIG1pc3NpbmduZXNzIGlzIHJlbGF0ZWQgdG8gdmFyaWFibGVzIHRoYXQgYXJlIG5vdCBpbiB0aGUgbW9kZWwgKGkuZS4sIGZvciByZWFzb25zIHRoYXQgYXJlIHVua25vd24pCiAgICAtIHVub2JzZXJ2ZWQgdmFyaWFibGVzIGNhcnJ5IGluZm9ybWF0aW9uIGFib3V0IHdoZXRoZXIgYSBjYXNlIHdpbGwgaGF2ZSBtaXNzaW5nIGRhdGEKCiMgQXBwcm9hY2hlcyB0byBIYW5kbGUgTWlzc2luZyBEYXRhCgojIyBGb3IgTUNBUi9NQVIgTWlzc2luZ25lc3MKCjEuIEZ1bGwgSW5mb3JtYXRpb24gTWF4aW11bSBMaWtlbGlob29kIChGSU1MKQoxLiBNdWx0aXBsZSBJbXB1dGF0aW9uCgojIyBGb3IgTU5BUiBNaXNzaW5nbmVzcwoKaHR0cHM6Ly9zdGVmdmFuYnV1cmVuLm5hbWUvZmltZC9zZWMtbm9uaWdub3JhYmxlLmh0bWwgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvTjdXVy1IRFpGKQoKaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL21pc3NpbmdIRS92aWduZXR0ZXMvRml0dGluZ19NTkFSX21vZGVsc19pbl9taXNzaW5nSEUuaHRtbCAoYXJjaGl2ZWQgYXQgaHR0cHM6Ly9wZXJtYS5jYy85WDI1LTVEOEcpCgoxLiBmaW5kIG1vcmUgZGF0YSBhYm91dCB0aGUgY2F1c2VzIGZvciB0aGUgbWlzc2luZ25lc3MKMS4gc2Vuc2l0aXZpdHkgYW5hbHlzZXMgKHdoYXQtaWYgYW5hbHlzZXMpIHRvIHNlZSBob3cgc2Vuc2l0aXZlIHRoZSByZXN1bHRzIGFyZSB1bmRlciB2YXJpb3VzIHNjZW5hcmlvcwogICAgLSBodHRwczovL3N0ZWZ2YW5idXVyZW4ubmFtZS9maW1kL3NlYy1NQ0FSLmh0bWwgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvOUtNOS0zTlgzKQoxLiBzZWxlY3Rpb24gbW9kZWxzCiAgICAtIHNpbXVsdGFuZW91c2x5IGVzdGltYXRlIHRoZSBmb2NhbCBtb2RlbCBhbmQgYSBtaXNzaW5nbmVzcyBtb2RlbCwgd2hlcmUgdGhlIG1pc3NpbmduZXNzIG1vZGVsIGhhcyB0aGUgbWlzc2luZyBkYXRhIGluZGljYXRvciBhcyBhIGRlcGVuZGVudCB2YXJpYWJsZSwgYXMgcHJlZGljdGVkIGJ5IHRoZSBvcmlnaW5hbCBkZXBlbmRlbnQgdmFyaWFibGUsIHRoZSBvcmlnaW5hbCBwcmVkaWN0b3JzLCBhbmQgYW55IGFkZGl0aW9uYWwgY292YXJpYXRlcyBldGMuCiAgICAtIGlmIHRoZSBtaXNzaW5nbmVzcyBtb2RlbCBpcyBhcHByb3hpbWF0ZWx5IGNvcnJlY3QsIHRoZSBmb2NhbCBtb2RlbCBhZGp1c3RzIGluIHdheSB0aGF0IHJlbW92ZXMgbm9ucmVzcG9uc2UgYmlhcwogICAgLSBzaW1pbGFyIHRvIGEgbWVkaWF0aW9uIHByb2Nlc3MKICAgICAgICAtIFgg4oaSIFkKICAgICAgICAtIFkg4oaSIG1pc3NpbmduZXNzCiAgICAgICAgLSBYIOKGkiBtaXNzaW5nbmVzcwogICAgLSBodHRwczovL3F1YW50aXR1ZGVwb2Qub3JnL3M0ZTA4LWNyYWlnLWVuZGVycy8gKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvRlk5TC1MM0Y3KQoxLiBwYXR0ZXJuLW1peHR1cmUgbW9kZWxzCiAgICAtIG1pc3NpbmcgZGF0YSBpbmRpY2F0b3IgaXMgYSBwcmVkaWN0b3IgdmFyaWFibGUKICAgIC0gc2ltaWxhciB0byBhIG1vZGVyYXRpb24gcHJvY2Vzczsgc3ViZ3JvdXBzIG9mIGNhc2VzIGhhdmUgZGlmZmVyZW50IHBhcmFtZXRlciBlc3RpbWF0ZXMKCiMgQXBwcm9hY2hlcyB0byBNdWx0aXBsZSBJbXB1dGF0aW9uCgotIG11bHRpcGxlIGltcHV0YXRpb24gYnkgam9pbnQgbW9kZWxpbmcKICAgIC0gYXNzdW1lcyB0aGF0IHRoZSB2YXJpYWJsZXMgaW4gZm9sbG93IGEgam9pbnQgZGlzdHJpYnV0aW9uLCBlLmcuOgogICAgICAgIC0gbXVsdGl2YXJpYXRlIG5vcm1hbCAoaS5lLiwgbXVsdGl2YXJpYXRlIG5vcm1hbCBpbXB1dGF0aW9uKQogICAgLSBgUmAgcGFja2FnZXM6CiAgICAgICAgLSBgam9tb2AKICAgICAgICAtIGBwYW5gCiAgICAgICAgLSBgQW1lbGlhYAogICAgICAgIC0gYE1wbHVzYAotIG11bHRpcGxlIGltcHV0YXRpb24gYnkgY2hhaW5lZCBlcXVhdGlvbnMKICAgIC0gYWthOgogICAgICAgIC0gZnVsbHkgY29uZGl0aW9uYWwgc3BlY2lmaWNhdGlvbiBtdWx0aXBsZSBpbXB1dGF0aW9uCiAgICAgICAgLSBzZXF1ZW50aWFsIHJlZ3Jlc3Npb24gbXVsdGlwbGUgaW1wdXRhdGlvbgogICAgLSBgUmAgcGFja2FnZXM6CiAgICAgICAgLSBgbWljZWAKICAgICAgICAgICAgLSBwcmVkaWN0aXZlIG1lYW4gbWF0Y2hpbmcgKGA/bWljZTo6bWljZS5pbXB1dGUucG1tYCkgY2FuIGJlIHVzZWZ1bCB0byBvYnRhaW4gYm91bmRlZCBpbXB1dGF0aW9ucyBmb3Igbm9uLW5vcm1hbGx5IGRpc3RyaWJ1dGVkIHZhcmlhYmxlcwoKIyBEZXNjcmliZSBNaXNzaW5nIERhdGEKCmBgYHtyfQpkZXNjcmliZShkYXRhVG9JbXB1dGUpCm1kLnBhdHRlcm4oZGF0YVRvSW1wdXRlLCByb3RhdGUubmFtZXMgPSBUUlVFKQpgYGAKCiMgUHJlLUltcHV0YXRpb24gU2V0dXAKCiMjIFNwZWNpZnkgTnVtYmVyIG9mIEltcHV0YXRpb25zCgpgYGB7cn0KbnVtSW1wdXRhdGlvbnMgPC0gNSAjIGdlbmVyYWxseSB1c2UgMTAwIGltcHV0YXRpb25zOyB0aGlzIGV4YW1wbGUgdXNlcyA1IGZvciBzcGVlZApgYGAKCiMjIERldGVjdCBDb3JlcwoKYGBge3J9Cm51bUNvcmVzIDwtIHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpIC0gMQpgYGAKCiMgTXVsdGlsZXZlbCBNdWx0aXBsZSBJbXB1dGF0aW9uCgojIyBUaHJlZS1MZXZlbCBhbmQgQ3Jvc3MtQ2xhc3NpZmllZCBEYXRhCgpodHRwczovL3NpbW9uZ3J1bmQxLmdpdGh1Yi5pby9wb3N0cy9tdWx0aXBsZS1pbXB1dGF0aW9uLWZvci10aHJlZS1sZXZlbC1hbmQtY3Jvc3MtY2xhc3NpZmllZC1kYXRhLyAoYXJjaGl2ZWQgYXQgaHR0cHM6Ly9wZXJtYS5jYy9ONFBQLUEzVjYpCgojIyBNZXRob2RzCgojIyMgYG1pY2VgIHsjbWljZX0KCiMjIyMgVHlwZXMKCiMjIyMjIENvbnRpbnVvdXMgT3V0Y29tZXMKCmh0dHBzOi8vc3RlZnZhbmJ1dXJlbi5uYW1lL2ZpbWQvc2VjLW11bHRpb3V0Y29tZS5odG1sI21ldGhvZHMgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvOENEQS1UUzNLKQoKYGBge3IsIGV2YWwgPSBGQUxTRX0KP21pY2U6Om1pY2UuaW1wdXRlLjJsLm5vcm0KP21pY2U6Om1pY2UuaW1wdXRlLjJsLnBhbgo/bWljZTo6bWljZS5pbXB1dGUuMmwubG1lcgo/bWljZWFkZHM6Om1pY2UuaW1wdXRlLjJsLnBtbQo/bWljZWFkZHM6Om1pY2UuaW1wdXRlLjJsLmNvbnRleHR1YWwucG1tCj9taWNlYWRkczo6bWljZS5pbXB1dGUuMmwuY29udGludW91cwo/bWljZW1kOjptaWNlLmltcHV0ZS4ybC4yc3RhZ2Uubm9ybQo/bWljZW1kOjptaWNlLmltcHV0ZS4ybC4yc3RhZ2UucG1tCj9taWNlbWQ6Om1pY2UuaW1wdXRlLjJsLmdsbS5ub3JtCj9taWNlbWQ6Om1pY2UuaW1wdXRlLjJsLmpvbW8KYGBgCgojIyMjIyBCaW5hcnkgT3V0Y29tZXMKCmh0dHBzOi8vc3RlZnZhbmJ1dXJlbi5uYW1lL2ZpbWQvc2VjLWNhdG91dGNvbWUuaHRtbCNtZXRob2RzLTEgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvNVFIRi1ZUlA2KQoKYGBge3IsIGV2YWwgPSBGQUxTRX0KP21pY2U6Om1pY2UuaW1wdXRlLjJsLmJpbgo/bWljZWFkZHM6Om1pY2UuaW1wdXRlLjJsLmJpbmFyeQo/bWljZWFkZHM6Om1pY2UuaW1wdXRlLjJsLnBtbQo/bWljZWFkZHM6Om1pY2UuaW1wdXRlLjJsLmNvbnRleHR1YWwucG1tCj9taWNlbWQ6Om1pY2UuaW1wdXRlLjJsLjJzdGFnZS5iaW4KP21pY2VtZDo6bWljZS5pbXB1dGUuMmwuZ2xtLmJpbgpgYGAKCiMjIyMjIE9yZGluYWwgT3V0Y29tZXMKCmh0dHBzOi8vc3RlZnZhbmJ1dXJlbi5uYW1lL2ZpbWQvc2VjLW11bHRpb3V0Y29tZS5odG1sI21ldGhvZHMgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvOENEQS1UUzNLKQoKYGBge3IsIGV2YWwgPSBGQUxTRX0KP21pY2VhZGRzOjptaWNlLmltcHV0ZS4ybC5wbW0KP21pY2VhZGRzOjptaWNlLmltcHV0ZS4ybC5jb250ZXh0dWFsLnBtbQo/bWljZW1kOjptaWNlLmltcHV0ZS4ybC4yc3RhZ2UucG1tCmBgYAoKIyMjIyMgQ291bnQgT3V0Y29tZXMKCmh0dHBzOi8vc3RlZnZhbmJ1dXJlbi5uYW1lL2ZpbWQvc2VjLWNhdG91dGNvbWUuaHRtbCNtZXRob2RzLTEgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvNVFIRi1ZUlA2KQoKYGBge3IsIGV2YWwgPSBGQUxTRX0KP21pY2VtZDo6bWljZS5pbXB1dGUuMmwuMnN0YWdlLnBvaXMKP21pY2VtZDo6bWljZS5pbXB1dGUuMmwuZ2xtLnBvaXMKP2NvdW50aW1wOjptaWNlLmltcHV0ZS4ybC5wb2lzc29uCj9jb3VudGltcDo6bWljZS5pbXB1dGUuMmwubmIyCj9jb3VudGltcDo6bWljZS5pbXB1dGUuMmwuemlobmIKYGBgCgojIyMjIFNwZWNpZnlpbmcgdGhlIEltcHV0YXRpb24gTWV0aG9kCgpgYGB7cn0KbWV0aCA8LSBtYWtlLm1ldGhvZChkYXRhVG9JbXB1dGUpCm1ldGhbMTpsZW5ndGgobWV0aCldIDwtICIiCm1ldGhbWV0gPC0gIjJsLnBtbSIgIyBzcGVjaWZ5IHRoZSBpbXB1dGF0aW9uIG1ldGhvZCBoZXJlOyB0aGlzIGNhbiBkaWZmZXIgYnkgb3V0Y29tZSB2YXJpYWJsZQpgYGAKCiMjIyMgU3BlY2lmeWluZyB0aGUgUHJlZGljdG9yIE1hdHJpeAoKQSBwcmVkaWN0b3IgbWF0cml4IGlzIGEgbWF0cml4IG9mIHZhbHVlcywgd2hlcmU6CgotIGNvbHVtbnMgd2l0aCBub24temVybyB2YWx1ZXMgYXJlIHByZWRpY3RvcnMgb2YgdGhlIHZhcmlhYmxlIHNwZWNpZmllZCBpbiB0aGUgZ2l2ZW4gcm93Ci0gdGhlIGRpYWdvbmFsIG9mIHRoZSBwcmVkaWN0b3IgbWF0cml4IHNob3VsZCBiZSB6ZXJvIGJlY2F1c2UgYSB2YXJpYWJsZSBjYW5ub3QgcHJlZGljdCBpdHNlbGYKClRoZSB2YWx1ZXMgYXJlOgoKLSBOT1QgYSBwcmVkaWN0b3Igb2YgdGhlIG91dGNvbWU6IGAwYAotIGNsdXN0ZXIgdmFyaWFibGU6IGAtMmAKLSBmaXhlZCBlZmZlY3Qgb2YgcHJlZGljdG9yOiBgMWAKLSBmaXhlZCBlZmZlY3QgYW5kIHJhbmRvbSBlZmZlY3Qgb2YgcHJlZGljdG9yOiBgMmAKLSBpbmNsdWRlIGNsdXN0ZXIgbWVhbiBvZiBwcmVkaWN0b3IgaW4gYWRkaXRpb24gdG8gZml4ZWQgZWZmZWN0IG9mIHByZWRpY3RvcjogYDNgCi0gaW5jbHVkZSBjbHVzdGVyIG1lYW4gb2YgcHJlZGljdG9yIGluIGFkZGl0aW9uIHRvIGZpeGVkIGVmZmVjdCBhbmQgcmFuZG9tIGVmZmVjdCBvZiBwcmVkaWN0b3I6IGA0YAoKYGBge3J9CnByZWQgPC0gbWFrZS5wcmVkaWN0b3JNYXRyaXgoZGF0YVRvSW1wdXRlKQpwcmVkWzE6bnJvdyhwcmVkKSwgMTpuY29sKHByZWQpXSA8LSAwCnByZWRbWSwgImlkIl0gPC0gKC0yKSAjIGNsdXN0ZXIgdmFyaWFibGUKcHJlZFtZLCAiT2NjYXNpb24iXSA8LSAxICMgZml4ZWQgZWZmZWN0IHByZWRpY3RvcgpwcmVkW1ksICJhZ2UiXSA8LSAyICMgcmFuZG9tIGVmZmVjdCBwcmVkaWN0b3IKcHJlZFtZLCBZXSA8LSAxICMgZml4ZWQgZWZmZWN0IHByZWRpY3RvcgoKZGlhZyhwcmVkKSA8LSAwCnByZWQKYGBgCgojIyMjIFN5bnRheAoKYGBge3J9Cm1pX21pY2UgPC0gbWljZSgKICBhcy5kYXRhLmZyYW1lKGRhdGFUb0ltcHV0ZSksCiAgbWV0aG9kID0gbWV0aCwKICBwcmVkaWN0b3JNYXRyaXggPSBwcmVkLAogIG0gPSBudW1JbXB1dGF0aW9ucywKICBtYXhpdCA9IDUsICMgZ2VuZXJhbGx5IHVzZSAxMDAgbWF4aW11bSBpdGVyYXRpb25zOyB0aGlzIGV4YW1wbGUgdXNlcyA1IGZvciBzcGVlZAogIHNlZWQgPSA1MjI0MikKYGBgCgojIyMgYGpvbW9gIHsjam9tb30KCmBgYHtyLCBldmFsID0gRkFMU0V9CmxldmVsMVZhcnMgPC0gYygiaGVpZ2h0IikKbGV2ZWwyVmFycyA8LSBjKCJ2MyIsInY0IikKY2x1c3RlclZhcnMgPC0gYygiaWQiKQpmdWxseU9ic2VydmVkQ292YXJpYXRlcyA8LSBjKCJhZ2UiLCJPY2Nhc2lvbiIpCgpzZXQuc2VlZCg1MjI0MikKCm1pX2pvbW8gPC0gam9tbygKICBZID0gZGF0YS5mcmFtZShkYXRhVG9JbXB1dGVbLCBsZXZlbDFWYXJzXSksCiAgI1kyID0gZGF0YS5mcmFtZShkYXRhVG9JbXB1dGVbLCBsZXZlbDJWYXJzXSksCiAgWCA9IGRhdGEuZnJhbWUoZGF0YVRvSW1wdXRlWywgZnVsbHlPYnNlcnZlZENvdmFyaWF0ZXNdKSwKICBjbHVzID0gZGF0YS5mcmFtZShkYXRhVG9JbXB1dGVbLCBjbHVzdGVyVmFyc10pLAogIG5pbXAgPSBudW1JbXB1dGF0aW9ucywKICBtZXRoID0gInJhbmRvbSIKKQpgYGAKCiMjIyBgQW1lbGlhYCB7I2FtZWxpYX0KCi0gYCFgIGluIHRoZSBjb25zb2xlIG91dHB1dCBpbmRpY2F0ZXMgdGhhdCB0aGUgY3VycmVudCBlc3RpbWF0ZWQgY29tcGxldGUgZGF0YSBjb3ZhcmlhbmNlIG1hdHJpeCBpcyBub3QgaW52ZXJ0aWJsZQotIGAqYCBpbiB0aGUgY29uc29sZSBvdXRwdXQgaW5kaWNhdGVzIHRoYXQgdGhlIGxpa2VsaWhvb2QgaGFzIG5vdCBtb25vdG9uaWNhbGx5IGluY3JlYXNlZCBpbiB0aGF0IHN0ZXAKCmBgYHtyfQpib3VuZFZhcnMgPC0gYygiaGVpZ2h0IikKYm91bmRDb2xzIDwtIHdoaWNoKG5hbWVzKGRhdGFUb0ltcHV0ZSkgJWluJSBib3VuZFZhcnMpCmJvdW5kTG93ZXIgPC0gMTAwCmJvdW5kVXBwZXIgPC0gMjAwCgp2YXJCb3VuZHMgPC0gY2JpbmQoYm91bmRDb2xzLCBib3VuZExvd2VyLCBib3VuZFVwcGVyKQoKc2V0LnNlZWQoNTIyNDIpCgptaV9hbWVsaWEgPC0gYW1lbGlhKAogIGRhdGFUb0ltcHV0ZSwKICBtID0gbnVtSW1wdXRhdGlvbnMsCiAgdHMgPSAiYWdlIiwKICBjcyA9ICJpZCIsCiAgcG9seXRpbWUgPSAxLAogIGludGVyY3MgPSBUUlVFLAogICNvcmRzID0gb3JkaW5hbFZhcnMsCiAgI2JvdW5kcyA9IHZhckJvdW5kcywKICBwYXJhbGxlbCA9ICJzbm93IiwKICAjbmNwdXMgPSBudW1Db3JlcywKICBlbXByaSA9IC4wMSpucm93KGRhdGFUb0ltcHV0ZSkpICMgcmlkZ2UgcHJpb3IgZm9yIG51bWVyaWNhbCBzdGFiaWxpdHkKYGBgCgojIyMgYE1wbHVzYCB7I21wbHVzfQoKIyMjIyBTYXZlIGBNcGx1c2AgRGF0YSBGaWxlCgpTYXZlIGBSYCBvYmplY3QgYXMgYE1wbHVzYCBkYXRhIGZpbGU6CgpgYGB7cn0KcHJlcGFyZU1wbHVzRGF0YShkYXRhVG9JbXB1dGUsIGZpbGUucGF0aCgiZGF0YVRvSW1wdXRlLmRhdCIpKQpgYGAKCiMjIyMgYE1wbHVzYCBTeW50YXggZm9yIE11bHRpbGV2ZWwgSW1wdXRhdGlvbgoKYE1wbHVzYCBzeW50YXggZm9yIG11bHRpbGV2ZWwgaW1wdXRhdGlvbjoKCmBgYE1wbHVzCiEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEKISEhISEgIE1QTFVTIFNZTlRBWCBMSU5FUyBDQU5OT1QgRVhDRUVEIDkwIENIQVJBQ1RFUlM7CiEhISEhICBWQVJJQUJMRSBOQU1FUyBBTkQgUEFSQU1FVEVSIExBQkVMUyBDQU5OT1QgRVhDRUVEIDggQ0hBUkFDVEVSUyBFQUNIOwohISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhCgpUSVRMRTogTW9kZWwgVGl0bGUKCkRBVEE6IEZJTEUgPSAiZGF0YVRvSW1wdXRlLmRhdCI7CgpWQVJJQUJMRToKICBOQU1FUyA9IGlkIGFnZSBoZWlnaHQgT2NjYXNpb247CiAgTUlTU0lORyA9IC47CiAgVVNFVkFSSUFCTEVTIEFSRSBhZ2UgaGVpZ2h0IE9jY2FzaW9uOwogICFDQVRFR09SSUNBTCBBUkUgSU5TRVJUX05BTUVTX09GX0NBVEVHT1JJQ0FMX1ZBUklBQkxFU19IRVJFOwogIENMVVNURVIgPSBpZDsKCkFOQUxZU0lTOgogIFRZUEUgPSB0d29sZXZlbCBiYXNpYzsKICBic2VlZCA9IDUyMjQyOwogIFBST0NFU1NPUlMgPSAyOwogICAgCkRBVEEgSU1QVVRBVElPTjoKICBJTVBVVEUgPSBhZ2UoMC0xMDApIGhlaWdodCBPY2Nhc2lvbjsgIXB1dCAnIChjKScgYWZ0ZXIgY2F0ZWdvcmljYWwgdmFycwogIE5EQVRBU0VUUyA9IDEwMDsKICBTQVZFID0gaW1wKi5kYXQKYGBgCgpQdXR0aW5nIGEgcmFuZ2Ugb2YgdmFsdWVzIGFmdGVyIGEgdmFyaWFibGUgKGUuZy4sIGAwLTEwMGApIHNldHMgdGhlIGxvd2VyIGFuZCB1cHBlciBib3VuZHMgb2YgdGhlIGltcHV0ZWQgdmFsdWVzLgpUaGlzIHdvdWxkIHNhdmUgYSBgaW1wbGlzdC5kYXRgIGZpbGUgdGhhdCBjYW4gYmUgdXNlZCB0byBydW4gdGhlIG1vZGVsIG9uIHRoZSBtdWx0aXBseSBpbXB1dGVkIGRhdGEsIGFzIHNob3duIFtoZXJlXShtcGx1cy5odG1sI211bHRpcGxlSW1wdXRhdGlvbikuCgojIFBhcmFsbGVsIFByb2Nlc3NpbmcKCmh0dHBzOi8vd3d3Lmdlcmtvdmluay5jb20vbWljZVZpZ25ldHRlcy9mdXR1cmVtaWNlL1ZpZ25ldHRlX2Z1dHVyZW1pY2UuaHRtbCAoYXJjaGl2ZWQgYXQgaHR0cHM6Ly9wZXJtYS5jYy80U05FLVJDU1IpCgpgYGB7cn0KbWlfcGFyYWxsZWxfbWljZSA8LSBmdXR1cmVtaWNlKAogIGRhdGFUb0ltcHV0ZSwKICBtZXRob2QgPSBtZXRoLAogIHByZWRpY3Rvck1hdHJpeCA9IHByZWQsCiAgbSA9IG51bUltcHV0YXRpb25zLAogIG1heGl0ID0gNSwgIyBnZW5lcmFsbHkgdXNlIDEwMCBtYXhpbXVtIGl0ZXJhdGlvbnM7IHRoaXMgZXhhbXBsZSB1c2VzIDUgZm9yIHNwZWVkCiAgcGFyYWxsZWxzZWVkID0gNTIyNDIsCiAgbi5jb3JlID0gbnVtQ29yZXMsCiAgcGFja2FnZXMgPSAibWljZWFkZHMiKQpgYGAKCiMgSW1wdXRhdGlvbiBEaWFnbm9zdGljcwoKIyMgTG9nZ2VkIEV2ZW50cwoKYGBge3J9Cm1pX21pY2UkbG9nZ2VkRXZlbnRzCmBgYAoKIyMgVHJhY2UgUGxvdHMKCk9uIGNvbnZlcmdlbmNlLCB0aGUgc3RyZWFtcyBvZiB0aGUgdHJhY2UgcGxvdHMgc2hvdWxkIGludGVybWluZ2xlIGFuZCBiZSBmcmVlIG9mIGFueSB0cmVuZCAoYXQgdGhlIGxhdGVyIGl0ZXJhdGlvbnMpLgpDb252ZXJnZW5jZSBpcyBkaWFnbm9zZWQgd2hlbiB0aGUgdmFyaWFuY2UgYmV0d2VlbiBkaWZmZXJlbnQgc2VxdWVuY2VzIGlzIG5vIGxhcmdlciB0aGFuIHRoZSB2YXJpYW5jZSB3aXRoaW4gZWFjaCBpbmRpdmlkdWFsIHNlcXVlbmNlLgoKLSBodHRwczovL3N0ZWZ2YW5idXVyZW4ubmFtZS9maW1kL3NlYy1hbGdvcHRpb25zLmh0bWwgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvNFM1NC00NjVSKQoKYGBge3J9CnBsb3QobWlfbWljZSwgYygiaGVpZ2h0IikpCmBgYAoKIyMgRGVuc2l0eSBQbG90cwoKYGBge3J9CmRlbnNpdHlwbG90KG1pX21pY2UpCmRlbnNpdHlwbG90KG1pX21pY2UsIH4gaGVpZ2h0KQpgYGAKCiMjIFN0cmlwIFBsb3RzCgpgYGB7cn0Kc3RyaXBwbG90KG1pX21pY2UpCnN0cmlwcGxvdChtaV9taWNlLCBoZWlnaHQgfiAuaW1wKQpgYGAKCiMgUG9zdC1Qcm9jZXNzaW5nCgojIyBNb2RpZnkvQ3JlYXRlIE5ldyBWYXJpYWJsZXMKCiMjIyBgbWljZWAKCmBgYHtyfQptaV9taWNlX2xvbmcgPC0gY29tcGxldGUoCiAgbWlfbWljZSwKICBhY3Rpb24gPSAibG9uZyIsCiAgaW5jbHVkZSA9IFRSVUUpCgptaV9taWNlX2xvbmckbmV3VmFyIDwtIG1pX21pY2VfbG9uZyRhZ2UgKiBtaV9taWNlX2xvbmckaGVpZ2h0CmBgYAoKIyMjIGBqb21vYAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbWlfam9tbyA8LSBtaV9qb21vICU+JSAKICByZW5hbWUoaGVpZ2h0ID0gZGF0YVRvSW1wdXRlLi4ubGV2ZWwxVmFycy4pCgptaV9qb21vJG5ld1ZhciA8LSBtaV9qb21vJGFnZSAqIG1pX2pvbW8kaGVpZ2h0CmBgYAoKIyMjIGBBbWVsaWFgCgpgYGB7cn0KZm9yKGkgaW4gMTpsZW5ndGgobWlfYW1lbGlhJGltcHV0YXRpb25zKSl7CiAgbWlfYW1lbGlhJGltcHV0YXRpb25zW1tpXV0kbmV3VmFyIDwtIG1pX2FtZWxpYSRpbXB1dGF0aW9uc1tbaV1dJGFnZSAqIG1pX2FtZWxpYSRpbXB1dGF0aW9uc1tbaV1dJGhlaWdodAp9CmBgYAoKIyMgQ29udmVydCB0byBgbWlkc2Agb2JqZWN0CgojIyMgYG1pY2VgCgpgYGB7cn0KbWlfbWljZV9taWRzIDwtIGFzLm1pZHMobWlfbWljZV9sb25nKQpgYGAKCiMjIyBgam9tb2AKCmBgYHtyLCBldmFsID0gRkFMU0V9Cm1pX2pvbW9fbWlkcyA8LSBtaWNlYWRkczo6am9tbzJtaWRzKG1pX2pvbW8pCmBgYAoKIyMjIGBBbWVsaWFgCgpgYGB7cn0KbWlfYW1lbGlhX21pZHMgPC0gbWljZWFkZHM6OmRhdGxpc3QybWlkcyhtaV9hbWVsaWEkaW1wdXRhdGlvbnMpCmBgYAoKIyMgRXhwb3J0IGZvciBgTXBsdXNgCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQptaWRzMm1wbHVzKG1pX21pY2VfbWlkcywgcGF0aCA9IGZpbGUucGF0aCgiSW5zZXJ0RmlsZVBhdGhIZXJlIiwgZnNlcCA9ICIiKSkKbWlkczJtcGx1cyhtaV9qb21vX21pZHMsIHBhdGggPSBmaWxlLnBhdGgoIkluc2VydEZpbGVQYXRoSGVyZSIsIGZzZXAgPSAiIikpCm1pZHMybXBsdXMobWlfYW1lbGlhX21pZHMsIHBhdGggPSBmaWxlLnBhdGgoIkluc2VydEZpbGVQYXRoSGVyZSIsIGZzZXAgPSAiIikpCmBgYAoKIyBSZXNvdXJjZXMKCmh0dHBzOi8vc3RlZnZhbmJ1dXJlbi5uYW1lL2ZpbWQgKGFyY2hpdmVkIGF0IGh0dHBzOi8vcGVybWEuY2MvNDZVMi1RVE02KQoKaHR0cHM6Ly93d3cuZ2Vya292aW5rLmNvbS9taWNlVmlnbmV0dGVzL011bHRpX2xldmVsL011bHRpX2xldmVsX2RhdGEuaHRtbCAoYXJjaGl2ZWQgYXQgaHR0cHM6Ly9wZXJtYS5jYy9TRjMyLUQ3WkYpCg==