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
PFAS estimate std.error p.value
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
PFAS estimate std.error p.value
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
PFAS estimate std.error p.value
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
PFAS estimate std.error p.value
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
PFAS estimate std.error p.value
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
PFAS estimate std.error p.value
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==