We will now work with Raman spectroscopy data from the luminescence of a ruby particle under high pressure: Data/rubis_xx.txt. This measurement is performed in a diamond anvil cell with a laser of wavelength \(\lambda_L\)=568.189 nm, and the pressure is known by calibrating the frequency of a diamond line.

The ruby luminescence contains two lines, and we want to use the position of the R1 line, which is the most intense and has the highest energy, to determine the pressure. We therefore need to determine the parameters \(A\) and \(B\) of the equation of state linking wavelength (in nm) and pressure (in GPa):

\[ P~[GPa] = \frac{A}{B}\left[\left(\frac{\lambda}{\lambda_0}\right)^B-1\right], \] where \(\lambda_0\) = 694.24 nm is the wavelength of the R1 line at zero pressure, and \(\lambda\) is the wavelength of the R1 line at \(P\) pressure. For a given line, the relationship between its measured Raman shift, \(\omega\), and its wavelength is given by \(\lambda = \frac{10^7}{\omega_L - \omega}\), with \(\omega_L= \frac{10^7}{\lambda_L}\) the frequency of the probe laser.

Open one of the files, for example Data/rubis_01.txt to see its structure - they’re all the same. They are made up of two columns, which are the Raman shifts in cm-1 and the intensities. The nomenclature of the files "Data/rubis_xx.txt" is such that xx is the pressure in GPa.

First, consider the steps involved in determining the parameters \(A\) and \(B\) of the equation of state.

  • What data do you need?
  • What steps do you need to take to obtain them?

  • Let’s start by defining the function Prubis(w,A,B), which returns the pressure in GPa as a function of the Raman shift in cm-1, and the parameters \(A\) and \(B\).
Prubis <- function(w,A,B){
    wl  <-  1e7/568.189
    lamb0  <-  694.24
    lamb  <-  1e7/(wl - w)
    return(A/B * ((lamb/lamb0)^B - 1))
}

Find in the folder “Data” the list of files with the pattern “rubis_” in their name (check out the list.files() function) and store it in flist

flist <- list.files(path="Data", pattern = "rubis_")

What is the length of flist?

length(flist)
## [1] 17

Initialize an empty tibble called spec that will store all spectra:

library(tidyverse)
spec   <- tibble()

Using a for loop, load each file in flist and store them into spec by making it tidy.

for(file in flist){
    d <- read_table(file.path("Data",file), col_names = c("w","int"))
    d$name <- file
    spec <- bind_rows(spec,d)
}
spec
## # A tibble: 17,255 × 3
##        w   int name        
##    <dbl> <dbl> <chr>       
##  1 3064.  43.9 rubis_01.txt
##  2 3064.  47.9 rubis_01.txt
##  3 3064.  44.5 rubis_01.txt
##  4 3065.  50.5 rubis_01.txt
##  5 3065.  50.5 rubis_01.txt
##  6 3065.  44.5 rubis_01.txt
##  7 3065.  44.9 rubis_01.txt
##  8 3066.  39.9 rubis_01.txt
##  9 3066.  49.5 rubis_01.txt
## 10 3066.  48.9 rubis_01.txt
## # ℹ 17,245 more rows

Another version of this that does not use a for loop would be like so (try to understand what’s happening in this code by running each line one after the other and looking at the help on the functions used):

# spec <- tibble(name=flist) %>% 
#     mutate(data=map(name, ~read_table(file.path("Data",.), 
#                                       col_names = c("w","int")))
#           ) %>% 
#     unnest(data)

Modify spec to get the pressure in GPa from the file name, and so that the intensity column is normalized to [0,1].

norm01 <- function(x){
    (x-min(x))/(max(x)-min(x))
}
spec <- spec %>% 
    mutate(pressure = as.numeric(str_extract(name, "[0-9]+"))) %>% 
    group_by(name) %>% 
    mutate(int=norm01(int))

Plot the first spectrum in spec to see the x range

spec %>% 
    filter(name==flist[1]) %>%
    ggplot(aes(x=w, y=int))+
        geom_line()+
        labs(x="Raman Shift [1/cm]", y="Intensity [arb. units]")+
        theme_bw()


Using ggplot2, plot all spectra normalized to [0,1] (with points) and stacked on top of each other with a vertical shift corresponding to the pressure in GPa

spec %>% 
    ggplot(aes(x=w, 
               y=int + pressure,
               group=name))+
        geom_point(size=.2, alpha=.2)+
        labs(x="Raman Shift [1/cm]", y="Pressure [GPa]")+
        theme_bw()


What we want to do now is to fit this data by the sum of two Lorentzians to find the precise position of the R1 line as a function of pressure. To do this, we’ll use the function nls(), which allows us to make a non-linear fit of the data. We also need to define the function lor(x,x0,gamma) which returns a Lorentzian centered at x0 and of width at half-height gamma. Start by defining the function lor(x,x0,gamma).

lor <- function(x,x0,gamma){
    2/pi/gamma / (1 + ((x-x0)/(gamma/2))^2)
}

Now create an empty tibble called results, in which we’ll store all the fitting parameters in a tidy tibble. Then fit each spectrum by the sum of two Lorentzians, and save all fit parameters in results, along with the file name and pressure. For the fit to work, you’ll need to give reasonable initial values for the fit parameters using the start=list(...) argument of nls(). For example, you can use the which.max() function to find the index of the maximum of a vector.

results <- tibble()
for(file in flist){
    d <- spec %>% filter(name==file)
    fit <- nls(int ~ A1*lor(w, x1, gamma1) + A2*lor(w, x2, gamma2), 
               data=d, 
               start=list(x1=d$w[which.max(d$int)], 
                          x2=d$w[which.max(d$int)]-30, 
                          gamma1=10, gamma2=10,
                          A1=10,A2=7))
    res <- tibble(file, 
                  pressure=unique(d$pressure),
                  as_tibble(t(coef(fit))))
    results <- bind_rows(results, res)
}
results
## # A tibble: 17 × 8
##    file         pressure    x1    x2 gamma1 gamma2    A1    A2
##    <chr>           <dbl> <dbl> <dbl>  <dbl>  <dbl> <dbl> <dbl>
##  1 rubis_01.txt        1 3203. 3173.   15.0  11.1   21.8 10.8 
##  2 rubis_02.txt        2 3212. 3182.   11.9   8.92  18.1  8.76
##  3 rubis_03.txt        3 3219. 3189.   11.9   8.96  18.3  8.78
##  4 rubis_04.txt        4 3226. 3196.   12.3   9.16  18.8  8.70
##  5 rubis_05.txt        5 3234. 3205.   13.9  10.2   21.4  9.68
##  6 rubis_06.txt        6 3241. 3212.   13.6  10.2   20.9  9.47
##  7 rubis_07.txt        7 3248. 3219.   14.6  10.7   22.6  9.71
##  8 rubis_08.txt        8 3256. 3226.   15.3  11.0   23.4  9.90
##  9 rubis_10.txt       10 3270. 3241.   18.2  12.5   27.7 10.4 
## 10 rubis_12.txt       12 3285. 3255.   19.5  13.5   29.7 10.8 
## 11 rubis_14.txt       14 3301. 3271.   22.0  15.2   33.4 11.3 
## 12 rubis_17.txt       17 3322. 3291.   23.8  14.8   37.0 11.0 
## 13 rubis_21.txt       21 3348. 3315.   29.7  17.4   46.0 11.9 
## 14 rubis_23.txt       23 3361. 3328.   32.1  17.4   50.1 11.7 
## 15 rubis_25.txt       25 3373. 3339.   31.8  16.8   49.9 11.9 
## 16 rubis_27.txt       27 3385. 3351.   31.4  18.1   47.1 12.4 
## 17 rubis_29.txt       29 3401. 3366.   30.4  15.6   46.5 11.3

Another way of doing that is to use the map() function from the purrr package, which allows us to apply a function to each element of a list. Here, we want to apply the nls() function to each spectrum in spec, and store the result in a new column of spec. We can do this like so:

library(broom)
specfitted <- spec %>% 
    nest(data=c(w,int)) %>% 
    mutate(fit=map(data, ~nls(int ~ A1*lor(w, x1, gamma1) + A2*lor(w, x2, gamma2), 
                              data=., 
                              start=list(x1=.$w[which.max(.$int)], 
                                         x2=.$w[which.max(.$int)]-30, 
                                         gamma1=10, gamma2=10,
                                         A1=10,A2=7))),
           tidied = map(fit, tidy),
           augmented = map(fit, augment))
results <- specfitted %>% 
    unnest(tidied) %>% 
    select(name, pressure, term, estimate)
specfitted <- specfitted %>% 
    unnest(augmented)

Plot the result of the fit on the graph above.

specfitted %>% 
    ggplot(aes(x=w, group=name))+
        geom_point(aes(y=int + pressure),size=.2, alpha=.2)+
        geom_line(aes(y=.fitted + pressure), color='red')+
        labs(x="Raman Shift [1/cm]", y="Pressure [GPa]")+
        theme_bw()


Now all you have to do is plot the position of the peak as a function of pressure, and model this data by the equation of state defined at the beginning. What values of A and B do you obtain? Compare with the literature values: A = 1876 GPa and B = 10.71.

tofit <- results %>% filter(term=='x1')
fit <- nls(pressure~Prubis(estimate, A, B), 
           data=tofit, 
           start=list(A=10, B=10))
A <- tidy(fit) %>% filter(term=='A') %>% 
        select(estimate,std.error) %>% round(1) %>% paste(collapse="±")
B <- tidy(fit) %>% filter(term=='B') %>% 
        select(estimate,std.error) %>% round(1) %>% paste(collapse="±")
tofit %>%  
    ggplot(aes(x=estimate, y=pressure))+
        geom_point(size=4)+
        geom_line(aes(y=predict(fit)), color='red')+
        labs(y="Pressure [GPa]", x="Raman Shift [1/cm]")+
        theme_bw()+
        ggtitle(glue::glue("A = {A} GPa, B = {B}"))

LS0tCnRpdGxlIDogIlIgRXhlcmNpc2VzIC0gU3BlY3Ryb3Njb3BpYyBkYXRhIC0gU29sdXRpb24iCmRhdGUgIDogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6IAogICAgaHRtbF9kb2N1bWVudDoKICAgICAgICB0b2MgICAgICAgICAgICA6IHRydWUKICAgICAgICB0b2NfZmxvYXQgICAgICA6IHRydWUKICAgICAgICB0b2NfZGVwdGggICAgICA6IDQKICAgICAgICBoaWdobGlnaHQgICAgICA6IHRhbmdvCiAgICAgICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgICAgIGNvZGVfZG93bmxvYWQgIDogdHJ1ZQpwYXJhbXM6IAogICAgc29sdXRpb246CiAgICAgICAgdmFsdWU6IHRydWUKLS0tCgoKYGBge3IgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZmlnLmFsaWduPSJjZW50ZXIifQpsaWJyYXJ5KGRvd25sb2FkdGhpcykKZG93bmxvYWRfbGluaygKICBsaW5rID0gImh0dHBzOi8vZ2l0aHViLmNvbS9jb2xpbmJvdXNpZ2UvcmNsYXNzL3Jhdy9tYWluL0V4by9zcGVjdHJvMi9BcmNoaXZlLnppcCIsCiAgb3V0cHV0X25hbWUgPSAiRGF0YSBGaWxlcyIsCiAgYnV0dG9uX2xhYmVsID0gIkRvd25sb2FkIERhdGEgRmlsZXMiLAogIGJ1dHRvbl90eXBlID0gImRlZmF1bHQiLAogIGhhc19pY29uID0gVFJVRSwKICBpY29uID0gImZhIGZhLXNhdmUiLAogIHNlbGZfY29udGFpbmVkID0gRkFMU0UKKQpgYGAKPGJyPgoKCgpXZSB3aWxsIG5vdyB3b3JrIHdpdGggUmFtYW4gc3BlY3Ryb3Njb3B5IGRhdGEgZnJvbSB0aGUgbHVtaW5lc2NlbmNlIG9mIGEgcnVieSBwYXJ0aWNsZSB1bmRlciBoaWdoIHByZXNzdXJlOiBgRGF0YS9ydWJpc194eC50eHRgLiAKVGhpcyBtZWFzdXJlbWVudCBpcyBwZXJmb3JtZWQgaW4gYSBkaWFtb25kIGFudmlsIGNlbGwgd2l0aCBhIGxhc2VyIG9mIHdhdmVsZW5ndGggJFxsYW1iZGFfTCQ9NTY4LjE4OSBubSwgYW5kIHRoZSBwcmVzc3VyZSBpcyBrbm93biBieSBjYWxpYnJhdGluZyB0aGUgZnJlcXVlbmN5IG9mIGEgZGlhbW9uZCBsaW5lLiAKClRoZSBydWJ5IGx1bWluZXNjZW5jZSBjb250YWlucyB0d28gbGluZXMsIGFuZCB3ZSB3YW50IHRvIHVzZSB0aGUgcG9zaXRpb24gb2YgdGhlIFIxIGxpbmUsIHdoaWNoIGlzIHRoZSBtb3N0IGludGVuc2UgYW5kIGhhcyB0aGUgaGlnaGVzdCBlbmVyZ3ksIHRvIGRldGVybWluZSB0aGUgcHJlc3N1cmUuIFdlIHRoZXJlZm9yZSBuZWVkIHRvIGRldGVybWluZSB0aGUgcGFyYW1ldGVycyAkQSQgYW5kICRCJCBvZiB0aGUgZXF1YXRpb24gb2Ygc3RhdGUgbGlua2luZyB3YXZlbGVuZ3RoIChpbiBubSkgYW5kIHByZXNzdXJlIChpbiBHUGEpOgoKJCQKUH5bR1BhXSA9IFxmcmFje0F9e0J9XGxlZnRbXGxlZnQoXGZyYWN7XGxhbWJkYX17XGxhbWJkYV8wfVxyaWdodCleQi0xXHJpZ2h0XSwKJCQKd2hlcmUgJFxsYW1iZGFfMCQgPSA2OTQuMjQgbm0gaXMgdGhlIHdhdmVsZW5ndGggb2YgdGhlIFIxIGxpbmUgYXQgemVybyBwcmVzc3VyZSwgYW5kICRcbGFtYmRhJCBpcyB0aGUgd2F2ZWxlbmd0aCBvZiB0aGUgUjEgbGluZSBhdCAkUCQgcHJlc3N1cmUuIApGb3IgYSBnaXZlbiBsaW5lLCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gaXRzIG1lYXN1cmVkIFJhbWFuIHNoaWZ0LCAkXG9tZWdhJCwgYW5kIGl0cyB3YXZlbGVuZ3RoIGlzIGdpdmVuIGJ5ICRcbGFtYmRhID0gXGZyYWN7MTBeN317XG9tZWdhX0wgLSBcb21lZ2F9JCwgd2l0aCAkXG9tZWdhX0w9IFxmcmFjezEwXjd9e1xsYW1iZGFfTH0kIHRoZSBmcmVxdWVuY3kgb2YgdGhlIHByb2JlIGxhc2VyLgoKT3BlbiBvbmUgb2YgdGhlIGZpbGVzLCBmb3IgZXhhbXBsZSBbRGF0YS9ydWJpc18wMS50eHRdKERhdGEvcnViaXNfMDEudHh0KSB0byBzZWUgaXRzIHN0cnVjdHVyZSAtIHRoZXkncmUgYWxsIHRoZSBzYW1lLiBUaGV5IGFyZSBtYWRlIHVwIG9mIHR3byBjb2x1bW5zLCB3aGljaCBhcmUgdGhlIFJhbWFuIHNoaWZ0cyBpbiBjbV4tMV4gYW5kIHRoZSBpbnRlbnNpdGllcy4gVGhlIG5vbWVuY2xhdHVyZSBvZiB0aGUgZmlsZXMgYCJEYXRhL3J1YmlzX3h4LnR4dCJgIGlzIHN1Y2ggdGhhdCBgeHhgIGlzIHRoZSBwcmVzc3VyZSBpbiBHUGEuCgoKPiBGaXJzdCwgY29uc2lkZXIgdGhlIHN0ZXBzIGludm9sdmVkIGluIGRldGVybWluaW5nIHRoZSBwYXJhbWV0ZXJzICRBJCBhbmQgJEIkIG9mIHRoZSBlcXVhdGlvbiBvZiBzdGF0ZS4gCj4gCj4gLSBXaGF0IGRhdGEgZG8geW91IG5lZWQ/IAo+IC0gV2hhdCBzdGVwcyBkbyB5b3UgbmVlZCB0byB0YWtlIHRvIG9idGFpbiB0aGVtPwoKLS0tLS0tLS0tLQoKLSBMZXQncyBzdGFydCBieSBkZWZpbmluZyB0aGUgZnVuY3Rpb24gYFBydWJpcyh3LEEsQilgLCB3aGljaCByZXR1cm5zIHRoZSBwcmVzc3VyZSBpbiBHUGEgYXMgYSBmdW5jdGlvbiBvZiB0aGUgUmFtYW4gc2hpZnQgaW4gY21eLTFeLCBhbmQgdGhlIHBhcmFtZXRlcnMgJEEkIGFuZCAkQiQuIAoKCmBgYHtyIGluY2x1ZGU9cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpQcnViaXMgPC0gZnVuY3Rpb24odyxBLEIpewogICAgd2wgIDwtICAxZTcvNTY4LjE4OQogICAgbGFtYjAgIDwtICA2OTQuMjQKICAgIGxhbWIgIDwtICAxZTcvKHdsIC0gdykKICAgIHJldHVybihBL0IgKiAoKGxhbWIvbGFtYjApXkIgLSAxKSkKfQpgYGAKCi0tLS0tLS0tLS0KCkZpbmQgaW4gdGhlIGZvbGRlciAiRGF0YSIgdGhlIGxpc3Qgb2YgZmlsZXMgd2l0aCB0aGUgcGF0dGVybiAicnViaXNcXyIgaW4gdGhlaXIgbmFtZSAoY2hlY2sgb3V0IHRoZSBgbGlzdC5maWxlcygpYCBmdW5jdGlvbikgYW5kIHN0b3JlIGl0IGluIGBmbGlzdGAKCmBgYHtyIGluY2x1ZGU9cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpmbGlzdCA8LSBsaXN0LmZpbGVzKHBhdGg9IkRhdGEiLCBwYXR0ZXJuID0gInJ1YmlzXyIpCmBgYAoKLS0tLS0tLS0tLQoKV2hhdCBpcyB0aGUgbGVuZ3RoIG9mIGBmbGlzdGA/CgpgYGB7ciBpbmNsdWRlPXBhcmFtcyRzb2x1dGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRX0KbGVuZ3RoKGZsaXN0KQpgYGAKCi0tLS0tLS0tLS0KCkluaXRpYWxpemUgYW4gZW1wdHkgdGliYmxlIGNhbGxlZCBgc3BlY2AgdGhhdCB3aWxsIHN0b3JlIGFsbCBzcGVjdHJhOgoKYGBge3IgaW5jbHVkZT1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpzcGVjICAgPC0gdGliYmxlKCkKYGBgCgotLS0tLS0tLS0tCgpVc2luZyBhIGBmb3JgIGxvb3AsIGxvYWQgZWFjaCBmaWxlIGluIGBmbGlzdGAgYW5kIHN0b3JlIHRoZW0gaW50byBgc3BlY2AgYnkgKm1ha2luZyBpdCB0aWR5Ki4KCmBgYHtyIGluY2x1ZGU9cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpmb3IoZmlsZSBpbiBmbGlzdCl7CiAgICBkIDwtIHJlYWRfdGFibGUoZmlsZS5wYXRoKCJEYXRhIixmaWxlKSwgY29sX25hbWVzID0gYygidyIsImludCIpKQogICAgZCRuYW1lIDwtIGZpbGUKICAgIHNwZWMgPC0gYmluZF9yb3dzKHNwZWMsZCkKfQpzcGVjCmBgYAoKQW5vdGhlciB2ZXJzaW9uIG9mIHRoaXMgdGhhdCBkb2VzIG5vdCB1c2UgYSBmb3IgbG9vcCB3b3VsZCBiZSBsaWtlIHNvICh0cnkgdG8gdW5kZXJzdGFuZCB3aGF0J3MgaGFwcGVuaW5nIGluIHRoaXMgY29kZSBieSBydW5uaW5nIGVhY2ggbGluZSBvbmUgYWZ0ZXIgdGhlIG90aGVyIGFuZCBsb29raW5nIGF0IHRoZSBoZWxwIG9uIHRoZSBmdW5jdGlvbnMgdXNlZCk6CgpgYGB7ciBpbmNsdWRlPXBhcmFtcyRzb2x1dGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRX0KIyBzcGVjIDwtIHRpYmJsZShuYW1lPWZsaXN0KSAlPiUgCiMgICAgIG11dGF0ZShkYXRhPW1hcChuYW1lLCB+cmVhZF90YWJsZShmaWxlLnBhdGgoIkRhdGEiLC4pLCAKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbF9uYW1lcyA9IGMoInciLCJpbnQiKSkpCiMgICAgICAgICAgICkgJT4lIAojICAgICB1bm5lc3QoZGF0YSkKYGBgCgotLS0tLS0tLS0tCgpNb2RpZnkgYHNwZWNgIHRvIGdldCB0aGUgcHJlc3N1cmUgaW4gR1BhIGZyb20gdGhlIGZpbGUgbmFtZSwgYW5kIHNvIHRoYXQgdGhlIGludGVuc2l0eSBjb2x1bW4gaXMgbm9ybWFsaXplZCB0byBbMCwxXS4KCmBgYHtyIGluY2x1ZGU9cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpub3JtMDEgPC0gZnVuY3Rpb24oeCl7CiAgICAoeC1taW4oeCkpLyhtYXgoeCktbWluKHgpKQp9CnNwZWMgPC0gc3BlYyAlPiUgCiAgICBtdXRhdGUocHJlc3N1cmUgPSBhcy5udW1lcmljKHN0cl9leHRyYWN0KG5hbWUsICJbMC05XSsiKSkpICU+JSAKICAgIGdyb3VwX2J5KG5hbWUpICU+JSAKICAgIG11dGF0ZShpbnQ9bm9ybTAxKGludCkpCmBgYAoKLS0tLS0tLS0tLQoKUGxvdCB0aGUgZmlyc3Qgc3BlY3RydW0gaW4gYHNwZWNgIHRvIHNlZSB0aGUgeCByYW5nZQoKYGBge3IgaW5jbHVkZT1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CnNwZWMgJT4lIAogICAgZmlsdGVyKG5hbWU9PWZsaXN0WzFdKSAlPiUKICAgIGdncGxvdChhZXMoeD13LCB5PWludCkpKwogICAgICAgIGdlb21fbGluZSgpKwogICAgICAgIGxhYnMoeD0iUmFtYW4gU2hpZnQgWzEvY21dIiwgeT0iSW50ZW5zaXR5IFthcmIuIHVuaXRzXSIpKwogICAgICAgIHRoZW1lX2J3KCkKYGBgCgotLS0tLS0tLS0tCgpVc2luZyBgZ2dwbG90MmAsIHBsb3QgYWxsIHNwZWN0cmEgbm9ybWFsaXplZCB0byBbMCwxXSAod2l0aCBwb2ludHMpIGFuZCBzdGFja2VkIG9uIHRvcCBvZiBlYWNoIG90aGVyIHdpdGggYSB2ZXJ0aWNhbCBzaGlmdCBjb3JyZXNwb25kaW5nIHRvIHRoZSBwcmVzc3VyZSBpbiBHUGEKCmBgYHtyIGluY2x1ZGU9cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpzcGVjICU+JSAKICAgIGdncGxvdChhZXMoeD13LCAKICAgICAgICAgICAgICAgeT1pbnQgKyBwcmVzc3VyZSwKICAgICAgICAgICAgICAgZ3JvdXA9bmFtZSkpKwogICAgICAgIGdlb21fcG9pbnQoc2l6ZT0uMiwgYWxwaGE9LjIpKwogICAgICAgIGxhYnMoeD0iUmFtYW4gU2hpZnQgWzEvY21dIiwgeT0iUHJlc3N1cmUgW0dQYV0iKSsKICAgICAgICB0aGVtZV9idygpCmBgYAoKLS0tLS0tLS0tLQoKV2hhdCB3ZSB3YW50IHRvIGRvIG5vdyBpcyB0byBmaXQgdGhpcyBkYXRhIGJ5IHRoZSBzdW0gb2YgdHdvIExvcmVudHppYW5zIHRvIGZpbmQgdGhlIHByZWNpc2UgcG9zaXRpb24gb2YgdGhlIFIxIGxpbmUgYXMgYSBmdW5jdGlvbiBvZiBwcmVzc3VyZS4gVG8gZG8gdGhpcywgd2UnbGwgdXNlIHRoZSBmdW5jdGlvbiBbYG5scygpYF0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3N0YXRzL3ZlcnNpb25zLzMuNi4yL3RvcGljcy9ubHMpLCB3aGljaCBhbGxvd3MgdXMgdG8gbWFrZSBhIG5vbi1saW5lYXIgZml0IG9mIHRoZSBkYXRhLiBXZSBhbHNvIG5lZWQgdG8gZGVmaW5lIHRoZSBmdW5jdGlvbiBgbG9yKHgseDAsZ2FtbWEpYCB3aGljaCByZXR1cm5zIGEgW0xvcmVudHppYW5dKGh0dHBzOi8vZnIud2lraXBlZGlhLm9yZy93aWtpL0ZvbmN0aW9uX2xvcmVudHppZW5uZSkgY2VudGVyZWQgYXQgYHgwYCBhbmQgb2Ygd2lkdGggYXQgaGFsZi1oZWlnaHQgYGdhbW1hYC4gU3RhcnQgYnkgZGVmaW5pbmcgdGhlIGZ1bmN0aW9uIGBsb3IoeCx4MCxnYW1tYSlgLgoKYGBge3IgaW5jbHVkZT1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmxvciA8LSBmdW5jdGlvbih4LHgwLGdhbW1hKXsKICAgIDIvcGkvZ2FtbWEgLyAoMSArICgoeC14MCkvKGdhbW1hLzIpKV4yKQp9CmBgYAoKLS0tLS0tLS0tLQoKTm93IGNyZWF0ZSBhbiBlbXB0eSB0aWJibGUgY2FsbGVkIGByZXN1bHRzYCwgaW4gd2hpY2ggd2UnbGwgc3RvcmUgYWxsIHRoZSBmaXR0aW5nIHBhcmFtZXRlcnMgaW4gYSB0aWR5IHRpYmJsZS4gVGhlbiBmaXQgZWFjaCBzcGVjdHJ1bSBieSB0aGUgc3VtIG9mIHR3byBMb3JlbnR6aWFucywgYW5kIHNhdmUgYWxsIGZpdCBwYXJhbWV0ZXJzIGluIGByZXN1bHRzYCwgYWxvbmcgd2l0aCB0aGUgZmlsZSBuYW1lIGFuZCBwcmVzc3VyZS4gRm9yIHRoZSBmaXQgdG8gd29yaywgeW91J2xsIG5lZWQgdG8gZ2l2ZSByZWFzb25hYmxlIGluaXRpYWwgdmFsdWVzIGZvciB0aGUgZml0IHBhcmFtZXRlcnMgdXNpbmcgdGhlIGBzdGFydD1saXN0KC4uLilgIGFyZ3VtZW50IG9mIGBubHMoKWAuIEZvciBleGFtcGxlLCB5b3UgY2FuIHVzZSB0aGUgYHdoaWNoLm1heCgpYCBmdW5jdGlvbiB0byBmaW5kIHRoZSBpbmRleCBvZiB0aGUgbWF4aW11bSBvZiBhIHZlY3Rvci4KCmBgYHtyIGluY2x1ZGU9cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpyZXN1bHRzIDwtIHRpYmJsZSgpCmZvcihmaWxlIGluIGZsaXN0KXsKICAgIGQgPC0gc3BlYyAlPiUgZmlsdGVyKG5hbWU9PWZpbGUpCiAgICBmaXQgPC0gbmxzKGludCB+IEExKmxvcih3LCB4MSwgZ2FtbWExKSArIEEyKmxvcih3LCB4MiwgZ2FtbWEyKSwgCiAgICAgICAgICAgICAgIGRhdGE9ZCwgCiAgICAgICAgICAgICAgIHN0YXJ0PWxpc3QoeDE9ZCR3W3doaWNoLm1heChkJGludCldLCAKICAgICAgICAgICAgICAgICAgICAgICAgICB4Mj1kJHdbd2hpY2gubWF4KGQkaW50KV0tMzAsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGdhbW1hMT0xMCwgZ2FtbWEyPTEwLAogICAgICAgICAgICAgICAgICAgICAgICAgIEExPTEwLEEyPTcpKQogICAgcmVzIDwtIHRpYmJsZShmaWxlLCAKICAgICAgICAgICAgICAgICAgcHJlc3N1cmU9dW5pcXVlKGQkcHJlc3N1cmUpLAogICAgICAgICAgICAgICAgICBhc190aWJibGUodChjb2VmKGZpdCkpKSkKICAgIHJlc3VsdHMgPC0gYmluZF9yb3dzKHJlc3VsdHMsIHJlcykKfQpyZXN1bHRzCmBgYAoKQW5vdGhlciB3YXkgb2YgZG9pbmcgdGhhdCBpcyB0byB1c2UgdGhlIGBtYXAoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYHB1cnJyYCBwYWNrYWdlLCB3aGljaCBhbGxvd3MgdXMgdG8gYXBwbHkgYSBmdW5jdGlvbiB0byBlYWNoIGVsZW1lbnQgb2YgYSBsaXN0LiBIZXJlLCB3ZSB3YW50IHRvIGFwcGx5IHRoZSBgbmxzKClgIGZ1bmN0aW9uIHRvIGVhY2ggc3BlY3RydW0gaW4gYHNwZWNgLCBhbmQgc3RvcmUgdGhlIHJlc3VsdCBpbiBhIG5ldyBjb2x1bW4gb2YgYHNwZWNgLiBXZSBjYW4gZG8gdGhpcyBsaWtlIHNvOgoKYGBge3IgaW5jbHVkZT1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmxpYnJhcnkoYnJvb20pCnNwZWNmaXR0ZWQgPC0gc3BlYyAlPiUgCiAgICBuZXN0KGRhdGE9Yyh3LGludCkpICU+JSAKICAgIG11dGF0ZShmaXQ9bWFwKGRhdGEsIH5ubHMoaW50IH4gQTEqbG9yKHcsIHgxLCBnYW1tYTEpICsgQTIqbG9yKHcsIHgyLCBnYW1tYTIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT0uLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnQ9bGlzdCh4MT0uJHdbd2hpY2gubWF4KC4kaW50KV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHgyPS4kd1t3aGljaC5tYXgoLiRpbnQpXS0zMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2FtbWExPTEwLCBnYW1tYTI9MTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQTE9MTAsQTI9NykpKSwKICAgICAgICAgICB0aWRpZWQgPSBtYXAoZml0LCB0aWR5KSwKICAgICAgICAgICBhdWdtZW50ZWQgPSBtYXAoZml0LCBhdWdtZW50KSkKcmVzdWx0cyA8LSBzcGVjZml0dGVkICU+JSAKICAgIHVubmVzdCh0aWRpZWQpICU+JSAKICAgIHNlbGVjdChuYW1lLCBwcmVzc3VyZSwgdGVybSwgZXN0aW1hdGUpCnNwZWNmaXR0ZWQgPC0gc3BlY2ZpdHRlZCAlPiUgCiAgICB1bm5lc3QoYXVnbWVudGVkKQpgYGAKCi0tLS0tLS0tLS0KClBsb3QgdGhlIHJlc3VsdCBvZiB0aGUgZml0IG9uIHRoZSBncmFwaCBhYm92ZS4gCgpgYGB7ciBpbmNsdWRlPXBhcmFtcyRzb2x1dGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRX0Kc3BlY2ZpdHRlZCAlPiUgCiAgICBnZ3Bsb3QoYWVzKHg9dywgZ3JvdXA9bmFtZSkpKwogICAgICAgIGdlb21fcG9pbnQoYWVzKHk9aW50ICsgcHJlc3N1cmUpLHNpemU9LjIsIGFscGhhPS4yKSsKICAgICAgICBnZW9tX2xpbmUoYWVzKHk9LmZpdHRlZCArIHByZXNzdXJlKSwgY29sb3I9J3JlZCcpKwogICAgICAgIGxhYnMoeD0iUmFtYW4gU2hpZnQgWzEvY21dIiwgeT0iUHJlc3N1cmUgW0dQYV0iKSsKICAgICAgICB0aGVtZV9idygpCmBgYAoKLS0tLS0tLS0tLQoKTm93IGFsbCB5b3UgaGF2ZSB0byBkbyBpcyBwbG90IHRoZSBwb3NpdGlvbiBvZiB0aGUgcGVhayBhcyBhIGZ1bmN0aW9uIG9mIHByZXNzdXJlLCBhbmQgbW9kZWwgdGhpcyBkYXRhIGJ5IHRoZSBlcXVhdGlvbiBvZiBzdGF0ZSBkZWZpbmVkIGF0IHRoZSBiZWdpbm5pbmcuIFdoYXQgdmFsdWVzIG9mIEEgYW5kIEIgZG8geW91IG9idGFpbj8gQ29tcGFyZSB3aXRoIHRoZSBbbGl0ZXJhdHVyZSB2YWx1ZXNdKGh0dHBzOi8vZG9pLm9yZy8xMC4xMDYzLzEuMjEzNTg3Nyk6IEEgPSAxODc2IEdQYSBhbmQgQiA9IDEwLjcxLgoKYGBge3IgaW5jbHVkZT1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CnRvZml0IDwtIHJlc3VsdHMgJT4lIGZpbHRlcih0ZXJtPT0neDEnKQpmaXQgPC0gbmxzKHByZXNzdXJlflBydWJpcyhlc3RpbWF0ZSwgQSwgQiksIAogICAgICAgICAgIGRhdGE9dG9maXQsIAogICAgICAgICAgIHN0YXJ0PWxpc3QoQT0xMCwgQj0xMCkpCkEgPC0gdGlkeShmaXQpICU+JSBmaWx0ZXIodGVybT09J0EnKSAlPiUgCiAgICAgICAgc2VsZWN0KGVzdGltYXRlLHN0ZC5lcnJvcikgJT4lIHJvdW5kKDEpICU+JSBwYXN0ZShjb2xsYXBzZT0iwrEiKQpCIDwtIHRpZHkoZml0KSAlPiUgZmlsdGVyKHRlcm09PSdCJykgJT4lIAogICAgICAgIHNlbGVjdChlc3RpbWF0ZSxzdGQuZXJyb3IpICU+JSByb3VuZCgxKSAlPiUgcGFzdGUoY29sbGFwc2U9IsKxIikKdG9maXQgJT4lICAKICAgIGdncGxvdChhZXMoeD1lc3RpbWF0ZSwgeT1wcmVzc3VyZSkpKwogICAgICAgIGdlb21fcG9pbnQoc2l6ZT00KSsKICAgICAgICBnZW9tX2xpbmUoYWVzKHk9cHJlZGljdChmaXQpKSwgY29sb3I9J3JlZCcpKwogICAgICAgIGxhYnMoeT0iUHJlc3N1cmUgW0dQYV0iLCB4PSJSYW1hbiBTaGlmdCBbMS9jbV0iKSsKICAgICAgICB0aGVtZV9idygpKwogICAgICAgIGdndGl0bGUoZ2x1ZTo6Z2x1ZSgiQSA9IHtBfSBHUGEsIEIgPSB7Qn0iKSkKYGBgCgo=