Instructions

Do exercises 5.1, 5.2, 5.3, 5.4 and 5.7 in the Hyndman book. Please submit your Rpubs link as well as your .pdf file showing your run code.

library(dplyr)
library(stringr)
library(fpp3)
library(cowplot)

5.1

Produce forecasts for the following series using whichever of NAIVE(y), SNAIVE(y) or RW(y ~ drift()) is more appropriate in each case:

i

Australian Population (global_economy)

df_aus <- global_economy %>%
            filter(Country == "Australia")

head(df_aus)
 aus_plot1 <- df_aus%>%
  autoplot(Population)+
  labs(title= "Australian Population")+
  annotate("text", x = Inf, y = -Inf, hjust = 1, vjust = -1,
           label = "There is an upward trend")

aus_fit <- df_aus %>%
            # no filter needed
            model(RW(Population ~ drift()))

aus_fc <- aus_fit %>%
            forecast(h = 10)

aus_plot2<- aus_fc %>% 
  autoplot(df_aus)



plot_grid(aus_plot1, aus_plot2, ncol = 2)

Alternatively the plotting can be done with one function

df_aus%>%
  model(RW(Population ~ drift()))%>%
    forecast(h = 10)%>%
      autoplot(df_aus)

In section 5.2 Drift method is explained to “allow the forecasts to increase or decrease over time”. Since the data did not show high seasonality and was not economic or financial, I did not use Naïve method and seasonal naïve. The example shown also uses random walk forecast in conjunction with drift (refer below)

                    bricks |> model(RW(Bricks ~ drift()))

ii

Bricks (aus_production)

head(aus_production)
brick_plot1<-aus_production%>%
                autoplot(Bricks)+
                labs(title= "Bricks Production")+
  annotate("text", x = Inf, y = -Inf, hjust = 1, vjust = -1,
           label = "There is obvious seasonality but not trend")

brick_plot2<-aus_production%>%
# Warning: Removed 20 rows containing missing values (`geom_line()`).
# therefore filter is added
  filter(!is.na(Bricks))%>%
  model(SNAIVE(Bricks~lag("year")))%>%
    forecast(h = 10)%>%
      autoplot(aus_production)+
# aus_production added twice to ensure visual has full plot
  labs(title= "Bricks Production Forecast")

plot_grid(brick_plot1,brick_plot2, ncol = 1)
Warning: Removed 20 rows containing missing values (`geom_line()`).Warning: Removed 20 rows containing missing values (`geom_line()`).

There was obvious seasonality despite the fact that annually there was no trend. Regardless SNAIVE(y) was used as it seemed to be the best fit.

iii

NSW Lambs (aus_livestock)

cat(paste(unique(aus_livestock$State), collapse = "\n"))
Australian Capital Territory
New South Wales
Northern Territory
Queensland
South Australia
Tasmania
Victoria
Western Australia
df_lambs<-aus_livestock%>%
  filter(State == "New South Wales", str_detect(Animal,"Lambs"))
head(df_lambs)
lambs_plot1 <- df_lambs%>%
                autoplot()+
                labs(title= "New South Wales Count")+
  annotate("text", x = Inf, y = -Inf, hjust = 1, vjust = -1,
           label = "There is no obvious seasonality or trend")
Plot variable not specified, automatically selected `.vars = Count`
lambs_plot2<-df_lambs%>%
   model(NAIVE(Count))%>%
     forecast(h = 10)%>%
       autoplot(df_lambs)+
   labs(title= "New South Wales Forecast")

No clear seasonality or trend so NAIVE(y) was most appropriate

iv

Household wealth (hh_budget).

head(hh_budget)
wealth_plot1<-hh_budget%>%
                autoplot(Wealth, show.legend= FALSE)+
                  facet_grid(Country~., scales = "free", space = "free_y")

wealth_plot2<- hh_budget%>%
                model(RW(Wealth~drift()))%>%
                  forecast(h=5)%>%
                    autoplot(hh_budget)

I genuinely considered the NAIVE(y) model, because the data was a time series regarding finance. I also debated the seasonal aspect and considered SNAIVE(y), noting the dip in wealth at certain intervals for certain countries. However, I think the trend is primarily upward, with exception of years that tie in with the recent recessions, therefore RW(y ~ drift()) or Drift method was used.

v

Australian takeaway food turnover (aus_retail).

cat(paste(unique(aus_retail$State), collapse = "\n"))
Australian Capital Territory
New South Wales
Northern Territory
Queensland
South Australia
Tasmania
Victoria
Western Australia
head(aus_retail)
aus_retail%>%
  filter(str_detect(Industry,"takeaway"))%>%
  autoplot(Turnover)+
  scale_color_discrete(name = "State", labels = unique(aus_retail$State))+
  labs(title = "Turnover (Australian takeaway) by State")

aus_retail %>%
  filter(str_detect(Industry,"takeaway")) %>%
  model(RW(Turnover ~ drift())) %>%
  forecast(h = 10) %>%
  autoplot(aus_retail)+
   facet_wrap(~State, scales = "free")

Just like the

          Australian Population (`global_economy`)
          

The data for all states showed an upward trend. So modeling each state using the Drift method made the mose sense.

5.2

Use the Facebook stock price (data set gafa_stock) to do the following:

a.

Produce a time plot of the series.

cat(paste(unique(gafa_stock$Symbol), collapse = "\n"))
AAPL
AMZN
FB
GOOG
distinct(gafa_stock, year = lubridate::year(Date))
df_fb <- gafa_stock %>%
  filter(Symbol == "FB")

head(df_fb)
# Re-index based on trading days
FB_stock <- df_fb %>%
# already filtered
  mutate(day = row_number()) %>%
  update_tsibble(index = day, regular = TRUE)

FB_stock%>%
  autoplot(Close)+
  labs(y = '$US', title = 'The Facebook Daily Closing Stock Price')

NA
NA

b.

Produce forecasts using the drift method and plot them.

AS PER Example: Google’s daily closing stock price

# Filter the year of interest
FB_2015 <- FB_stock %>% filter(year(Date) == 2015)
# Fit the models
FB_fit <- FB_2015 |>
  model(
    Drift = NAIVE(Close ~ drift())
  )
# Produce forecasts for the trading days in January 2016
FB_jan_2016 <- FB_stock |>
  filter(yearmonth(Date) == yearmonth("2016 Jan"))
FB_fc <- FB_fit |>
  forecast(new_data = FB_jan_2016)
# Plot the forecasts
FB_fc |>
  autoplot(FB_2015, level = NULL) +
  autolayer(FB_jan_2016, Close, colour = "black") +
  labs(y = "$US",
       title = "Facebook daily closing stock prices",
       subtitle = "(Jan 2015 - Jan 2016)") +
  guides(colour = guide_legend(title = "Forecast"))

c.

Show that the forecasts are identical to extending the line drawn between the first and last observations.

FB_fc%>% 
  autoplot(FB_2015, level = NULL) +
  geom_line(data = slice(FB_2015, range(cumsum(!is.na(Close)))),
                         aes(y=Close), linetype = 'dashed')

d.

Try using some of the other benchmark functions to forecast the same data set. Which do you think is best? Why?

FB_fit2 <- FB_2015 %>%
  model(
    Mean = MEAN(Close),
    Naive = NAIVE(Close)
  )
# to make the forecasts for the trading days in January 2016
FB_jan_2016 <- FB_stock %>%
  filter(yearmonth(Date) == yearmonth("2016 Jan"))

FB_fc2 <- FB_fit2 %>%
  forecast(new_data = FB_jan_2016)
# Plotting
FB_fc2 %>%
  autoplot(FB_2015, level = NULL) +
  autolayer(FB_jan_2016, Close, colour = "green") +
  labs(y = "$USD",
       title = "FB  Closing Stock Prices (Daily)",
       subtitle = "(Jan 2015 - Jan 2016)") +
  guides(colour = guide_legend(title = "The Forecast"))

Naive I believe is the most accurate, b/c there is no seasonality and Naive works best with financial data according to the textbook

        "This method works remarkably well for many economic and financial time series."

5.3

Apply a seasonal naïve method to the quarterly Australian beer production data from 1992. Check if the residuals look like white noise, and plot the forecasts. The following code will help.

# Extract data of interest
recent_production <- aus_production |>
  filter(year(Quarter) >= 1992)
# Define and estimate a model
fit <- recent_production |> model(SNAIVE(Beer))
# Look at the residuals
fit |> gg_tsresiduals()

# Look a some forecasts
fit |> forecast() |> autoplot(recent_production)

What do you conclude?

I don’t believe this is white noise. This does not appear to be an unpredictable sequence of numbers, rather it seems to have a constant variance, and .resid that centers around 0. For the lag value of 4 in the acf plot, it is larger than normal but also follows the occurrence every 4 quarters. So the model can be improved, it is not likely white noise and therefore seasonal naïve method is valid.

5.4

Repeat the previous exercise using the Australian Exports series from global_economy and the Bricks series from aus_production. Use whichever of NAIVE() or SNAIVE() is more appropriate in each case.

** Note**: df_aus established in 5.1 for Australian exports

i

Australian Exports series from global_economy

# Define and estimate a model
df_aus_fit <- df_aus %>% 
  model(NAIVE(Exports))

# Look at the residuals
df_aus_fit %>% 
  gg_tsresiduals()


# Look at some forecasts
df_aus_fit %>% 
  forecast() %>% autoplot(df_aus)

aus_aug |> features(.innov, box_pierce, lag = 10)

aus_aug |> features(.innov, ljung_box, lag = 10)
NA

What do you conclude?

BP p-value of 0.1481135 and LB p-value of 0.08963678 suggests there’s not significant autocorrelation, since they’re both greater than 0.05. There’s also constant variation. I cannot differentiate this from white noise and I’m unsure if the model could be improved.

ii

# Define and estimate a model
aus_prod_fit <- aus_production %>% 
  model(NAIVE(Bricks))

# Look at the residuals
aus_prod_fit %>% 
  gg_tsresiduals()


# Look at some forecasts
aus_prod_fit %>% 
  forecast() %>% autoplot(aus_production)

aus_prod_aug<-aus_prod_fit%>%
          augment()

aus_prod_aug |> features(.innov, box_pierce, lag = 10)

aus_prod_aug |> features(.innov, ljung_box, lag = 10)
NA

The acf being consistently past the dotted line suggests significant periodic autocorrelation and seasonality. The evidence of autocorrelation is supported by the BP and LB statistic.

5.7

For your retail time series (from Exercise 7 in Section 2.10):

a.

Create a training dataset consisting of observations before 2011 using

set.seed(123)


myseries <- aus_retail %>%
  filter(`Series ID` == sample(aus_retail$`Series ID`,1))

myseries_train <- myseries |>
  filter(year(Month) < 2011)

b.

Check that your data have been split appropriately by producing the following plot.

autoplot(myseries, Turnover) +
  autolayer(myseries_train, Turnover, colour = "red")

c. 

Fit a seasonal naïve model using SNAIVE() applied to your training data (myseries_train).

fit <- myseries_train |>
  model(SNAIVE(Turnover))

d. 

Check the residuals.

fit |> gg_tsresiduals()

Do the residuals appear to be uncorrelated and normally distributed?

The data appears to be correlated, but have a constant variation changing from positive to negative at lag 10 indicating heteroscedasticity. I also does not appear to be normally distributed or center around 0.

e.

Produce forecasts for the test data

fc <- fit |>
  forecast(new_data = anti_join(myseries, myseries_train))
Joining with `by = join_by(State, Industry, `Series ID`, Month, Turnover)`
fc |> autoplot(myseries)

f.

Compare the accuracy of your forecasts against the actual values.

fit |> accuracy()
fc |> accuracy(myseries)
NA

The forecast models does not perform well on the test data and the errors for training seem smaller in comparison.

g.

How sensitive are the accuracy measures to the amount of training data used?

The measures are highly sensitive to the quantity of data involved, which may be negatively impacted the measures.

LS0tDQp0aXRsZTogJ0RBVEEgNjI0OiBQUkVESUNUSVZFIEFOQUxZVElDUyBIVzMnDQphdXRob3I6ICJHYWJyaWVsIENhbXBvcyINCmRhdGU6ICJMYXN0IGVkaXRlZCBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBnZW9tZXRyeTogbGVmdD0wLjVjbSxyaWdodD0wLjVjbSx0b3A9MWNtLGJvdHRvbT0yY20NCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgcGRmX2RvY3VtZW50Og0KICAgIGxhdGV4X2VuZ2luZTogeGVsYXRleA0KdXJsY29sb3I6IGJsdWUNCi0tLQ0KDQojIEluc3RydWN0aW9ucw0KDQpEbyBleGVyY2lzZXMgNS4xLCA1LjIsIDUuMywgNS40IGFuZCA1LjcgaW4gdGhlIEh5bmRtYW4gYm9vay4gIFBsZWFzZSBzdWJtaXQgeW91ciBbUnB1YnMgbGlua10oaHR0cHM6Ly9ycHVicy5jb20vZ2NhbXBvczEwMC9EQVRBXzYyNF9IVzMpIGFzIHdlbGwgYXMgeW91ciAucGRmIGZpbGUgc2hvd2luZyB5b3VyIHJ1biBjb2RlLg0KDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkoZnBwMykNCmxpYnJhcnkoY293cGxvdCkNCmBgYA0KDQojIDUuMQ0KDQpQcm9kdWNlIGZvcmVjYXN0cyBmb3IgdGhlIGZvbGxvd2luZyBzZXJpZXMgdXNpbmcgd2hpY2hldmVyIG9mIGBOQUlWRSh5KWAsIGBTTkFJVkUoeSlgIG9yIGBSVyh5IH4gZHJpZnQoKSlgIGlzIG1vcmUgYXBwcm9wcmlhdGUgaW4gZWFjaCBjYXNlOg0KDQojIyBpDQoNCkF1c3RyYWxpYW4gUG9wdWxhdGlvbiAoYGdsb2JhbF9lY29ub215YCkNCg0KYGBge3J9DQpkZl9hdXMgPC0gZ2xvYmFsX2Vjb25vbXkgJT4lDQogICAgICAgICAgICBmaWx0ZXIoQ291bnRyeSA9PSAiQXVzdHJhbGlhIikNCg0KaGVhZChkZl9hdXMpDQpgYGANCg0KYGBge3J9DQogYXVzX3Bsb3QxIDwtIGRmX2F1cyU+JQ0KICBhdXRvcGxvdChQb3B1bGF0aW9uKSsNCiAgbGFicyh0aXRsZT0gIkF1c3RyYWxpYW4gUG9wdWxhdGlvbiIpKw0KICBhbm5vdGF0ZSgidGV4dCIsIHggPSBJbmYsIHkgPSAtSW5mLCBoanVzdCA9IDEsIHZqdXN0ID0gLTEsDQogICAgICAgICAgIGxhYmVsID0gIlRoZXJlIGlzIGFuIHVwd2FyZCB0cmVuZCIpDQoNCmF1c19maXQgPC0gZGZfYXVzICU+JQ0KICAgICAgICAgICAgIyBubyBmaWx0ZXIgbmVlZGVkDQogICAgICAgICAgICBtb2RlbChSVyhQb3B1bGF0aW9uIH4gZHJpZnQoKSkpDQoNCmF1c19mYyA8LSBhdXNfZml0ICU+JQ0KICAgICAgICAgICAgZm9yZWNhc3QoaCA9IDEwKQ0KDQphdXNfcGxvdDI8LSBhdXNfZmMgJT4lIA0KICBhdXRvcGxvdChkZl9hdXMpDQoNCg0KDQpwbG90X2dyaWQoYXVzX3Bsb3QxLCBhdXNfcGxvdDIsIG5jb2wgPSAyKQ0KYGBgDQoNCkFsdGVybmF0aXZlbHkgdGhlIHBsb3R0aW5nIGNhbiBiZSBkb25lIHdpdGggb25lIGZ1bmN0aW9uIA0KDQpgYGB7cn0NCmRmX2F1cyU+JQ0KICBtb2RlbChSVyhQb3B1bGF0aW9uIH4gZHJpZnQoKSkpJT4lDQogICAgZm9yZWNhc3QoaCA9IDEwKSU+JQ0KICAgICAgYXV0b3Bsb3QoZGZfYXVzKQ0KYGBgDQoNCg0KSW4gc2VjdGlvbiA1LjIgRHJpZnQgbWV0aG9kIGlzIGV4cGxhaW5lZCB0byAiYWxsb3cgdGhlIGZvcmVjYXN0cyB0byBpbmNyZWFzZSBvciBkZWNyZWFzZSBvdmVyIHRpbWUiLiBTaW5jZSB0aGUgZGF0YSBkaWQgbm90IHNob3cgaGlnaCBzZWFzb25hbGl0eSBhbmQgd2FzIG5vdCBlY29ub21pYyBvciBmaW5hbmNpYWwsIEkgZGlkIG5vdCB1c2UgYE5hw692ZWAgbWV0aG9kIGFuZCBgc2Vhc29uYWwgbmHDr3ZlYC4gVGhlIGV4YW1wbGUgc2hvd24gYWxzbyB1c2VzIHJhbmRvbSB3YWxrIGZvcmVjYXN0IGluIGNvbmp1bmN0aW9uIHdpdGggZHJpZnQgKHJlZmVyIGJlbG93KQ0KDQogICAgICAgICAgICAgICAgICAgICAgICBicmlja3MgfD4gbW9kZWwoUlcoQnJpY2tzIH4gZHJpZnQoKSkpDQoNCiMjIGlpDQoNCkJyaWNrcyAoYGF1c19wcm9kdWN0aW9uYCkNCg0KYGBge3J9DQpoZWFkKGF1c19wcm9kdWN0aW9uKQ0KYGBgDQoNCg0KYGBge3J9DQpicmlja19wbG90MTwtYXVzX3Byb2R1Y3Rpb24lPiUNCiAgICAgICAgICAgICAgICBhdXRvcGxvdChCcmlja3MpKw0KICAgICAgICAgICAgICAgIGxhYnModGl0bGU9ICJCcmlja3MgUHJvZHVjdGlvbiIpKw0KICBhbm5vdGF0ZSgidGV4dCIsIHggPSBJbmYsIHkgPSAtSW5mLCBoanVzdCA9IDEsIHZqdXN0ID0gLTEsDQogICAgICAgICAgIGxhYmVsID0gIlRoZXJlIGlzIG9idmlvdXMgc2Vhc29uYWxpdHkgYnV0IG5vdCB0cmVuZCIpDQoNCmJyaWNrX3Bsb3QyPC1hdXNfcHJvZHVjdGlvbiU+JQ0KIyBXYXJuaW5nOiBSZW1vdmVkIDIwIHJvd3MgY29udGFpbmluZyBtaXNzaW5nIHZhbHVlcyAoYGdlb21fbGluZSgpYCkuDQojIHRoZXJlZm9yZSBmaWx0ZXIgaXMgYWRkZWQNCiAgZmlsdGVyKCFpcy5uYShCcmlja3MpKSU+JQ0KICBtb2RlbChTTkFJVkUoQnJpY2tzfmxhZygieWVhciIpKSklPiUNCiAgICBmb3JlY2FzdChoID0gMTApJT4lDQogICAgICBhdXRvcGxvdChhdXNfcHJvZHVjdGlvbikrDQojIGF1c19wcm9kdWN0aW9uIGFkZGVkIHR3aWNlIHRvIGVuc3VyZSB2aXN1YWwgaGFzIGZ1bGwgcGxvdA0KICBsYWJzKHRpdGxlPSAiQnJpY2tzIFByb2R1Y3Rpb24gRm9yZWNhc3QiKQ0KDQpwbG90X2dyaWQoYnJpY2tfcGxvdDEsYnJpY2tfcGxvdDIsIG5jb2wgPSAxKQ0KYGBgDQpUaGVyZSB3YXMgb2J2aW91cyBzZWFzb25hbGl0eSBkZXNwaXRlIHRoZSBmYWN0IHRoYXQgYW5udWFsbHkgdGhlcmUgd2FzIG5vIHRyZW5kLiBSZWdhcmRsZXNzIGBTTkFJVkUoeSlgIHdhcyB1c2VkIGFzIGl0IHNlZW1lZCB0byBiZSB0aGUgYmVzdCBmaXQuDQoNCiMjIGlpaQ0KDQpOU1cgTGFtYnMgKGBhdXNfbGl2ZXN0b2NrYCkNCg0KYGBge3J9DQpjYXQocGFzdGUodW5pcXVlKGF1c19saXZlc3RvY2skU3RhdGUpLCBjb2xsYXBzZSA9ICJcbiIpKQ0KDQpgYGANCg0KDQpgYGB7cn0NCmRmX2xhbWJzPC1hdXNfbGl2ZXN0b2NrJT4lDQogIGZpbHRlcihTdGF0ZSA9PSAiTmV3IFNvdXRoIFdhbGVzIiwgc3RyX2RldGVjdChBbmltYWwsIkxhbWJzIikpDQpoZWFkKGRmX2xhbWJzKQ0KYGBgDQoNCg0KYGBge3J9DQpsYW1ic19wbG90MSA8LSBkZl9sYW1icyU+JQ0KICAgICAgICAgICAgICAgIGF1dG9wbG90KCkrDQogICAgICAgICAgICAgICAgbGFicyh0aXRsZT0gIk5ldyBTb3V0aCBXYWxlcyBDb3VudCIpKw0KICBhbm5vdGF0ZSgidGV4dCIsIHggPSBJbmYsIHkgPSAtSW5mLCBoanVzdCA9IDEsIHZqdXN0ID0gLTEsDQogICAgICAgICAgIGxhYmVsID0gIlRoZXJlIGlzIG5vIG9idmlvdXMgc2Vhc29uYWxpdHkgb3IgdHJlbmQiKQ0KDQpsYW1ic19wbG90MjwtZGZfbGFtYnMlPiUNCiAgIG1vZGVsKE5BSVZFKENvdW50KSklPiUNCiAgICAgZm9yZWNhc3QoaCA9IDEwKSU+JQ0KICAgICAgIGF1dG9wbG90KGRmX2xhbWJzKSsNCiAgIGxhYnModGl0bGU9ICJOZXcgU291dGggV2FsZXMgRm9yZWNhc3QiKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KbGFtYnNfcGxvdDENCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmxhbWJzX3Bsb3QyDQpgYGANCg0KDQpObyBjbGVhciBzZWFzb25hbGl0eSBvciB0cmVuZCBzbyBgTkFJVkUoeSlgIHdhcyBtb3N0IGFwcHJvcHJpYXRlDQoNCiMjIGl2DQoNCkhvdXNlaG9sZCB3ZWFsdGggKGBoaF9idWRnZXRgKS4NCg0KYGBge3J9DQpoZWFkKGhoX2J1ZGdldCkNCmBgYA0KDQpgYGB7cn0NCndlYWx0aF9wbG90MTwtaGhfYnVkZ2V0JT4lDQogICAgICAgICAgICAgICAgYXV0b3Bsb3QoV2VhbHRoLCBzaG93LmxlZ2VuZD0gRkFMU0UpKw0KICAgICAgICAgICAgICAgICAgZmFjZXRfZ3JpZChDb3VudHJ5fi4sIHNjYWxlcyA9ICJmcmVlIiwgc3BhY2UgPSAiZnJlZV95IikNCg0Kd2VhbHRoX3Bsb3QyPC0gaGhfYnVkZ2V0JT4lDQogICAgICAgICAgICAgICAgbW9kZWwoUlcoV2VhbHRofmRyaWZ0KCkpKSU+JQ0KICAgICAgICAgICAgICAgICAgZm9yZWNhc3QoaD01KSU+JQ0KICAgICAgICAgICAgICAgICAgICBhdXRvcGxvdChoaF9idWRnZXQpDQoNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCndlYWx0aF9wbG90MQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0Kd2VhbHRoX3Bsb3QyDQpgYGANCg0KSSBnZW51aW5lbHkgY29uc2lkZXJlZCB0aGUgYE5BSVZFKHkpYCBtb2RlbCwgYmVjYXVzZSB0aGUgZGF0YSB3YXMgYSB0aW1lIHNlcmllcyByZWdhcmRpbmcgZmluYW5jZS4gSSBhbHNvIGRlYmF0ZWQgdGhlIHNlYXNvbmFsIGFzcGVjdCBhbmQgY29uc2lkZXJlZCBgU05BSVZFKHkpYCwgbm90aW5nIHRoZSBkaXAgaW4gd2VhbHRoIGF0IGNlcnRhaW4gaW50ZXJ2YWxzIGZvciBjZXJ0YWluIGNvdW50cmllcy4gSG93ZXZlciwgSSB0aGluayB0aGUgdHJlbmQgaXMgcHJpbWFyaWx5IHVwd2FyZCwgd2l0aCBleGNlcHRpb24gb2YgeWVhcnMgdGhhdCB0aWUgaW4gd2l0aCB0aGUgcmVjZW50IHJlY2Vzc2lvbnMsIHRoZXJlZm9yZSBgUlcoeSB+IGRyaWZ0KCkpYCBvciBEcmlmdCBtZXRob2Qgd2FzIHVzZWQuDQoNCiMjIHYNCg0KQXVzdHJhbGlhbiB0YWtlYXdheSBmb29kIHR1cm5vdmVyIChgYXVzX3JldGFpbGApLg0KDQpgYGB7cn0NCmNhdChwYXN0ZSh1bmlxdWUoYXVzX3JldGFpbCRTdGF0ZSksIGNvbGxhcHNlID0gIlxuIikpDQpgYGANCg0KDQpgYGB7cn0NCmhlYWQoYXVzX3JldGFpbCkNCmBgYA0KDQpgYGB7cn0NCmF1c19yZXRhaWwlPiUNCiAgZmlsdGVyKHN0cl9kZXRlY3QoSW5kdXN0cnksInRha2Vhd2F5IikpJT4lDQogIGF1dG9wbG90KFR1cm5vdmVyKSsNCiAgc2NhbGVfY29sb3JfZGlzY3JldGUobmFtZSA9ICJTdGF0ZSIsIGxhYmVscyA9IHVuaXF1ZShhdXNfcmV0YWlsJFN0YXRlKSkrDQogIGxhYnModGl0bGUgPSAiVHVybm92ZXIgKEF1c3RyYWxpYW4gdGFrZWF3YXkpIGJ5IFN0YXRlIikNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01fQ0KYXVzX3JldGFpbCAlPiUNCiAgZmlsdGVyKHN0cl9kZXRlY3QoSW5kdXN0cnksInRha2Vhd2F5IikpICU+JQ0KICBtb2RlbChSVyhUdXJub3ZlciB+IGRyaWZ0KCkpKSAlPiUNCiAgZm9yZWNhc3QoaCA9IDEwKSAlPiUNCiAgYXV0b3Bsb3QoYXVzX3JldGFpbCkrDQogICBmYWNldF93cmFwKH5TdGF0ZSwgc2NhbGVzID0gImZyZWUiKQ0KYGBgDQoNCkp1c3QgbGlrZSB0aGUgDQogICAgDQogICAgICAgICAgICAgIEF1c3RyYWxpYW4gUG9wdWxhdGlvbiAoYGdsb2JhbF9lY29ub215YCkNCiAgICAgICAgICAgICAgDQpUaGUgZGF0YSBmb3IgYWxsIHN0YXRlcyBzaG93ZWQgYW4gdXB3YXJkIHRyZW5kLiBTbyBtb2RlbGluZyBlYWNoIHN0YXRlIHVzaW5nIHRoZSBEcmlmdCBtZXRob2QgbWFkZSB0aGUgbW9zZSBzZW5zZS4NCg0KIyA1LjINCg0KVXNlIHRoZSBGYWNlYm9vayBzdG9jayBwcmljZSAoZGF0YSBzZXQgYGdhZmFfc3RvY2tgKSB0byBkbyB0aGUgZm9sbG93aW5nOg0KDQojIyBhLg0KDQpQcm9kdWNlIGEgdGltZSBwbG90IG9mIHRoZSBzZXJpZXMuDQoNCmBgYHtyfQ0KY2F0KHBhc3RlKHVuaXF1ZShnYWZhX3N0b2NrJFN5bWJvbCksIGNvbGxhcHNlID0gIlxuIikpDQpgYGANCg0KYGBge3J9DQpkaXN0aW5jdChnYWZhX3N0b2NrLCB5ZWFyID0gbHVicmlkYXRlOjp5ZWFyKERhdGUpKQ0KYGBgDQoNCg0KYGBge3J9DQpkZl9mYiA8LSBnYWZhX3N0b2NrICU+JQ0KICBmaWx0ZXIoU3ltYm9sID09ICJGQiIpDQoNCmhlYWQoZGZfZmIpDQpgYGANCg0KYGBge3J9DQojIFJlLWluZGV4IGJhc2VkIG9uIHRyYWRpbmcgZGF5cw0KRkJfc3RvY2sgPC0gZGZfZmIgJT4lDQojIGFscmVhZHkgZmlsdGVyZWQNCiAgbXV0YXRlKGRheSA9IHJvd19udW1iZXIoKSkgJT4lDQogIHVwZGF0ZV90c2liYmxlKGluZGV4ID0gZGF5LCByZWd1bGFyID0gVFJVRSkNCg0KRkJfc3RvY2slPiUNCiAgYXV0b3Bsb3QoQ2xvc2UpKw0KICBsYWJzKHkgPSAnJFVTJywgdGl0bGUgPSAnVGhlIEZhY2Vib29rIERhaWx5IENsb3NpbmcgU3RvY2sgUHJpY2UnKQ0KDQoNCmBgYA0KDQoNCiMjIGIuDQoNClByb2R1Y2UgZm9yZWNhc3RzIHVzaW5nIHRoZSBkcmlmdCBtZXRob2QgYW5kIHBsb3QgdGhlbS4NCg0KKipBUyBQRVIgRXhhbXBsZTogR29vZ2xl4oCZcyBkYWlseSBjbG9zaW5nIHN0b2NrIHByaWNlKioNCg0KYGBge3J9DQojIEZpbHRlciB0aGUgeWVhciBvZiBpbnRlcmVzdA0KRkJfMjAxNSA8LSBGQl9zdG9jayAlPiUgZmlsdGVyKHllYXIoRGF0ZSkgPT0gMjAxNSkNCiMgRml0IHRoZSBtb2RlbHMNCkZCX2ZpdCA8LSBGQl8yMDE1IHw+DQogIG1vZGVsKA0KICAgIERyaWZ0ID0gTkFJVkUoQ2xvc2UgfiBkcmlmdCgpKQ0KICApDQojIFByb2R1Y2UgZm9yZWNhc3RzIGZvciB0aGUgdHJhZGluZyBkYXlzIGluIEphbnVhcnkgMjAxNg0KRkJfamFuXzIwMTYgPC0gRkJfc3RvY2sgfD4NCiAgZmlsdGVyKHllYXJtb250aChEYXRlKSA9PSB5ZWFybW9udGgoIjIwMTYgSmFuIikpDQpGQl9mYyA8LSBGQl9maXQgfD4NCiAgZm9yZWNhc3QobmV3X2RhdGEgPSBGQl9qYW5fMjAxNikNCiMgUGxvdCB0aGUgZm9yZWNhc3RzDQpGQl9mYyB8Pg0KICBhdXRvcGxvdChGQl8yMDE1LCBsZXZlbCA9IE5VTEwpICsNCiAgYXV0b2xheWVyKEZCX2phbl8yMDE2LCBDbG9zZSwgY29sb3VyID0gImJsYWNrIikgKw0KICBsYWJzKHkgPSAiJFVTIiwNCiAgICAgICB0aXRsZSA9ICJGYWNlYm9vayBkYWlseSBjbG9zaW5nIHN0b2NrIHByaWNlcyIsDQogICAgICAgc3VidGl0bGUgPSAiKEphbiAyMDE1IC0gSmFuIDIwMTYpIikgKw0KICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIkZvcmVjYXN0IikpDQpgYGANCg0KDQojIyBjLg0KDQpTaG93IHRoYXQgdGhlIGZvcmVjYXN0cyBhcmUgaWRlbnRpY2FsIHRvIGV4dGVuZGluZyB0aGUgbGluZSBkcmF3biBiZXR3ZWVuIHRoZSBmaXJzdCBhbmQgbGFzdCBvYnNlcnZhdGlvbnMuDQoNCmBgYHtyfQ0KRkJfZmMlPiUgDQogIGF1dG9wbG90KEZCXzIwMTUsIGxldmVsID0gTlVMTCkgKw0KICBnZW9tX2xpbmUoZGF0YSA9IHNsaWNlKEZCXzIwMTUsIHJhbmdlKGN1bXN1bSghaXMubmEoQ2xvc2UpKSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh5PUNsb3NlKSwgbGluZXR5cGUgPSAnZGFzaGVkJykNCmBgYA0KDQoNCiMjIGQuDQoNClRyeSB1c2luZyBzb21lIG9mIHRoZSBvdGhlciBiZW5jaG1hcmsgZnVuY3Rpb25zIHRvIGZvcmVjYXN0IHRoZSBzYW1lIGRhdGEgc2V0LiBXaGljaCBkbyB5b3UgdGhpbmsgaXMgYmVzdD8gV2h5Pw0KDQpgYGB7cn0NCkZCX2ZpdDIgPC0gRkJfMjAxNSAlPiUNCiAgbW9kZWwoDQogICAgTWVhbiA9IE1FQU4oQ2xvc2UpLA0KICAgIE5haXZlID0gTkFJVkUoQ2xvc2UpDQogICkNCiMgdG8gbWFrZSB0aGUgZm9yZWNhc3RzIGZvciB0aGUgdHJhZGluZyBkYXlzIGluIEphbnVhcnkgMjAxNg0KRkJfamFuXzIwMTYgPC0gRkJfc3RvY2sgJT4lDQogIGZpbHRlcih5ZWFybW9udGgoRGF0ZSkgPT0geWVhcm1vbnRoKCIyMDE2IEphbiIpKQ0KDQpGQl9mYzIgPC0gRkJfZml0MiAlPiUNCiAgZm9yZWNhc3QobmV3X2RhdGEgPSBGQl9qYW5fMjAxNikNCiMgUGxvdHRpbmcNCkZCX2ZjMiAlPiUNCiAgYXV0b3Bsb3QoRkJfMjAxNSwgbGV2ZWwgPSBOVUxMKSArDQogIGF1dG9sYXllcihGQl9qYW5fMjAxNiwgQ2xvc2UsIGNvbG91ciA9ICJncmVlbiIpICsNCiAgbGFicyh5ID0gIiRVU0QiLA0KICAgICAgIHRpdGxlID0gIkZCICBDbG9zaW5nIFN0b2NrIFByaWNlcyAoRGFpbHkpIiwNCiAgICAgICBzdWJ0aXRsZSA9ICIoSmFuIDIwMTUgLSBKYW4gMjAxNikiKSArDQogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQodGl0bGUgPSAiVGhlIEZvcmVjYXN0IikpDQpgYGANCg0KTmFpdmUgSSBiZWxpZXZlIGlzIHRoZSBtb3N0IGFjY3VyYXRlLCBiL2MgdGhlcmUgaXMgbm8gc2Vhc29uYWxpdHkgYW5kIE5haXZlIHdvcmtzIGJlc3Qgd2l0aCBmaW5hbmNpYWwgZGF0YSBhY2NvcmRpbmcgdG8gdGhlIHRleHRib29rDQoNCiAgICAgICAgICAgICJUaGlzIG1ldGhvZCB3b3JrcyByZW1hcmthYmx5IHdlbGwgZm9yIG1hbnkgZWNvbm9taWMgYW5kIGZpbmFuY2lhbCB0aW1lIHNlcmllcy4iDQoNCiMgNS4zDQoNCkFwcGx5IGEgc2Vhc29uYWwgbmHDr3ZlIG1ldGhvZCB0byB0aGUgcXVhcnRlcmx5IEF1c3RyYWxpYW4gYmVlciBwcm9kdWN0aW9uIGRhdGEgZnJvbSAxOTkyLiBDaGVjayBpZiB0aGUgcmVzaWR1YWxzIGxvb2sgbGlrZSB3aGl0ZSBub2lzZSwgYW5kIHBsb3QgdGhlIGZvcmVjYXN0cy4gVGhlIGZvbGxvd2luZyBjb2RlIHdpbGwgaGVscC4NCg0KYGBge3J9DQojIEV4dHJhY3QgZGF0YSBvZiBpbnRlcmVzdA0KcmVjZW50X3Byb2R1Y3Rpb24gPC0gYXVzX3Byb2R1Y3Rpb24gfD4NCiAgZmlsdGVyKHllYXIoUXVhcnRlcikgPj0gMTk5MikNCiMgRGVmaW5lIGFuZCBlc3RpbWF0ZSBhIG1vZGVsDQpmaXQgPC0gcmVjZW50X3Byb2R1Y3Rpb24gfD4gbW9kZWwoU05BSVZFKEJlZXIpKQ0KIyBMb29rIGF0IHRoZSByZXNpZHVhbHMNCmZpdCB8PiBnZ190c3Jlc2lkdWFscygpDQojIExvb2sgYSBzb21lIGZvcmVjYXN0cw0KZml0IHw+IGZvcmVjYXN0KCkgfD4gYXV0b3Bsb3QocmVjZW50X3Byb2R1Y3Rpb24pDQoNCmBgYA0KDQpXaGF0IGRvIHlvdSBjb25jbHVkZT8NCg0KSSBkb24ndCBiZWxpZXZlIHRoaXMgaXMgd2hpdGUgbm9pc2UuIFRoaXMgZG9lcyBub3QgYXBwZWFyIHRvIGJlIGFuIHVucHJlZGljdGFibGUgc2VxdWVuY2Ugb2YgbnVtYmVycywgcmF0aGVyIGl0IHNlZW1zIHRvIGhhdmUgYSBjb25zdGFudCB2YXJpYW5jZSwgYW5kIGAucmVzaWRgIHRoYXQgY2VudGVycyBhcm91bmQgMC4gRm9yIHRoZSBsYWcgdmFsdWUgb2YgNCBpbiB0aGUgYWNmIHBsb3QsIGl0IGlzIGxhcmdlciB0aGFuIG5vcm1hbCBidXQgYWxzbyBmb2xsb3dzIHRoZSBvY2N1cnJlbmNlIGV2ZXJ5IDQgcXVhcnRlcnMuIFNvIHRoZSBtb2RlbCBjYW4gYmUgaW1wcm92ZWQsIGl0IGlzIG5vdCBsaWtlbHkgd2hpdGUgbm9pc2UgYW5kIHRoZXJlZm9yZSBzZWFzb25hbCBuYcOvdmUgbWV0aG9kIGlzIHZhbGlkLg0KDQojIDUuNCANCg0KUmVwZWF0IHRoZSBwcmV2aW91cyBleGVyY2lzZSB1c2luZyB0aGUgQXVzdHJhbGlhbiBFeHBvcnRzIHNlcmllcyBmcm9tIGBnbG9iYWxfZWNvbm9teWAgYW5kIHRoZSBCcmlja3Mgc2VyaWVzIGZyb20gYGF1c19wcm9kdWN0aW9uLmAgVXNlIHdoaWNoZXZlciBvZiBgTkFJVkUoKWAgb3IgYFNOQUlWRSgpYCBpcyBtb3JlIGFwcHJvcHJpYXRlIGluIGVhY2ggY2FzZS4NCg0KKiogTm90ZSoqOiBgZGZfYXVzYCAqZXN0YWJsaXNoZWQgaW4gNS4xIGZvciBBdXN0cmFsaWFuIGV4cG9ydHMqDQoNCiMjIGkNCg0KKkF1c3RyYWxpYW4gRXhwb3J0cyBzZXJpZXMgZnJvbSogYGdsb2JhbF9lY29ub215YA0KDQpgYGB7cn0NCiMgRGVmaW5lIGFuZCBlc3RpbWF0ZSBhIG1vZGVsDQpkZl9hdXNfZml0IDwtIGRmX2F1cyAlPiUgDQogIG1vZGVsKE5BSVZFKEV4cG9ydHMpKQ0KDQojIExvb2sgYXQgdGhlIHJlc2lkdWFscw0KZGZfYXVzX2ZpdCAlPiUgDQogIGdnX3RzcmVzaWR1YWxzKCkNCg0KIyBMb29rIGF0IHNvbWUgZm9yZWNhc3RzDQpkZl9hdXNfZml0ICU+JSANCiAgZm9yZWNhc3QoKSAlPiUgYXV0b3Bsb3QoZGZfYXVzKQ0KYGBgDQoNCmBgYHtyfQ0KYXVzX2F1ZzwtZGZfYXVzX2ZpdCU+JQ0KICAgICAgICAgIGF1Z21lbnQoKQ0KDQphdXNfYXVnIHw+IGZlYXR1cmVzKC5pbm5vdiwgYm94X3BpZXJjZSwgbGFnID0gMTApDQoNCmF1c19hdWcgfD4gZmVhdHVyZXMoLmlubm92LCBsanVuZ19ib3gsIGxhZyA9IDEwKQ0KDQpgYGANCg0KV2hhdCBkbyB5b3UgY29uY2x1ZGU/DQoNCiBCUCBwLXZhbHVlIG9mIDAuMTQ4MTEzNSBhbmQgTEIgcC12YWx1ZSBvZiAwLjA4OTYzNjc4IHN1Z2dlc3RzIHRoZXJlJ3Mgbm90IHNpZ25pZmljYW50IGF1dG9jb3JyZWxhdGlvbiwgc2luY2UgdGhleSdyZSBib3RoIGdyZWF0ZXIgdGhhbiAwLjA1LiBUaGVyZSdzIGFsc28gY29uc3RhbnQgdmFyaWF0aW9uLiBJIGNhbm5vdCBkaWZmZXJlbnRpYXRlIHRoaXMgZnJvbSB3aGl0ZSBub2lzZSBhbmQgSSdtIHVuc3VyZSBpZiB0aGUgbW9kZWwgY291bGQgYmUgaW1wcm92ZWQuDQogDQojIyBpaSANCiANCmBgYHtyfQ0KIyBEZWZpbmUgYW5kIGVzdGltYXRlIGEgbW9kZWwNCmF1c19wcm9kX2ZpdCA8LSBhdXNfcHJvZHVjdGlvbiAlPiUgDQogIG1vZGVsKE5BSVZFKEJyaWNrcykpDQoNCiMgTG9vayBhdCB0aGUgcmVzaWR1YWxzDQphdXNfcHJvZF9maXQgJT4lIA0KICBnZ190c3Jlc2lkdWFscygpDQoNCiMgTG9vayBhdCBzb21lIGZvcmVjYXN0cw0KYXVzX3Byb2RfZml0ICU+JSANCiAgZm9yZWNhc3QoKSAlPiUgYXV0b3Bsb3QoYXVzX3Byb2R1Y3Rpb24pDQpgYGANCg0KYGBge3J9DQphdXNfcHJvZF9hdWc8LWF1c19wcm9kX2ZpdCU+JQ0KICAgICAgICAgIGF1Z21lbnQoKQ0KDQphdXNfcHJvZF9hdWcgfD4gZmVhdHVyZXMoLmlubm92LCBib3hfcGllcmNlLCBsYWcgPSAxMCkNCg0KYXVzX3Byb2RfYXVnIHw+IGZlYXR1cmVzKC5pbm5vdiwgbGp1bmdfYm94LCBsYWcgPSAxMCkNCg0KYGBgDQpUaGUgYWNmIGJlaW5nIGNvbnNpc3RlbnRseSBwYXN0IHRoZSBkb3R0ZWQgbGluZSBzdWdnZXN0cyBzaWduaWZpY2FudCBwZXJpb2RpYyBhdXRvY29ycmVsYXRpb24gYW5kIHNlYXNvbmFsaXR5LiBUaGUgZXZpZGVuY2Ugb2YgYXV0b2NvcnJlbGF0aW9uIGlzIHN1cHBvcnRlZCBieSB0aGUgQlAgYW5kIExCIHN0YXRpc3RpYy4NCg0KIyA1LjcNCg0KRm9yIHlvdXIgcmV0YWlsIHRpbWUgc2VyaWVzIChmcm9tIEV4ZXJjaXNlIDcgaW4gU2VjdGlvbiBbMi4xMF0oaHR0cHM6Ly9vdGV4dHMuY29tL2ZwcDMvZ3JhcGhpY3MtZXhlcmNpc2VzLmh0bWwjZ3JhcGhpY3MtZXhlcmNpc2VzKSk6DQoNCiMjIGEuIA0KDQpDcmVhdGUgYSB0cmFpbmluZyBkYXRhc2V0IGNvbnNpc3Rpbmcgb2Ygb2JzZXJ2YXRpb25zIGJlZm9yZSAyMDExIHVzaW5nDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0KDQoNCm15c2VyaWVzIDwtIGF1c19yZXRhaWwgJT4lDQogIGZpbHRlcihgU2VyaWVzIElEYCA9PSBzYW1wbGUoYXVzX3JldGFpbCRgU2VyaWVzIElEYCwxKSkNCg0KbXlzZXJpZXNfdHJhaW4gPC0gbXlzZXJpZXMgfD4NCiAgZmlsdGVyKHllYXIoTW9udGgpIDwgMjAxMSkNCg0KYGBgDQoNCiMjIGIuDQoNCkNoZWNrIHRoYXQgeW91ciBkYXRhIGhhdmUgYmVlbiBzcGxpdCBhcHByb3ByaWF0ZWx5IGJ5IHByb2R1Y2luZyB0aGUgZm9sbG93aW5nIHBsb3QuDQoNCmBgYHtyfQ0KYXV0b3Bsb3QobXlzZXJpZXMsIFR1cm5vdmVyKSArDQogIGF1dG9sYXllcihteXNlcmllc190cmFpbiwgVHVybm92ZXIsIGNvbG91ciA9ICJyZWQiKQ0KDQpgYGANCg0KIyMgYy4gDQoNCkZpdCBhIHNlYXNvbmFsIG5hw692ZSBtb2RlbCB1c2luZyBTTkFJVkUoKSBhcHBsaWVkIHRvIHlvdXIgdHJhaW5pbmcgZGF0YSAoYG15c2VyaWVzX3RyYWluYCkuDQoNCmBgYHtyfQ0KZml0IDwtIG15c2VyaWVzX3RyYWluIHw+DQogIG1vZGVsKFNOQUlWRShUdXJub3ZlcikpDQoNCmBgYA0KDQojIyBkLiANCg0KQ2hlY2sgdGhlIHJlc2lkdWFscy4NCmBgYHtyfQ0KZml0IHw+IGdnX3RzcmVzaWR1YWxzKCkNCg0KYGBgDQoNCkRvIHRoZSByZXNpZHVhbHMgYXBwZWFyIHRvIGJlIHVuY29ycmVsYXRlZCBhbmQgbm9ybWFsbHkgZGlzdHJpYnV0ZWQ/DQoNClRoZSBkYXRhIGFwcGVhcnMgdG8gYmUgY29ycmVsYXRlZCwgYnV0IGhhdmUgYSBjb25zdGFudCB2YXJpYXRpb24gY2hhbmdpbmcgZnJvbSBwb3NpdGl2ZSB0byBuZWdhdGl2ZSBhdCBsYWcgMTAgaW5kaWNhdGluZyBoZXRlcm9zY2VkYXN0aWNpdHkuIEkgYWxzbyBkb2VzIG5vdCBhcHBlYXIgdG8gYmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgb3IgY2VudGVyIGFyb3VuZCAwLg0KDQojIyBlLiANCg0KUHJvZHVjZSBmb3JlY2FzdHMgZm9yIHRoZSB0ZXN0IGRhdGENCg0KYGBge3J9DQpmYyA8LSBmaXQgfD4NCiAgZm9yZWNhc3QobmV3X2RhdGEgPSBhbnRpX2pvaW4obXlzZXJpZXMsIG15c2VyaWVzX3RyYWluKSkNCmZjIHw+IGF1dG9wbG90KG15c2VyaWVzKQ0KDQpgYGANCg0KIyMgZi4NCg0KQ29tcGFyZSB0aGUgYWNjdXJhY3kgb2YgeW91ciBmb3JlY2FzdHMgYWdhaW5zdCB0aGUgYWN0dWFsIHZhbHVlcy4NCg0KYGBge3J9DQpmaXQgfD4gYWNjdXJhY3koKQ0KZmMgfD4gYWNjdXJhY3kobXlzZXJpZXMpDQoNCmBgYA0KDQpUaGUgZm9yZWNhc3QgbW9kZWxzIGRvZXMgbm90IHBlcmZvcm0gd2VsbCBvbiB0aGUgdGVzdCBkYXRhIGFuZCB0aGUgZXJyb3JzIGZvciB0cmFpbmluZyBzZWVtIHNtYWxsZXIgaW4gY29tcGFyaXNvbi4NCg0KIyMgZy4gDQoNCkhvdyBzZW5zaXRpdmUgYXJlIHRoZSBhY2N1cmFjeSBtZWFzdXJlcyB0byB0aGUgYW1vdW50IG9mIHRyYWluaW5nIGRhdGEgdXNlZD8NCg0KVGhlIG1lYXN1cmVzIGFyZSBoaWdobHkgc2Vuc2l0aXZlIHRvIHRoZSBxdWFudGl0eSBvZiBkYXRhIGludm9sdmVkLCB3aGljaCBtYXkgYmUgbmVnYXRpdmVseSBpbXBhY3RlZCB0aGUgbWVhc3VyZXMuDQo=