require(Hmisc)
require(plotly)
require(htmlTable)
source('~/R/hreport/R/Misc.r')
source('~/R/hreport/R/accrualReport.r')
source('~/R/hreport/R/exReport.r')
mu <- markupSpecs$html # in Hmisc - HTML markups
frac <- mu$frac
mu$styles() # define HTML styles, functions
## Generate test data
set.seed(1)
n <- 500
d <- data.frame(country=sample(c('US', 'Canada', 'Spain', 'France',
'Germany'), n, TRUE),
site=sample(1:10, n, TRUE))
d$site <- paste(substring(d$country, 1, 2), d$site, sep='')
d$region <- factor(ifelse(d$country %in% c('US', 'Canada'),
'North America', 'Europe'))
d <- upData(d, edate = as.Date('2005-01-01') +
round(rgamma(n, 2, .01)) - 600 * (country == 'US'),
rdate = edate + round(runif(n, 1, 30)), print=FALSE)
d$rdate[runif(nrow(d)) < 0.5] <- NA # non-randomized subjects )
# with(d, table(region, country))
# For US manually compute # randomized per month
us <- subset(d, country == 'US')
site <- us$site
ed <- us$edate
rd <- us$rdate
months <- difftime(as.Date('2007-12-31'), ed, units='days') /
(365.25 / 12)
m <- max(months)
a <- sum(!is.na(rd)) / as.numeric(m) # .8545774 (agrees with chart)
# Compute maximum months elapsed for each site then sum over sites
maxpersite <- tapply(months, site, max)
b <- sum(!is.na(rd)) / sum(maxpersite)
## 0.0864429 = 47 / 543.6715 chart: .08645 (rounded)
## Suppose there are more subjects enrolled and randomized than really
## made their way into the dataset
denom <- c(enrolled=nrow(d) * 1.1,
randomized=sum(!is.na(d$rdate)) + 10)
sethreportOption(tx.var='treat', denom=denom)
## Initialize file to hold appendix information such as subject IDs
## so all later writing to this file can use append=TRUE
appfile <- gethreportOption('appfile')
cat('', file=appfile)
Introduction
Interactive Graphs
Most of the graphs produced here are semi-interactive. One can hover over elements of graphs with the mouse to have detailed information pop up.
Extended Box Plots
For depicting distributions of continuous variables, many of the following displays use extended box plots, also called box–percentile plots. A prototype, with explanations, is below.
bpplt()

Dot Charts
Dot charts are used to present stratified proportions. In these charts the area of the symbols is proportional to the square root of the denominator. The legend shows representative denominators and their corresponding symbol areas, using denominators that actually occurred in the data and extended from the minimum observed to the maximum observed sample size.???
Survival Curves
Graphs containing pairs of Kaplan-Meier survival curves show a shaded region centered at the midpoint of the two survival estimates and having a height equal to the half-width of the approximate 0.95 pointwise confidence interval for the difference of the two survival probabilities. Time points at which the two survival estimates do not touch the shaded region denote approximately significantly different survival estimates, without any multiplicity correction.
Accrual
accrualReport(enroll(edate) + randomize(rdate) ~
region(region) + country(country) + site(site),
data=d,
dateRange=c('2005-01-01', '2007-12-31'),
targetN=
data.frame(edate=c(500, 1000), rdate=c(250, 500)),
targetDate=c('2006-01-01', '2007-12-31'),
closeDate='2007-12-31')
Study Numbers
Number
|
Category
|
5
|
Countries
|
50
|
Sites
|
500
|
Participants enrolled
|
260
|
Participants randomized
|
5.2
|
Participants per site
|
50
|
Sites randomizing
|
5.2
|
Subjects randomized per randomizing site
|
55.1
|
Months from first subject randomized (2003-05-29) to 2007-12-31
|
1873.8
|
Site-months for sites randomizing
|
37.5
|
Average months since a site first randomized
|
0.14
|
Participants randomized per site per month
|
15
|
Mean days from enrollment to randomization
|
15
|
Median days from enrollment to randomization
|
∟ Participants enrolled over time
The blue line depicts the cumulative frequency. The thick grayscale line represent targets.
|
Category
|
N
|
Used
|
Enrolled
|
550
|
500
|
|
|
∟ Participants randomized over time
The blue line depicts the cumulative frequency. The thick grayscale line represent targets.
|
Category
|
N
|
Used
|
Enrolled
|
550
|
260
|
Randomized
|
270
|
260
|
|
|
∟ Days from enrollment to randomization
Quartiles and mean number of days by region and country
|
Category
|
N
|
Used
|
Enrolled
|
550
|
260
|
Randomized
|
270
|
260
|
|
|
∟ Number of sites × number of participants
Number of sites having the given number of participants
|
Category
|
N
|
Used
|
Enrolled
|
550
|
260
|
Randomized
|
270
|
260
|
|
|
∟ Participants enrolled by region and country
∟ Participants randomized by region and country
∟ Sites that enrolled by region and country
∟ Sites that randomized by region and country
∟ Fraction of enrolled participants randomized by region and country
∟ Participants randomized per month by region and country
∟ Partipants randomized per site per month by region and country
Exclusions
d <- upData(d,
subjid = 1 : n,
pend = rbinom(n, 1, .1),
e1 = rbinom(n, 1, .02),
e2 = rbinom(n, 1, .02),
e3 = rbinom(n, 1, .02),
e4 = ifelse(runif(n) < 0.25, NA, rbinom(n, 1, .10)),
tested = rbinom(n, 1, .75),
e5 = ifelse(tested, rbinom(n, 1, .04), NA),
e6 = rbinom(n, 1, .02),
e7 = rbinom(n, 1, .02),
rndz = rbinom(n, 1, .75),
labels=c(e1='Prior MI', e2='History of Asthma',
e3='History of Upper GI Bleeding',
e4='No Significant CAD', e5='Inadequate Renal Function',
e6='Pneumonia within 6 weeks', e7='Prior cardiac surgery'),
print=FALSE)
erd <- data.frame(subjid = 1 : 50,
loc = sample(c('gastric', 'lung', 'trachea'), 50, TRUE))
# To check warning messages, greportOption denom does not match pend, e1-e7
exReport(~ pending(pend) + e1 + e2 + e3 + e4 + e5 + e6 + e7 +
randomized(rndz) + id(subjid) + cond(e5, 'Tested', tested),
erdata = erd,
whenapp= c(e4='CCTA done'), data=d) #, hc=3.75, h=4)
∟ Cumulative exclusions
Cumulative number of exclusions (\(y\)-axis) and number of additional exclusions after exclusions placed higher, for participants not actually randomized. Exclusions are sorted by descending number of incremental exclusions. 550 participants were enrolled, 12 non-excluded participants are pending randomization, and 20 participants were excluded. 371 participants were randomized. Note: Number of observations (500) does not equal number officially enrolled (550). Note: Number of enrolled (488) minus number excluded (20) does not match official number randomized (270).
◫ Exclusions
Incremental exclusions are those in addition to exclusions in earlier rows. Marginal exclusions are numbers of participants excluded for the indicated reason whether or not she was excluded for other reasons. The three Fractions are based on incremental exclusions.
Exclusions
|
Incremental Exclusions
|
Marginal Exclusions
|
Fraction of Enrolled
|
Fraction of Exclusions
|
Fraction Remaining
|
|
No Significant CAD (CCTA done, n=386)
|
6
|
6
|
0.012
|
0.30
|
0.988
|
Prior Cardiac Surgery
|
4
|
4
|
0.008
|
0.20
|
0.980
|
Inadequate Renal Function / 373
|
3
|
3
|
0.006
|
0.15
|
0.973
|
3⁄367 = 0.008 of Tested
|
Prior MI
|
2
|
3
|
0.004
|
0.10
|
0.969
|
History of Upper GI Bleeding
|
2
|
2
|
0.004
|
0.10
|
0.965
|
Pneumonia within 6 Weeks
|
2
|
2
|
0.004
|
0.10
|
0.961
|
History of Asthma
|
1
|
1
|
0.002
|
0.05
|
0.959
|
Total
|
20
|
|
0.041
|
1.00
|
0.959
|
◫ Exclusions in randomized participants
Frequency of exclusions for participants marked as randomized
Exclusion
|
Frequency
|
Prior MI
|
8
|
History of Asthma
|
7
|
History of Upper GI Bleeding
|
7
|
No Significant CAD
|
42
|
Inadequate Renal Function
|
15
|
Pneumonia within 6 Weeks
|
7
|
Prior Cardiac Surgery
|
8
|
Total Partcipants with Any Exclusion
|
84
|
▼Click arrow at left to show participant IDs:
Exclusion
|
IDs
|
Prior MI
|
87, 127, 209, 250, 274, 321, 442, 448
|
History of Asthma
|
17, 62, 68, 121, 122, 127, 462
|
History of Upper GI Bleeding
|
64, 202, 307, 310, 330, 449, 462
|
No Significant CAD
|
6, 7, 10, 22, 27, 37, 39, 53, 67, 112, 113, 118, 122, 143, 145, 175, 179, 184, 226, 227, 249, 255, 278, 282, 289, 291, 297, 321, 330, 344, 359, 361, 371, 377, 385, 402, 438, 451, 455, 471, 482, 499
|
Inadequate Renal Function
|
24, 37, 46, 70, 88, 154, 183, 194, 226, 234, 242, 292, 295, 322, 484
|
Pneumonia within 6 Weeks
|
29, 166, 230, 235, 313, 447, 473
|
Prior Cardiac Surgery
|
171, 191, 344, 377, 388, 389, 477, 482
|
▼Click arrow at left to see more information about those participants:
subjid
|
loc
|
6
|
gastric
|
7
|
gastric
|
10
|
trachea
|
17
|
lung
|
22
|
trachea
|
24
|
gastric
|
27
|
lung
|
29
|
trachea
|
37
|
trachea
|
39
|
lung
|
46
|
gastric
|
# Show exclusions in original variable order
if(FALSE) exReport(~ pending(pend) + e1 + e2 + e3 + e4 + e5 + e6 + e7 +
randomized(rndz) + id(subjid) + cond(e5, 'Tested', tested),
erdata=erd,
whenapp=c(e4='CCTA done'), data=d, #hc=3.75, h=4,
sort=FALSE, app=FALSE)
LS0tCnRpdGxlOiAiRFNNQiBSZXBvcnQgZm9yIEVYQU1QTEUgVHJpYWwiCmF1dGhvcjogIkZFIEhhcnJlbGwiCmRhdGU6ICdgciBTeXMuRGF0ZSgpYCcKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogeWVzCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiB5ZXMKLS0tCmBgYHtyIHNldHVwLHJlc3VsdHM9J2hpZGUnfQpyZXF1aXJlKEhtaXNjKQpyZXF1aXJlKHBsb3RseSkKcmVxdWlyZShodG1sVGFibGUpCnNvdXJjZSgnfi9SL2hyZXBvcnQvUi9NaXNjLnInKQpzb3VyY2UoJ34vUi9ocmVwb3J0L1IvYWNjcnVhbFJlcG9ydC5yJykKc291cmNlKCd+L1IvaHJlcG9ydC9SL2V4UmVwb3J0LnInKQptdSA8LSBtYXJrdXBTcGVjcyRodG1sICAgIyBpbiBIbWlzYyAtIEhUTUwgbWFya3VwcwpmcmFjIDwtIG11JGZyYWMKYGBgCmBgYHtyIHNldHVwMn0KbXUkc3R5bGVzKCkgICAgICAgICAgICAgICMgZGVmaW5lIEhUTUwgc3R5bGVzLCBmdW5jdGlvbnMKYGBgCjwhLS0gTWF5IG5lZWQgdG8gcnVuIHRoZSBmb2xsb3dpbmcgc3lzdGVtIGNvbW1hbmRzIG9uZSB0aW1lOgpjZCAvdXNyL2xvY2FsL2JpbgpzdWRvIGxuIC1zIC91c3IvbGliL3JzdHVkaW8vYmluL3BhbmRvYy9wYW5kb2MtY2l0ZXByb2MKLS0+CgpgYGB7ciBnZW5kYXRhfQojIyBHZW5lcmF0ZSB0ZXN0IGRhdGEKc2V0LnNlZWQoMSkKbiA8LSA1MDAKZCA8LSBkYXRhLmZyYW1lKGNvdW50cnk9c2FtcGxlKGMoJ1VTJywgJ0NhbmFkYScsICdTcGFpbicsICdGcmFuY2UnLAogICAgICAgICAgICAgICAgICAnR2VybWFueScpLCBuLCBUUlVFKSwKICAgICAgICAgICAgICAgIHNpdGU9c2FtcGxlKDE6MTAsIG4sIFRSVUUpKQpkJHNpdGUgICA8LSBwYXN0ZShzdWJzdHJpbmcoZCRjb3VudHJ5LCAxLCAyKSwgZCRzaXRlLCBzZXA9JycpCmQkcmVnaW9uIDwtIGZhY3RvcihpZmVsc2UoZCRjb3VudHJ5ICVpbiUgYygnVVMnLCAnQ2FuYWRhJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgJ05vcnRoIEFtZXJpY2EnLCAnRXVyb3BlJykpCgpkIDwtIHVwRGF0YShkLCBlZGF0ZSA9IGFzLkRhdGUoJzIwMDUtMDEtMDEnKSArCiAgICAgICAgICAgIHJvdW5kKHJnYW1tYShuLCAyLCAuMDEpKSAtIDYwMCAqIChjb3VudHJ5ID09ICdVUycpLAogICAgICAgICAgICByZGF0ZSA9IGVkYXRlICsgcm91bmQocnVuaWYobiwgMSwgMzApKSwgcHJpbnQ9RkFMU0UpCmQkcmRhdGVbcnVuaWYobnJvdyhkKSkgPCAwLjVdIDwtIE5BICAjIG5vbi1yYW5kb21pemVkIHN1YmplY3RzICkKCiMgd2l0aChkLCB0YWJsZShyZWdpb24sIGNvdW50cnkpKQoKIyBGb3IgVVMgbWFudWFsbHkgY29tcHV0ZSAjIHJhbmRvbWl6ZWQgcGVyIG1vbnRoCnVzICAgPC0gc3Vic2V0KGQsIGNvdW50cnkgPT0gJ1VTJykKc2l0ZSA8LSB1cyRzaXRlCmVkICAgPC0gdXMkZWRhdGUKcmQgICA8LSB1cyRyZGF0ZQptb250aHMgPC0gZGlmZnRpbWUoYXMuRGF0ZSgnMjAwNy0xMi0zMScpLCBlZCwgdW5pdHM9J2RheXMnKSAvCiAgKDM2NS4yNSAvIDEyKQptIDwtIG1heChtb250aHMpCmEgPC0gc3VtKCFpcy5uYShyZCkpIC8gYXMubnVtZXJpYyhtKSAgICMgLjg1NDU3NzQgKGFncmVlcyB3aXRoIGNoYXJ0KQojIENvbXB1dGUgbWF4aW11bSBtb250aHMgZWxhcHNlZCBmb3IgZWFjaCBzaXRlIHRoZW4gc3VtIG92ZXIgc2l0ZXMKbWF4cGVyc2l0ZSA8LSB0YXBwbHkobW9udGhzLCBzaXRlLCBtYXgpCmIgPC0gc3VtKCFpcy5uYShyZCkpIC8gc3VtKG1heHBlcnNpdGUpCiMjIDAuMDg2NDQyOSA9IDQ3IC8gNTQzLjY3MTUgY2hhcnQ6IC4wODY0NSAocm91bmRlZCkKCiMjIFN1cHBvc2UgdGhlcmUgYXJlIG1vcmUgc3ViamVjdHMgZW5yb2xsZWQgYW5kIHJhbmRvbWl6ZWQgdGhhbiByZWFsbHkKIyMgbWFkZSB0aGVpciB3YXkgaW50byB0aGUgZGF0YXNldApkZW5vbSA8LSBjKGVucm9sbGVkPW5yb3coZCkgKiAxLjEsCiAgICAgICAgICAgcmFuZG9taXplZD1zdW0oIWlzLm5hKGQkcmRhdGUpKSArIDEwKQoKc2V0aHJlcG9ydE9wdGlvbih0eC52YXI9J3RyZWF0JywgZGVub209ZGVub20pCiMjIEluaXRpYWxpemUgZmlsZSB0byBob2xkIGFwcGVuZGl4IGluZm9ybWF0aW9uIHN1Y2ggYXMgc3ViamVjdCBJRHMKIyMgc28gYWxsIGxhdGVyIHdyaXRpbmcgdG8gdGhpcyBmaWxlIGNhbiB1c2UgYXBwZW5kPVRSVUUKYXBwZmlsZSA8LSBnZXRocmVwb3J0T3B0aW9uKCdhcHBmaWxlJykKY2F0KCcnLCBmaWxlPWFwcGZpbGUpCmBgYCAKCiMgSW50cm9kdWN0aW9uCiMjIEludGVyYWN0aXZlIEdyYXBocwpNb3N0IG9mIHRoZSBncmFwaHMgcHJvZHVjZWQgaGVyZSBhcmUgc2VtaS1pbnRlcmFjdGl2ZS4gIE9uZSBjYW4gaG92ZXIgb3ZlciBlbGVtZW50cyBvZiBncmFwaHMgd2l0aCB0aGUgbW91c2UgdG8gaGF2ZSBkZXRhaWxlZCBpbmZvcm1hdGlvbiBwb3AgdXAuCgojIyBGaWd1cmUgQ2FwdGlvbnMKTmVlZGxlcyByZXByZXNlbnQgdGhlIGZyYWN0aW9uIG9mIG9ic2VydmF0aW9ucyB1c2VkIGluIHRoZSBjdXJyZW50CmFuYWx5c2lzLiAgVGhlIGZpcnN0IG5lZWRsZSAocmVkKSBzaG93cyB0aGUgZnJhY3Rpb24gb2YgZW5yb2xsZWQKcGF0aWVudHMgdXNlZC4gIElmIHJhbmRvbWl6YXRpb24gd2FzIHRha2VuIGludG8gYWNjb3VudCwgYSBzZWNvbmQKbmVlZGxlIChncmVlbikgcmVwcmVzZW50cyB0aGUgZnJhY3Rpb24gb2YgcmFuZG9taXplZCBzdWJqZWN0cyBpbmNsdWRlZAppbiB0aGUgYW5hbHlzaXMuICBXaGVuIHRoZSBhbmFseXNlcyBjb25zaWRlciB0cmVhdG1lbnQgYXNzaWdubWVudCwgdHdvCm1vcmUgbmVlZGxlcyBtYXkgYmUgYWRkZWQgdG8gdGhlIGRpc3BsYXksIHNob3dpbmcsIHJlc3BlY3RpdmVseSwgdGhlCmZyYWN0aW9uIG9mIHN1YmplY3RzIHJhbmRvbWl6ZWQgdG8gdHJlYXRtZW50IEEgdXNlZCBpbiB0aGUgYW5hbHlzaXMKYW5kIHRoZSBmcmFjdGlvbiBvZiBzdWJqZWN0cyBvbiB0cmVhdG1lbnQgQiB3aG8gd2VyZSBhbmFseXplZC4gIFRoZQpjb2xvcnMgb2YgdGhlc2UgbGFzdCB0d28gbmVlZGxlcyBhcmUgdGhlIGNvbG9ycyB1c2VkIGZvciB0aGUgdHdvCnRyZWF0bWVudHMgdGhyb3VnaG91dCB0aGUgcmVwb3J0LiAgVGhlIGZvbGxvd2luZyB0YWJsZSBzaG93cyBzb21lCmV4YW1wbGVzLiAgYGROZWVkbGVgIHVzZXMgY29sb3JzIGluIGBzZXRocmVwb3J0T3B0aW9uKHR4LmNvbD0sIGVyLmNvbD0pYC4KCmBgYHtyIG5lZWRsZWRlZnN9CiMgU3RvcmUgdXNpbmcgc2hvcnQgdmFyaWFibGUgbmFtZXMgc28gUm1hcmtkb3duIHRhYmxlIGNvbHVtbgojIHdpZHRoIHdpbGwgbm90IGJlIHdpZGVyIHRoYW4gYWN0dWFsbHkgbmVlZGVkCmQxIDwtIGROZWVkbGUoMSkKZDIgPC0gZE5lZWRsZSgoMzo0KS80KQpkMyA8LSBkTmVlZGxlKCgxOjIpLzQpCmQ0IDwtIGROZWVkbGUoYygxLDIsMywxKS80KQpgYGAKCnxTaWducG9zdCAgIHwgSW50ZXJwcmV0YXRpb24gfAp8LS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18CnwgYHIgZDFgIHwgQWxsIGVucm9sbGVkIHN1YmplY3RzIGFuYWx5emVkLCByYW5kb21pemF0aW9uIG5vdCBjb25zaWRlcmVkfAp8IGByIGQyYCB8IEFuYWx5c2lzIHVzZXMgYHIgZnJhYygzLDQpYCBvZiBlbnJvbGxlZCBzdWJqZWN0cywgYW5kIGFsbCByYW5kb21pemVkIHN1YmplY3RzfAp8IGByIGQzYCB8IEFuYWx5c2lzIHVzZXMgYHIgZnJhYygxLDQpYCBvZiBlbnJvbGxlZCBzdWJqZWN0cywgYW5kIGByIGZyYWMoMSwyKWAgb2YgcmFuZG9taXplZCBzdWJqZWN0c3wKfCBgciBkNGAgfCBTYW1lIGFzIHByZXZpb3VzIGV4YW1wbGUsIGFuZCBpbiBhZGRpdGlvbiB0aGUgYW5hbHlzaXMgdXRpbGl6ZWQgdHJlYXRtZW50IGFzc2lnbm1lbnQsIGFuYWx5emluZyBgciBmcmFjKDMsNClgIG9mIHRob3NlIHJhbmRvbWl6ZWQgdG8gQSBhbmQgYHIgZnJhYygxLDQpYCBvZiB0aG9zZSByYW5kb21pemVkIHRvIEJ8CgojIEV4dGVuZGVkIEJveCBQbG90cwpGb3IgZGVwaWN0aW5nIGRpc3RyaWJ1dGlvbnMgb2YgY29udGludW91cyB2YXJpYWJsZXMsIG1hbnkgb2YgdGhlCmZvbGxvd2luZyBkaXNwbGF5cyB1c2UgZXh0ZW5kZWQgYm94IHBsb3RzLCBhbHNvIGNhbGxlZApib3gtLXBlcmNlbnRpbGUgcGxvdHMuICBBIHByb3RvdHlwZSwgd2l0aCBleHBsYW5hdGlvbnMsIGlzIGJlbG93LgpgYGB7ciBicHBsdH0KYnBwbHQoKQpgYGAKCiMjIERvdCBDaGFydHMKRG90IGNoYXJ0cyBhcmUgdXNlZCB0byBwcmVzZW50IHN0cmF0aWZpZWQgcHJvcG9ydGlvbnMuICBJbiB0aGVzZQpjaGFydHMgdGhlIGFyZWEgb2YgdGhlIHN5bWJvbHMgaXMgcHJvcG9ydGlvbmFsIHRvIHRoZSBzcXVhcmUgcm9vdCBvZgp0aGUgZGVub21pbmF0b3IuICBUaGUgbGVnZW5kIHNob3dzIHJlcHJlc2VudGF0aXZlIGRlbm9taW5hdG9ycyBhbmQKdGhlaXIgY29ycmVzcG9uZGluZyBzeW1ib2wgYXJlYXMsIHVzaW5nIGRlbm9taW5hdG9ycyB0aGF0IGFjdHVhbGx5Cm9jY3VycmVkIGluIHRoZSBkYXRhIGFuZCBleHRlbmRlZCBmcm9tIHRoZSBtaW5pbXVtIG9ic2VydmVkIHRvIHRoZQptYXhpbXVtIG9ic2VydmVkIHNhbXBsZSBzaXplLj8/PwoKCiMjIFN1cnZpdmFsIEN1cnZlcwpHcmFwaHMgY29udGFpbmluZyBwYWlycyBvZiBLYXBsYW4tTWVpZXIgc3Vydml2YWwgY3VydmVzIHNob3cgYSBzaGFkZWQKcmVnaW9uIGNlbnRlcmVkIGF0IHRoZSBtaWRwb2ludCBvZiB0aGUgdHdvIHN1cnZpdmFsIGVzdGltYXRlcyBhbmQKaGF2aW5nIGEgaGVpZ2h0IGVxdWFsIHRvIHRoZSBoYWxmLXdpZHRoIG9mIHRoZSBhcHByb3hpbWF0ZSAwLjk1IHBvaW50d2lzZQpjb25maWRlbmNlIGludGVydmFsIGZvciB0aGUgZGlmZmVyZW5jZSBvZiB0aGUgdHdvIHN1cnZpdmFsCnByb2JhYmlsaXRpZXMuICBUaW1lIHBvaW50cyBhdCB3aGljaCB0aGUgdHdvIHN1cnZpdmFsIGVzdGltYXRlcyBkbyBub3QKdG91Y2ggdGhlIHNoYWRlZCByZWdpb24gZGVub3RlIGFwcHJveGltYXRlbHkgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQKc3Vydml2YWwgZXN0aW1hdGVzLCB3aXRob3V0IGFueSBtdWx0aXBsaWNpdHkgY29ycmVjdGlvbi4KCiMgQWNjcnVhbAoKYGBge3IgYWNjcnVhbCxyZXN1bHRzPSdhc2lzJ30KYWNjcnVhbFJlcG9ydChlbnJvbGwoZWRhdGUpICsgcmFuZG9taXplKHJkYXRlKSB+CiAgICAgICAgICAgICAgcmVnaW9uKHJlZ2lvbikgKyBjb3VudHJ5KGNvdW50cnkpICsgc2l0ZShzaXRlKSwKICAgICAgICAgICAgICBkYXRhPWQsCiAgICAgICAgICAgICAgZGF0ZVJhbmdlPWMoJzIwMDUtMDEtMDEnLCAnMjAwNy0xMi0zMScpLAogICAgICAgICAgICAgIHRhcmdldE49CiAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKGVkYXRlPWMoNTAwLCAxMDAwKSwgcmRhdGU9YygyNTAsIDUwMCkpLAogICAgICAgICAgICAgIHRhcmdldERhdGU9YygnMjAwNi0wMS0wMScsICcyMDA3LTEyLTMxJyksCiAgICAgICAgICAgICAgY2xvc2VEYXRlPScyMDA3LTEyLTMxJykKYGBgCgojIEV4Y2x1c2lvbnMKYGBge3IgZXhjbCxyZXN1bHRzPSdhc2lzJ30KZCA8LSB1cERhdGEoZCwKICAgICAgICAgICAgc3ViamlkID0gMSA6IG4sCiAgICAgICAgICAgIHBlbmQgICA9IHJiaW5vbShuLCAxLCAuMSksCiAgICAgICAgICAgIGUxICAgICA9IHJiaW5vbShuLCAxLCAuMDIpLAogICAgICAgICAgICBlMiAgICAgPSByYmlub20obiwgMSwgLjAyKSwKICAgICAgICAgICAgZTMgICAgID0gcmJpbm9tKG4sIDEsIC4wMiksCiAgICAgICAgICAgIGU0ICAgICA9IGlmZWxzZShydW5pZihuKSA8IDAuMjUsIE5BLCByYmlub20obiwgMSwgLjEwKSksCiAgICAgICAgICAgIHRlc3RlZCA9IHJiaW5vbShuLCAxLCAuNzUpLAogICAgICAgICAgICBlNSAgICAgPSBpZmVsc2UodGVzdGVkLCByYmlub20obiwgMSwgLjA0KSwgTkEpLAogICAgICAgICAgICBlNiAgICAgPSByYmlub20obiwgMSwgLjAyKSwKICAgICAgICAgICAgZTcgICAgID0gcmJpbm9tKG4sIDEsIC4wMiksCiAgICAgICAgICAgIHJuZHogICA9IHJiaW5vbShuLCAxLCAuNzUpLAogICAgICAgICAgICBsYWJlbHM9YyhlMT0nUHJpb3IgTUknLCBlMj0nSGlzdG9yeSBvZiBBc3RobWEnLAogICAgICAgICAgICAgIGUzPSdIaXN0b3J5IG9mIFVwcGVyIEdJIEJsZWVkaW5nJywKICAgICAgICAgICAgICBlND0nTm8gU2lnbmlmaWNhbnQgQ0FEJywgZTU9J0luYWRlcXVhdGUgUmVuYWwgRnVuY3Rpb24nLAogICAgICAgICAgICAgIGU2PSdQbmV1bW9uaWEgd2l0aGluIDYgd2Vla3MnLCBlNz0nUHJpb3IgY2FyZGlhYyBzdXJnZXJ5JyksCiAgICAgICAgICAgIHByaW50PUZBTFNFKQoKZXJkIDwtIGRhdGEuZnJhbWUoc3ViamlkID0gMSA6IDUwLAogICAgICAgICAgICAgICAgICBsb2MgICA9IHNhbXBsZShjKCdnYXN0cmljJywgJ2x1bmcnLCAndHJhY2hlYScpLCA1MCwgVFJVRSkpCgojIFRvIGNoZWNrIHdhcm5pbmcgbWVzc2FnZXMsIGdyZXBvcnRPcHRpb24gZGVub20gZG9lcyBub3QgbWF0Y2ggcGVuZCwgZTEtZTcKZXhSZXBvcnQofiBwZW5kaW5nKHBlbmQpICsgZTEgKyBlMiArIGUzICsgZTQgKyBlNSArIGU2ICsgZTcgKwogICAgICAgICByYW5kb21pemVkKHJuZHopICsgaWQoc3ViamlkKSArIGNvbmQoZTUsICdUZXN0ZWQnLCB0ZXN0ZWQpLAogICAgICAgICBlcmRhdGEgPSBlcmQsCiAgICAgICAgIHdoZW5hcHA9IGMoZTQ9J0NDVEEgZG9uZScpLCBkYXRhPWQpICMsIGhjPTMuNzUsIGg9NCkKCiMgU2hvdyBleGNsdXNpb25zIGluIG9yaWdpbmFsIHZhcmlhYmxlIG9yZGVyCmlmKEZBTFNFKSBleFJlcG9ydCh+IHBlbmRpbmcocGVuZCkgKyBlMSArIGUyICsgZTMgKyBlNCArIGU1ICsgZTYgKyBlNyArCiAgICAgICAgIHJhbmRvbWl6ZWQocm5keikgKyBpZChzdWJqaWQpICsgY29uZChlNSwgJ1Rlc3RlZCcsIHRlc3RlZCksCiAgICAgICAgIGVyZGF0YT1lcmQsCiAgICAgICAgIHdoZW5hcHA9YyhlND0nQ0NUQSBkb25lJyksIGRhdGE9ZCwgI2hjPTMuNzUsIGg9NCwKICAgICAgICAgc29ydD1GQUxTRSwgYXBwPUZBTFNFKQpgYGAK