Demonstration of covariance matrix and basis function implementation of Gaussian process model in Stan.

The basics of the covariance matrix approach is based on the Chapter 10 of Stan User’s Guide, Version 2.26 by Stan Development Team (2021). https://mc-stan.org/docs/stan-users-guide/

The basics of the Hilbert space basis function approximation is based on Riutort-Mayol, Bürkner, Andersen, Solin, and Vehtari (2020). Practical Hilbert space approximate Bayesian Gaussian processes for probabilistic programming. https://arxiv.org/abs/2004.11408

Motorcycle

Data are measurements of head acceleration in a simulated motorcycle accident, used to test crash helmets.

Data are modelled with normal distribution having Gaussian process prior on mean and log standard deviation: \[ y \sim \mbox{normal}(\mu(x), \exp(\eta(x))\\ \mu \sim GP(0, K_1)\\ \eta \sim GP(0, K_2) \] \(K_1\) and \(K_2\) are exponentiated quadratic covariance functions.

Load packages

library(cmdstanr) 
library(posterior)
options(pillar.neg = FALSE, pillar.subtle=FALSE, pillar.sigfig=2)
library(tidyr) 
library(dplyr) 
library(ggplot2)
library(bayesplot)
theme_set(bayesplot::theme_default(base_family = "sans", base_size=16))
set1 <- RColorBrewer::brewer.pal(7, "Set1")
SEED <- 48927 # set random seed for reproducability

Motorcycle accident acceleration data

Load data

data(mcycle, package="MASS")
head(mcycle)
  times accel
1   2.4   0.0
2   2.6  -1.3
3   3.2  -2.7
4   3.6   0.0
5   4.0  -2.7
6   6.2  -2.7

Plot data

mcycle %>%
  ggplot(aes(x=times,y=accel))+
  geom_point()+
  labs(x="Time (ms)", y="Acceleration (g)")

GP model with Hilbert basis functions

Model code

file1 <- "gpbf1.stan"
writeLines(readLines(file1))
functions {
#include gpbasisfun_functions.stan
}
data {
  int<lower=1> N;      // number of observations
  vector[N] x;         // univariate covariate
  vector[N] y;         // target variable
        
  real<lower=0> c_f;   // factor c to determine the boundary value L
  int<lower=1> M_f;    // number of basis functions
  real<lower=0> c_g;   // factor c to determine the boundary value L
  int<lower=1> M_g;    // number of basis functions
}
transformed data {
  // Normalize data
  real xmean = mean(x);
  real ymean = mean(y);
  real xsd = sd(x);
  real ysd = sd(y);
  vector[N] xn = (x - xmean)/xsd;
  vector[N] yn = (y - ymean)/ysd;
  // Basis functions for f
  real L_f = c_f*max(xn);
  matrix[N,M_f] PHI_f = PHI_EQ(N, M_f, L_f, xn);
  // Basis functions for g
  real L_g= c_g*max(xn);
  matrix[N,M_g] PHI_g = PHI_EQ(N, M_g, L_g, xn);
}
parameters {
  real intercept;              // 
  vector[M_f] beta_f;          // the basis functions coefficients
  vector[M_g] beta_g;          // the basis functions coefficients
  real<lower=0> lengthscale_f; // lengthscale of f
  real<lower=0> sigma_f;       // scale of f
  real<lower=0> lengthscale_g; // lengthscale of g
  real<lower=0> sigma_g;       // scale of g
}
model {
  // spectral densities for f and g
  vector[M_f] diagSPD_f = diagSPD_EQ(sigma_f, lengthscale_f, L_f, M_f);
  vector[M_g] diagSPD_g = diagSPD_EQ(sigma_g, lengthscale_g, L_g, M_g);
  // priors
  intercept ~ normal(0, 1);
  beta_f ~ normal(0, 1);
  beta_g ~ normal(0, 1);
  lengthscale_f ~ normal(0, 1);
  lengthscale_g ~ normal(0, 1);
  sigma_f ~ normal(0, .5);
  sigma_g ~ normal(0, .5);
  // model
  yn ~ normal(intercept + PHI_f * (diagSPD_f .* beta_f),
          exp(PHI_g * (diagSPD_g .* beta_g)));
}
generated quantities {
  vector[N] f;
  vector[N] sigma;
  {
    // spectral densities
    vector[M_f] diagSPD_f = diagSPD_EQ(sigma_f, lengthscale_f, L_f, M_f);
    vector[M_g] diagSPD_g = diagSPD_EQ(sigma_g, lengthscale_g, L_g, M_g);
    // function scaled back to the original scale
    f = (intercept + PHI_f * (diagSPD_f .* beta_f))*ysd + ymean;
    sigma = exp(PHI_g * (diagSPD_g .* beta_g))*ysd;
  }
}

The model code includes Hilbert space basis function helpers

writeLines(readLines("gpbasisfun_functions.stan"))
vector diagSPD_EQ(real alpha, real rho, real L, int M) {
  return sqrt((alpha^2) * sqrt(2*pi()) * rho * exp(-0.5*(rho*pi()/2/L)^2 * linspaced_vector(M, 1, M)^2));
}
/* real spd_Matt(real alpha, real rho, real w) { */
/*   real S = 4*alpha^2 * (sqrt(3)/rho)^3 * 1/((sqrt(3)/rho)^2 + w^2)^2; */
/*   return sqrt(S); */
/* } */
vector diagSPD_periodic(real alpha, real rho, int M) {
  real a = 1/rho^2;
  int one_to_M[M];
  for (m in 1:M) one_to_M[m] = m;
  vector[M] q = sqrt(alpha^2 * 2 / exp(a) * to_vector(modified_bessel_first_kind(one_to_M, a)));
  return append_row(q,q);
}
matrix PHI_EQ(int N, int M, real L, vector x) {
  return sin(diag_post_multiply(rep_matrix(pi()/(2*L) * (x+L), M), linspaced_vector(M, 1, M)))/sqrt(L);
}
matrix PHI_periodic(int N, int M, real w0, vector x) {
  matrix[N,M] mw0x = diag_post_multiply(rep_matrix(w0*x, M), linspaced_vector(M, 1, M));
  return append_col(cos(mw0x), sin(mw0x));
}

Compile Stan model

model1 <- cmdstan_model(stan_file = file1, include_paths = ".")

Data to be passed to Stan

standata1 <- list(x=mcycle$times,
                  y=mcycle$accel,
                  N=length(mcycle$times),
                  c_f=1.5, # factor c of basis functions for GP for f1
                  M_f=40,  # number of basis functions for GP for f1
                  c_g=1.5, # factor c of basis functions for GP for g3
                  M_g=40)  # number of basis functions for GP for g3

Sample using dynamic HMC

fit1 <- model1$sample(data=standata1, iter_warmup=500, iter_sampling=500,
                      chains=4, parallel_chains=2, adapt_delta=0.9)

Check whether parameters have reasonable values

draws1 <- fit1$draws()
summarise_draws(subset(draws1, variable=c('intercept','sigma_','lengthscale_'), regex=TRUE))
# A tibble: 5 x 10
  variable       mean median    sd   mad    q5   q95  rhat ess_bulk ess_tail
  <chr>         <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>    <dbl>    <dbl>
1 intercept      0.27   0.26 0.32  0.30  -0.26  0.81   1.0    1717.    1173.
2 sigma_f        0.81   0.79 0.17  0.16   0.58  1.1    1.0    1355.    1520.
3 sigma_g        1.3    1.3  0.22  0.22   0.95  1.7    1.0    1388.    1306.
4 lengthscale_f  0.34   0.34 0.050 0.052  0.26  0.43   1.0     945.    1627.
5 lengthscale_g  0.52   0.52 0.095 0.092  0.37  0.68   1.0    1214.    1352.

Compare the model to the data

draws1m <- as_draws_matrix(draws1)
Ef <- colMeans(subset(draws1m, variable='f'))
sigma <- colMeans(subset(draws1m, variable='sigma'))
pred<-data.frame(Ef=Ef,sigma=sigma)
cbind(mcycle,pred) %>%
  ggplot(aes(x=times,y=accel))+
  geom_point()+
  labs(x="Time (ms)", y="Acceleration (g)")+
  geom_line(aes(y=Ef), color=set1[1])+
  geom_line(aes(y=Ef-2*sigma), color=set1[1],linetype="dashed")+
  geom_line(aes(y=Ef+2*sigma), color=set1[1],linetype="dashed")

Plot posterior draws and posterior mean of the mean function

subset(draws1, variable="f") %>%
  thin_draws(thin=5)%>%
  as_draws_df() %>%
  pivot_longer(!starts_with("."),
               names_to="ind",
               names_transform = list(ind = readr::parse_number),
               values_to="mu") %>%
  mutate(time=mcycle$times[ind])%>%
  ggplot(aes(time, mu, group = .draw)) +
  geom_line(color=set1[2], alpha = 0.1) +
  geom_point(data=mcycle, mapping=aes(x=times,y=accel), inherit.aes=FALSE)+
  geom_line(data=cbind(mcycle,pred), mapping=aes(x=times,y=Ef), inherit.aes=FALSE, color=set1[1], size=1)+
  labs(x="Time (ms)", y="Acceleration (g)")

GP with covariance matrices

Model code

file2 <- "gpcov.stan"
writeLines(readLines(file2))
data {
  int<lower=1> N;      // number of observations
  vector[N] x;         // univariate covariate
  vector[N] y;         // target variable
}
transformed data {
  // Normalize data
  real xmean = mean(x);
  real ymean = mean(y);
  real xsd = sd(x);
  real ysd = sd(y);
  real xn[N] = to_array_1d((x - xmean)/xsd);
  vector[N] yn = (y - ymean)/ysd;
  real sigma_intercept = 0.1;
  vector[N] jitter = rep_vector(1e-9, N);
}
parameters {
  real<lower=0> lengthscale_f; // lengthscale of f
  real<lower=0> sigma_f;       // scale of f
  real<lower=0> lengthscale_g; // lengthscale of g
  real<lower=0> sigma_g;       // scale of g
  vector[N] z_f;
  vector[N] z_g;
}
model {
  // covariances and Cholesky decompositions
  matrix[N, N] K_f = gp_exp_quad_cov(xn, sigma_f, lengthscale_f)+
                     sigma_intercept;
  matrix[N, N] L_f = cholesky_decompose(add_diag(K_f, jitter));
  matrix[N, N] K_g = gp_exp_quad_cov(xn, sigma_g, lengthscale_g)+
                     sigma_intercept;
  matrix[N, N] L_g = cholesky_decompose(add_diag(K_g, jitter));
  // priors
  z_f ~ std_normal();
  z_g ~ std_normal();
  lengthscale_f ~ lognormal(log(.3), .2);
  lengthscale_g ~ lognormal(log(.5), .2);
  sigma_f ~ normal(0, .5);
  sigma_g ~ normal(0, .5);
  // model
  yn ~ normal(L_f * z_f, exp(L_g * z_g));
}
generated quantities {
  vector[N] f;
  vector[N] sigma;
  {
    // covariances and Cholesky decompositions
    matrix[N, N] K_f = gp_exp_quad_cov(xn, sigma_f, lengthscale_f)+
                       sigma_intercept;
    matrix[N, N] L_f = cholesky_decompose(add_diag(K_f, jitter));
    matrix[N, N] K_g = gp_exp_quad_cov(xn, sigma_g, lengthscale_g)+
                       sigma_intercept;
    matrix[N, N] L_g = cholesky_decompose(add_diag(K_g, jitter));
    // function scaled back to the original scale
    f = (L_f * z_f)*ysd + ymean;
    sigma = exp(L_g * z_g)*ysd;
  }
}

Compile Stan model

model2 <- cmdstan_model(stan_file = file2)

Data to be passed to Stan

standata2 <- list(x=mcycle$times,
                  y=mcycle$accel,
                  N=length(mcycle$times))

Sample using dynamic HMC

fit2 <- model2$sample(data=standata2, iter_warmup=100, iter_sampling=100,
                      chains=4, parallel_chains=2, refresh=10)

Check whether parameters have reasonable values

draws2 <- fit2$draws()
summarise_draws(subset(draws2, variable=c('sigma_','lengthscale_'), regex=TRUE))
# A tibble: 4 x 10
  variable       mean median    sd   mad    q5   q95  rhat ess_bulk ess_tail
  <chr>         <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>    <dbl>    <dbl>
1 sigma_f        0.79   0.77 0.15  0.14   0.56  1.1    1.0     406.     362.
2 sigma_g        1.2    1.1  0.23  0.22   0.83  1.6    1.0     428.     259.
3 lengthscale_f  0.32   0.32 0.037 0.036  0.26  0.38   1.0     186.     238.
4 lengthscale_g  0.48   0.48 0.070 0.075  0.37  0.60   1.0     185.     225.

Compare the model to the data

draws2m <- as_draws_matrix(draws2)
Ef <- colMeans(subset(draws2m, variable='f'))
sigma <- colMeans(subset(draws2m, variable='sigma'))
pred<-data.frame(Ef=Ef,sigma=sigma)
cbind(mcycle,pred) %>%
  ggplot(aes(x=times,y=accel))+
  geom_point()+
  labs(x="Time (ms)", y="Acceleration (g)")+
  geom_line(aes(y=Ef), color=set1[1])+
  geom_line(aes(y=Ef-2*sigma), color=set1[1],linetype="dashed")+
  geom_line(aes(y=Ef+2*sigma), color=set1[1],linetype="dashed")

Plot posterior draws and posterior mean of the mean function

subset(draws2, variable="f") %>%
  as_draws_df() %>%
  pivot_longer(!starts_with("."),
               names_to="ind",
               names_transform = list(ind = readr::parse_number),
               values_to="mu") %>%
  mutate(time=mcycle$times[ind])%>%
  ggplot(aes(time, mu, group = .draw)) +
  geom_line(color=set1[2], alpha = 0.1) +
  geom_point(data=mcycle, mapping=aes(x=times,y=accel), inherit.aes=FALSE)+
  geom_line(data=cbind(mcycle,pred), mapping=aes(x=times,y=Ef), inherit.aes=FALSE, color=set1[1], size=1) +
  labs(x="Time (ms)", y="Acceleration (g)")

IycgLS0tCiMnIHRpdGxlOiAiR2F1c3NpYW4gcHJvY2VzcyBkZW1vbnN0cmF0aW9uIHdpdGggU3RhbiIKIycgYXV0aG9yOiAiQWtpIFZlaHRhcmkiCiMnIGRhdGU6ICJGaXJzdCB2ZXJzaW9uIDIwMjEtMDEtMjguIExhc3QgbW9kaWZpZWQgYHIgZm9ybWF0KFN5cy5EYXRlKCkpYC4iCiMnIG91dHB1dDoKIycgICBodG1sX2RvY3VtZW50OgojJyAgICAgdGhlbWU6IHJlYWRhYmxlCiMnICAgICB0b2M6IHRydWUKIycgICAgIHRvY19kZXB0aDogMgojJyAgICAgdG9jX2Zsb2F0OiB0cnVlCiMnICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiMnIC0tLQoKCiMnIERlbW9uc3RyYXRpb24gb2YgY292YXJpYW5jZSBtYXRyaXggYW5kIGJhc2lzIGZ1bmN0aW9uCiMnIGltcGxlbWVudGF0aW9uIG9mIEdhdXNzaWFuIHByb2Nlc3MgbW9kZWwgaW4gU3Rhbi4KIycKIycgVGhlIGJhc2ljcyBvZiB0aGUgY292YXJpYW5jZSBtYXRyaXggYXBwcm9hY2ggaXMgYmFzZWQgb24gdGhlIENoYXB0ZXIKIycgMTAgb2YgU3RhbiBVc2Vy4oCZcyBHdWlkZSwgVmVyc2lvbiAyLjI2IGJ5IFN0YW4gRGV2ZWxvcG1lbnQgVGVhbQojJyAoMjAyMSkuIGh0dHBzOi8vbWMtc3Rhbi5vcmcvZG9jcy9zdGFuLXVzZXJzLWd1aWRlLwojJwojJyBUaGUgYmFzaWNzIG9mIHRoZSBIaWxiZXJ0IHNwYWNlIGJhc2lzIGZ1bmN0aW9uIGFwcHJveGltYXRpb24gaXMKIycgYmFzZWQgb24gUml1dG9ydC1NYXlvbCwgQsO8cmtuZXIsIEFuZGVyc2VuLCBTb2xpbiwgYW5kIFZlaHRhcmkKIycgKDIwMjApLiBQcmFjdGljYWwgSGlsYmVydCBzcGFjZSBhcHByb3hpbWF0ZSBCYXllc2lhbiBHYXVzc2lhbgojJyBwcm9jZXNzZXMgZm9yIHByb2JhYmlsaXN0aWMgcHJvZ3JhbW1pbmcuIGh0dHBzOi8vYXJ4aXYub3JnL2Ficy8yMDA0LjExNDA4CiMnCiMnICMjIE1vdG9yY3ljbGUKIycgCiMnIERhdGEgYXJlIG1lYXN1cmVtZW50cyBvZiBoZWFkIGFjY2VsZXJhdGlvbiBpbiBhIHNpbXVsYXRlZAojJyBtb3RvcmN5Y2xlIGFjY2lkZW50LCB1c2VkIHRvIHRlc3QgY3Jhc2ggaGVsbWV0cy4KIycKIycgRGF0YSBhcmUgbW9kZWxsZWQgd2l0aCBub3JtYWwgZGlzdHJpYnV0aW9uIGhhdmluZyBHYXVzc2lhbiBwcm9jZXNzCiMnIHByaW9yIG9uIG1lYW4gYW5kIGxvZyBzdGFuZGFyZCBkZXZpYXRpb246CiMnICQkCiMnIHkgXHNpbSBcbWJveHtub3JtYWx9KFxtdSh4KSwgXGV4cChcZXRhKHgpKVxcCiMnIFxtdSBcc2ltIEdQKDAsIEtfMSlcXAojJyBcZXRhIFxzaW0gR1AoMCwgS18yKQojJyAkJAojJyAkS18xJCBhbmQgJEtfMiQgYXJlIGV4cG9uZW50aWF0ZWQgcXVhZHJhdGljIGNvdmFyaWFuY2UgZnVuY3Rpb25zLgojJwoKIysgc2V0dXAsIGluY2x1ZGU9RkFMU0UKa25pdHI6Om9wdHNfY2h1bmskc2V0KG1lc3NhZ2U9RkFMU0UsIGVycm9yPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjb21tZW50PU5BLCBjYWNoZT1GQUxTRSkKCiMnICMjIyMgTG9hZCBwYWNrYWdlcwpsaWJyYXJ5KGNtZHN0YW5yKSAKbGlicmFyeShwb3N0ZXJpb3IpCm9wdGlvbnMocGlsbGFyLm5lZyA9IEZBTFNFLCBwaWxsYXIuc3VidGxlPUZBTFNFLCBwaWxsYXIuc2lnZmlnPTIpCmxpYnJhcnkodGlkeXIpIApsaWJyYXJ5KGRwbHlyKSAKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGJheWVzcGxvdCkKdGhlbWVfc2V0KGJheWVzcGxvdDo6dGhlbWVfZGVmYXVsdChiYXNlX2ZhbWlseSA9ICJzYW5zIiwgYmFzZV9zaXplPTE2KSkKc2V0MSA8LSBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoNywgIlNldDEiKQpTRUVEIDwtIDQ4OTI3ICMgc2V0IHJhbmRvbSBzZWVkIGZvciByZXByb2R1Y2FiaWxpdHkKCiMnICMjIE1vdG9yY3ljbGUgYWNjaWRlbnQgYWNjZWxlcmF0aW9uIGRhdGEKIycgCiMnIExvYWQgZGF0YQpkYXRhKG1jeWNsZSwgcGFja2FnZT0iTUFTUyIpCmhlYWQobWN5Y2xlKQoKIycgUGxvdCBkYXRhCm1jeWNsZSAlPiUKICBnZ3Bsb3QoYWVzKHg9dGltZXMseT1hY2NlbCkpKwogIGdlb21fcG9pbnQoKSsKICBsYWJzKHg9IlRpbWUgKG1zKSIsIHk9IkFjY2VsZXJhdGlvbiAoZykiKQoKIycgIyMgR1AgbW9kZWwgd2l0aCBIaWxiZXJ0IGJhc2lzIGZ1bmN0aW9ucwojJwojJyBNb2RlbCBjb2RlCmZpbGUxIDwtICJncGJmMS5zdGFuIgp3cml0ZUxpbmVzKHJlYWRMaW5lcyhmaWxlMSkpCiMnIFRoZSBtb2RlbCBjb2RlIGluY2x1ZGVzIEhpbGJlcnQgc3BhY2UgYmFzaXMgZnVuY3Rpb24gaGVscGVycwp3cml0ZUxpbmVzKHJlYWRMaW5lcygiZ3BiYXNpc2Z1bl9mdW5jdGlvbnMuc3RhbiIpKQoKIycgQ29tcGlsZSBTdGFuIG1vZGVsCm1vZGVsMSA8LSBjbWRzdGFuX21vZGVsKHN0YW5fZmlsZSA9IGZpbGUxLCBpbmNsdWRlX3BhdGhzID0gIi4iKQoKIycgRGF0YSB0byBiZSBwYXNzZWQgdG8gU3RhbgpzdGFuZGF0YTEgPC0gbGlzdCh4PW1jeWNsZSR0aW1lcywKICAgICAgICAgICAgICAgICAgeT1tY3ljbGUkYWNjZWwsCiAgICAgICAgICAgICAgICAgIE49bGVuZ3RoKG1jeWNsZSR0aW1lcyksCiAgICAgICAgICAgICAgICAgIGNfZj0xLjUsICMgZmFjdG9yIGMgb2YgYmFzaXMgZnVuY3Rpb25zIGZvciBHUCBmb3IgZjEKICAgICAgICAgICAgICAgICAgTV9mPTQwLCAgIyBudW1iZXIgb2YgYmFzaXMgZnVuY3Rpb25zIGZvciBHUCBmb3IgZjEKICAgICAgICAgICAgICAgICAgY19nPTEuNSwgIyBmYWN0b3IgYyBvZiBiYXNpcyBmdW5jdGlvbnMgZm9yIEdQIGZvciBnMwogICAgICAgICAgICAgICAgICBNX2c9NDApICAjIG51bWJlciBvZiBiYXNpcyBmdW5jdGlvbnMgZm9yIEdQIGZvciBnMwoKIycgU2FtcGxlIHVzaW5nIGR5bmFtaWMgSE1DCiMrIGZpdDEsIHJlc3VsdHM9J2hpZGUnCmZpdDEgPC0gbW9kZWwxJHNhbXBsZShkYXRhPXN0YW5kYXRhMSwgaXRlcl93YXJtdXA9NTAwLCBpdGVyX3NhbXBsaW5nPTUwMCwKICAgICAgICAgICAgICAgICAgICAgIGNoYWlucz00LCBwYXJhbGxlbF9jaGFpbnM9MiwgYWRhcHRfZGVsdGE9MC45KQoKIycgQ2hlY2sgd2hldGhlciBwYXJhbWV0ZXJzIGhhdmUgcmVhc29uYWJsZSB2YWx1ZXMKZHJhd3MxIDwtIGZpdDEkZHJhd3MoKQpzdW1tYXJpc2VfZHJhd3Moc3Vic2V0KGRyYXdzMSwgdmFyaWFibGU9YygnaW50ZXJjZXB0Jywnc2lnbWFfJywnbGVuZ3Roc2NhbGVfJyksIHJlZ2V4PVRSVUUpKQoKIycgQ29tcGFyZSB0aGUgbW9kZWwgdG8gdGhlIGRhdGEKZHJhd3MxbSA8LSBhc19kcmF3c19tYXRyaXgoZHJhd3MxKQpFZiA8LSBjb2xNZWFucyhzdWJzZXQoZHJhd3MxbSwgdmFyaWFibGU9J2YnKSkKc2lnbWEgPC0gY29sTWVhbnMoc3Vic2V0KGRyYXdzMW0sIHZhcmlhYmxlPSdzaWdtYScpKQpwcmVkPC1kYXRhLmZyYW1lKEVmPUVmLHNpZ21hPXNpZ21hKQpjYmluZChtY3ljbGUscHJlZCkgJT4lCiAgZ2dwbG90KGFlcyh4PXRpbWVzLHk9YWNjZWwpKSsKICBnZW9tX3BvaW50KCkrCiAgbGFicyh4PSJUaW1lIChtcykiLCB5PSJBY2NlbGVyYXRpb24gKGcpIikrCiAgZ2VvbV9saW5lKGFlcyh5PUVmKSwgY29sb3I9c2V0MVsxXSkrCiAgZ2VvbV9saW5lKGFlcyh5PUVmLTIqc2lnbWEpLCBjb2xvcj1zZXQxWzFdLGxpbmV0eXBlPSJkYXNoZWQiKSsKICBnZW9tX2xpbmUoYWVzKHk9RWYrMipzaWdtYSksIGNvbG9yPXNldDFbMV0sbGluZXR5cGU9ImRhc2hlZCIpCgojJyBQbG90IHBvc3RlcmlvciBkcmF3cyBhbmQgcG9zdGVyaW9yIG1lYW4gb2YgdGhlIG1lYW4gZnVuY3Rpb24Kc3Vic2V0KGRyYXdzMSwgdmFyaWFibGU9ImYiKSAlPiUKICB0aGluX2RyYXdzKHRoaW49NSklPiUKICBhc19kcmF3c19kZigpICU+JQogIHBpdm90X2xvbmdlcighc3RhcnRzX3dpdGgoIi4iKSwKICAgICAgICAgICAgICAgbmFtZXNfdG89ImluZCIsCiAgICAgICAgICAgICAgIG5hbWVzX3RyYW5zZm9ybSA9IGxpc3QoaW5kID0gcmVhZHI6OnBhcnNlX251bWJlciksCiAgICAgICAgICAgICAgIHZhbHVlc190bz0ibXUiKSAlPiUKICBtdXRhdGUodGltZT1tY3ljbGUkdGltZXNbaW5kXSklPiUKICBnZ3Bsb3QoYWVzKHRpbWUsIG11LCBncm91cCA9IC5kcmF3KSkgKwogIGdlb21fbGluZShjb2xvcj1zZXQxWzJdLCBhbHBoYSA9IDAuMSkgKwogIGdlb21fcG9pbnQoZGF0YT1tY3ljbGUsIG1hcHBpbmc9YWVzKHg9dGltZXMseT1hY2NlbCksIGluaGVyaXQuYWVzPUZBTFNFKSsKICBnZW9tX2xpbmUoZGF0YT1jYmluZChtY3ljbGUscHJlZCksIG1hcHBpbmc9YWVzKHg9dGltZXMseT1FZiksIGluaGVyaXQuYWVzPUZBTFNFLCBjb2xvcj1zZXQxWzFdLCBzaXplPTEpKwogIGxhYnMoeD0iVGltZSAobXMpIiwgeT0iQWNjZWxlcmF0aW9uIChnKSIpCgojJyAjIyBHUCB3aXRoIGNvdmFyaWFuY2UgbWF0cmljZXMKIycgCiMnIE1vZGVsIGNvZGUKZmlsZTIgPC0gImdwY292LnN0YW4iCndyaXRlTGluZXMocmVhZExpbmVzKGZpbGUyKSkKCiMnIENvbXBpbGUgU3RhbiBtb2RlbAptb2RlbDIgPC0gY21kc3Rhbl9tb2RlbChzdGFuX2ZpbGUgPSBmaWxlMikKCiMnIERhdGEgdG8gYmUgcGFzc2VkIHRvIFN0YW4Kc3RhbmRhdGEyIDwtIGxpc3QoeD1tY3ljbGUkdGltZXMsCiAgICAgICAgICAgICAgICAgIHk9bWN5Y2xlJGFjY2VsLAogICAgICAgICAgICAgICAgICBOPWxlbmd0aChtY3ljbGUkdGltZXMpKQoKIycgU2FtcGxlIHVzaW5nIGR5bmFtaWMgSE1DCiMrIGZpdDIsIHJlc3VsdHM9J2hpZGUnCmZpdDIgPC0gbW9kZWwyJHNhbXBsZShkYXRhPXN0YW5kYXRhMiwgaXRlcl93YXJtdXA9MTAwLCBpdGVyX3NhbXBsaW5nPTEwMCwKICAgICAgICAgICAgICAgICAgICAgIGNoYWlucz00LCBwYXJhbGxlbF9jaGFpbnM9MiwgcmVmcmVzaD0xMCkKCiMnIENoZWNrIHdoZXRoZXIgcGFyYW1ldGVycyBoYXZlIHJlYXNvbmFibGUgdmFsdWVzCmRyYXdzMiA8LSBmaXQyJGRyYXdzKCkKc3VtbWFyaXNlX2RyYXdzKHN1YnNldChkcmF3czIsIHZhcmlhYmxlPWMoJ3NpZ21hXycsJ2xlbmd0aHNjYWxlXycpLCByZWdleD1UUlVFKSkKCiMnIENvbXBhcmUgdGhlIG1vZGVsIHRvIHRoZSBkYXRhCmRyYXdzMm0gPC0gYXNfZHJhd3NfbWF0cml4KGRyYXdzMikKRWYgPC0gY29sTWVhbnMoc3Vic2V0KGRyYXdzMm0sIHZhcmlhYmxlPSdmJykpCnNpZ21hIDwtIGNvbE1lYW5zKHN1YnNldChkcmF3czJtLCB2YXJpYWJsZT0nc2lnbWEnKSkKcHJlZDwtZGF0YS5mcmFtZShFZj1FZixzaWdtYT1zaWdtYSkKY2JpbmQobWN5Y2xlLHByZWQpICU+JQogIGdncGxvdChhZXMoeD10aW1lcyx5PWFjY2VsKSkrCiAgZ2VvbV9wb2ludCgpKwogIGxhYnMoeD0iVGltZSAobXMpIiwgeT0iQWNjZWxlcmF0aW9uIChnKSIpKwogIGdlb21fbGluZShhZXMoeT1FZiksIGNvbG9yPXNldDFbMV0pKwogIGdlb21fbGluZShhZXMoeT1FZi0yKnNpZ21hKSwgY29sb3I9c2V0MVsxXSxsaW5ldHlwZT0iZGFzaGVkIikrCiAgZ2VvbV9saW5lKGFlcyh5PUVmKzIqc2lnbWEpLCBjb2xvcj1zZXQxWzFdLGxpbmV0eXBlPSJkYXNoZWQiKQoKIycgUGxvdCBwb3N0ZXJpb3IgZHJhd3MgYW5kIHBvc3RlcmlvciBtZWFuIG9mIHRoZSBtZWFuIGZ1bmN0aW9uCnN1YnNldChkcmF3czIsIHZhcmlhYmxlPSJmIikgJT4lCiAgYXNfZHJhd3NfZGYoKSAlPiUKICBwaXZvdF9sb25nZXIoIXN0YXJ0c193aXRoKCIuIiksCiAgICAgICAgICAgICAgIG5hbWVzX3RvPSJpbmQiLAogICAgICAgICAgICAgICBuYW1lc190cmFuc2Zvcm0gPSBsaXN0KGluZCA9IHJlYWRyOjpwYXJzZV9udW1iZXIpLAogICAgICAgICAgICAgICB2YWx1ZXNfdG89Im11IikgJT4lCiAgbXV0YXRlKHRpbWU9bWN5Y2xlJHRpbWVzW2luZF0pJT4lCiAgZ2dwbG90KGFlcyh0aW1lLCBtdSwgZ3JvdXAgPSAuZHJhdykpICsKICBnZW9tX2xpbmUoY29sb3I9c2V0MVsyXSwgYWxwaGEgPSAwLjEpICsKICBnZW9tX3BvaW50KGRhdGE9bWN5Y2xlLCBtYXBwaW5nPWFlcyh4PXRpbWVzLHk9YWNjZWwpLCBpbmhlcml0LmFlcz1GQUxTRSkrCiAgZ2VvbV9saW5lKGRhdGE9Y2JpbmQobWN5Y2xlLHByZWQpLCBtYXBwaW5nPWFlcyh4PXRpbWVzLHk9RWYpLCBpbmhlcml0LmFlcz1GQUxTRSwgY29sb3I9c2V0MVsxXSwgc2l6ZT0xKSArCiAgbGFicyh4PSJUaW1lIChtcykiLCB5PSJBY2NlbGVyYXRpb24gKGcpIikK