1 Description

Uses a collapsed (marginalizes over a, b, amp numerically) gibbs sampler which samples from the conditionals with slice sampling. Also re-parameterizes density with (sqrt(gwidth^2+lwidth^2), gwidth-lwidth) instead of (gwidth, lwitdh) if min(gwidth/lwidth, lwidth/gwidth) > 0.05 (otherwise transformation becomes unstable). Finds global maxima in posterior using population based golden section search and uses it as the starting point for the slice sampler. About x10 times faster than the previous implementation. Using Zell Prior could be another x10.

2 Data

library('plotly')
Loading required package: ggplot2

Attaching package: 'plotly'
The following object is masked from 'package:ggplot2':

    last_plot
The following object is masked from 'package:stats':

    filter
The following object is masked from 'package:graphics':

    layout
library(spectralmc)

#toluene data
cdata <- get_toluene_measured_data()
x <- cdata$x
y <- cdata$y

3 Algorithm

peak_spawner <- function() {
  params <- list(
          amp = rnorm(1, 1, 1), # doesnt matter
          lwidth = rnorm(1, 0.1, 0.001), # matters!!!
          gwidth = rnorm(1, 5, 0.001), # matters!!!
          pos = runif(1, 1, 1), # doesnt matter
          a = 0, # doesnt matter
          b = 0 # doesnt matter
  )
  return(params)
}

fit_kp <- 30
max_a <- 1e-2
lower <- c(-Inf, -max_a, 0)
upper <- c(Inf, max_a, Inf)
noise_sigma <- 100 # estimated by eye; intentionally to big such that tiny peaks are overnoised
signal_model <- voigt.model

res <- chains.rjmcmc_like_collapsed_slicer_gibbs(
        x,
        y,
        signal_model,
        peak_spawner,
        lower,
        upper,
        noise_sigma,
        fit_kp = fit_kp,
        add_peak_every = 10,
        iter = 20000,
        print_bar = TRUE,
        half_steps = TRUE,
        decorr_trafo = TRUE,
        save_loc = save_loc,
        checkpoint_every = 50,
)

4 Results

4.1 Fit

4.1.1 All

plt.fit.chain.interactive(x, y, res$samples, signal_model, step_width = 200)

4.1.2 Burn-in

plt.fit.chain.interactive(x, y, res$samples[seq(1, 500)], signal_model, step_width = 5)

4.1.3 Traceplots

plt.traceplot(res$samples, "pos")
plt.traceplot(res$samples, "amp")
plt.traceplot(res$samples, "lwidth")
plt.traceplot(res$samples, "gwidth")
plt.traceplot(res$samples, "a")
plt.traceplot(res$samples, "b")

4.1.4 Posterior plot

plt.posterior(res$post_vals, steps = NULL)

4.1.5 Fit vs True

plt.samples_vs_true(samples, 'gwidth')
plt.samples_vs_true(samples, 'lwidth')

5 Todo

  • Critical Bug amp, a, b are not sampled instead they are set to roughly their means, easy to fix.
  • Not a bug: Uses a safeguard which prevents messing up the fit if the slice sampler fails, this however destroys the gibbs sampler property and causes rejection plateaus.

  1. RWTH Aachen, ↩︎

  2. This work was developed under a Seed Fund Project (2021) of the RWTH Aachen University, funded under "the Excellence Strategy of the Federal Government and the Länder". Project: "Spectra-Bayes: A Bayesian statistical machine learning model for spectral reconstruction" (Project Leaders: Prof. Dr. Maria Kateri, Prof. Dr.-Ing. Hans-Jürgen Koß) ↩︎

LS0tDQp0aXRsZTogIkNvbGxhcHNlZCBHaWJicyBTYW1wbGVyIHdpdGggR1NTIFNsaWNlIFNhbXBsaW5nIg0KYXV0aG9yOiBKYW4gTWVpw59uZXJeW1JXVEggQWFjaGVuLCBwaGlsaXBwLm1laXNzbmVyQHJ3dGgtYWFjaGVuLmRlXQ0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogNQ0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogZmFsc2UNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCi0tLQ0KDQo8c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCI+DQogIGZ1bmN0aW9uIGp1bXBfaGVhZGVyKGtleSl7DQogICAgJCgnOmhlYWRlcjpjb250YWlucygnK2tleSsnKScpWzBdLnNjcm9sbEludG9WaWV3KCk7DQogIH0NCg0KICBmdW5jdGlvbiBqdW1wX21hcmtlZChrZXkpew0KICAgICQoJyMnICsga2V5KVswXS5zY3JvbGxJbnRvVmlldygpOw0KICB9DQoNCiAgZnVuY3Rpb24gcGxvdFpvb20oZWwpew0KICAgICAgaWYoZG9jdW1lbnQuZnVsbHNjcmVlbikgew0KICAgICAgICBkb2N1bWVudC5leGl0RnVsbHNjcmVlbigpDQogICAgICB9IGVsc2Ugew0KICAgICAgICAkKGVsKS5jbG9zZXN0KCcuanMtcGxvdGx5LXBsb3QnKVswXS5yZXF1ZXN0RnVsbHNjcmVlbigpOw0KICAgICAgfQ0KICB9DQoNCiAgJCggZG9jdW1lbnQgKS5yZWFkeShmdW5jdGlvbigpIHsNCiAgICAkKCIubW9kZWJhci1idG4ucGxvdGx5anNpY29uLm1vZGViYXItYnRuLS1sb2dvIikucmVwbGFjZVdpdGgoDQogICAgYA0KICAgIDxhIHJlbD0idG9vbHRpcCIgb25jbGljaz1wbG90Wm9vbSh0aGlzKSBjbGFzcz0ibW9kZWJhci1idG4gZnVsbHNjcmVlbi1idG4iIGRhdGEtdGl0bGU9IkZ1bGwgU2NyZWVuIiBkYXRhLWF0dHI9Inpvb20iIGRhdGEtdmFsPSJhdXRvIiBkYXRhLXRvZ2dsZT0iZmFsc2UiIGRhdGEtZ3Jhdml0eT0ibiIgPg0KICAgICAgPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NDggNTEyIiBjbGFzcz0iaWNvbiIgaGVpZ2h0PSIxZW0iIHdpZHRoPSIxZW0iPg0KICAgICAgICA8IS0tISBGb250IEF3ZXNvbWUgUHJvIDYuMS4xIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlIChDb21tZXJjaWFsIExpY2Vuc2UpIENvcHlyaWdodCAyMDIyIEZvbnRpY29ucywgSW5jLiAtLT4NCiAgICAgICAgPHBhdGggZD0iTTEyOCAzMkgzMkMxNC4zMSAzMiAwIDQ2LjMxIDAgNjR2OTZjMCAxNy42OSAxNC4zMSAzMiAzMiAzMnMzMi0xNC4zMSAzMi0zMlY5Nmg2NGMxNy42OSAwIDMyLTE0LjMxIDMyLTMyUzE0NS43IDMyIDEyOCAzMnpNNDE2IDMyaC05NmMtMTcuNjkgMC0zMiAxNC4zMS0zMiAzMnMxNC4zMSAzMiAzMiAzMmg2NHY2NGMwIDE3LjY5IDE0LjMxIDMyIDMyIDMyczMyLTE0LjMxIDMyLTMyVjY0QzQ0OCA0Ni4zMSA0MzMuNyAzMiA0MTYgMzJ6TTEyOCA0MTZINjR2LTY0YzAtMTcuNjktMTQuMzEtMzItMzItMzJzLTMyIDE0LjMxLTMyIDMydjk2YzAgMTcuNjkgMTQuMzEgMzIgMzIgMzJoOTZjMTcuNjkgMCAzMi0xNC4zMSAzMi0zMlMxNDUuNyA0MTYgMTI4IDQxNnpNNDE2IDMyMGMtMTcuNjkgMC0zMiAxNC4zMS0zMiAzMnY2NGgtNjRjLTE3LjY5IDAtMzIgMTQuMzEtMzIgMzJzMTQuMzEgMzIgMzIgMzJoOTZjMTcuNjkgMCAzMi0xNC4zMSAzMi0zMnYtOTZDNDQ4IDMzNC4zIDQzMy43IDMyMCA0MTYgMzIweiIvPg0KICAgICAgPC9zdmc+DQogICAgPC9hPg0KICAgIGApOw0KICB9KTsNCjwvc2NyaXB0Pg0KDQoNCiMgRGVzY3JpcHRpb24NClVzZXMgYSBjb2xsYXBzZWQgKG1hcmdpbmFsaXplcyBvdmVyIGBhYCwgYGJgLCBgYW1wYCBudW1lcmljYWxseSkNCmdpYmJzIHNhbXBsZXIgd2hpY2ggc2FtcGxlcyBmcm9tIHRoZSBjb25kaXRpb25hbHMgd2l0aCBzbGljZSBzYW1wbGluZy4gQWxzbyByZS1wYXJhbWV0ZXJpemVzIGRlbnNpdHkgd2l0aCBgKHNxcnQoZ3dpZHRoXjIrbHdpZHRoXjIpLCBnd2lkdGgtbHdpZHRoKWAgaW5zdGVhZCBvZiBgKGd3aWR0aCwgbHdpdGRoKWAgaWYgYG1pbihnd2lkdGgvbHdpZHRoLCBsd2lkdGgvZ3dpZHRoKSA+IDAuMDVgIChvdGhlcndpc2UgdHJhbnNmb3JtYXRpb24gYmVjb21lcyB1bnN0YWJsZSkuIEZpbmRzIGdsb2JhbCBtYXhpbWEgaW4gcG9zdGVyaW9yIHVzaW5nIHBvcHVsYXRpb24gYmFzZWQgZ29sZGVuIHNlY3Rpb24gc2VhcmNoIGFuZCB1c2VzIGl0IGFzIHRoZSBzdGFydGluZyBwb2ludCBmb3IgdGhlIHNsaWNlIHNhbXBsZXIuIEFib3V0IHgxMCB0aW1lcyBmYXN0ZXIgdGhhbiB0aGUgcHJldmlvdXMgaW1wbGVtZW50YXRpb24uIFVzaW5nIFplbGwgUHJpb3IgY291bGQgYmUgYW5vdGhlciB4MTAuDQoNCiMgRGF0YQ0KYGBge3IgZWNobyA9IEZBTFNFfQ0KI3NldHdkKCdDOi9Vc2Vycy9KYW4vRGVza3RvcC9JU1dfU3BlY3RyYUJheWVzL0lTV19TcGVjdHJhQmF5ZXMvYmF5ZXNfbWNtYycpDQoNCmdldF90b2x1ZW5lX21lYXN1cmVkX2RhdGEgPC0gZnVuY3Rpb24oKXsNCiAgcGF0aCA8LSAiQzovVXNlcnMvSmFuL0Rlc2t0b3AvSVNXX1NwZWN0cmFCYXllcy9JU1dfU3BlY3RyYUJheWVzL2JheWVzX21jbWMvbm90ZWJvb2tzL2dvbGRlbl9naWJic190b2x1bmUvdG9sdWVuZV9tZWFzdXJlZC5jc3YiDQogIHRvbHVlbmVfbWVhc3VyZWQgPC0gcmVhZC5jc3YocGF0aCwgaGVhZGVyPUZBTFNFLCBzZXA9IjsiKQ0KICB4IDwtIGFzLnZlY3Rvcih0b2x1ZW5lX21lYXN1cmVkWywxXSkNCiAgeXRydWUgPC0gYXMudmVjdG9yKHRvbHVlbmVfbWVhc3VyZWRbLDJdKQ0KICByZXR1cm4obGlzdCh4PXgseT15dHJ1ZSkpDQp9DQpgYGANCmBgYHtyfQ0KbGlicmFyeSgncGxvdGx5JykNCmxpYnJhcnkoc3BlY3RyYWxtYykNCg0KI3RvbHVlbmUgZGF0YQ0KY2RhdGEgPC0gZ2V0X3RvbHVlbmVfbWVhc3VyZWRfZGF0YSgpDQp4IDwtIGNkYXRhJHgNCnkgPC0gY2RhdGEkeQ0KYGBgDQojIEFsZ29yaXRobQ0KYGBge3IgZXZhbCA9IEZBTFNFfQ0KcGVha19zcGF3bmVyIDwtIGZ1bmN0aW9uKCkgew0KICBwYXJhbXMgPC0gbGlzdCgNCiAgICAgICAgICBhbXAgPSBybm9ybSgxLCAxLCAxKSwgIyBkb2VzbnQgbWF0dGVyDQogICAgICAgICAgbHdpZHRoID0gcm5vcm0oMSwgMC4xLCAwLjAwMSksICMgbWF0dGVycyEhIQ0KICAgICAgICAgIGd3aWR0aCA9IHJub3JtKDEsIDUsIDAuMDAxKSwgIyBtYXR0ZXJzISEhDQogICAgICAgICAgcG9zID0gcnVuaWYoMSwgMSwgMSksICMgZG9lc250IG1hdHRlcg0KICAgICAgICAgIGEgPSAwLCAjIGRvZXNudCBtYXR0ZXINCiAgICAgICAgICBiID0gMCAjIGRvZXNudCBtYXR0ZXINCiAgKQ0KICByZXR1cm4ocGFyYW1zKQ0KfQ0KDQpmaXRfa3AgPC0gMzANCm1heF9hIDwtIDFlLTINCmxvd2VyIDwtIGMoLUluZiwgLW1heF9hLCAwKQ0KdXBwZXIgPC0gYyhJbmYsIG1heF9hLCBJbmYpDQpub2lzZV9zaWdtYSA8LSAxMDAgIyBlc3RpbWF0ZWQgYnkgZXllOyBpbnRlbnRpb25hbGx5IHRvIGJpZyBzdWNoIHRoYXQgdGlueSBwZWFrcyBhcmUgb3Zlcm5vaXNlZA0Kc2lnbmFsX21vZGVsIDwtIHZvaWd0Lm1vZGVsDQoNCnJlcyA8LSBjaGFpbnMucmptY21jX2xpa2VfY29sbGFwc2VkX3NsaWNlcl9naWJicygNCiAgICAgICAgeCwNCiAgICAgICAgeSwNCiAgICAgICAgc2lnbmFsX21vZGVsLA0KICAgICAgICBwZWFrX3NwYXduZXIsDQogICAgICAgIGxvd2VyLA0KICAgICAgICB1cHBlciwNCiAgICAgICAgbm9pc2Vfc2lnbWEsDQogICAgICAgIGZpdF9rcCA9IGZpdF9rcCwNCiAgICAgICAgYWRkX3BlYWtfZXZlcnkgPSAxMCwNCiAgICAgICAgaXRlciA9IDIwMDAwLA0KICAgICAgICBwcmludF9iYXIgPSBUUlVFLA0KICAgICAgICBoYWxmX3N0ZXBzID0gVFJVRSwNCiAgICAgICAgZGVjb3JyX3RyYWZvID0gVFJVRSwNCiAgICAgICAgc2F2ZV9sb2MgPSBzYXZlX2xvYywNCiAgICAgICAgY2hlY2twb2ludF9ldmVyeSA9IDUwLA0KKQ0KYGBgDQpgYGB7ciAgZWNobz1GQUxTRX0NCnNldHdkKCdDOi9Vc2Vycy9KYW4vRGVza3RvcC9JU1dfU3BlY3RyYUJheWVzL0lTV19TcGVjdHJhQmF5ZXMvYmF5ZXNfbWNtYycpDQpyZXMgPC0gcmVhZFJEUygibm90ZWJvb2tzL2dvbGRlbl9naWJic190b2x1bmUvcmVzLlJEYXRhIikNCmZpdF9rcCA8LSAzMA0KbWF4X2EgPC0gMWUtMg0KbG93ZXIgPC0gYygtSW5mLCAtbWF4X2EsIDApDQp1cHBlciA8LSBjKEluZiwgbWF4X2EsIEluZikNCm5vaXNlX3NpZ21hIDwtIDEwMCAjIGVzdGltYXRlZCBieSBleWU7IGludGVudGlvbmFsbHkgdG8gYmlnIHN1Y2ggdGhhdCB0aW55IHBlYWtzIGFyZSBvdmVybm9pc2VkDQpzaWduYWxfbW9kZWwgPC0gdm9pZ3QubW9kZWwNCmBgYA0KIyBSZXN1bHRzDQoNCiMjIEZpdA0KDQojIyMgQWxsDQpgYGB7cn0NCnBsdC5maXQuY2hhaW4uaW50ZXJhY3RpdmUoeCwgeSwgcmVzJHNhbXBsZXMsIHNpZ25hbF9tb2RlbCwgc3RlcF93aWR0aCA9IDIwMCkNCmBgYA0KDQojIyMgQnVybi1pbg0KYGBge3J9DQpwbHQuZml0LmNoYWluLmludGVyYWN0aXZlKHgsIHksIHJlcyRzYW1wbGVzW3NlcSgxLCA1MDApXSwgc2lnbmFsX21vZGVsLCBzdGVwX3dpZHRoID0gNSkNCmBgYA0KIyMjIFRyYWNlcGxvdHMNCmBgYHtyfQ0KcGx0LnRyYWNlcGxvdChyZXMkc2FtcGxlcywgInBvcyIpDQpwbHQudHJhY2VwbG90KHJlcyRzYW1wbGVzLCAiYW1wIikNCnBsdC50cmFjZXBsb3QocmVzJHNhbXBsZXMsICJsd2lkdGgiKQ0KcGx0LnRyYWNlcGxvdChyZXMkc2FtcGxlcywgImd3aWR0aCIpDQpwbHQudHJhY2VwbG90KHJlcyRzYW1wbGVzLCAiYSIpDQpwbHQudHJhY2VwbG90KHJlcyRzYW1wbGVzLCAiYiIpDQpgYGANCiMjIyBQb3N0ZXJpb3IgcGxvdA0KYGBge3J9DQpwbHQucG9zdGVyaW9yKHJlcyRwb3N0X3ZhbHMsIHN0ZXBzID0gTlVMTCkNCmBgYA0KDQpgYGB7ciAgZWNobz1GQUxTRX0NCg0KcGx0LnNhbXBsZXNfdnNfdHJ1ZSA8LSBmdW5jdGlvbiAoc2FtcGxlcywgb3RoZXJuYW1lID0gJ2d3aWR0aCcpIHsNCiAgZmxhdHNnX2RhdGEgPC0gc3BlY3RyYWxtYzo6dXRpbHMuZmxhdHRlbl9zYW1wbGVzKHNhbXBsZXMpDQogIG4gPC0gbGVuZ3RoKHNhbXBsZXNbWzFdXSRwb3MpDQoNCiAgYWxsX3BvcyA8LSBjKCkNCiAgZm9yIChpIGluIHNlcShuKSl7DQogICAgYWxsX3BvcyA8LSBjKGFsbF9wb3MsIGZsYXRzZ19kYXRhJGZsYXRfc2FtcGxlc1twYXN0ZTAoJ3Bvc1snLGksJ10nKV0pDQogIH0NCiAgYWxsX3BvcyA8LSBSZWR1Y2UoYyxhbGxfcG9zKQ0KDQogIGFsbF9vdGhlciA8LSBjKCkNCiAgZm9yIChpIGluIHNlcShuKSl7DQogICAgYWxsX290aGVyIDwtIGMoYWxsX290aGVyLCBmbGF0c2dfZGF0YSRmbGF0X3NhbXBsZXNbcGFzdGUwKG90aGVybmFtZSwnWycsaSwnXScpXSkNCiAgfQ0KICBhbGxfb3RoZXIgPC0gUmVkdWNlKGMsYWxsX290aGVyKQ0KDQogIHBlYWtzX3RydWUgPC0gcGVha3NfdHJ1ZVtjKDEsMyw0KSwgc2VxX2xlbihuY29sKHBlYWtzX3RydWUpKV0NCiAgdHJ1ZV9wb3MgPC0gdW5saXN0KHBlYWtzX3RydWVbMSxzZXFfbGVuKG5jb2wocGVha3NfdHJ1ZSkpXSkNCiAgaWYgKG90aGVybmFtZSA9PSAnZ3dpZHRoJyl7DQogICAgdHJ1ZV9vdGhlciA8LSB1bmxpc3QocGVha3NfdHJ1ZVsyLHNlcV9sZW4obmNvbChwZWFrc190cnVlKSldKQ0KICB9IGVsc2Ugew0KICAgIHRydWVfb3RoZXIgPC0gdW5saXN0KHBlYWtzX3RydWVbMyxzZXFfbGVuKG5jb2wocGVha3NfdHJ1ZSkpXSkNCiAgfQ0KDQogIGZpZyA8LSBwbG90X2x5KHggPSBhbGxfcG9zLCB5ID0gYWxsX290aGVyLCB0eXBlID0gJ3NjYXR0ZXInLCBuYW1lID0gJ1NhbXBsZXMnLA0KICAgICAgICAgICAgICAgICBtb2RlID0gJ21hcmtlcnMnLCBtYXJrZXIgPSBsaXN0KHNpemUgPSA1LCBvcGFjaXR5PTAuNSkpDQogIGZpZyA8LSBmaWcgJT4lIHBsb3RseTo6YWRkX21hcmtlcnMoeCA9IHRydWVfcG9zLCB5ID0gdHJ1ZV9vdGhlciwgdHlwZSA9ICdzY2F0dGVyJywgbmFtZSA9ICdUcnVlIFBhcmFtcycsIG1vZGUgPSAnbWFya2VycycsIG1hcmtlciA9IGxpc3QoY29sb3IgPSAncmVkJywgc3ltYm9sID0gJ3gnLCBvcGFjaXR5PTEpKQ0KICBmaWcgPC0gZmlnICU+JSBwbG90bHk6OmxheW91dCh0aXRsZSA9ICJQZWFrIFBhcmFtZXRlcnM6IFNhbXBsZXMgdnMuIFRydWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiUG9zaXRpb24iLCB6ZXJvbGluZSA9IEZBTFNFKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gb3RoZXJuYW1lLCBleHBvbmVudGZvcm1hdCA9ICdlJykpDQogIGZpZw0KfQ0KDQpsaWJyYXJ5KCdwbG90bHknKQ0KDQpzZXR3ZCgnQzovVXNlcnMvSmFuL0Rlc2t0b3AvSVNXX1NwZWN0cmFCYXllcy9JU1dfU3BlY3RyYUJheWVzL2JheWVzX21jbWMnKQ0KcmVzIDwtIHJlYWRSRFMoIm5vdGVib29rcy9nb2xkZW5fZ2liYnNfdG9sdW5lL3Jlcy5SRGF0YSIpDQpwYXRoIDwtICJDOi9Vc2Vycy9KYW4vRGVza3RvcC9JU1dfU3BlY3RyYUJheWVzL0lTV19TcGVjdHJhQmF5ZXMvYmF5ZXNfbWNtYy9ub3RlYm9va3MvZ29sZGVuX2dpYmJzX3RvbHVuZS90b2x1ZW5lX3BlYWtzLmNzdiINCg0KcGVha3NfdHJ1ZSA8LSByZWFkLmNzdihwYXRoLCBoZWFkZXI9RkFMU0UsIHNlcD0iLCIpDQoNCnNhbXBsZXMgPC0gcmVzJHNhbXBsZXNbMTUwMDpsZW5ndGgocmVzJHNhbXBsZXMpXQ0Kc2FtcGxlcyA8LSBzYW1wbGVzW3NlcSgxLCBsZW5ndGgoc2FtcGxlcyksIGxlbmd0aC5vdXQgPSA1MCldDQpgYGANCiMjIyBGaXQgdnMgVHJ1ZQ0KYGBge3J9DQpwbHQuc2FtcGxlc192c190cnVlKHNhbXBsZXMsICdnd2lkdGgnKQ0KcGx0LnNhbXBsZXNfdnNfdHJ1ZShzYW1wbGVzLCAnbHdpZHRoJykNCmBgYA0KDQojIFRvZG8NCi0gKipDcml0aWNhbCBCdWcqKiBhbXAsIGEsIGIgYXJlIG5vdCBzYW1wbGVkIGluc3RlYWQgdGhleSBhcmUgc2V0IHRvIHJvdWdobHkgdGhlaXIgbWVhbnMgKG5vdCByZWFsbHkgZWl0aGVyKTsgRWFzeSB0byBmaXguDQotICoqTm90IGEgYnVnKio6IFVzZXMgYSBzYWZlZ3VhcmQgd2hpY2ggcHJldmVudHMgbWVzc2luZyB1cCB0aGUgZml0IGlmIHRoZSBzbGljZSBzYW1wbGVyIGZhaWxzLCB0aGlzIGhvd2V2ZXIgZGVzdHJveXMgdGhlIGdpYmJzIHNhbXBsZXIgcHJvcGVydHkgYW5kIGNhdXNlcyByZWplY3Rpb24gcGxhdGVhdXMuDQotICoqRG9uZSoqOiBudSwgYmUgdHJhbnNmb3JtYXRpb24gYmVjb21lcyBiYWQgKHdoeT8pIGlmIGVpdGhlciBsd2lkdGggb3IgZ3dpZHRoIGlzIHJlYWxseSBzbWFsbCBjb21wYXJlZCB0byB0aGUgb3RoZXIsIHNob3VsZCBzd2l0Y2ggdG8gbnUsYmUgb25seSBpZiBgbWluKGx3aWR0aC9nd2lkdGgsIGd3aWR0aC9sd2lkdGgpID4gMC4wNWANCg==