Psychometric function (PF)

The psychometric function (PF) is about relation between human performance (e.g., classification) on a psychophysical task and sensory inputs (e.g., stimulus intensity). Recall that a general psychometric function:

\[\Psi(x,\alpha, \beta, \gamma, \lambda) = \gamma + (1-\gamma -\lambda) F(x, \alpha, \beta)\] where \(\alpha\) is the threshold, \(\beta\) sensitivity, \(\gamma\) chance level, and \(\lambda\) lapse rate. Several candidate functions can be used for psychometric functions \(F(x,\alpha, \beta)\):

The Logit function is relative simple:

\[ \log \frac{P}{1-P} = \beta(x-\alpha), \] where \(P\) is the prop. of the positive responses, and \(1-P\) is the prop. of the negative response. \(\log \frac{P}{1-P}\) is log-likelihood of two alternative responses (sometimes it is called decision variable), which is assumed to be a linear function of the stimulus intensity \(X\). The above equation can be transformed to

\[ P = \frac{1}{1+ e^{-\beta(x-\alpha)}}\]

From the equation, we can easily obtained the point of subjective equality (PSE, \(\alpha\)) and just noticeable difference (JND). Notice, the JND is defined by the difference between thresholds \(P=0.5\) and \(P=0.75\), so

\[JND = \log3/\beta\] That is, when the slope \(\beta\) becomes steeper, the JND becomes smaller.

Case Study: Influence of music tempo on duration judgments.

In the study, same classic music pieces were manipulated and played in three different tempo: slow, medium, and faster. The length of the music piece was randomly select from 2 to 8 seconds. Participants had to judge if the music piece was a ‘short’ or a ‘long’ one. Participants had to estimate the ‘short’ or ‘long’ based on the past trials they received. The research question is if the music tempo alters the time judgment.

A sample experimental data that contains three participants’ responses can be found here.

Please download the data file first to your local folder. Here I put in a subfolder called ‘data’ in my local computer.

1. Import data and tidy the data

Here we do the ‘tidyverse’ way. First, we need to load necessary packages, such as tidyverse, broom, and we also need quickpsy for a fast estimation of psychometric function.

Note, the raw csv file uses ; as a separation, so we need to explicitly specify the separation when we use read.csv() function. The responses for two alternative choices were coded as ‘1’ (short) and ‘2’ (long). You can count how many ‘2’ among one condition to calculate the proportion. A better faster way is to recode the responses to 0 and 1, and use sum() and mean() functions to count the number of ‘long’ responses and the mean. In addition, for better understanding, we also need to recode / factorize the factor Tempo.

# 1. Import necessary libraries 
library(tidyverse)
library(broom)
# If you haven't install quickpsy, uncomment the following line
#install.packages(quickpsy)
library(quickpsy)
# 2. import raw data, and make it readible. 
# read raw data, this csv file uses ';' as separation
# --- please change the location of the data file
raw_bisection <- read.csv('data/music_bisection.csv',sep=';')
# change response from 1,2 to 0,1 (short vs. long)
raw_bisection$resp <- raw_bisection$Decision -1
# convert numbers (tempo) to factor (slow, medium, fast)
raw_bisection$Tempo <- factor(raw_bisection$Tempo,labels = c('Slow','Medium','Fast'))
head(raw_bisection)
  Participant Duration Tempo Stimuli_Nu Decision Music_Duration resp
1      Sub101        2  Slow         10        1       2.001604    0
2      Sub101        6  Fast         10        2       6.000169    1
3      Sub101        5  Slow          4        2       5.000097    1
4      Sub101        7  Slow          8        2       7.000105    1
5      Sub101        2  Fast          1        1       2.000080    0
6      Sub101        7  Slow          6        2       7.000084    1

2. Average data and visualize average responses.

After loading the raw dta, we now to calculate the average responses for each condition, individual participants. We also count the number of ‘long’ and ‘short’ responses, those are needed for later GLM logistic regression.

# average resp of 'long'
msub <- raw_bisection %>% 
  group_by(Participant, Tempo, Duration) %>%  
  summarise(prop = mean(resp), long = sum(resp), 
            n = n(), short = n-long) 
head(msub)

We now visualize the empirical psychometric curves. Recall we can add facet_wrap() in ggplot to plot separate participants, and use different color for different Tempos.

msub %>% ggplot(aes(x = Duration, 
                    y = prop, 
                    color = Tempo)) + 
  geom_point() + geom_line() +
  facet_wrap(~Participant) + 
  ylab("Prop. of 'long' responses")

By visual inspection, we can roughly know that the fast tempo (blue lines) shifted the psychometric function leftwards, and the slow tempo shifted the curve rightward. In the fast tempo condition, the bisection point (the middle point between 2 and 8 seconds) shifted lower. That is, the fast tempo music piece, compared to the slow tempo, requires less amount of duration to reach the subjective middle point. In other words, the fast tempo may dilate the subjective time.

3. Psychometric function estimated from GLM approach

To formally estimate the psychometric function and its parameters, here we use the general linear model (GLM) approach. Recall the formula format in R, and also check the help information of the function glm(). The logistic regression requires you to specify the family parameters with binomial(logit) in the glm() function.

# glm for logistic regression
glm_results = msub %>% 
    group_by(Participant, Tempo) %>%
    do(tidy (glm(cbind(long,short) ~ Duration, 
                 family = binomial(logit), data=.)))
head(glm_results)

In the above tidy-type analysis, we use do(tidy()) function, which extracts the key parameters from the GLM model, and concatenate them into a table. Note that there are two parameters Intercept and Duration in the table. They are two parameters \(a\) and \(b\) from the logistic function

\[\log\frac{P}{1-P}= a + b \cdot Duration\]

With the above format, the PSE is \(-a/b\), and the b remains the same \(\log(3)/b\).

Now we calculate the PSEs and JNDs

psy_results = glm_results %>%
    select(., one_of(c('Participant','Tempo', 'term','estimate'))) %>%
    spread(.,term, estimate) %>% rename(., b=Duration, a = `(Intercept)`) %>%
    mutate(., pse = -a/b, jnd = log(3)/b)
head(psy_results)

Visualize the estimated psychometric functions together with empirical data. Note, ggplot also provides the direct estimation and draw the logistic curves using geom_smooth(). In this layer, you need to specify the method with glm, and the arguments for the method with methods.args = list(family = binomial(logit)).

# Psychometric curves. 
msub %>%ggplot( aes(x=Duration, y=prop, color=Tempo)) +  
  geom_point(aes(shape = Tempo), size=3) + 
  geom_smooth(method=glm, method.args= list(family = binomial(logit)), 
              se = FALSE, aes(linetype=Tempo)) +
  xlab('Durations (sec)') + ylab('Prop. of Long Response') +
  facet_wrap(~Participant)

In the end, we visualize the mean PSEs and JNDs.

psy_results %>% group_by(Tempo) %>%
  summarise(mPSE = mean(pse)) %>% 
  ggplot(aes(Tempo, mPSE)) + geom_bar(stat = 'identity', width = 0.5) +
  theme_classic()

NA

Your task: please add errorbars and also plot the figure for mean JNDs.

One more Note: the GLM approach does not assume any lapse rate, and guess rate here. This is because logistic regression assume the range from 0 to 1.

4. Quickpsy approach

Quickpsy is an R package developed by Daniel Linares and Joan Loópez-Moliner to quickly fit and plot psychometric fucntions for multiple conditions. You can find the introduction and quick tutorial from their official website.

First, please open quickpsy help file and check it’s grammar. It has several important parameters: x for stimulus intensity, k for response variable. The response variable could be the number of trials in which a yes-type response was given or a vector of 0s and 1s indicating the response on each trial. In addition, the group variable grouping, where you can specify multiple group variables with the operator .().

In addition, you can estimate the lapse rate, by state lapses = TRUE.

Let’s first estimate the psychometric function using the raw data.

# Estimate psychometric function and PSEs
quickpsy(raw_bisection, x = Duration, k = resp, 
         grouping = .(Tempo,Participant), lapses = TRUE, 
         bootstrap = 'none') -> qp
# show the thresholds
head(qp$thresholds)

There is one special function called plotcurves() for visualized the estimated psychometric functions.

plotcurves(qp)

Please compare the estimations from GLM and quickpsy, and discuss with your classmates the potential impacts of adding the lapse rate in the estimation.

Your task: please extract the thresholds (PSEs), JNDs, and lapse rates from the quickpsy estimations, and visualise them.

LS0tCnRpdGxlOiAnQ291cnNlIEMgVHV0b3JpYWwgMzogUHN5Y2hvbWV0cmljIGZ1bmN0aW9uJwphdXRob3I6ICJaaHVhbmdodWEgU2hpIChodHRwOi8vbXNlbnNlLmRlKSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCi0tLQoKIyBQc3ljaG9tZXRyaWMgZnVuY3Rpb24gKFBGKQoKVGhlIHBzeWNob21ldHJpYyBmdW5jdGlvbiAoUEYpIGlzIGFib3V0IHJlbGF0aW9uIGJldHdlZW4gaHVtYW4gcGVyZm9ybWFuY2UgKGUuZy4sIGNsYXNzaWZpY2F0aW9uKSBvbiBhIHBzeWNob3BoeXNpY2FsIHRhc2sgYW5kIHNlbnNvcnkgaW5wdXRzIChlLmcuLCBzdGltdWx1cyBpbnRlbnNpdHkpLiBSZWNhbGwgdGhhdCBhIGdlbmVyYWwgcHN5Y2hvbWV0cmljIGZ1bmN0aW9uOgoKJCRcUHNpKHgsXGFscGhhLCBcYmV0YSwgXGdhbW1hLCBcbGFtYmRhKSA9IFxnYW1tYSArICgxLVxnYW1tYSAtXGxhbWJkYSkgRih4LCBcYWxwaGEsIFxiZXRhKSQkCndoZXJlICRcYWxwaGEkIGlzIHRoZSB0aHJlc2hvbGQsICRcYmV0YSQgc2Vuc2l0aXZpdHksICRcZ2FtbWEkIGNoYW5jZSBsZXZlbCwgYW5kICRcbGFtYmRhJCBsYXBzZSByYXRlLiAKU2V2ZXJhbCBjYW5kaWRhdGUgZnVuY3Rpb25zIGNhbiBiZSB1c2VkIGZvciBwc3ljaG9tZXRyaWMgZnVuY3Rpb25zICRGKHgsXGFscGhhLCBcYmV0YSkkOgoKKiBsb2dpdCBmdW5jdGlvbgoqIGN1bXVsYXRpdmUgR2F1c3NpYW4gZnVuY3Rpb24KKiBXZWlidWxsCgpUaGUgTG9naXQgZnVuY3Rpb24gaXMgcmVsYXRpdmUgc2ltcGxlOgoKJCQgXGxvZyBcZnJhY3tQfXsxLVB9ID0gXGJldGEoeC1cYWxwaGEpLCAkJAp3aGVyZSAkUCQgaXMgdGhlIHByb3AuIG9mIHRoZSBwb3NpdGl2ZSByZXNwb25zZXMsIGFuZCAkMS1QJCBpcyB0aGUgcHJvcC4gb2YgdGhlIG5lZ2F0aXZlIHJlc3BvbnNlLiAkXGxvZyBcZnJhY3tQfXsxLVB9JCBpcyBsb2ctbGlrZWxpaG9vZCBvZiB0d28gYWx0ZXJuYXRpdmUgcmVzcG9uc2VzIChzb21ldGltZXMgaXQgaXMgY2FsbGVkIGRlY2lzaW9uIHZhcmlhYmxlKSwgd2hpY2ggaXMgYXNzdW1lZCB0byBiZSBhIGxpbmVhciBmdW5jdGlvbiBvZiB0aGUgc3RpbXVsdXMgaW50ZW5zaXR5ICRYJC4gVGhlIGFib3ZlIGVxdWF0aW9uIGNhbiBiZSB0cmFuc2Zvcm1lZCB0byAKCiQkIFAgPSBcZnJhY3sxfXsxKyBlXnstXGJldGEoeC1cYWxwaGEpfX0kJAoKRnJvbSB0aGUgZXF1YXRpb24sIHdlIGNhbiBlYXNpbHkgb2J0YWluZWQgdGhlIHBvaW50IG9mIHN1YmplY3RpdmUgZXF1YWxpdHkgKFBTRSwgJFxhbHBoYSQpIGFuZCBqdXN0IG5vdGljZWFibGUgZGlmZmVyZW5jZSAoSk5EKS4gTm90aWNlLCB0aGUgSk5EIGlzIGRlZmluZWQgYnkgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aHJlc2hvbGRzICRQPTAuNSQgYW5kICRQPTAuNzUkLCBzbyAKCiQkSk5EID0gXGxvZzMvXGJldGEkJApUaGF0IGlzLCB3aGVuIHRoZSBzbG9wZSAkXGJldGEkIGJlY29tZXMgc3RlZXBlciwgdGhlIEpORCBiZWNvbWVzIHNtYWxsZXIuIAoKIyBDYXNlIFN0dWR5OiBJbmZsdWVuY2Ugb2YgbXVzaWMgdGVtcG8gb24gZHVyYXRpb24ganVkZ21lbnRzLiAKCkluIHRoZSBzdHVkeSwgc2FtZSBjbGFzc2ljIG11c2ljIHBpZWNlcyB3ZXJlIG1hbmlwdWxhdGVkIGFuZCBwbGF5ZWQgaW4gdGhyZWUgZGlmZmVyZW50IHRlbXBvOiBzbG93LCBtZWRpdW0sIGFuZCBmYXN0ZXIuIFRoZSBsZW5ndGggb2YgdGhlIG11c2ljIHBpZWNlIHdhcyByYW5kb21seSBzZWxlY3QgZnJvbSAyIHRvIDggc2Vjb25kcy4gUGFydGljaXBhbnRzIGhhZCB0byBqdWRnZSBpZiB0aGUgbXVzaWMgcGllY2Ugd2FzIGEgJ3Nob3J0JyBvciBhICdsb25nJyBvbmUuIFBhcnRpY2lwYW50cyBoYWQgdG8gZXN0aW1hdGUgdGhlICdzaG9ydCcgb3IgJ2xvbmcnIGJhc2VkIG9uIHRoZSBwYXN0IHRyaWFscyB0aGV5IHJlY2VpdmVkLiBUaGUgcmVzZWFyY2ggcXVlc3Rpb24gaXMgaWYgdGhlIG11c2ljIHRlbXBvIGFsdGVycyB0aGUgdGltZSBqdWRnbWVudC4gIAoKQSBzYW1wbGUgZXhwZXJpbWVudGFsIGRhdGEgdGhhdCBjb250YWlucyB0aHJlZSBwYXJ0aWNpcGFudHMnIHJlc3BvbnNlcyBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvN3JlZ2p1djdsN3k0bDVkL211c2ljX2Jpc2VjdGlvbi5jc3Y/ZGw9MCkuCgpQbGVhc2UgZG93bmxvYWQgdGhlIGRhdGEgZmlsZSBmaXJzdCB0byB5b3VyIGxvY2FsIGZvbGRlci4gSGVyZSBJIHB1dCBpbiBhIHN1YmZvbGRlciBjYWxsZWQgJ2RhdGEnIGluIG15IGxvY2FsIGNvbXB1dGVyLiAKCiMjIDEuIEltcG9ydCBkYXRhIGFuZCB0aWR5IHRoZSBkYXRhCgpIZXJlIHdlIGRvIHRoZSAndGlkeXZlcnNlJyB3YXkuIEZpcnN0LCB3ZSBuZWVkIHRvIGxvYWQgbmVjZXNzYXJ5IHBhY2thZ2VzLCBzdWNoIGFzIGB0aWR5dmVyc2VgLCBgYnJvb21gLCBhbmQgd2UgYWxzbyBuZWVkIGBxdWlja3BzeWAgZm9yIGEgZmFzdCBlc3RpbWF0aW9uIG9mIHBzeWNob21ldHJpYyBmdW5jdGlvbi4gCgpOb3RlLCB0aGUgcmF3IGNzdiBmaWxlIHVzZXMgYDtgIGFzIGEgc2VwYXJhdGlvbiwgc28gd2UgbmVlZCB0byBleHBsaWNpdGx5IHNwZWNpZnkgdGhlIHNlcGFyYXRpb24gd2hlbiB3ZSB1c2UgYHJlYWQuY3N2KClgIGZ1bmN0aW9uLiBUaGUgcmVzcG9uc2VzIGZvciB0d28gYWx0ZXJuYXRpdmUgY2hvaWNlcyB3ZXJlIGNvZGVkIGFzICcxJyAoc2hvcnQpIGFuZCAnMicgKGxvbmcpLiBZb3UgY2FuIGNvdW50IGhvdyBtYW55ICcyJyBhbW9uZyBvbmUgY29uZGl0aW9uIHRvIGNhbGN1bGF0ZSB0aGUgcHJvcG9ydGlvbi4gQSBiZXR0ZXIgZmFzdGVyIHdheSBpcyB0byByZWNvZGUgdGhlIHJlc3BvbnNlcyB0byAwIGFuZCAxLCBhbmQgdXNlIGBzdW0oKWAgYW5kIGBtZWFuKClgIGZ1bmN0aW9ucyB0byBjb3VudCB0aGUgbnVtYmVyIG9mICdsb25nJyByZXNwb25zZXMgYW5kIHRoZSBtZWFuLiBJbiBhZGRpdGlvbiwgZm9yIGJldHRlciB1bmRlcnN0YW5kaW5nLCB3ZSBhbHNvIG5lZWQgdG8gcmVjb2RlIC8gZmFjdG9yaXplIHRoZSBmYWN0b3IgYFRlbXBvYC4gCgpgYGB7ciBpbml0fQojIDEuIEltcG9ydCBuZWNlc3NhcnkgbGlicmFyaWVzIApsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShicm9vbSkKIyBJZiB5b3UgaGF2ZW4ndCBpbnN0YWxsIHF1aWNrcHN5LCB1bmNvbW1lbnQgdGhlIGZvbGxvd2luZyBsaW5lCiNpbnN0YWxsLnBhY2thZ2VzKHF1aWNrcHN5KQpsaWJyYXJ5KHF1aWNrcHN5KQoKIyAyLiBpbXBvcnQgcmF3IGRhdGEsIGFuZCBtYWtlIGl0IHJlYWRpYmxlLiAKIyByZWFkIHJhdyBkYXRhLCB0aGlzIGNzdiBmaWxlIHVzZXMgJzsnIGFzIHNlcGFyYXRpb24KIyAtLS0gcGxlYXNlIGNoYW5nZSB0aGUgbG9jYXRpb24gb2YgdGhlIGRhdGEgZmlsZQpyYXdfYmlzZWN0aW9uIDwtIHJlYWQuY3N2KCdkYXRhL211c2ljX2Jpc2VjdGlvbi5jc3YnLHNlcD0nOycpCgojIGNoYW5nZSByZXNwb25zZSBmcm9tIDEsMiB0byAwLDEgKHNob3J0IHZzLiBsb25nKQpyYXdfYmlzZWN0aW9uJHJlc3AgPC0gcmF3X2Jpc2VjdGlvbiREZWNpc2lvbiAtMQojIGNvbnZlcnQgbnVtYmVycyAodGVtcG8pIHRvIGZhY3RvciAoc2xvdywgbWVkaXVtLCBmYXN0KQpyYXdfYmlzZWN0aW9uJFRlbXBvIDwtIGZhY3RvcihyYXdfYmlzZWN0aW9uJFRlbXBvLGxhYmVscyA9IGMoJ1Nsb3cnLCdNZWRpdW0nLCdGYXN0JykpCgpoZWFkKHJhd19iaXNlY3Rpb24pCgpgYGAKCgojIyAyLiBBdmVyYWdlIGRhdGEgYW5kIHZpc3VhbGl6ZSBhdmVyYWdlIHJlc3BvbnNlcy4gCgpBZnRlciBsb2FkaW5nIHRoZSByYXcgZHRhLCB3ZSBub3cgdG8gY2FsY3VsYXRlIHRoZSBhdmVyYWdlIHJlc3BvbnNlcyBmb3IgZWFjaCBjb25kaXRpb24sIGluZGl2aWR1YWwgcGFydGljaXBhbnRzLiBXZSBhbHNvIGNvdW50IHRoZSBudW1iZXIgb2YgJ2xvbmcnIGFuZCAnc2hvcnQnIHJlc3BvbnNlcywgdGhvc2UgYXJlIG5lZWRlZCBmb3IgbGF0ZXIgR0xNIGxvZ2lzdGljIHJlZ3Jlc3Npb24uIAoKYGBge3J9CiMgYXZlcmFnZSByZXNwIG9mICdsb25nJwptc3ViIDwtIHJhd19iaXNlY3Rpb24gJT4lIAogIGdyb3VwX2J5KFBhcnRpY2lwYW50LCBUZW1wbywgRHVyYXRpb24pICU+JSAgCiAgc3VtbWFyaXNlKHByb3AgPSBtZWFuKHJlc3ApLCBsb25nID0gc3VtKHJlc3ApLCAKICAgICAgICAgICAgbiA9IG4oKSwgc2hvcnQgPSBuLWxvbmcpIApoZWFkKG1zdWIpCmBgYAoKV2Ugbm93IHZpc3VhbGl6ZSB0aGUgZW1waXJpY2FsIHBzeWNob21ldHJpYyBjdXJ2ZXMuIFJlY2FsbCB3ZSBjYW4gYWRkIGBmYWNldF93cmFwKClgIGluIGdncGxvdCB0byBwbG90IHNlcGFyYXRlIHBhcnRpY2lwYW50cywgYW5kIHVzZSBkaWZmZXJlbnQgY29sb3IgZm9yIGRpZmZlcmVudCBUZW1wb3MuIAoKYGBge3J9Cm1zdWIgJT4lIGdncGxvdChhZXMoeCA9IER1cmF0aW9uLCAKICAgICAgICAgICAgICAgICAgICB5ID0gcHJvcCwgCiAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBUZW1wbykpICsgCiAgZ2VvbV9wb2ludCgpICsgZ2VvbV9saW5lKCkgKwogIGZhY2V0X3dyYXAoflBhcnRpY2lwYW50KSArIAogIHlsYWIoIlByb3AuIG9mICdsb25nJyByZXNwb25zZXMiKQoKYGBgCgpCeSB2aXN1YWwgaW5zcGVjdGlvbiwgd2UgY2FuIHJvdWdobHkga25vdyB0aGF0IHRoZSBmYXN0IHRlbXBvIChibHVlIGxpbmVzKSBzaGlmdGVkIHRoZSBwc3ljaG9tZXRyaWMgZnVuY3Rpb24gbGVmdHdhcmRzLCBhbmQgdGhlIHNsb3cgdGVtcG8gc2hpZnRlZCB0aGUgY3VydmUgcmlnaHR3YXJkLiBJbiB0aGUgZmFzdCB0ZW1wbyBjb25kaXRpb24sIHRoZSBiaXNlY3Rpb24gcG9pbnQgKHRoZSBtaWRkbGUgcG9pbnQgYmV0d2VlbiAyIGFuZCA4IHNlY29uZHMpIHNoaWZ0ZWQgbG93ZXIuIFRoYXQgaXMsIHRoZSBmYXN0IHRlbXBvIG11c2ljIHBpZWNlLCBjb21wYXJlZCB0byB0aGUgc2xvdyB0ZW1wbywgcmVxdWlyZXMgbGVzcyBhbW91bnQgb2YgZHVyYXRpb24gdG8gcmVhY2ggdGhlIHN1YmplY3RpdmUgbWlkZGxlIHBvaW50LiBJbiBvdGhlciB3b3JkcywgdGhlIGZhc3QgdGVtcG8gbWF5IF9kaWxhdGVfIHRoZSBzdWJqZWN0aXZlIHRpbWUuIAoKIyMgMy4gUHN5Y2hvbWV0cmljIGZ1bmN0aW9uIGVzdGltYXRlZCBmcm9tIEdMTSBhcHByb2FjaAoKVG8gZm9ybWFsbHkgZXN0aW1hdGUgdGhlIHBzeWNob21ldHJpYyBmdW5jdGlvbiBhbmQgaXRzIHBhcmFtZXRlcnMsIGhlcmUgd2UgdXNlIHRoZSBnZW5lcmFsIGxpbmVhciBtb2RlbCAoR0xNKSBhcHByb2FjaC4gUmVjYWxsIHRoZSBmb3JtdWxhIGZvcm1hdCBpbiBSLCBhbmQgYWxzbyBjaGVjayB0aGUgaGVscCBpbmZvcm1hdGlvbiBvZiB0aGUgZnVuY3Rpb24gYGdsbSgpYC4gVGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gcmVxdWlyZXMgeW91IHRvIHNwZWNpZnkgdGhlIGBmYW1pbHlgIHBhcmFtZXRlcnMgd2l0aCBgYmlub21pYWwobG9naXQpYCBpbiB0aGUgYGdsbSgpYCBmdW5jdGlvbi4gCgpgYGB7cn0KIyBnbG0gZm9yIGxvZ2lzdGljIHJlZ3Jlc3Npb24KZ2xtX3Jlc3VsdHMgPSBtc3ViICU+JSAKICAgIGdyb3VwX2J5KFBhcnRpY2lwYW50LCBUZW1wbykgJT4lCiAgICBkbyh0aWR5IChnbG0oY2JpbmQobG9uZyxzaG9ydCkgfiBEdXJhdGlvbiwgCiAgICAgICAgICAgICAgICAgZmFtaWx5ID0gYmlub21pYWwobG9naXQpLCBkYXRhPS4pKSkKCmhlYWQoZ2xtX3Jlc3VsdHMpCmBgYAoKSW4gdGhlIGFib3ZlIGB0aWR5YC10eXBlIGFuYWx5c2lzLCB3ZSB1c2UgYGRvKHRpZHkoKSlgIGZ1bmN0aW9uLCB3aGljaCBleHRyYWN0cyB0aGUga2V5IHBhcmFtZXRlcnMgZnJvbSB0aGUgR0xNIG1vZGVsLCBhbmQgY29uY2F0ZW5hdGUgdGhlbSBpbnRvIGEgdGFibGUuIE5vdGUgdGhhdCB0aGVyZSBhcmUgdHdvIHBhcmFtZXRlcnMgYEludGVyY2VwdGAgYW5kIGBEdXJhdGlvbmAgaW4gdGhlIHRhYmxlLiBUaGV5IGFyZSB0d28gcGFyYW1ldGVycyAkYSQgYW5kICRiJCBmcm9tIHRoZSBsb2dpc3RpYyBmdW5jdGlvbiAKCiQkXGxvZ1xmcmFje1B9ezEtUH09IGEgKyBiIFxjZG90IER1cmF0aW9uJCQKCldpdGggdGhlIGFib3ZlIGZvcm1hdCwgdGhlIFBTRSBpcyAkLWEvYiQsIGFuZCB0aGUgYiByZW1haW5zIHRoZSBzYW1lICRcbG9nKDMpL2IkLiAKCk5vdyB3ZSBjYWxjdWxhdGUgdGhlIFBTRXMgYW5kIEpORHMKCmBgYHtyfQpwc3lfcmVzdWx0cyA9IGdsbV9yZXN1bHRzICU+JQogICAgc2VsZWN0KC4sIG9uZV9vZihjKCdQYXJ0aWNpcGFudCcsJ1RlbXBvJywgJ3Rlcm0nLCdlc3RpbWF0ZScpKSkgJT4lCiAgICBzcHJlYWQoLix0ZXJtLCBlc3RpbWF0ZSkgJT4lIHJlbmFtZSguLCBiPUR1cmF0aW9uLCBhID0gYChJbnRlcmNlcHQpYCkgJT4lCiAgICBtdXRhdGUoLiwgcHNlID0gLWEvYiwgam5kID0gbG9nKDMpL2IpCgpoZWFkKHBzeV9yZXN1bHRzKQpgYGAKClZpc3VhbGl6ZSB0aGUgZXN0aW1hdGVkIHBzeWNob21ldHJpYyBmdW5jdGlvbnMgdG9nZXRoZXIgd2l0aCBlbXBpcmljYWwgZGF0YS4gTm90ZSwgYGdncGxvdGAgYWxzbyBwcm92aWRlcyB0aGUgZGlyZWN0IGVzdGltYXRpb24gYW5kIGRyYXcgdGhlIGxvZ2lzdGljIGN1cnZlcyB1c2luZyBgZ2VvbV9zbW9vdGgoKWAuIEluIHRoaXMgbGF5ZXIsIHlvdSBuZWVkIHRvIHNwZWNpZnkgdGhlIG1ldGhvZCB3aXRoIGBnbG1gLCBhbmQgdGhlIGFyZ3VtZW50cyBmb3IgdGhlIG1ldGhvZCB3aXRoIGBtZXRob2RzLmFyZ3MgPSBsaXN0KGZhbWlseSA9IGJpbm9taWFsKGxvZ2l0KSlgLiAKCmBgYHtyfQojIFBzeWNob21ldHJpYyBjdXJ2ZXMuIAptc3ViICU+JWdncGxvdCggYWVzKHg9RHVyYXRpb24sIHk9cHJvcCwgY29sb3I9VGVtcG8pKSArICAKICBnZW9tX3BvaW50KGFlcyhzaGFwZSA9IFRlbXBvKSwgc2l6ZT0zKSArIAogIGdlb21fc21vb3RoKG1ldGhvZD1nbG0sIG1ldGhvZC5hcmdzPSBsaXN0KGZhbWlseSA9IGJpbm9taWFsKGxvZ2l0KSksIAogICAgICAgICAgICAgIHNlID0gRkFMU0UsIGFlcyhsaW5ldHlwZT1UZW1wbykpICsKICB4bGFiKCdEdXJhdGlvbnMgKHNlYyknKSArIHlsYWIoJ1Byb3AuIG9mIExvbmcgUmVzcG9uc2UnKSArCiAgZmFjZXRfd3JhcCh+UGFydGljaXBhbnQpCmBgYAoKSW4gdGhlIGVuZCwgd2UgdmlzdWFsaXplIHRoZSBtZWFuIFBTRXMgYW5kIEpORHMuIApgYGB7cn0KcHN5X3Jlc3VsdHMgJT4lIGdyb3VwX2J5KFRlbXBvKSAlPiUKICBzdW1tYXJpc2UobVBTRSA9IG1lYW4ocHNlKSkgJT4lIAogIGdncGxvdChhZXMoVGVtcG8sIG1QU0UpKSArIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknLCB3aWR0aCA9IDAuNSkgKwogIHRoZW1lX2NsYXNzaWMoKQogIApgYGAKCllvdXIgdGFzazogcGxlYXNlIGFkZCBlcnJvcmJhcnMgYW5kIGFsc28gcGxvdCB0aGUgZmlndXJlIGZvciBtZWFuIEpORHMuIAoKT25lIG1vcmUgTm90ZTogdGhlIEdMTSBhcHByb2FjaCBkb2VzIG5vdCBhc3N1bWUgYW55IGxhcHNlIHJhdGUsIGFuZCBndWVzcyByYXRlIGhlcmUuIFRoaXMgaXMgYmVjYXVzZSBsb2dpc3RpYyByZWdyZXNzaW9uIGFzc3VtZSB0aGUgcmFuZ2UgZnJvbSAwIHRvIDEuCgojIyA0LiBgUXVpY2twc3lgIGFwcHJvYWNoCgpRdWlja3BzeSBpcyBhbiBSIHBhY2thZ2UgZGV2ZWxvcGVkIGJ5IERhbmllbCBMaW5hcmVzIGFuZCBKb2FuIExvw7NwZXotTW9saW5lciB0byBxdWlja2x5IGZpdCBhbmQgcGxvdCBwc3ljaG9tZXRyaWMgZnVjbnRpb25zIGZvciBtdWx0aXBsZSBjb25kaXRpb25zLiBZb3UgY2FuIGZpbmQgdGhlIGludHJvZHVjdGlvbiBhbmQgcXVpY2sgdHV0b3JpYWwgZnJvbSBbdGhlaXIgb2ZmaWNpYWwgd2Vic2l0ZV0oaHR0cDovL2RsaW5hcmVzLm9yZy9xdWlja3BzeS5odG1sKS4gCgpGaXJzdCwgcGxlYXNlIG9wZW4gYHF1aWNrcHN5YCBoZWxwIGZpbGUgYW5kIGNoZWNrIGl0J3MgZ3JhbW1hci4gSXQgaGFzIHNldmVyYWwgaW1wb3J0YW50IHBhcmFtZXRlcnM6IGB4YCBmb3Igc3RpbXVsdXMgaW50ZW5zaXR5LCBga2AgZm9yIHJlc3BvbnNlIHZhcmlhYmxlLiBUaGUgcmVzcG9uc2UgdmFyaWFibGUgY291bGQgYmUgdGhlIG51bWJlciBvZiB0cmlhbHMgaW4gd2hpY2ggYSB5ZXMtdHlwZSByZXNwb25zZSB3YXMgZ2l2ZW4gb3IgYSB2ZWN0b3Igb2YgMHMgYW5kIDFzIGluZGljYXRpbmcgdGhlIHJlc3BvbnNlIG9uIGVhY2ggdHJpYWwuIEluIGFkZGl0aW9uLCB0aGUgZ3JvdXAgdmFyaWFibGUgYGdyb3VwaW5nYCwgd2hlcmUgeW91IGNhbiBzcGVjaWZ5IG11bHRpcGxlIGdyb3VwIHZhcmlhYmxlcyB3aXRoIHRoZSBvcGVyYXRvciBgLigpYC4gCgpJbiBhZGRpdGlvbiwgeW91IGNhbiBlc3RpbWF0ZSB0aGUgbGFwc2UgcmF0ZSwgYnkgc3RhdGUgYGxhcHNlcyA9IFRSVUVgLiAKCkxldCdzIGZpcnN0IGVzdGltYXRlIHRoZSBwc3ljaG9tZXRyaWMgZnVuY3Rpb24gdXNpbmcgdGhlIHJhdyBkYXRhLgoKYGBge3J9CiMgRXN0aW1hdGUgcHN5Y2hvbWV0cmljIGZ1bmN0aW9uIGFuZCBQU0VzCnF1aWNrcHN5KHJhd19iaXNlY3Rpb24sIHggPSBEdXJhdGlvbiwgayA9IHJlc3AsIAogICAgICAgICBncm91cGluZyA9IC4oVGVtcG8sUGFydGljaXBhbnQpLCBsYXBzZXMgPSBUUlVFLCAKICAgICAgICAgYm9vdHN0cmFwID0gJ25vbmUnKSAtPiBxcAojIHNob3cgdGhlIHRocmVzaG9sZHMKaGVhZChxcCR0aHJlc2hvbGRzKQpgYGAKClRoZXJlIGlzIG9uZSBzcGVjaWFsIGZ1bmN0aW9uIGNhbGxlZCBgcGxvdGN1cnZlcygpYCBmb3IgdmlzdWFsaXplZCB0aGUgZXN0aW1hdGVkIHBzeWNob21ldHJpYyBmdW5jdGlvbnMuIAoKYGBge3J9CnBsb3RjdXJ2ZXMocXApCmBgYAoKUGxlYXNlIGNvbXBhcmUgdGhlIGVzdGltYXRpb25zIGZyb20gR0xNIGFuZCBxdWlja3BzeSwgYW5kIGRpc2N1c3Mgd2l0aCB5b3VyIGNsYXNzbWF0ZXMgdGhlIHBvdGVudGlhbCBpbXBhY3RzIG9mIGFkZGluZyB0aGUgbGFwc2UgcmF0ZSBpbiB0aGUgZXN0aW1hdGlvbi4gCgpZb3VyIHRhc2s6IHBsZWFzZSBleHRyYWN0IHRoZSB0aHJlc2hvbGRzIChQU0VzKSwgSk5EcywgYW5kIGxhcHNlIHJhdGVzIGZyb20gdGhlIHF1aWNrcHN5IGVzdGltYXRpb25zLCBhbmQgdmlzdWFsaXNlIHRoZW0uIAo=