Data and package pre-work
library(tidyverse, quietly=T)
library(readxl)
library(broom)
library(knitr)
plantdata <- read_excel("Soil-Plant Stacked Data 8-22-25.xlsx", sheet="Comprehensive Data")
plantdata$PFAS <- factor(plantdata$PFAS, levels=c("PFBS","PFHxS","PFHpS","PFOS","PFNS","PFBA","PFPeA","PFHxA","PFOA","PFHpA","PFNA","PFDA","PFOSA","8:2 FTS"))
Data exploration
Kale concentration over time
ggplot() +
geom_point(data=plantdata|>filter(Material=="Kale"), aes(x=`Age of plant (d)`, y=`concentration µg/kg`, color=PFAS)) +
geom_smooth(data=plantdata|>filter(Material=="Kale"), aes(x=`Age of plant (d)`, y=`concentration µg/kg`, color=PFAS), method="lm", formula=y~x) +
facet_wrap(~PFAS)+
scale_y_log10()
## Warning in scale_y_log10(): log-10 transformation introduced infinite values.
## log-10 transformation introduced infinite values.
## Warning: Removed 87 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 59 rows containing missing values or values outside the scale range
## (`geom_point()`).

ggplot() +
geom_point(data=plantdata|>filter(Material=="Kale"), aes(x=`Age of plant (d)`, y=`concentration µg/kg`, color=PFAS)) +
geom_smooth(data=plantdata|>filter(Material=="Kale"), aes(x=`Age of plant (d)`, y=`concentration µg/kg`, color=PFAS), method="lm", formula=y~x) +
facet_wrap(~PFAS, scales="free")
## Warning: Removed 59 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Removed 59 rows containing missing values or values outside the scale range
## (`geom_point()`).

Soil concentration over time
ggplot() +
geom_point(data=plantdata|>filter(Material=="Soil"), aes(x=`Age of plant (d)`, y=`concentration µg/kg`, color=PFAS)) +
geom_smooth(data=plantdata|>filter(Material=="Soil"), aes(x=`Age of plant (d)`, y=`concentration µg/kg`, color=PFAS), method="lm", formula=y~x) +
facet_wrap(~PFAS)+
scale_y_log10()
## Warning in scale_y_log10(): log-10 transformation introduced infinite values.
## log-10 transformation introduced infinite values.
## Warning: Removed 7 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 5 rows containing missing values or values outside the scale range
## (`geom_point()`).

ggplot() +
geom_point(data=plantdata|>filter(Material=="Soil"), aes(x=`Age of plant (d)`, y=`concentration µg/kg`, color=PFAS)) +
geom_smooth(data=plantdata|>filter(Material=="Soil"), aes(x=`Age of plant (d)`, y=`concentration µg/kg`, color=PFAS), method="lm", formula=y~x) +
facet_wrap(~PFAS, scales="free")
## Warning: Removed 5 rows containing non-finite outside the scale range (`stat_smooth()`).
## Removed 5 rows containing missing values or values outside the scale range
## (`geom_point()`).

Data split
soilonly <- plantdata |>
filter(Material=="Soil")
kaleonly <- plantdata |>
filter(Material=="Kale")
combodata <- full_join(
x=soilonly,
y=kaleonly,
by=c("Pairing ID","PFAS")
)
combocleanup <- data.frame(
PFAS=combodata$PFAS,
Pairing_ID=combodata$`Pairing ID`,
time=combodata$`Age of plant (d).x`,
soilconc=combodata$`concentration µg/kg.x`,
kaleconc=combodata$`concentration µg/kg.y`
)
combocleanup$BCF <- combocleanup$kaleconc/combocleanup$soilconc
combocleanup$logkaleconc <- log(combocleanup$kaleconc)
combocleanup$logsoilconc <- log(combocleanup$soilconc)
combocleanup$logBCF <- log(combocleanup$BCF)
BCF plot exploration
ggplot() +
geom_point(data=combocleanup, aes(x=time, y=BCF, color=PFAS)) +
geom_smooth(data=combocleanup, aes(x=time, y=BCF, color=PFAS), method="lm", formula=y~x) +
facet_wrap(~PFAS)+
scale_y_log10()
## Warning in scale_y_log10(): log-10 transformation introduced infinite values.
## log-10 transformation introduced infinite values.
## Warning: Removed 93 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 65 rows containing missing values or values outside the scale range
## (`geom_point()`).

ggplot() +
geom_point(data=combocleanup, aes(x=time, y=BCF, color=PFAS)) +
geom_smooth(data=combocleanup, aes(x=time, y=BCF, color=PFAS), method="lm", formula=y~x) +
facet_wrap(~PFAS, scales="free")
## Warning: Removed 66 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Removed 65 rows containing missing values or values outside the scale range
## (`geom_point()`).

combocleanup |>
group_by(PFAS) |>
summarize(countBCF=sum(!is.na(BCF)),
meanBCF=mean(BCF, na.rm=T),
sdBCF=sd(BCF, na.rm=T),
upperBCF=quantile(BCF, probs=0.975, names=F, na.rm=T),
lowerBCF=quantile(BCF, probs=0.025, names=F, na.rm=T),
semBCF=sdBCF/countBCF
)
linear regression work
kale only
kalereg <- combocleanup |>
nest(data = -PFAS) |>
mutate(
fit = map(data, ~ lm(kaleconc ~ time, data = .x)),
tidied = map(fit, tidy)
) |>
unnest(tidied)
kalereg |>
filter(term=="time") |>
dplyr::select(c("PFAS","estimate","std.error","p.value")) |>
kable(digits=3, caption="Slope of [kale] by time")
Slope of [kale] by time
PFOS |
0.364 |
0.119 |
0.004 |
8:2 FTS |
-0.400 |
1.102 |
0.719 |
PFBA |
1.875 |
5.358 |
0.728 |
PFOSA |
-0.012 |
0.021 |
0.582 |
PFHpS |
1.129 |
0.365 |
0.004 |
PFOA |
2.376 |
0.691 |
0.001 |
PFHpA |
2.407 |
0.817 |
0.005 |
PFBS |
2.659 |
1.560 |
0.095 |
PFHxA |
3.059 |
1.250 |
0.018 |
PFHxS |
2.160 |
0.665 |
0.002 |
PFPeA |
2.885 |
3.809 |
0.453 |
PFNA |
0.062 |
0.038 |
0.135 |
PFDA |
-0.140 |
0.119 |
0.262 |
PFNS |
-0.002 |
0.007 |
0.797 |
logkalereg <- combocleanup |>
filter(!logkaleconc%in%c(NA,"-Inf"))|>
nest(data = -PFAS) |>
mutate(
fit = map(data, ~ lm(logkaleconc ~ time, data = .x)),
tidied = map(fit, tidy)
) |>
unnest(tidied)
logkalereg |>
filter(term=="time") |>
dplyr::select(c("PFAS","estimate","std.error","p.value")) |>
kable(digits=3, caption="Slope of ln([kale]) by time")
Slope of ln([kale]) by time
PFOS |
0.060 |
0.015 |
0.000 |
8:2 FTS |
0.049 |
0.024 |
0.045 |
PFBA |
0.023 |
0.015 |
0.131 |
PFOSA |
0.082 |
0.045 |
0.130 |
PFHpS |
0.061 |
0.015 |
0.000 |
PFOA |
0.066 |
0.016 |
0.000 |
PFHpA |
0.045 |
0.014 |
0.002 |
PFBS |
0.042 |
0.016 |
0.013 |
PFHxA |
0.054 |
0.017 |
0.003 |
PFHxS |
0.060 |
0.015 |
0.000 |
PFPeA |
0.037 |
0.017 |
0.037 |
PFNA |
0.067 |
0.039 |
0.117 |
PFDA |
NA |
NA |
NA |
PFNS |
-0.004 |
0.008 |
0.662 |
Soil only
soilreg <- combocleanup |>
nest(data = -PFAS) |>
mutate(
fit = map(data, ~ lm(soilconc ~ time, data = .x)),
tidied = map(fit, tidy)
) |>
unnest(tidied)
soilreg |>
filter(term=="time") |>
dplyr::select(c("PFAS","estimate","std.error","p.value")) |>
kable(digits=3, caption="Slope of [soil] by time")
Slope of [soil] by time
PFOS |
0.052 |
0.271 |
0.847 |
8:2 FTS |
-0.055 |
0.126 |
0.665 |
PFBA |
-0.157 |
0.184 |
0.400 |
PFOSA |
-0.189 |
0.232 |
0.421 |
PFHpS |
-0.171 |
0.276 |
0.538 |
PFOA |
-0.214 |
0.163 |
0.195 |
PFHpA |
-0.195 |
0.155 |
0.215 |
PFBS |
-0.191 |
0.316 |
0.549 |
PFHxA |
-0.071 |
0.200 |
0.724 |
PFHxS |
-0.057 |
0.185 |
0.762 |
PFPeA |
-0.122 |
0.332 |
0.716 |
PFNA |
0.001 |
0.006 |
0.842 |
PFDA |
-0.001 |
0.001 |
0.296 |
PFNS |
0.009 |
0.006 |
0.183 |
logsoilreg <- combocleanup |>
filter(!logsoilconc%in%c(NA,"-Inf"))|>
nest(data = -PFAS) |>
mutate(
fit = map(data, ~ lm(logsoilconc ~ time, data = .x)),
tidied = map(fit, tidy)
) |>
unnest(tidied)
logsoilreg |>
filter(term=="time") |>
dplyr::select(c("PFAS","estimate","std.error","p.value")) |>
kable(digits=3, caption="Slope of ln([soil]) by time")
Slope of ln([soil]) by time
PFOS |
0.002 |
0.011 |
0.873 |
8:2 FTS |
-0.010 |
0.009 |
0.287 |
PFBA |
-0.030 |
0.017 |
0.087 |
PFOSA |
-0.011 |
0.013 |
0.376 |
PFHpS |
-0.015 |
0.014 |
0.290 |
PFOA |
-0.027 |
0.015 |
0.084 |
PFHpA |
-0.031 |
0.018 |
0.098 |
PFBS |
-0.021 |
0.020 |
0.303 |
PFHxA |
-0.008 |
0.019 |
0.677 |
PFHxS |
-0.015 |
0.018 |
0.417 |
PFPeA |
-0.018 |
0.021 |
0.399 |
PFNA |
0.008 |
0.014 |
0.577 |
PFDA |
-0.015 |
0.007 |
0.046 |
PFNS |
0.011 |
0.017 |
0.542 |
BCF only
BCFreg <- combocleanup |>
filter(!BCF%in%c(NA,"Inf","NaN"))|>
nest(data = -PFAS) |>
mutate(
fit = map(data, ~ lm(BCF ~ time, data = .x)),
tidied = map(fit, tidy)
) |>
unnest(tidied)
BCFreg |>
filter(term=="time") |>
dplyr::select(c("PFAS","estimate","std.error","p.value")) |>
kable(digits=3, caption="Slope of [BCF] by time")
Slope of [BCF] by time
PFOS |
0.021 |
0.006 |
0.001 |
8:2 FTS |
-0.062 |
0.163 |
0.705 |
PFBA |
4.268 |
7.666 |
0.581 |
PFOSA |
-0.001 |
0.002 |
0.695 |
PFHpS |
0.175 |
0.066 |
0.011 |
PFOA |
1.233 |
0.542 |
0.028 |
PFHpA |
3.110 |
1.566 |
0.053 |
PFBS |
3.304 |
3.894 |
0.401 |
PFHxA |
1.478 |
1.616 |
0.365 |
PFHxS |
1.485 |
1.061 |
0.169 |
PFPeA |
0.730 |
8.180 |
0.929 |
PFNA |
0.226 |
0.128 |
0.108 |
PFDA |
-1.079 |
0.873 |
0.242 |
PFNS |
0.061 |
0.105 |
0.569 |
BCFmedianpredictions <- combocleanup |>
filter(!BCF%in%c(NA,"Inf","NaN"))|>
nest(data = -PFAS) |>
mutate(
fit = map(data, ~ lm(BCF ~ time, data = .x))
) |>
mutate(
prediction=map2(fit,data,~augment(.x, newdata=.y))
) |>
unnest(prediction)
BCFmedianpredictionssumm <- BCFmedianpredictions |>
filter(time==61)|>
group_by(PFAS) |>
summarize(estimate=mean(.fitted),
sd=sd(.resid),
count=sum(!is.na(.fitted)),
sem=sd/sqrt(count))
logBCFreg <- combocleanup |>
filter(!logBCF%in%c(NA,"-Inf","Inf","NaN"))|>
nest(data = -PFAS) |>
mutate(
fit = map(data, ~ lm(logBCF ~ time, data = .x)),
tidied = map(fit, tidy)
) |>
unnest(tidied)
logBCFreg |>
filter(term=="time") |>
dplyr::select(c("PFAS","estimate","std.error","p.value")) |>
kable(digits=3, caption="Slope of ln([BCF]) by time")
Slope of ln([BCF]) by time
PFOS |
0.057 |
0.017 |
0.002 |
8:2 FTS |
0.058 |
0.024 |
0.022 |
PFBA |
0.053 |
0.023 |
0.030 |
PFOSA |
0.064 |
0.053 |
0.282 |
PFHpS |
0.073 |
0.020 |
0.001 |
PFOA |
0.093 |
0.023 |
0.000 |
PFHpA |
0.076 |
0.025 |
0.003 |
PFBS |
0.063 |
0.025 |
0.017 |
PFHxA |
0.062 |
0.024 |
0.012 |
PFHxS |
0.070 |
0.024 |
0.006 |
PFPeA |
0.055 |
0.026 |
0.037 |
PFNA |
0.066 |
0.030 |
0.052 |
PFDA |
NA |
NA |
NA |
PFNS |
-0.016 |
0.024 |
0.510 |
boxplot of BCFs
boxplot(logBCF~PFAS, data=combocleanup, las=2)
## Warning in bplt(at[i], wid = width[i], stats = z$stats[, i], out =
## z$out[z$group == : Outliers (Inf, -Inf) in boxplot 12 are not drawn
## Warning in bplt(at[i], wid = width[i], stats = z$stats[, i], out =
## z$out[z$group == : Outlier (-Inf) in boxplot 14 is not drawn

boxplot(BCF~PFAS, data=combocleanup, ylim=c(0,10))
## Warning in bplt(at[i], wid = width[i], stats = z$stats[, i], out =
## z$out[z$group == : Outlier (Inf) in boxplot 12 is not drawn

Marginal means for BCF at time while accounting for time
ggplot()+
geom_point(data=BCFmedianpredictionssumm, aes(x=PFAS,y=estimate, color=PFAS), show.legend=F, size=5) +
geom_linerange(data=BCFmedianpredictionssumm, aes(x=PFAS, ymax=estimate+1.96*sd, ymin=estimate-1.96*sd, color=PFAS), show.legend=F, size=1.1)+
scale_y_log10()+
labs(y="estimate plus CI")+
theme_classic()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning in transformation$transform(x): NaNs produced
## Warning in scale_y_log10(): log-10 transformation introduced infinite values.
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_segment()`).

ggplot()+
geom_point(data=BCFmedianpredictionssumm, aes(x=PFAS,y=log10(estimate), color=PFAS), show.legend=F, size=5) +
geom_linerange(data=BCFmedianpredictionssumm, aes(x=PFAS, ymax=log10(estimate+1.96*sd), ymin=log10(estimate-1.96*sd), color=PFAS), show.legend=F, size=1.1)+
#scale_y_log10()+
labs(y="log10(estimate) plus CI")+
theme_classic()
## Warning in FUN(X[[i]], ...): NaNs produced
## Warning in FUN(X[[i]], ...): Removed 1 row containing missing values or values outside the scale range
## (`geom_segment()`).

LS0tDQp0aXRsZTogIlBsYW50IFBGQVMgbWl4dHVyZSB1cHRha2UgYW5kIEJDRiINCmF1dGhvcjogIkFuZHJldyBFYXN0Ig0KZGF0ZTogIjIwMjUtMDgtMjUiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50OiANCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQotLS0NCg0KIyBEYXRhIGFuZCBwYWNrYWdlIHByZS13b3JrICANCg0KDQoNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0NCg0KbGlicmFyeSh0aWR5dmVyc2UsIHF1aWV0bHk9VCkNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShicm9vbSkNCmxpYnJhcnkoa25pdHIpDQpwbGFudGRhdGEgPC0gcmVhZF9leGNlbCgiU29pbC1QbGFudCBTdGFja2VkIERhdGEgOC0yMi0yNS54bHN4Iiwgc2hlZXQ9IkNvbXByZWhlbnNpdmUgRGF0YSIpDQpwbGFudGRhdGEkUEZBUyA8LSBmYWN0b3IocGxhbnRkYXRhJFBGQVMsIGxldmVscz1jKCJQRkJTIiwiUEZIeFMiLCJQRkhwUyIsIlBGT1MiLCJQRk5TIiwiUEZCQSIsIlBGUGVBIiwiUEZIeEEiLCJQRk9BIiwiUEZIcEEiLCJQRk5BIiwiUEZEQSIsIlBGT1NBIiwiODoyIEZUUyIpKQ0KDQpgYGANCg0KIyBEYXRhIGV4cGxvcmF0aW9uICANCg0KIyMgS2FsZSBjb25jZW50cmF0aW9uIG92ZXIgdGltZSAgDQoNCg0KYGBge3J9DQoNCg0KDQpnZ3Bsb3QoKSArDQogIGdlb21fcG9pbnQoZGF0YT1wbGFudGRhdGF8PmZpbHRlcihNYXRlcmlhbD09IkthbGUiKSwgYWVzKHg9YEFnZSBvZiBwbGFudCAoZClgLCB5PWBjb25jZW50cmF0aW9uICDCtWcva2dgLCBjb2xvcj1QRkFTKSkgKw0KICBnZW9tX3Ntb290aChkYXRhPXBsYW50ZGF0YXw+ZmlsdGVyKE1hdGVyaWFsPT0iS2FsZSIpLCBhZXMoeD1gQWdlIG9mIHBsYW50IChkKWAsIHk9YGNvbmNlbnRyYXRpb24gIMK1Zy9rZ2AsIGNvbG9yPVBGQVMpLCBtZXRob2Q9ImxtIiwgZm9ybXVsYT15fngpICsNCiAgZmFjZXRfd3JhcCh+UEZBUykrDQogIHNjYWxlX3lfbG9nMTAoKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fcG9pbnQoZGF0YT1wbGFudGRhdGF8PmZpbHRlcihNYXRlcmlhbD09IkthbGUiKSwgYWVzKHg9YEFnZSBvZiBwbGFudCAoZClgLCB5PWBjb25jZW50cmF0aW9uICDCtWcva2dgLCBjb2xvcj1QRkFTKSkgKw0KICBnZW9tX3Ntb290aChkYXRhPXBsYW50ZGF0YXw+ZmlsdGVyKE1hdGVyaWFsPT0iS2FsZSIpLCBhZXMoeD1gQWdlIG9mIHBsYW50IChkKWAsIHk9YGNvbmNlbnRyYXRpb24gIMK1Zy9rZ2AsIGNvbG9yPVBGQVMpLCBtZXRob2Q9ImxtIiwgZm9ybXVsYT15fngpICsNCiAgZmFjZXRfd3JhcCh+UEZBUywgc2NhbGVzPSJmcmVlIikNCg0KYGBgDQoNCg0KIyMgU29pbCBjb25jZW50cmF0aW9uIG92ZXIgdGltZSAgDQoNCg0KYGBge3J9DQoNCmdncGxvdCgpICsNCiAgZ2VvbV9wb2ludChkYXRhPXBsYW50ZGF0YXw+ZmlsdGVyKE1hdGVyaWFsPT0iU29pbCIpLCBhZXMoeD1gQWdlIG9mIHBsYW50IChkKWAsIHk9YGNvbmNlbnRyYXRpb24gIMK1Zy9rZ2AsIGNvbG9yPVBGQVMpKSArDQogIGdlb21fc21vb3RoKGRhdGE9cGxhbnRkYXRhfD5maWx0ZXIoTWF0ZXJpYWw9PSJTb2lsIiksIGFlcyh4PWBBZ2Ugb2YgcGxhbnQgKGQpYCwgeT1gY29uY2VudHJhdGlvbiAgwrVnL2tnYCwgY29sb3I9UEZBUyksIG1ldGhvZD0ibG0iLCBmb3JtdWxhPXl+eCkgKw0KICBmYWNldF93cmFwKH5QRkFTKSsNCiAgc2NhbGVfeV9sb2cxMCgpDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9wb2ludChkYXRhPXBsYW50ZGF0YXw+ZmlsdGVyKE1hdGVyaWFsPT0iU29pbCIpLCBhZXMoeD1gQWdlIG9mIHBsYW50IChkKWAsIHk9YGNvbmNlbnRyYXRpb24gIMK1Zy9rZ2AsIGNvbG9yPVBGQVMpKSArDQogIGdlb21fc21vb3RoKGRhdGE9cGxhbnRkYXRhfD5maWx0ZXIoTWF0ZXJpYWw9PSJTb2lsIiksIGFlcyh4PWBBZ2Ugb2YgcGxhbnQgKGQpYCwgeT1gY29uY2VudHJhdGlvbiAgwrVnL2tnYCwgY29sb3I9UEZBUyksIG1ldGhvZD0ibG0iLCBmb3JtdWxhPXl+eCkgKw0KICBmYWNldF93cmFwKH5QRkFTLCBzY2FsZXM9ImZyZWUiKQ0KDQpgYGANCg0KDQoNCiMgRGF0YSBzcGxpdCAgDQoNCg0KYGBge3J9DQoNCnNvaWxvbmx5IDwtIHBsYW50ZGF0YSB8Pg0KICBmaWx0ZXIoTWF0ZXJpYWw9PSJTb2lsIikNCg0Ka2FsZW9ubHkgPC0gcGxhbnRkYXRhIHw+DQogIGZpbHRlcihNYXRlcmlhbD09IkthbGUiKQ0KDQpjb21ib2RhdGEgPC0gZnVsbF9qb2luKA0KICB4PXNvaWxvbmx5LA0KICB5PWthbGVvbmx5LA0KICBieT1jKCJQYWlyaW5nIElEIiwiUEZBUyIpDQopDQoNCmNvbWJvY2xlYW51cCA8LSBkYXRhLmZyYW1lKA0KICBQRkFTPWNvbWJvZGF0YSRQRkFTLA0KICBQYWlyaW5nX0lEPWNvbWJvZGF0YSRgUGFpcmluZyBJRGAsDQogIHRpbWU9Y29tYm9kYXRhJGBBZ2Ugb2YgcGxhbnQgKGQpLnhgLA0KICBzb2lsY29uYz1jb21ib2RhdGEkYGNvbmNlbnRyYXRpb24gIMK1Zy9rZy54YCwNCiAga2FsZWNvbmM9Y29tYm9kYXRhJGBjb25jZW50cmF0aW9uICDCtWcva2cueWANCikNCg0KY29tYm9jbGVhbnVwJEJDRiA8LSBjb21ib2NsZWFudXAka2FsZWNvbmMvY29tYm9jbGVhbnVwJHNvaWxjb25jDQoNCmNvbWJvY2xlYW51cCRsb2drYWxlY29uYyA8LSBsb2coY29tYm9jbGVhbnVwJGthbGVjb25jKSANCmNvbWJvY2xlYW51cCRsb2dzb2lsY29uYyA8LSBsb2coY29tYm9jbGVhbnVwJHNvaWxjb25jKSANCmNvbWJvY2xlYW51cCRsb2dCQ0YgPC0gbG9nKGNvbWJvY2xlYW51cCRCQ0YpIA0KDQoNCmBgYA0KDQoNCiMgQkNGIHBsb3QgZXhwbG9yYXRpb24gIA0KDQoNCmBgYHtyfQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fcG9pbnQoZGF0YT1jb21ib2NsZWFudXAsIGFlcyh4PXRpbWUsIHk9QkNGLCBjb2xvcj1QRkFTKSkgKw0KICBnZW9tX3Ntb290aChkYXRhPWNvbWJvY2xlYW51cCwgYWVzKHg9dGltZSwgeT1CQ0YsIGNvbG9yPVBGQVMpLCBtZXRob2Q9ImxtIiwgZm9ybXVsYT15fngpICsNCiAgZmFjZXRfd3JhcCh+UEZBUykrDQogIHNjYWxlX3lfbG9nMTAoKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fcG9pbnQoZGF0YT1jb21ib2NsZWFudXAsIGFlcyh4PXRpbWUsIHk9QkNGLCBjb2xvcj1QRkFTKSkgKw0KICBnZW9tX3Ntb290aChkYXRhPWNvbWJvY2xlYW51cCwgYWVzKHg9dGltZSwgeT1CQ0YsIGNvbG9yPVBGQVMpLCBtZXRob2Q9ImxtIiwgZm9ybXVsYT15fngpICsNCiAgZmFjZXRfd3JhcCh+UEZBUywgc2NhbGVzPSJmcmVlIikNCg0KY29tYm9jbGVhbnVwIHw+DQogIGdyb3VwX2J5KFBGQVMpIHw+DQogIHN1bW1hcml6ZShjb3VudEJDRj1zdW0oIWlzLm5hKEJDRikpLA0KICAgICAgICAgICAgbWVhbkJDRj1tZWFuKEJDRiwgbmEucm09VCksDQogICAgICAgICAgICBzZEJDRj1zZChCQ0YsIG5hLnJtPVQpLA0KICAgICAgICAgICAgdXBwZXJCQ0Y9cXVhbnRpbGUoQkNGLCBwcm9icz0wLjk3NSwgbmFtZXM9RiwgbmEucm09VCksDQogICAgICAgICAgICBsb3dlckJDRj1xdWFudGlsZShCQ0YsIHByb2JzPTAuMDI1LCBuYW1lcz1GLCBuYS5ybT1UKSwNCiAgICAgICAgICAgIHNlbUJDRj1zZEJDRi9jb3VudEJDRg0KICAgICAgICAgICAgKQ0KDQpgYGANCg0KDQojIGxpbmVhciByZWdyZXNzaW9uIHdvcmsgIA0KDQojIyBrYWxlIG9ubHkgIA0KDQpgYGB7cn0NCg0Ka2FsZXJlZyA8LSBjb21ib2NsZWFudXAgfD4NCiAgbmVzdChkYXRhID0gLVBGQVMpIHw+DQogIG11dGF0ZSgNCiAgICBmaXQgPSBtYXAoZGF0YSwgfiBsbShrYWxlY29uYyB+IHRpbWUsIGRhdGEgPSAueCkpLA0KICAgIHRpZGllZCA9IG1hcChmaXQsIHRpZHkpDQogICkgfD4NCiAgdW5uZXN0KHRpZGllZCkNCg0Ka2FsZXJlZyB8Pg0KICBmaWx0ZXIodGVybT09InRpbWUiKSB8Pg0KICBkcGx5cjo6c2VsZWN0KGMoIlBGQVMiLCJlc3RpbWF0ZSIsInN0ZC5lcnJvciIsInAudmFsdWUiKSkgfD4NCiAga2FibGUoZGlnaXRzPTMsIGNhcHRpb249IlNsb3BlIG9mIFtrYWxlXSBieSB0aW1lIikNCg0KbG9na2FsZXJlZyA8LSBjb21ib2NsZWFudXAgfD4NCiAgZmlsdGVyKCFsb2drYWxlY29uYyVpbiVjKE5BLCItSW5mIikpfD4NCiAgbmVzdChkYXRhID0gLVBGQVMpIHw+DQogIG11dGF0ZSgNCiAgICBmaXQgPSBtYXAoZGF0YSwgfiBsbShsb2drYWxlY29uYyB+IHRpbWUsIGRhdGEgPSAueCkpLA0KICAgIHRpZGllZCA9IG1hcChmaXQsIHRpZHkpDQogICkgfD4NCiAgdW5uZXN0KHRpZGllZCkNCg0KbG9na2FsZXJlZyB8Pg0KICBmaWx0ZXIodGVybT09InRpbWUiKSB8Pg0KICBkcGx5cjo6c2VsZWN0KGMoIlBGQVMiLCJlc3RpbWF0ZSIsInN0ZC5lcnJvciIsInAudmFsdWUiKSkgfD4NCiAga2FibGUoZGlnaXRzPTMsIGNhcHRpb249IlNsb3BlIG9mIGxuKFtrYWxlXSkgYnkgdGltZSIpDQoNCg0KYGBgDQoNCg0KIyMgU29pbCBvbmx5ICANCg0KYGBge3J9DQoNCnNvaWxyZWcgPC0gY29tYm9jbGVhbnVwIHw+DQogIG5lc3QoZGF0YSA9IC1QRkFTKSB8Pg0KICBtdXRhdGUoDQogICAgZml0ID0gbWFwKGRhdGEsIH4gbG0oc29pbGNvbmMgfiB0aW1lLCBkYXRhID0gLngpKSwNCiAgICB0aWRpZWQgPSBtYXAoZml0LCB0aWR5KQ0KICApIHw+DQogIHVubmVzdCh0aWRpZWQpDQoNCnNvaWxyZWcgfD4NCiAgZmlsdGVyKHRlcm09PSJ0aW1lIikgfD4NCiAgZHBseXI6OnNlbGVjdChjKCJQRkFTIiwiZXN0aW1hdGUiLCJzdGQuZXJyb3IiLCJwLnZhbHVlIikpIHw+DQogIGthYmxlKGRpZ2l0cz0zLCBjYXB0aW9uPSJTbG9wZSBvZiBbc29pbF0gYnkgdGltZSIpDQoNCmxvZ3NvaWxyZWcgPC0gY29tYm9jbGVhbnVwIHw+DQogIGZpbHRlcighbG9nc29pbGNvbmMlaW4lYyhOQSwiLUluZiIpKXw+DQogIG5lc3QoZGF0YSA9IC1QRkFTKSB8Pg0KICBtdXRhdGUoDQogICAgZml0ID0gbWFwKGRhdGEsIH4gbG0obG9nc29pbGNvbmMgfiB0aW1lLCBkYXRhID0gLngpKSwNCiAgICB0aWRpZWQgPSBtYXAoZml0LCB0aWR5KQ0KICApIHw+DQogIHVubmVzdCh0aWRpZWQpDQoNCmxvZ3NvaWxyZWcgfD4NCiAgZmlsdGVyKHRlcm09PSJ0aW1lIikgfD4NCiAgZHBseXI6OnNlbGVjdChjKCJQRkFTIiwiZXN0aW1hdGUiLCJzdGQuZXJyb3IiLCJwLnZhbHVlIikpIHw+DQogIGthYmxlKGRpZ2l0cz0zLCBjYXB0aW9uPSJTbG9wZSBvZiBsbihbc29pbF0pIGJ5IHRpbWUiKQ0KDQoNCmBgYA0KDQoNCg0KDQojIyBCQ0Ygb25seSAgDQoNCmBgYHtyfQ0KDQpCQ0ZyZWcgPC0gY29tYm9jbGVhbnVwIHw+DQogIGZpbHRlcighQkNGJWluJWMoTkEsIkluZiIsIk5hTiIpKXw+DQogIG5lc3QoZGF0YSA9IC1QRkFTKSB8Pg0KICBtdXRhdGUoDQogICAgZml0ID0gbWFwKGRhdGEsIH4gbG0oQkNGIH4gdGltZSwgZGF0YSA9IC54KSksDQogICAgdGlkaWVkID0gbWFwKGZpdCwgdGlkeSkNCiAgKSB8Pg0KICB1bm5lc3QodGlkaWVkKQ0KDQpCQ0ZyZWcgfD4NCiAgZmlsdGVyKHRlcm09PSJ0aW1lIikgfD4NCiAgZHBseXI6OnNlbGVjdChjKCJQRkFTIiwiZXN0aW1hdGUiLCJzdGQuZXJyb3IiLCJwLnZhbHVlIikpIHw+DQogIGthYmxlKGRpZ2l0cz0zLCBjYXB0aW9uPSJTbG9wZSBvZiBbQkNGXSBieSB0aW1lIikNCg0KDQoNCkJDRm1lZGlhbnByZWRpY3Rpb25zIDwtIGNvbWJvY2xlYW51cCB8Pg0KICBmaWx0ZXIoIUJDRiVpbiVjKE5BLCJJbmYiLCJOYU4iKSl8Pg0KICBuZXN0KGRhdGEgPSAtUEZBUykgfD4NCiAgbXV0YXRlKA0KICAgIGZpdCA9IG1hcChkYXRhLCB+IGxtKEJDRiB+IHRpbWUsIGRhdGEgPSAueCkpDQogICkgfD4NCiAgbXV0YXRlKA0KICAgIHByZWRpY3Rpb249bWFwMihmaXQsZGF0YSx+YXVnbWVudCgueCwgbmV3ZGF0YT0ueSkpDQogICkgfD4NCiAgdW5uZXN0KHByZWRpY3Rpb24pDQoNCkJDRm1lZGlhbnByZWRpY3Rpb25zc3VtbSA8LSBCQ0ZtZWRpYW5wcmVkaWN0aW9ucyB8Pg0KICBmaWx0ZXIodGltZT09NjEpfD4NCiAgZ3JvdXBfYnkoUEZBUykgfD4NCiAgc3VtbWFyaXplKGVzdGltYXRlPW1lYW4oLmZpdHRlZCksDQogICAgICAgICAgICBzZD1zZCgucmVzaWQpLA0KICAgICAgICAgICAgY291bnQ9c3VtKCFpcy5uYSguZml0dGVkKSksDQogICAgICAgICAgICBzZW09c2Qvc3FydChjb3VudCkpDQoNCg0KDQoNCg0KbG9nQkNGcmVnIDwtIGNvbWJvY2xlYW51cCB8Pg0KICBmaWx0ZXIoIWxvZ0JDRiVpbiVjKE5BLCItSW5mIiwiSW5mIiwiTmFOIikpfD4NCiAgbmVzdChkYXRhID0gLVBGQVMpIHw+DQogIG11dGF0ZSgNCiAgICBmaXQgPSBtYXAoZGF0YSwgfiBsbShsb2dCQ0YgfiB0aW1lLCBkYXRhID0gLngpKSwNCiAgICB0aWRpZWQgPSBtYXAoZml0LCB0aWR5KQ0KICApIHw+DQogIHVubmVzdCh0aWRpZWQpDQoNCg0KbG9nQkNGcmVnIHw+DQogIGZpbHRlcih0ZXJtPT0idGltZSIpIHw+DQogIGRwbHlyOjpzZWxlY3QoYygiUEZBUyIsImVzdGltYXRlIiwic3RkLmVycm9yIiwicC52YWx1ZSIpKSB8Pg0KICBrYWJsZShkaWdpdHM9MywgY2FwdGlvbj0iU2xvcGUgb2YgbG4oW0JDRl0pIGJ5IHRpbWUiKQ0KDQoNCmBgYA0KDQoNCg0KDQoNCg0KDQojIGJveHBsb3Qgb2YgQkNGcyAgDQoNCg0KYGBge3J9DQoNCmJveHBsb3QobG9nQkNGflBGQVMsIGRhdGE9Y29tYm9jbGVhbnVwLCBsYXM9MikNCg0KYm94cGxvdChCQ0Z+UEZBUywgZGF0YT1jb21ib2NsZWFudXAsIHlsaW09YygwLDEwKSkNCg0KYGBgDQoNCg0KDQojIE1hcmdpbmFsIG1lYW5zIGZvciBCQ0YgYXQgdGltZSB3aGlsZSBhY2NvdW50aW5nIGZvciB0aW1lICANCg0KYGBge3J9DQoNCmdncGxvdCgpKw0KICBnZW9tX3BvaW50KGRhdGE9QkNGbWVkaWFucHJlZGljdGlvbnNzdW1tLCBhZXMoeD1QRkFTLHk9ZXN0aW1hdGUsIGNvbG9yPVBGQVMpLCBzaG93LmxlZ2VuZD1GLCBzaXplPTUpICsNCiAgZ2VvbV9saW5lcmFuZ2UoZGF0YT1CQ0ZtZWRpYW5wcmVkaWN0aW9uc3N1bW0sIGFlcyh4PVBGQVMsIHltYXg9ZXN0aW1hdGUrMS45NipzZCwgeW1pbj1lc3RpbWF0ZS0xLjk2KnNkLCBjb2xvcj1QRkFTKSwgc2hvdy5sZWdlbmQ9Riwgc2l6ZT0xLjEpKw0KICBzY2FsZV95X2xvZzEwKCkrDQogIGxhYnMoeT0iZXN0aW1hdGUgcGx1cyBDSSIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KZ2dwbG90KCkrDQogIGdlb21fcG9pbnQoZGF0YT1CQ0ZtZWRpYW5wcmVkaWN0aW9uc3N1bW0sIGFlcyh4PVBGQVMseT1sb2cxMChlc3RpbWF0ZSksIGNvbG9yPVBGQVMpLCBzaG93LmxlZ2VuZD1GLCBzaXplPTUpICsNCiAgZ2VvbV9saW5lcmFuZ2UoZGF0YT1CQ0ZtZWRpYW5wcmVkaWN0aW9uc3N1bW0sIGFlcyh4PVBGQVMsIHltYXg9bG9nMTAoZXN0aW1hdGUrMS45NipzZCksIHltaW49bG9nMTAoZXN0aW1hdGUtMS45NipzZCksIGNvbG9yPVBGQVMpLCBzaG93LmxlZ2VuZD1GLCBzaXplPTEuMSkrDQogICNzY2FsZV95X2xvZzEwKCkrDQogIGxhYnMoeT0ibG9nMTAoZXN0aW1hdGUpIHBsdXMgQ0kiKSsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCmBgYA0KDQoNCg0KDQoNCg==