The first major assumption is that you have multiple PFAS measures paired per animal and per tissue.
i.e. 1633 results with dosed PFAS and other co-occuring PFAS.

The second major assumption is that you desire to relate tissue to tissue or dose to tissue while accounting or not accounting for mixture interactions. But, don’t have a “full factorial” study design, so can’t actually do the full work up.

We used a mixed effects model, maximum likelihood methods, so not Bayesian hierarchical approaches, but using this method, the toxicologists will listen. Importantly, this is still a hierarchical model–change the y-intercept based upon the PFAS and then estimate using the mean slope, etc. I also reported the indvidual-derived residuals as a 3rd level to highlight the amount of variability that is observed in the data.

(this is kind of an Andrew axe to grind, but individuals are more variable than the trends when we have laboratory study sample sizes, so folks tend to have likely inflated concerns about field data given the noise they observe in smaller sample sizes)

packages

library(knitr)
library(tidyverse)
library(lme4)
library(equatiomatic)
library(broom.mixed)

stacked data

Data should be “stacked” with sufficient ID columns and then a column for PFAS ID, a column for Tissue #1 ID, and a column for Tissue #2 ID.

Use log base e to clean things up, requires considering parameters as multiplicative per unit changes and some farting around to predict, but data are generally going to be log normal anyways, so I’m not concerned about it.

dataframe <- data.frame(
  animalID=c(1,1,1,1,1,2,2,2,2,2),
  treatment=c(rep("C68Low",10)),
  Sex=factor(c(rep("M",5),rep("F",5)), levels=c("F","M")),
  PFAS=factor(c("PFOS","PFHxS","PFOA","PFHxA","PFPeS","PFOS","PFHxS","PFOA","PFHxA","PFPeS")),
  Dose=c(0.428,0.3350,0.125,0.207,NA,0.428,0.3350,0.125,0.207,NA),
  Wholebody=c(3,2,1,2,1,3.1,2.1,1.1,2.1,1.1)
) # example vectors showing 2 animals.

dataframe$logDose <- log(dataframe$Dose)
dataframe$logWholebody <- log(dataframe$Wholebody)

full mixture model (i.e. normally distributed (by PFAS) slope and y-intercept)

mixturemodel <- lmer(logWholebody~logDose+Sex+(logDose|PFAS), data=dataframe, REML=T)  # this reprex doesn't work because too few data

# use below if you have many NAs in dataset:
# mixturemodel <- lmer(logWholebody~logDose+Sex+(logDose|PFAS), data=dataframe|>filter(!is.na(logWholebody)), REML=T) 

# this reprex works, but is ugly:
# mixturemodel <- lmer(logWholebody~logDose+Sex+(logDose|PFAS), data=rbind(dataframe,dataframe), REML=T)  # singular fit because overly simplistic reprex

summary(mixturemodel) # full model summary output  
# Pay attention to variance and stdev of y-intercept and slope by PFAS against residual

coef(mixturemodel) # actual parameter estimates
ranef(mixturemodel) # PFAS-specific adjustments to the mean model y-intercept and slope
fixef(mixturemodel) # overall mean model y-intercept and slope

In the mouse analysis, the variance and stdev associated with the PFAS-specific slope was substantially smaller than the variance and stdev associated with residuals. The interpretation is then that the effect of a PFAS-specific slope is smaller than the individual-to-individual variability–i.e. you could not detect a mixture interaction in the field, so it likely does not exist.

additive mixture model (i.e. normally distributed (by PFAS) y-intercept only)

nomixturemodel <- lmer(logWholebody~logDose+Sex+(1|PFAS), data=dataframe, REML=T)  # this reprex works because simpler model

# use below if you have many NAs in dataset:
# nomixturemodel <- lmer(logWholebody~logDose+Sex+(1|PFAS), data=dataframe|>filter(!is.na(logWholebody)), REML=T) 

summary(nomixturemodel) # full model summary output  
## Linear mixed model fit by REML ['lmerMod']
## Formula: logWholebody ~ logDose + Sex + (1 | PFAS)
##    Data: dataframe
## 
## REML criterion at convergence: -11.3
## 
## Scaled residuals: 
##     Min      1Q  Median      3Q     Max 
## -1.0398 -0.3319  0.0047  0.3450  0.9970 
## 
## Random effects:
##  Groups   Name        Variance  Std.Dev.
##  PFAS     (Intercept) 0.0383413 0.19581 
##  Residual             0.0003645 0.01909 
## Number of obs: 8, groups:  PFAS, 4
## 
## Fixed effects:
##             Estimate Std. Error t value
## (Intercept)  1.73790    0.30725   5.656
## logDose      0.75776    0.20803   3.643
## SexM        -0.05642    0.01350  -4.179
## 
## Correlation of Fixed Effects:
##         (Intr) logDos
## logDose  0.947       
## SexM    -0.022  0.000
# Pay attention to variance and stdev of y-intercept by PFAS against residual

coef(nomixturemodel) # actual parameter estimates
## $PFAS
##       (Intercept)   logDose        SexM
## PFHxA    1.938303 0.7577625 -0.05642008
## PFHxS    1.575233 0.7577625 -0.05642008
## PFOA     1.651996 0.7577625 -0.05642008
## PFOS     1.786050 0.7577625 -0.05642008
## 
## attr(,"class")
## [1] "coef.mer"
ranef(nomixturemodel) # PFAS-specific adjustments to the mean model y-intercept 
## $PFAS
##       (Intercept)
## PFHxA  0.20040747
## PFHxS -0.16266227
## PFOA  -0.08589941
## PFOS   0.04815421
## 
## with conditional variances for "PFAS"
fixef(nomixturemodel) # overall mean model y-intercept 
## (Intercept)     logDose        SexM 
##  1.73789565  0.75776247 -0.05642008
# see the broom and broom.mixed packages for increased efficiency and labeling cleanup.
nomixturemodel |>
  broom.mixed::tidy() |>
  #dplyr::select(effect, term, estimate, std.error)|>
  group_by(effect, term) |>
  summarize(`Mean, SE`=paste(round(estimate,3),", ",round(std.error,3))) |>
  pivot_wider(names_from=c(effect,term), values_from=`Mean, SE`) 
## `summarise()` has grouped output by 'effect'. You can override using the
## `.groups` argument.
#equatiomatic just makes nice html math from the lmer functions.

extract_eq(nomixturemodel, 
           use_coefs=T,
           mean_separate=T,
           swap_subscript_names=c("M"="Male"),
           swap_var_names=c("logWholebody"="ln(Wholebody, ng/g)",
                            "logDose"="ln(Dose, mg/kg-d)")
           )

\[ \begin{aligned} \operatorname{\widehat{ln(Wholebody,\ ng/g)}}_{i} &\sim N \left(\mu, \sigma^2 \right) \\ \mu &=1.74_{\alpha_{j[i]}} - 0.06_{\beta_{1}}(\operatorname{Sex}_{\operatorname{Male}}) \\ \alpha_{j} &\sim N \left(0.76_{\gamma_{1}^{\alpha}}(\operatorname{ln(Dose,\ mg/kg-d)}), 0.2 \right) \text{, for PFAS j = 1,} \dots \text{,J} \end{aligned} \]

The below is from a mix of hand coding and R tricks to get nice math.

\[ \widehat{ln(Whole\ Body,\ ng/g)} \sim N (\mu, 0.02) \\ \mu =\begin{bmatrix} PFHxA & 1.94 \\ PFHxS & 1.58 \\ PFOA & 1.65 \\ PFOS & 1.79 \\ \end{bmatrix} + 0.76(ln(Dose, mg/kg))+ -0.06(Sex_{M=1;F=0}) \]

Comes from:

$$ 
\widehat{ln(Whole\ Body,\ ng/g)}  \sim N (\mu, `r round(as.data.frame(VarCorr(nomixturemodel))[2,5],2)`) \\
\mu =\begin{bmatrix}
  PFHxA & 1.94 \\ 
  PFHxS & 1.58 \\ 
  PFOA & 1.65 \\ 
  PFOS & 1.79 \\ 
   \end{bmatrix} + `r round(fixef(nomixturemodel),2)[[2]]`(ln(Dose, mg/kg))+ `r round(fixef(nomixturemodel),2)[[3]]`(Sex_{M=1;F=0})
$$

written in line in the .Rmd file.

LS0tDQp0aXRsZTogIkV2YWx1YXRpbmcgVGlzc3VlLXRvLVRpc3N1ZSBSZWxhdGlvbnNoaXBzIHdpdGggTWl4dHVyZXMgQ29udGV4dCINCmF1dGhvcjogIkFuZHJldyBFYXN0Ig0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KLS0tDQoNCg0KVGhlIGZpcnN0IG1ham9yIGFzc3VtcHRpb24gaXMgdGhhdCB5b3UgaGF2ZSBtdWx0aXBsZSBQRkFTIG1lYXN1cmVzIHBhaXJlZCBwZXIgYW5pbWFsIGFuZCBwZXIgdGlzc3VlLiAgDQppLmUuIDE2MzMgcmVzdWx0cyB3aXRoIGRvc2VkIFBGQVMgYW5kIG90aGVyIGNvLW9jY3VyaW5nIFBGQVMuICANCg0KVGhlIHNlY29uZCBtYWpvciBhc3N1bXB0aW9uIGlzIHRoYXQgeW91IGRlc2lyZSB0byByZWxhdGUgdGlzc3VlIHRvIHRpc3N1ZSBvciBkb3NlIHRvIHRpc3N1ZSB3aGlsZSBhY2NvdW50aW5nIG9yIG5vdCBhY2NvdW50aW5nIGZvciBtaXh0dXJlIGludGVyYWN0aW9ucy4gQnV0LCBkb24ndCBoYXZlIGEgImZ1bGwgZmFjdG9yaWFsIiBzdHVkeSBkZXNpZ24sIHNvIGNhbid0IGFjdHVhbGx5IGRvIHRoZSBmdWxsIHdvcmsgdXAuICANCg0KV2UgdXNlZCBhIG1peGVkIGVmZmVjdHMgbW9kZWwsIG1heGltdW0gbGlrZWxpaG9vZCBtZXRob2RzLCBzbyBub3QgQmF5ZXNpYW4gaGllcmFyY2hpY2FsIGFwcHJvYWNoZXMsIGJ1dCB1c2luZyB0aGlzIG1ldGhvZCwgdGhlIHRveGljb2xvZ2lzdHMgd2lsbCBsaXN0ZW4uIEltcG9ydGFudGx5LCB0aGlzIGlzIHN0aWxsIGEgaGllcmFyY2hpY2FsIG1vZGVsLS1jaGFuZ2UgdGhlIHktaW50ZXJjZXB0IGJhc2VkIHVwb24gdGhlIFBGQVMgYW5kIHRoZW4gZXN0aW1hdGUgdXNpbmcgdGhlIG1lYW4gc2xvcGUsIGV0Yy4gSSBhbHNvIHJlcG9ydGVkIHRoZSBpbmR2aWR1YWwtZGVyaXZlZCByZXNpZHVhbHMgYXMgYSAzcmQgbGV2ZWwgdG8gaGlnaGxpZ2h0IHRoZSBhbW91bnQgb2YgdmFyaWFiaWxpdHkgdGhhdCBpcyBvYnNlcnZlZCBpbiB0aGUgZGF0YS4gIA0KDQoodGhpcyBpcyBraW5kIG9mIGFuIEFuZHJldyBheGUgdG8gZ3JpbmQsIGJ1dCBpbmRpdmlkdWFscyBhcmUgbW9yZSB2YXJpYWJsZSB0aGFuIHRoZSB0cmVuZHMgd2hlbiB3ZSBoYXZlIGxhYm9yYXRvcnkgc3R1ZHkgc2FtcGxlIHNpemVzLCBzbyBmb2xrcyB0ZW5kIHRvIGhhdmUgbGlrZWx5IGluZmxhdGVkIGNvbmNlcm5zIGFib3V0IGZpZWxkIGRhdGEgZ2l2ZW4gdGhlIG5vaXNlIHRoZXkgb2JzZXJ2ZSBpbiBzbWFsbGVyIHNhbXBsZSBzaXplcykNCg0KDQojIHBhY2thZ2VzDQoNCmBgYHtyLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0NCg0KbGlicmFyeShrbml0cikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShsbWU0KQ0KbGlicmFyeShlcXVhdGlvbWF0aWMpDQpsaWJyYXJ5KGJyb29tLm1peGVkKQ0KDQpgYGANCg0KIyBzdGFja2VkIGRhdGEgIA0KDQpEYXRhIHNob3VsZCBiZSAic3RhY2tlZCIgd2l0aCBzdWZmaWNpZW50IElEIGNvbHVtbnMgYW5kIHRoZW4gYSBjb2x1bW4gZm9yIFBGQVMgSUQsIGEgY29sdW1uIGZvciBUaXNzdWUgIzEgSUQsIGFuZCBhIGNvbHVtbiBmb3IgVGlzc3VlICMyIElELiAgDQoNClVzZSBsb2cgYmFzZSBlIHRvIGNsZWFuIHRoaW5ncyB1cCwgcmVxdWlyZXMgY29uc2lkZXJpbmcgcGFyYW1ldGVycyBhcyBtdWx0aXBsaWNhdGl2ZSBwZXIgdW5pdCBjaGFuZ2VzIGFuZCBzb21lIGZhcnRpbmcgYXJvdW5kIHRvIHByZWRpY3QsIGJ1dCBkYXRhIGFyZSBnZW5lcmFsbHkgZ29pbmcgdG8gYmUgbG9nIG5vcm1hbCBhbnl3YXlzLCBzbyBJJ20gbm90IGNvbmNlcm5lZCBhYm91dCBpdC4gIA0KDQpgYGB7cn0NCg0KZGF0YWZyYW1lIDwtIGRhdGEuZnJhbWUoDQogIGFuaW1hbElEPWMoMSwxLDEsMSwxLDIsMiwyLDIsMiksDQogIHRyZWF0bWVudD1jKHJlcCgiQzY4TG93IiwxMCkpLA0KICBTZXg9ZmFjdG9yKGMocmVwKCJNIiw1KSxyZXAoIkYiLDUpKSwgbGV2ZWxzPWMoIkYiLCJNIikpLA0KICBQRkFTPWZhY3RvcihjKCJQRk9TIiwiUEZIeFMiLCJQRk9BIiwiUEZIeEEiLCJQRlBlUyIsIlBGT1MiLCJQRkh4UyIsIlBGT0EiLCJQRkh4QSIsIlBGUGVTIikpLA0KICBEb3NlPWMoMC40MjgsMC4zMzUwLDAuMTI1LDAuMjA3LE5BLDAuNDI4LDAuMzM1MCwwLjEyNSwwLjIwNyxOQSksDQogIFdob2xlYm9keT1jKDMsMiwxLDIsMSwzLjEsMi4xLDEuMSwyLjEsMS4xKQ0KKSAjIGV4YW1wbGUgdmVjdG9ycyBzaG93aW5nIDIgYW5pbWFscy4NCg0KZGF0YWZyYW1lJGxvZ0Rvc2UgPC0gbG9nKGRhdGFmcmFtZSREb3NlKQ0KZGF0YWZyYW1lJGxvZ1dob2xlYm9keSA8LSBsb2coZGF0YWZyYW1lJFdob2xlYm9keSkNCg0KYGBgDQoNCiMgZnVsbCBtaXh0dXJlIG1vZGVsIChpLmUuIG5vcm1hbGx5IGRpc3RyaWJ1dGVkIChieSBQRkFTKSBzbG9wZSBhbmQgeS1pbnRlcmNlcHQpICANCg0KYGBge3IsIGV2YWw9Rn0NCg0KbWl4dHVyZW1vZGVsIDwtIGxtZXIobG9nV2hvbGVib2R5fmxvZ0Rvc2UrU2V4Kyhsb2dEb3NlfFBGQVMpLCBkYXRhPWRhdGFmcmFtZSwgUkVNTD1UKSAgIyB0aGlzIHJlcHJleCBkb2Vzbid0IHdvcmsgYmVjYXVzZSB0b28gZmV3IGRhdGENCg0KIyB1c2UgYmVsb3cgaWYgeW91IGhhdmUgbWFueSBOQXMgaW4gZGF0YXNldDoNCiMgbWl4dHVyZW1vZGVsIDwtIGxtZXIobG9nV2hvbGVib2R5fmxvZ0Rvc2UrU2V4Kyhsb2dEb3NlfFBGQVMpLCBkYXRhPWRhdGFmcmFtZXw+ZmlsdGVyKCFpcy5uYShsb2dXaG9sZWJvZHkpKSwgUkVNTD1UKSANCg0KIyB0aGlzIHJlcHJleCB3b3JrcywgYnV0IGlzIHVnbHk6DQojIG1peHR1cmVtb2RlbCA8LSBsbWVyKGxvZ1dob2xlYm9keX5sb2dEb3NlK1NleCsobG9nRG9zZXxQRkFTKSwgZGF0YT1yYmluZChkYXRhZnJhbWUsZGF0YWZyYW1lKSwgUkVNTD1UKSAgIyBzaW5ndWxhciBmaXQgYmVjYXVzZSBvdmVybHkgc2ltcGxpc3RpYyByZXByZXgNCg0Kc3VtbWFyeShtaXh0dXJlbW9kZWwpICMgZnVsbCBtb2RlbCBzdW1tYXJ5IG91dHB1dCAgDQojIFBheSBhdHRlbnRpb24gdG8gdmFyaWFuY2UgYW5kIHN0ZGV2IG9mIHktaW50ZXJjZXB0IGFuZCBzbG9wZSBieSBQRkFTIGFnYWluc3QgcmVzaWR1YWwNCg0KY29lZihtaXh0dXJlbW9kZWwpICMgYWN0dWFsIHBhcmFtZXRlciBlc3RpbWF0ZXMNCnJhbmVmKG1peHR1cmVtb2RlbCkgIyBQRkFTLXNwZWNpZmljIGFkanVzdG1lbnRzIHRvIHRoZSBtZWFuIG1vZGVsIHktaW50ZXJjZXB0IGFuZCBzbG9wZQ0KZml4ZWYobWl4dHVyZW1vZGVsKSAjIG92ZXJhbGwgbWVhbiBtb2RlbCB5LWludGVyY2VwdCBhbmQgc2xvcGUNCg0KYGBgDQoNCkluIHRoZSBtb3VzZSBhbmFseXNpcywgdGhlIHZhcmlhbmNlIGFuZCBzdGRldiBhc3NvY2lhdGVkIHdpdGggdGhlIFBGQVMtc3BlY2lmaWMgc2xvcGUgd2FzIHN1YnN0YW50aWFsbHkgc21hbGxlciB0aGFuIHRoZSB2YXJpYW5jZSBhbmQgc3RkZXYgYXNzb2NpYXRlZCB3aXRoIHJlc2lkdWFscy4gVGhlIGludGVycHJldGF0aW9uIGlzIHRoZW4gdGhhdCB0aGUgZWZmZWN0IG9mIGEgUEZBUy1zcGVjaWZpYyBzbG9wZSBpcyBzbWFsbGVyIHRoYW4gdGhlIGluZGl2aWR1YWwtdG8taW5kaXZpZHVhbCB2YXJpYWJpbGl0eS0taS5lLiB5b3UgY291bGQgbm90IGRldGVjdCBhIG1peHR1cmUgaW50ZXJhY3Rpb24gaW4gdGhlIGZpZWxkLCBzbyBpdCBsaWtlbHkgZG9lcyBub3QgZXhpc3QuICANCg0KDQojIGFkZGl0aXZlIG1peHR1cmUgbW9kZWwgKGkuZS4gbm9ybWFsbHkgZGlzdHJpYnV0ZWQgKGJ5IFBGQVMpIHktaW50ZXJjZXB0IG9ubHkpICANCg0KYGBge3J9DQoNCm5vbWl4dHVyZW1vZGVsIDwtIGxtZXIobG9nV2hvbGVib2R5fmxvZ0Rvc2UrU2V4KygxfFBGQVMpLCBkYXRhPWRhdGFmcmFtZSwgUkVNTD1UKSAgIyB0aGlzIHJlcHJleCB3b3JrcyBiZWNhdXNlIHNpbXBsZXIgbW9kZWwNCg0KIyB1c2UgYmVsb3cgaWYgeW91IGhhdmUgbWFueSBOQXMgaW4gZGF0YXNldDoNCiMgbm9taXh0dXJlbW9kZWwgPC0gbG1lcihsb2dXaG9sZWJvZHl+bG9nRG9zZStTZXgrKDF8UEZBUyksIGRhdGE9ZGF0YWZyYW1lfD5maWx0ZXIoIWlzLm5hKGxvZ1dob2xlYm9keSkpLCBSRU1MPVQpIA0KDQpzdW1tYXJ5KG5vbWl4dHVyZW1vZGVsKSAjIGZ1bGwgbW9kZWwgc3VtbWFyeSBvdXRwdXQgIA0KIyBQYXkgYXR0ZW50aW9uIHRvIHZhcmlhbmNlIGFuZCBzdGRldiBvZiB5LWludGVyY2VwdCBieSBQRkFTIGFnYWluc3QgcmVzaWR1YWwNCg0KY29lZihub21peHR1cmVtb2RlbCkgIyBhY3R1YWwgcGFyYW1ldGVyIGVzdGltYXRlcw0KcmFuZWYobm9taXh0dXJlbW9kZWwpICMgUEZBUy1zcGVjaWZpYyBhZGp1c3RtZW50cyB0byB0aGUgbWVhbiBtb2RlbCB5LWludGVyY2VwdCANCmZpeGVmKG5vbWl4dHVyZW1vZGVsKSAjIG92ZXJhbGwgbWVhbiBtb2RlbCB5LWludGVyY2VwdCANCg0KIyBzZWUgdGhlIGJyb29tIGFuZCBicm9vbS5taXhlZCBwYWNrYWdlcyBmb3IgaW5jcmVhc2VkIGVmZmljaWVuY3kgYW5kIGxhYmVsaW5nIGNsZWFudXAuDQpub21peHR1cmVtb2RlbCB8Pg0KICBicm9vbS5taXhlZDo6dGlkeSgpIHw+DQogICNkcGx5cjo6c2VsZWN0KGVmZmVjdCwgdGVybSwgZXN0aW1hdGUsIHN0ZC5lcnJvcil8Pg0KICBncm91cF9ieShlZmZlY3QsIHRlcm0pIHw+DQogIHN1bW1hcml6ZShgTWVhbiwgU0VgPXBhc3RlKHJvdW5kKGVzdGltYXRlLDMpLCIsICIscm91bmQoc3RkLmVycm9yLDMpKSkgfD4NCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT1jKGVmZmVjdCx0ZXJtKSwgdmFsdWVzX2Zyb209YE1lYW4sIFNFYCkgDQoNCiNlcXVhdGlvbWF0aWMganVzdCBtYWtlcyBuaWNlIGh0bWwgbWF0aCBmcm9tIHRoZSBsbWVyIGZ1bmN0aW9ucy4NCg0KZXh0cmFjdF9lcShub21peHR1cmVtb2RlbCwgDQogICAgICAgICAgIHVzZV9jb2Vmcz1ULA0KICAgICAgICAgICBtZWFuX3NlcGFyYXRlPVQsDQogICAgICAgICAgIHN3YXBfc3Vic2NyaXB0X25hbWVzPWMoIk0iPSJNYWxlIiksDQogICAgICAgICAgIHN3YXBfdmFyX25hbWVzPWMoImxvZ1dob2xlYm9keSI9ImxuKFdob2xlYm9keSwgbmcvZykiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsb2dEb3NlIj0ibG4oRG9zZSwgbWcva2ctZCkiKQ0KICAgICAgICAgICApDQoNCg0KDQpgYGANCg0KDQpUaGUgYmVsb3cgaXMgZnJvbSBhIG1peCBvZiBoYW5kIGNvZGluZyBhbmQgUiB0cmlja3MgdG8gZ2V0IG5pY2UgbWF0aC4gIA0KDQoNCg0KJCQgDQpcd2lkZWhhdHtsbihXaG9sZVwgQm9keSxcIG5nL2cpfSAgXHNpbSBOIChcbXUsIGByIHJvdW5kKGFzLmRhdGEuZnJhbWUoVmFyQ29ycihub21peHR1cmVtb2RlbCkpWzIsNV0sMilgKSBcXA0KXG11ID1cYmVnaW57Ym1hdHJpeH0NCiAgUEZIeEEgJiAxLjk0IFxcIA0KICBQRkh4UyAmIDEuNTggXFwgDQogIFBGT0EgJiAxLjY1IFxcIA0KICBQRk9TICYgMS43OSBcXCANCiAgIFxlbmR7Ym1hdHJpeH0gKyBgciByb3VuZChmaXhlZihub21peHR1cmVtb2RlbCksMilbWzJdXWAobG4oRG9zZSwgbWcva2cpKSsgYHIgcm91bmQoZml4ZWYobm9taXh0dXJlbW9kZWwpLDIpW1szXV1gKFNleF97TT0xO0Y9MH0pDQokJA0KDQpDb21lcyBmcm9tOiAgDQpgYGB7dmVyYmF0aW19DQokJCANClx3aWRlaGF0e2xuKFdob2xlXCBCb2R5LFwgbmcvZyl9ICBcc2ltIE4gKFxtdSwgYHIgcm91bmQoYXMuZGF0YS5mcmFtZShWYXJDb3JyKG5vbWl4dHVyZW1vZGVsKSlbMiw1XSwyKWApIFxcDQpcbXUgPVxiZWdpbntibWF0cml4fQ0KICBQRkh4QSAmIDEuOTQgXFwgDQogIFBGSHhTICYgMS41OCBcXCANCiAgUEZPQSAmIDEuNjUgXFwgDQogIFBGT1MgJiAxLjc5IFxcIA0KICAgXGVuZHtibWF0cml4fSArIGByIHJvdW5kKGZpeGVmKG5vbWl4dHVyZW1vZGVsKSwyKVtbMl1dYChsbihEb3NlLCBtZy9rZykpKyBgciByb3VuZChmaXhlZihub21peHR1cmVtb2RlbCksMilbWzNdXWAoU2V4X3tNPTE7Rj0wfSkNCiQkDQpgYGANCndyaXR0ZW4gaW4gbGluZSBpbiB0aGUgLlJtZCBmaWxlLg0KDQo=