Introduction

This analysis is based on research that is publicly available on GitHub. The code and description have been adapted for this write-up. Here is are some expository excerpts from the paper:

Dengue, a mosquito-borne virus prevalent throughout the tropics and sub-tropics, infects an estimated 390 million people every year. While the majority of infections are mild or asymptomatic, the more severe forms of dengue infection – dengue shock syndrome (DSS) and dengue hemorrhagic fever (DHF) – can result in organ failure or death.

… In Thailand, dengue infection is endemic with substantial annual and geographic variation in incidence across its 76 provinces and 13 health regions. Over the past 15 years, an average of 43,137 (range 14,952-106,320) DHF cases have been reported to the Thailand Ministry of Public Health (MOPH) each year. Within a typical year, incidence rates in different provinces can vary by an order of magnitude, with some provinces experiencing less than 10 DHF cases per 100,000 population and others over 100 per 100,000 population.

… Whether an infectious disease spreads within a population depends on the transmission rate of the disease and the number of susceptible individuals, thus long-term forecasting models for DHF incidence may need to account for climatic factors that could affect transmission as well as population susceptibility. Climatic factors, such as temperature, rainfall, and humidity, may impact both the prevalence and distribution of the dengue vector, the mosquito, as well as the transmission efficiency of dengue virus. Such climatic factors vary significantly across Thailand.

… We obtained data on DHF cases (from the MOPH), population (National Statistical Office of Thailand), and weather (NOAA). These data were summarized across time frames ranging from one month to one year to create covariates for consideration by our model selection algorithm. … We made forecasts using the data available in April of each year, the month when the MOPH has historically finalized the incidence reports obtained from all provinces for the prior calendar year. Hence, all ``annual’’ forecasts are for DHF incidence between April and December of the year they are made.

… For the NOAA and NCDC weather station data, we found the most consistently reported weather station for each province and extracted the daily maximum and minimum temperature, maximum humidity, and rainfall. We aggregated these measures into monthly covariates for maximum, minimum, and mean temperature, maximum and mean humidity, and maximum and total rainfall across the low-season, January, February, and March.

The data

The dataset shown here and provided in this repository is the same one used in the analysis. Some columns have been removed for simplicity. Here, we split the dataset into a training dataset from 2000 through 2012 and a testing dataset from 2013 and 2014.

dat <- read.csv("dengue-data.csv")
train_dat <- filter(dat, year<2013) ## all but 2013 and 2014
test_dat <- filter(dat, year>=2013)
str(train_dat)
'data.frame':   988 obs. of  27 variables:
 $ pid               : Factor w/ 76 levels "TH01","TH02",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ year              : int  2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 ...
 $ obs               : int  1 63 28 35 49 18 13 61 92 173 ...
 $ population        : int  210537 210399 210260 210121 209983 209844 209706 209568 209430 209291 ...
 $ population_density: num  16.6 16.6 16.6 16.6 16.6 ...
 $ pre_season_rate   : num  0 0.475 0 0.476 0.476 ...
 $ last_post_rate    : num  0 0 0.951 1.427 2.38 ...
 $ last_high_rate    : num  22.8 0 29 11.9 14.3 ...
 $ sus_pct           : num  17.1 36.6 29.4 16.1 28.9 ...
 $ mean_temp_grid    : num  22.5 23.9 23.1 23.2 23.2 ...
 $ total_rain_grid   : num  213.4 95.7 69.6 149 60.1 ...
 $ Jan_temp_grid     : num  22.1 22.1 21.9 21.1 21.3 ...
 $ Feb_temp_grid     : num  23.8 25.1 24 24.4 25.2 ...
 $ Mar_temp_grid     : num  26.8 28.6 27.2 28.6 28.6 ...
 $ Jan_rain_grid     : num  34.15 2.02 14.1 9.32 5.38 ...
 $ Feb_rain_grid     : num  42.179 71.425 7.046 26.814 0.958 ...
 $ Mar_rain_grid     : num  125.8 12.5 30 59.1 48.7 ...
 $ max_humid_ncdc    : num  91.7 94.6 91.6 94.4 92.8 ...
 $ min_humid_ncdc    : num  42.1 47 41.9 52.8 38.9 ...
 $ Jan_humid_ncdc    : num  93.6 96.4 94.6 96.4 98.9 ...
 $ Feb_humid_ncdc    : num  89.8 94 91.2 94.4 91.4 ...
 $ Mar_humid_ncdc    : num  84.4 88.1 82.3 89 80.8 ...
 $ max_temp_ncdc     : num  30.8 31.7 31.3 30.7 32 ...
 $ min_temp_ncdc     : num  17.1 17.8 17.4 18.6 17 ...
 $ Jan_temp_ncdc     : num  30.5 30.8 28.8 27.8 30.4 ...
 $ Feb_temp_ncdc     : num  32 33.6 33.2 31.6 32.8 ...
 $ Mar_temp_ncdc     : num  34.4 34.3 36.3 35.6 37.1 ...
ggplot(dat, aes(x=year, y=reorder(pid, population), fill=obs/population)) + geom_tile() + 
    scale_fill_gradient(low="grey100", high="firebrick") + 
    ylab("Province (by population, large at top)") + xlab(NULL) 

Before we begin modeling, let’s see if there is evidence that we will need to compensate for overdispersion in a Poisson model.

disp_dat <- train_dat %>% 
    group_by(pid) %>%
    summarize(
        avg_inc_rate = mean(obs),
        var_inc_rate = var(obs),
        var_over_avg = var_inc_rate/avg_inc_rate
    )
quantile(disp_dat$var_over_avg)
         0%         25%         50%         75%        100% 
   5.489567  108.919350  192.394499  345.826018 1724.248223 

There sure is. The per-province variance ranges from 7 to over 1500 times the accompanying mean.

Modeling

Negative binomial model

The model that we used to forecast annual DHF incidence for this study is a generalized additive model . Specifically, we use a generalized additive model with a negative binomial family, separate penalized smoothing splines for each covariate, and province-level random effects:

\[ Y_{i,t} \sim \text{NB}(n_{i,t}\lambda_{i,t}, r) \] \[ \log \Big [ \mathbf{E}(Y_{i,t}) \Big ] = \beta_0 + \log(n_{i,t}) + \alpha_i + \sum_{j=1}^J g_j(x_{j,i,t}|\boldsymbol{\theta}) \] \[ \alpha_i \sim \text{Normal}(\mu, \sigma^2) \]

We model the incidence (\(Y_{i,t}\)) for province \(i\) in year \(t\) as following a negative binomial distribution with the mean equal to the province population (\(n_{i,t}\)) times the incidence rate (\(\lambda_{i,t}\)) and a dispersion parameter \(r\). After a log transformation, we model the mean of this distribution using an intercept (\(\beta_0\)), a random effect for each province (\(\alpha_i\)) and a cubic spline for each of \(J\) covariates (\(g_j(x_{j,i,t}|\boldsymbol{\theta})\)).

Let’s fit a model and look at the results.

library(mgcv)
fmla <- formula("obs ~ offset(log(population)) + 
                s(pid, bs = 're') +
                s(pre_season_rate, k = 3, m=2, bs = 'cr') +
                s(Jan_temp_grid, k = 3, m=2, bs = 'cr') +
                s(last_high_rate, k = 3, m=2, bs = 'cr')")
mod_nb <- gam(fmla, data = train_dat, family = nb())
summary(mod_nb)

Family: Negative Binomial(2.74) 
Link function: log 

Formula:
obs ~ offset(log(population)) + s(pid, bs = "re") + s(pre_season_rate, 
    k = 3, m = 2, bs = "cr") + s(Jan_temp_grid, k = 3, m = 2, 
    bs = "cr") + s(last_high_rate, k = 3, m = 2, bs = "cr")

Parametric coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept) -7.64116    0.03424  -223.1   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Approximate significance of smooth terms:
                      edf Ref.df Chi.sq  p-value    
s(pid)             50.403 75.000 144.64 2.07e-15 ***
s(pre_season_rate)  1.991  1.999 539.05  < 2e-16 ***
s(Jan_temp_grid)    1.953  1.990  22.20 3.04e-05 ***
s(last_high_rate)   1.774  1.944  23.23 1.75e-05 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

R-sq.(adj) =  0.776   Deviance explained = 73.5%
-REML = 6523.7  Scale est. = 1         n = 988

The random effects term is significant but notice that the degrees of freedom is fairly high relative to the number of provinces (76), indicating quite a bit of variation between the provinces and not a lot of shrinkage or pooling of information. It also looks like all of our chosen covariates are “highly significant”, but because these relationships are modeled as smooth functions of the covariates, there are no coefficients to interpret. What do these relationships look like?

par(mfrow=c(2,2))
plot(mod_nb)

We can also fit some reduced models to compare deviance:

mod_ref <- gam(obs ~ offset(log(population))+1, data = train_dat, family = nb)
mod_randint <- gam(obs ~ offset(log(population)) + s(pid, bs = 're'), data = train_dat, family = nb)
anova(mod_ref, mod_randint, mod_nb)
Analysis of Deviance Table

Model 1: obs ~ offset(log(population)) + 1
Model 2: obs ~ offset(log(population)) + s(pid, bs = "re")
Model 3: obs ~ offset(log(population)) + s(pid, bs = "re") + s(pre_season_rate, 
    k = 3, m = 2, bs = "cr") + s(Jan_temp_grid, k = 3, m = 2, 
    bs = "cr") + s(last_high_rate, k = 3, m = 2, bs = "cr")
  Resid. Df Resid. Dev      Df Deviance
1    987.00    1097.24                 
2    914.41    1018.17 72.5871   79.063
3    912.85     997.45  1.5616   20.719

And here is a Poisson model for comparison. It’s interesting to see how the Poisson model has quite a bit less uncertainty in the estimated relationships.

mod_pois <- gam(fmla, data = train_dat, family = poisson)
par(mfrow=c(2,2))
plot(mod_pois)

Obtaining predictive distribution

Originally, this model was fit with the primary goal of predicting incidence rather than describing relationships between variables. This of course serves the purpose of doing “predictive model checking”. In particular, we wanted to obtain predictive distributions of incidence. This turned out to be not a straight-forward process.

To obtain predictive distribution samples, we use a two-stage procedure to incorporate the uncertainty from our model parameter estimates and from the negative binomial distribution. We first draw 100 sample parameter sets from a multivariate normal distribution with mean equal to the point estimates of the parameters (\(\boldsymbol{\theta}, \mu, \sigma^2\)) from Equations ()-() and covariance equal to the matrix of standard errors. Each of these sampled parameter sets yields a corresponding \(\widehat{\lambda}_{i,t}\). We then draw 100 samples from the negative binomial distribution given in Equation () for each \(\widehat{\lambda}_{i,t}\) with the fixed estimate of \(r\) to obtain a sample of size 10,000 from the predictive distribution for \(Y_{i,t}\). We calculate the point estimate for each province-year, \(\widehat Y_{i,t}\), as the median of these samples from the predictive distribution. The lower and upper limits of the 80% prediction intervals were defined by taking the 10\(^{th}\) and 90\(^{th}\) percentiles of these samples from the predictive distribution.

Above, the predicted incidence is in black (with 80% prediction interval) and the red dots are the observed data. In the paper itself, there are similar graphics with comparisons to a simple model that just predicts the average incidence in a given year.

The 80% coverage for the two test seasons can be calculated as:

test_dat$coverage <- with(test_dat, (obs >= ci_80_lb) & (obs <= ci_80_ub))
sum(test_dat$coverage)/nrow(test_dat)
[1] 0.7236842
LS0tCnRpdGxlOiAiVXNpbmcgYSBOZWdhdGl2ZSBCaW5vbWlhbCBtb2RlbCB3aXRoIHNtb290aCBzcGxpbmVzIHRvIHByZWRpY3QgaW5mZWN0aW91cyBkaXNlYXNlIGNhc2UgY291bnRzIgphdXRob3I6ICJOaWNrIFJlaWNoLCBhZGFwdGVkIGZyb20gd29yayBieSBTdGV2ZSBMYXVlciBldCBhbCIKb3V0cHV0OiBodG1sX25vdGVib29rCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKQpgYGAKCiMjIEludHJvZHVjdGlvbgoKVGhpcyBhbmFseXNpcyBpcyBiYXNlZCBvbiByZXNlYXJjaCB0aGF0IGlzIFtwdWJsaWNseSBhdmFpbGFibGUgb24gR2l0SHViXShodHRwczovL2dpdGh1Yi5jb20vcmVpY2hsYWIvYW5udWFsLXByZWRpY3Rpb25zLXBhcGVyKS4gVGhlIGNvZGUgYW5kIGRlc2NyaXB0aW9uIGhhdmUgYmVlbiBhZGFwdGVkIGZvciB0aGlzIHdyaXRlLXVwLiBIZXJlIGlzIGFyZSBzb21lIGV4cG9zaXRvcnkgZXhjZXJwdHMgZnJvbSB0aGUgcGFwZXI6Cgo+IERlbmd1ZSwgYSBtb3NxdWl0by1ib3JuZSB2aXJ1cyBwcmV2YWxlbnQgdGhyb3VnaG91dCB0aGUgdHJvcGljcyBhbmQgc3ViLXRyb3BpY3MsIGluZmVjdHMgYW4gZXN0aW1hdGVkIDM5MCBtaWxsaW9uIHBlb3BsZSBldmVyeSB5ZWFyLgpXaGlsZSB0aGUgbWFqb3JpdHkgb2YgaW5mZWN0aW9ucyBhcmUgbWlsZCBvciBhc3ltcHRvbWF0aWMsIHRoZSBtb3JlIHNldmVyZSBmb3JtcyBvZiBkZW5ndWUgaW5mZWN0aW9uIC0tIGRlbmd1ZSBzaG9jayBzeW5kcm9tZSAoRFNTKSBhbmQgZGVuZ3VlIGhlbW9ycmhhZ2ljIGZldmVyIChESEYpIC0tIGNhbiByZXN1bHQgaW4gb3JnYW4gZmFpbHVyZSBvciBkZWF0aC4KCgo+IC4uLiBJbiBUaGFpbGFuZCwgZGVuZ3VlIGluZmVjdGlvbiBpcyBlbmRlbWljIHdpdGggc3Vic3RhbnRpYWwgYW5udWFsIGFuZCBnZW9ncmFwaGljIHZhcmlhdGlvbiBpbiBpbmNpZGVuY2UgYWNyb3NzIGl0cyA3NiBwcm92aW5jZXMgYW5kIDEzIGhlYWx0aCByZWdpb25zLiBPdmVyIHRoZSBwYXN0IDE1IHllYXJzLCBhbiBhdmVyYWdlIG9mIDQzLDEzNyAocmFuZ2UgMTQsOTUyLTEwNiwzMjApIERIRiBjYXNlcyBoYXZlIGJlZW4gcmVwb3J0ZWQgdG8gdGhlIFRoYWlsYW5kIE1pbmlzdHJ5IG9mIFB1YmxpYyBIZWFsdGggKE1PUEgpIGVhY2ggeWVhci4KV2l0aGluIGEgdHlwaWNhbCB5ZWFyLCBpbmNpZGVuY2UgcmF0ZXMgaW4gZGlmZmVyZW50IHByb3ZpbmNlcyBjYW4gdmFyeSBieSBhbiBvcmRlciBvZiBtYWduaXR1ZGUsIHdpdGggc29tZSBwcm92aW5jZXMgZXhwZXJpZW5jaW5nIGxlc3MgdGhhbiAxMCBESEYgY2FzZXMgcGVyIDEwMCwwMDAgcG9wdWxhdGlvbiBhbmQgb3RoZXJzIG92ZXIgMTAwIHBlciAxMDAsMDAwIHBvcHVsYXRpb24uCgo+IC4uLiBXaGV0aGVyIGFuIGluZmVjdGlvdXMgZGlzZWFzZSBzcHJlYWRzIHdpdGhpbiBhIHBvcHVsYXRpb24gZGVwZW5kcyBvbiB0aGUgdHJhbnNtaXNzaW9uIHJhdGUgb2YgdGhlIGRpc2Vhc2UgYW5kIHRoZSBudW1iZXIgb2Ygc3VzY2VwdGlibGUgaW5kaXZpZHVhbHMsIHRodXMgbG9uZy10ZXJtIGZvcmVjYXN0aW5nIG1vZGVscyBmb3IgREhGIGluY2lkZW5jZSBtYXkgbmVlZCB0byBhY2NvdW50IGZvciBjbGltYXRpYyBmYWN0b3JzIHRoYXQgY291bGQgYWZmZWN0IHRyYW5zbWlzc2lvbiBhcyB3ZWxsIGFzIHBvcHVsYXRpb24gc3VzY2VwdGliaWxpdHkuCkNsaW1hdGljIGZhY3RvcnMsIHN1Y2ggYXMgdGVtcGVyYXR1cmUsIHJhaW5mYWxsLCBhbmQgaHVtaWRpdHksIG1heSBpbXBhY3QgYm90aCB0aGUgcHJldmFsZW5jZSBhbmQgZGlzdHJpYnV0aW9uIG9mIHRoZSBkZW5ndWUgdmVjdG9yLCB0aGUgXHRleHRpdHtBZWRlc30gbW9zcXVpdG8sIGFzIHdlbGwgYXMgdGhlIHRyYW5zbWlzc2lvbiBlZmZpY2llbmN5IG9mIGRlbmd1ZSB2aXJ1cy4KU3VjaCBjbGltYXRpYyBmYWN0b3JzIHZhcnkgc2lnbmlmaWNhbnRseSBhY3Jvc3MgVGhhaWxhbmQuCgo+IC4uLiBXZSBvYnRhaW5lZCBkYXRhIG9uIERIRiBjYXNlcyAoZnJvbSB0aGUgTU9QSCksIHBvcHVsYXRpb24gKE5hdGlvbmFsIFN0YXRpc3RpY2FsIE9mZmljZSBvZiBUaGFpbGFuZCksIGFuZCB3ZWF0aGVyIChOT0FBKS4KVGhlc2UgZGF0YSB3ZXJlIHN1bW1hcml6ZWQgYWNyb3NzIHRpbWUgZnJhbWVzIHJhbmdpbmcgZnJvbSBvbmUgbW9udGggdG8gb25lIHllYXIgdG8gY3JlYXRlIGNvdmFyaWF0ZXMgZm9yIGNvbnNpZGVyYXRpb24gYnkgb3VyIG1vZGVsIHNlbGVjdGlvbiBhbGdvcml0aG0uIC4uLgpXZSBtYWRlIGZvcmVjYXN0cyB1c2luZyB0aGUgZGF0YSBhdmFpbGFibGUgaW4gQXByaWwgb2YgZWFjaCB5ZWFyLCB0aGUgbW9udGggd2hlbiB0aGUgTU9QSCBoYXMgaGlzdG9yaWNhbGx5IGZpbmFsaXplZCB0aGUgaW5jaWRlbmNlIHJlcG9ydHMgb2J0YWluZWQgZnJvbSBhbGwgcHJvdmluY2VzIGZvciB0aGUgcHJpb3IgY2FsZW5kYXIgeWVhci4KSGVuY2UsIGFsbCBgYGFubnVhbCcnIGZvcmVjYXN0cyBhcmUgZm9yIERIRiBpbmNpZGVuY2UgYmV0d2VlbiBBcHJpbCBhbmQgRGVjZW1iZXIgb2YgdGhlIHllYXIgdGhleSBhcmUgbWFkZS4KCj4gLi4uIEZvciB0aGUgTk9BQSBhbmQgTkNEQyB3ZWF0aGVyIHN0YXRpb24gZGF0YSwgd2UgZm91bmQgdGhlIG1vc3QgY29uc2lzdGVudGx5IHJlcG9ydGVkIHdlYXRoZXIgc3RhdGlvbiBmb3IgZWFjaCBwcm92aW5jZSBhbmQgZXh0cmFjdGVkIHRoZSBkYWlseSBtYXhpbXVtIGFuZCBtaW5pbXVtIHRlbXBlcmF0dXJlLCBtYXhpbXVtIGh1bWlkaXR5LCBhbmQgcmFpbmZhbGwuCldlIGFnZ3JlZ2F0ZWQgdGhlc2UgbWVhc3VyZXMgaW50byBtb250aGx5IGNvdmFyaWF0ZXMgZm9yIG1heGltdW0sIG1pbmltdW0sIGFuZCBtZWFuIHRlbXBlcmF0dXJlLCBtYXhpbXVtIGFuZCBtZWFuIGh1bWlkaXR5LCBhbmQgbWF4aW11bSBhbmQgdG90YWwgcmFpbmZhbGwgYWNyb3NzIHRoZSBsb3ctc2Vhc29uLCBKYW51YXJ5LCBGZWJydWFyeSwgYW5kIE1hcmNoLgoKCgojIyBUaGUgZGF0YQoKVGhlIGRhdGFzZXQgc2hvd24gaGVyZSBhbmQgcHJvdmlkZWQgaW4gdGhpcyByZXBvc2l0b3J5IGlzIHRoZSBzYW1lIG9uZSB1c2VkIGluIHRoZSBhbmFseXNpcy4gU29tZSBjb2x1bW5zIGhhdmUgYmVlbiByZW1vdmVkIGZvciBzaW1wbGljaXR5LiBIZXJlLCB3ZSBzcGxpdCB0aGUgZGF0YXNldCBpbnRvIGEgdHJhaW5pbmcgZGF0YXNldCBmcm9tIDIwMDAgdGhyb3VnaCAyMDEyIGFuZCBhIHRlc3RpbmcgZGF0YXNldCBmcm9tIDIwMTMgYW5kIDIwMTQuCgpgYGB7cn0KZGF0IDwtIHJlYWQuY3N2KCJkZW5ndWUtZGF0YS5jc3YiKQp0cmFpbl9kYXQgPC0gZmlsdGVyKGRhdCwgeWVhcjwyMDEzKSAjIyBhbGwgYnV0IDIwMTMgYW5kIDIwMTQKdGVzdF9kYXQgPC0gZmlsdGVyKGRhdCwgeWVhcj49MjAxMykKc3RyKHRyYWluX2RhdCkKYGBgCgoKYGBge3J9CmdncGxvdChkYXQsIGFlcyh4PXllYXIsIHk9cmVvcmRlcihwaWQsIHBvcHVsYXRpb24pLCBmaWxsPW9icy9wb3B1bGF0aW9uKSkgKyBnZW9tX3RpbGUoKSArIAogICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9ImdyZXkxMDAiLCBoaWdoPSJmaXJlYnJpY2siKSArIAogICAgeWxhYigiUHJvdmluY2UgKGJ5IHBvcHVsYXRpb24sIGxhcmdlIGF0IHRvcCkiKSArIHhsYWIoTlVMTCkgCmBgYAoKQmVmb3JlIHdlIGJlZ2luIG1vZGVsaW5nLCBsZXQncyBzZWUgaWYgdGhlcmUgaXMgZXZpZGVuY2UgdGhhdCB3ZSB3aWxsIG5lZWQgdG8gY29tcGVuc2F0ZSBmb3Igb3ZlcmRpc3BlcnNpb24gaW4gYSBQb2lzc29uIG1vZGVsLgoKYGBge3J9CmRpc3BfZGF0IDwtIHRyYWluX2RhdCAlPiUgCiAgICBncm91cF9ieShwaWQpICU+JQogICAgc3VtbWFyaXplKAogICAgICAgIGF2Z19pbmNfcmF0ZSA9IG1lYW4ob2JzKSwKICAgICAgICB2YXJfaW5jX3JhdGUgPSB2YXIob2JzKSwKICAgICAgICB2YXJfb3Zlcl9hdmcgPSB2YXJfaW5jX3JhdGUvYXZnX2luY19yYXRlCiAgICApCnF1YW50aWxlKGRpc3BfZGF0JHZhcl9vdmVyX2F2ZykKYGBgCgpUaGVyZSBzdXJlIGlzLiBUaGUgcGVyLXByb3ZpbmNlIHZhcmlhbmNlIHJhbmdlcyBmcm9tIDcgdG8gb3ZlciAxNTAwIHRpbWVzIHRoZSBhY2NvbXBhbnlpbmcgbWVhbi4KCgojIyBNb2RlbGluZwoKIyMjIE5lZ2F0aXZlIGJpbm9taWFsIG1vZGVsCgo+IFRoZSBtb2RlbCB0aGF0IHdlIHVzZWQgdG8gZm9yZWNhc3QgYW5udWFsIERIRiBpbmNpZGVuY2UgZm9yIHRoaXMgc3R1ZHkgaXMgYSBnZW5lcmFsaXplZCBhZGRpdGl2ZSBtb2RlbCBcY2l0ZXtIYXN0aWUyMDA5fS4KU3BlY2lmaWNhbGx5LCB3ZSB1c2UgYSBnZW5lcmFsaXplZCBhZGRpdGl2ZSBtb2RlbCB3aXRoIGEgbmVnYXRpdmUgYmlub21pYWwgZmFtaWx5LCBzZXBhcmF0ZSBwZW5hbGl6ZWQgc21vb3RoaW5nIHNwbGluZXMgZm9yIGVhY2ggY292YXJpYXRlLCBhbmQgcHJvdmluY2UtbGV2ZWwgcmFuZG9tIGVmZmVjdHM6CgokJCAgICBZX3tpLHR9IFxzaW0gXHRleHR7TkJ9KG5fe2ksdH1cbGFtYmRhX3tpLHR9LCByKSAkJAokJCAgICBcbG9nIFxCaWcgWyBcbWF0aGJme0V9KFlfe2ksdH0pIFxCaWcgXSA9IFxiZXRhXzAgKyBcbG9nKG5fe2ksdH0pICsgXGFscGhhX2kgKyBcc3VtX3tqPTF9XkogZ19qKHhfe2osaSx0fXxcYm9sZHN5bWJvbHtcdGhldGF9KSAkJAokJCAgICAgXGFscGhhX2kgXHNpbSBcdGV4dHtOb3JtYWx9KFxtdSwgXHNpZ21hXjIpICQkCgo+IFdlIG1vZGVsIHRoZSBpbmNpZGVuY2UgKCRZX3tpLHR9JCkgZm9yIHByb3ZpbmNlICRpJCBpbiB5ZWFyICR0JCBhcyBmb2xsb3dpbmcgYSBuZWdhdGl2ZSBiaW5vbWlhbCBkaXN0cmlidXRpb24gd2l0aCB0aGUgbWVhbiBlcXVhbCB0byB0aGUgcHJvdmluY2UgcG9wdWxhdGlvbiAoJG5fe2ksdH0kKSB0aW1lcyB0aGUgaW5jaWRlbmNlIHJhdGUgKCRcbGFtYmRhX3tpLHR9JCkgYW5kIGEgZGlzcGVyc2lvbiBwYXJhbWV0ZXIgJHIkLgpBZnRlciBhIGxvZyB0cmFuc2Zvcm1hdGlvbiwgd2UgbW9kZWwgdGhlIG1lYW4gb2YgdGhpcyBkaXN0cmlidXRpb24gdXNpbmcgYW4gaW50ZXJjZXB0ICgkXGJldGFfMCQpLCBhIHJhbmRvbSBlZmZlY3QgZm9yIGVhY2ggcHJvdmluY2UgKCRcYWxwaGFfaSQpIGFuZCBhIGN1YmljIHNwbGluZSBmb3IgZWFjaCBvZiAkSiQgY292YXJpYXRlcyAoJGdfaih4X3tqLGksdH18XGJvbGRzeW1ib2x7XHRoZXRhfSkkKS4KCkxldCdzIGZpdCBhIG1vZGVsIGFuZCBsb29rIGF0IHRoZSByZXN1bHRzLgpgYGB7cn0KbGlicmFyeShtZ2N2KQpmbWxhIDwtIGZvcm11bGEoIm9icyB+IG9mZnNldChsb2cocG9wdWxhdGlvbikpICsgCiAgICAgICAgICAgICAgICBzKHBpZCwgYnMgPSAncmUnKSArCiAgICAgICAgICAgICAgICBzKHByZV9zZWFzb25fcmF0ZSwgayA9IDMsIG09MiwgYnMgPSAnY3InKSArCiAgICAgICAgICAgICAgICBzKEphbl90ZW1wX2dyaWQsIGsgPSAzLCBtPTIsIGJzID0gJ2NyJykgKwogICAgICAgICAgICAgICAgcyhsYXN0X2hpZ2hfcmF0ZSwgayA9IDMsIG09MiwgYnMgPSAnY3InKSIpCm1vZF9uYiA8LSBnYW0oZm1sYSwgZGF0YSA9IHRyYWluX2RhdCwgZmFtaWx5ID0gbmIoKSkKc3VtbWFyeShtb2RfbmIpCmBgYAoKVGhlIHJhbmRvbSBlZmZlY3RzIHRlcm0gaXMgc2lnbmlmaWNhbnQgYnV0IG5vdGljZSB0aGF0IHRoZSBkZWdyZWVzIG9mIGZyZWVkb20gaXMgZmFpcmx5IGhpZ2ggcmVsYXRpdmUgdG8gdGhlIG51bWJlciBvZiBwcm92aW5jZXMgKDc2KSwgaW5kaWNhdGluZyBxdWl0ZSBhIGJpdCBvZiB2YXJpYXRpb24gYmV0d2VlbiB0aGUgcHJvdmluY2VzIGFuZCBub3QgYSBsb3Qgb2Ygc2hyaW5rYWdlIG9yIHBvb2xpbmcgb2YgaW5mb3JtYXRpb24uCkl0IGFsc28gbG9va3MgbGlrZSBhbGwgb2Ygb3VyIGNob3NlbiBjb3ZhcmlhdGVzIGFyZSAiaGlnaGx5IHNpZ25pZmljYW50IiwgYnV0IGJlY2F1c2UgdGhlc2UgcmVsYXRpb25zaGlwcyBhcmUgbW9kZWxlZCBhcyBzbW9vdGggZnVuY3Rpb25zIG9mIHRoZSBjb3ZhcmlhdGVzLCB0aGVyZSBhcmUgbm8gY29lZmZpY2llbnRzIHRvIGludGVycHJldC4gV2hhdCBkbyB0aGVzZSByZWxhdGlvbnNoaXBzIGxvb2sgbGlrZT8KCmBgYHtyfQpwYXIobWZyb3c9YygyLDIpKQpwbG90KG1vZF9uYikKYGBgCgpXZSBjYW4gYWxzbyBmaXQgc29tZSByZWR1Y2VkIG1vZGVscyB0byBjb21wYXJlIGRldmlhbmNlOgpgYGB7cn0KbW9kX3JlZiA8LSBnYW0ob2JzIH4gb2Zmc2V0KGxvZyhwb3B1bGF0aW9uKSkrMSwgZGF0YSA9IHRyYWluX2RhdCwgZmFtaWx5ID0gbmIpCm1vZF9yYW5kaW50IDwtIGdhbShvYnMgfiBvZmZzZXQobG9nKHBvcHVsYXRpb24pKSArIHMocGlkLCBicyA9ICdyZScpLCBkYXRhID0gdHJhaW5fZGF0LCBmYW1pbHkgPSBuYikKYW5vdmEobW9kX3JlZiwgbW9kX3JhbmRpbnQsIG1vZF9uYikKYGBgCgpBbmQgaGVyZSBpcyBhIFBvaXNzb24gbW9kZWwgZm9yIGNvbXBhcmlzb24uIEl0J3MgaW50ZXJlc3RpbmcgdG8gc2VlIGhvdyB0aGUgUG9pc3NvbiBtb2RlbCBoYXMgcXVpdGUgYSBiaXQgbGVzcyB1bmNlcnRhaW50eSBpbiB0aGUgZXN0aW1hdGVkIHJlbGF0aW9uc2hpcHMuCgpgYGB7cn0KbW9kX3BvaXMgPC0gZ2FtKGZtbGEsIGRhdGEgPSB0cmFpbl9kYXQsIGZhbWlseSA9IHBvaXNzb24pCnBhcihtZnJvdz1jKDIsMikpCnBsb3QobW9kX3BvaXMpCmBgYAoKCiMjIE9idGFpbmluZyBwcmVkaWN0aXZlIGRpc3RyaWJ1dGlvbgoKT3JpZ2luYWxseSwgdGhpcyBtb2RlbCB3YXMgZml0IHdpdGggdGhlIHByaW1hcnkgZ29hbCBvZiBwcmVkaWN0aW5nIGluY2lkZW5jZSByYXRoZXIgdGhhbiBkZXNjcmliaW5nIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiB2YXJpYWJsZXMuIFRoaXMgb2YgY291cnNlIHNlcnZlcyB0aGUgcHVycG9zZSBvZiBkb2luZyAicHJlZGljdGl2ZSBtb2RlbCBjaGVja2luZyIuIEluIHBhcnRpY3VsYXIsIHdlIHdhbnRlZCB0byBvYnRhaW4gcHJlZGljdGl2ZSBkaXN0cmlidXRpb25zIG9mIGluY2lkZW5jZS4gVGhpcyB0dXJuZWQgb3V0IHRvIGJlIG5vdCBhIHN0cmFpZ2h0LWZvcndhcmQgcHJvY2Vzcy4KCj4gVG8gb2J0YWluIHByZWRpY3RpdmUgZGlzdHJpYnV0aW9uIHNhbXBsZXMsIHdlIHVzZSBhIHR3by1zdGFnZSBwcm9jZWR1cmUgdG8gaW5jb3Jwb3JhdGUgdGhlIHVuY2VydGFpbnR5IGZyb20gb3VyIG1vZGVsIHBhcmFtZXRlciBlc3RpbWF0ZXMgYW5kIGZyb20gdGhlIG5lZ2F0aXZlIGJpbm9taWFsIGRpc3RyaWJ1dGlvbi4KV2UgZmlyc3QgZHJhdyAxMDAgc2FtcGxlIHBhcmFtZXRlciBzZXRzIGZyb20gYSBtdWx0aXZhcmlhdGUgbm9ybWFsIGRpc3RyaWJ1dGlvbiB3aXRoIG1lYW4gZXF1YWwgdG8gdGhlIHBvaW50IGVzdGltYXRlcyBvZiB0aGUgcGFyYW1ldGVycyAoJFxib2xkc3ltYm9se1x0aGV0YX0sIFxtdSwgXHNpZ21hXjIkKSBmcm9tIEVxdWF0aW9ucyAoXHJlZntlcW46cmVncmVzc2lvbn0pLShccmVme2VxbjpSRX0pIGFuZCBjb3ZhcmlhbmNlIGVxdWFsIHRvIHRoZSBtYXRyaXggb2Ygc3RhbmRhcmQgZXJyb3JzLgpFYWNoIG9mIHRoZXNlIHNhbXBsZWQgcGFyYW1ldGVyIHNldHMgeWllbGRzIGEgY29ycmVzcG9uZGluZyAkXHdpZGVoYXR7XGxhbWJkYX1fe2ksdH0kLgpXZSB0aGVuIGRyYXcgMTAwIHNhbXBsZXMgZnJvbSB0aGUgbmVnYXRpdmUgYmlub21pYWwgZGlzdHJpYnV0aW9uIGdpdmVuIGluIEVxdWF0aW9uIChccmVme2VxbjpOQn0pIGZvciBlYWNoICRcd2lkZWhhdHtcbGFtYmRhfV97aSx0fSQgd2l0aCB0aGUgZml4ZWQgZXN0aW1hdGUgb2YgJHIkIHRvIG9idGFpbiBhIHNhbXBsZSBvZiBzaXplIDEwLDAwMCBmcm9tIHRoZSBwcmVkaWN0aXZlIGRpc3RyaWJ1dGlvbiBmb3IgJFlfe2ksdH0kLgpXZSBjYWxjdWxhdGUgdGhlIHBvaW50IGVzdGltYXRlIGZvciBlYWNoIHByb3ZpbmNlLXllYXIsICRcd2lkZWhhdCBZX3tpLHR9JCwgYXMgdGhlIG1lZGlhbiBvZiB0aGVzZSBzYW1wbGVzIGZyb20gdGhlIHByZWRpY3RpdmUgZGlzdHJpYnV0aW9uLgpUaGUgbG93ZXIgYW5kIHVwcGVyIGxpbWl0cyBvZiB0aGUgODBcJSBwcmVkaWN0aW9uIGludGVydmFscyB3ZXJlIGRlZmluZWQgYnkgdGFraW5nIHRoZSAxMCRee3RofSQgYW5kIDkwJF57dGh9JCBwZXJjZW50aWxlcyBvZiB0aGVzZSBzYW1wbGVzIGZyb20gdGhlIHByZWRpY3RpdmUgZGlzdHJpYnV0aW9uLgoKYGBge3J9CiMnIFByZWRpY3Rpb24gaW50ZXJ2YWxzIGZvciBuZWdhdGl2ZSBiaW5vbWlhbCBHQU0gcmVncmVzc2lvbgojJyBSYW5kb21seSBzYW1wbGVzIHRoZSBtdWx0aXZhcmlhdGUgbm9ybWFsIGRpc3RyaWJ1dGlvbiBvZiB0aGUgY29lZmZpY2llbnQgcGFyYW1ldGVycyBhbmQgZHJhd3MgZnJvbSBhIG5lZ2F0aXZlIGJpbm9taWFsIGRpc3RyaWJ1dGlvbiB0byBjcmVhdGUgcHJlZGljdGlvbiBpbnRlcnZhbHMgZm9yIGEgR0FNIHNwbGluZSBvZiB0aGUgbmVnYXRpdmUgYmlub21pYWwgZmFtaWx5LgojJwojJwojJyBAcGFyYW0gdHJhaW5fZml0IGdhbSBvYmplY3QgZnJvbSBtZ2N2J3MgZ2FtKCksIGNvbmR1Y3RlZCB3aXRoIGZhbWlseSA9IG5iKCkKIycgQHBhcmFtIHRlc3RfZGF0IGRhdGEgdG8gbWFrZSBwcmVkaWN0aW9ucyBvbgojJyBAcGFyYW0gY29lZl9wZXJtcyBudW1iZXIgb2Ygbm9ybWFsbHkgZGlzdHJpYnV0ZWQgY29lZmZpY2llbnQgcGVybXV0YXRpb25zCiMnIEBwYXJhbSBuYl9kcmF3cyBudW1iZXIgb2YgZHJhd3MgZnJvbSBhIG5lZ2F0aXZlIGJpbm9taWFsIGZvciBlYWNoIGNvZWZmaWNpZW50IHBlcm11dGF0aW9uCnByZWRfZGlzdCA8LSBmdW5jdGlvbih0cmFpbl9maXQsCiAgICAgICAgICAgICAgICAgICAgICB0ZXN0X2RhdCwKICAgICAgICAgICAgICAgICAgICAgIGNvZWZfcGVybXMsCiAgICAgICAgICAgICAgICAgICAgICBuYl9kcmF3cyl7CiAgICByZXF1aXJlKG1nY3YpCiAgICBpZihtaXNzaW5nKG5iX2RyYXdzKSkKICAgICAgICBuYl9kcmF3cyA8LSBjb2VmX3Blcm1zCiAgICAjIyBnZXQgdGhlIG1lYW4gYW5kIGNvdmFyaWFuY2UgbWF0cml4IGZvciBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdGluZyBkYXRhCiAgICB0ZXN0X3ByZWRzIDwtIHByZWRpY3QodHJhaW5fZml0LCB0ZXN0X2RhdCwgdHlwZSA9ICJscG1hdHJpeCIpCiAgICAjIyByZXBsaWNhdGUgcGFyYW1ldGVyIHZlY3RvcnMKICAgIHJlcF9wYXJhbXMgPC0gcm12bihjb2VmX3Blcm1zLGNvZWYodHJhaW5fZml0KSx0cmFpbl9maXQkVnApCiAgICBwcmVkX2NvdW50IDwtIG1hdHJpeCgwLCBucm93PW5yb3codGVzdF9kYXQpLCBuY29sPWNvZWZfcGVybXMpCiAgICAjIyByZXBsaWNhdGUgcHJlZGljdGlvbnMgZm9yIGVhY2ggc2V0IG9mIHBhcmFtZXRlcnMKICAgIGZvciAoaSBpbiAxOmNvZWZfcGVybXMpIHsKICAgICAgICByZXBfcHJlZHMgPC0gdGVzdF9wcmVkcyAlKiUgcmVwX3BhcmFtc1tpLF0KICAgICAgICBwcmVkX2NvdW50WyxpXSA8LSBleHAocmVwX3ByZWRzKSp0ZXN0X2RhdCRwb3B1bGF0aW9uCiAgICB9CiAgICAjIyBmaW5kIHRoZSBkaXNwZXJzaW9uIHBhcmFtZXRlciBmb3IgdGhlIG5lZ2F0aXZlIGJpbm9taWFsCiAgICByIDwtIHRyYWluX2ZpdCRmYW1pbHkkZ2V0VGhldGEodHJhbnM9VFJVRSkKICAgICMjIHNhbXBsZSBmcm9tIHRoZSBuZWdhdGl2ZSBiaW5vbWlhbCBkaXN0cmlidXRpb24KICAgIGFsbF9wcmVkcyA8LSBtYXRyaXgocm5iaW5vbShucm93KHByZWRfY291bnQpKmNvZWZfcGVybXMqbmJfZHJhd3MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXU9YXMudmVjdG9yKHByZWRfY291bnQpLCBzaXplPXIpLAogICAgICAgICAgICAgICAgICAgICAgICBucm93PW5yb3codGVzdF9kYXQpKSAlPiUKICAgICAgICBhcy5kYXRhLmZyYW1lKCkKICAgIHJldHVybihhbGxfcHJlZHMpCn0KcHJlZHMgPC0gcHJlZF9kaXN0KHRyYWluX2ZpdD1tb2RfbmIsCiAgICB0ZXN0X2RhdD10ZXN0X2RhdCwKICAgIGNvZWZfcGVybXM9MTAwLAogICAgbmJfZHJhd3M9MTApCgp0ZXN0X2RhdCRwb2ludF9lc3QgPC0gYXBwbHkocHJlZHMsIE1BUj0xLCBGVU49bWVkaWFuKQp0ZXN0X2RhdCRjaV84MF9sYiA8LSBhcHBseShwcmVkcywgTUFSPTEsIEZVTj1mdW5jdGlvbih4KSBxdWFudGlsZSh4LCAuMSkpCnRlc3RfZGF0JGNpXzgwX3ViIDwtIGFwcGx5KHByZWRzLCBNQVI9MSwgRlVOPWZ1bmN0aW9uKHgpIHF1YW50aWxlKHgsIC45KSkKdGVzdF9kYXQkcGlkIDwtIHJlb3JkZXIodGVzdF9kYXQkcGlkLCB0ZXN0X2RhdCRwb3B1bGF0aW9uKQoKIyBwcmVkaWN0aW9uX2RhdCA8LSBkYXRhX2ZyYW1lKHllYXIgPSB0ZXN0X2RhdCR5ZWFyLAojICAgICBwaWQgPSB0ZXN0X2RhdCRwaWQsCiMgICAgIG9ic19jb3VudHMgPSB0ZXN0X2RhdCRvYnMpICU+JQojICAgICBiaW5kX2NvbHMocHJlZHMpICU+JQojICAgICBnYXRoZXIoInNpbSIsICJwcmVkX2NvdW50cyIsIHN0YXJ0c193aXRoKCJWIikpICU+JQojICAgICBmaWx0ZXIoIWlzLm5hKHByZWRfY291bnRzKSkKCmdncGxvdCh0ZXN0X2RhdCwgYWVzKHg9cGlkKSkgKyAKICAgIGdlb21fc2VnbWVudChhZXMoeGVuZD1waWQsIHk9Y2lfODBfbGIsIHllbmQ9Y2lfODBfdWIpLCBhbHBoYT0uNSwgc2l6ZT0xKSArCiAgICBnZW9tX3BvaW50KGFlcyh5PXBvaW50X2VzdCkpICsgCiAgICBnZW9tX3BvaW50KGFlcyh5PW9icyksIGNvbG9yPSJyZWQiLCBzaGFwZT0ieCIpICsKICAgIHNjYWxlX3lfbG9nMTAoKSArIHlsYWIoImluY2lkZW5jZSIpICsKICAgIHhsYWIoInByb3ZpbmNlIChieSBwb3B1bGF0aW9uIHNpemUpIikgKyBzY2FsZV94X2Rpc2NyZXRlKGxhYmVsPU5VTEwpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBmYWNldF9ncmlkKC5+eWVhcikKCmBgYAoKQWJvdmUsIHRoZSBwcmVkaWN0ZWQgaW5jaWRlbmNlIGlzIGluIGJsYWNrICh3aXRoIDgwJSBwcmVkaWN0aW9uIGludGVydmFsKSBhbmQgdGhlIHJlZCBkb3RzIGFyZSB0aGUgb2JzZXJ2ZWQgZGF0YS4gSW4gdGhlIHBhcGVyIGl0c2VsZiwgdGhlcmUgYXJlIHNpbWlsYXIgZ3JhcGhpY3Mgd2l0aCBjb21wYXJpc29ucyB0byBhIHNpbXBsZSBtb2RlbCB0aGF0IGp1c3QgcHJlZGljdHMgdGhlIGF2ZXJhZ2UgaW5jaWRlbmNlIGluIGEgZ2l2ZW4geWVhci4KClRoZSA4MCUgY292ZXJhZ2UgZm9yIHRoZSB0d28gdGVzdCBzZWFzb25zIGNhbiBiZSBjYWxjdWxhdGVkIGFzOgpgYGB7cn0KdGVzdF9kYXQkY292ZXJhZ2UgPC0gd2l0aCh0ZXN0X2RhdCwgKG9icyA+PSBjaV84MF9sYikgJiAob2JzIDw9IGNpXzgwX3ViKSkKc3VtKHRlc3RfZGF0JGNvdmVyYWdlKS9ucm93KHRlc3RfZGF0KQpgYGAKCgo=