• 1 Getting started
    • 1.1 clean up
    • 1.2 general custom functions
    • 1.3 necessary packages
  • 2 Data import
  • 3 MMs
  • 4 AMCEs
    • 4.1 Reference category diagnostics for AMCEs
  • 5 Subgroup analysis
    • 5.1 Subgroup marginal means
    • 5.2 Plot
  • 6 Diagnostics
    • 6.1 Frequencies
  • References

To copy the code, click the button in the upper right corner of the code-chunks.

1 Getting started

1.1 clean up

rm(list = ls())
gc()


1.2 general custom functions

  • fpackage.check: Check if packages are installed (and install if not) in R
  • fsave: Function to save data with time stamp in correct directory
  • fload: Function to load R-objects under new names
  • fshowdf: Print objects (tibble / data.frame) nicely on screen in .Rmd
  • ftheme: pretty ggplot2 theme
fpackage.check <- function(packages) {
    lapply(packages, FUN = function(x) {
        if (!require(x, character.only = TRUE)) {
            install.packages(x, dependencies = TRUE)
            library(x, character.only = TRUE)
        }
    })
}

fsave <- function(x, file, location = "./data/processed/", ...) {
    if (!dir.exists(location))
        dir.create(location)
    datename <- substr(gsub("[:-]", "", Sys.time()), 1, 8)
    totalname <- paste(location, datename, file, sep = "")
    print(paste("SAVED: ", totalname, sep = ""))
    save(x, file = totalname)
}


fload <- function(fileName) {
    load(fileName)
    get(ls()[ls() != "fileName"])
}


fshowdf <- function(x, caption = NULL, ...) {
    knitr::kable(x, digits = 2, "html", caption = caption, ...) %>%
        kableExtra::kable_styling(bootstrap_options = c("striped", "hover")) %>%
        kableExtra::scroll_box(width = "100%", height = "300px")
}

ftheme <- function() {
    # download font at https://fonts.google.com/specimen/Jost/
    theme_minimal(base_family = "Jost") + theme(panel.grid.minor = element_blank(), plot.title = element_text(family = "Jost",
        face = "bold"), axis.title = element_text(family = "Jost Medium"), axis.title.x = element_text(hjust = 0),
        axis.title.y = element_text(hjust = 1), strip.text = element_text(family = "Jost", face = "bold",
            size = rel(0.75), hjust = 0), strip.background = element_rect(fill = "grey90", color = NA),
        legend.position = "bottom")
}


1.3 necessary packages

  • tidyverse: data wrangling
  • cregg: calculate and visualize marginal means and average marginal component effects
  • ggtext: text rendering for ggplot2
  • ggpubr: format ggplot2 plots
packages = c("tidyverse", "cregg", "ggtext", "ggpubr")
fpackage.check(packages)


2 Data import

Load in the replicated dataset

You may also obtain it by downloading: Download conjoint.Rda.

today <- gsub("-", "", Sys.Date())
data <- fload(paste0("./data/processed/", today, "conjoint.Rda"))



3 MMs

We report (unadjusted) marginal means (MMs) to provide a descriptive summary of respondent preferences, reflecting the percentage of, here sports partners, with a particular attribute-level, that is chosen by respondents.

In our choice design, respondents were presented with 3 alternatives in each choice-set, resulting in MMs that average at about 0.33. We therefore subtract this baseline probability from the marginal mean, such that scores above (below) zero indicate feature levels that increase (decrease) profile attractiveness.

f1 <- choice ~ comparison + knowledge + companionship + encouragement

# estimate marginal means
mm <- cregg::mm(data, f1, id = ~id)

# substract baseline grand mean
mm$estimate <- mm$estimate - (1/3)
mm$upper <- mm$upper - (1/3)
mm$lower <- mm$lower - (1/3)

# nice color palette
cbPalette <- c("#999999", "#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7")

# also (short) labels for the levels (including bold headers)
newlabels <- c(expression(bold("(Performance Comparison)")), "Dislike", "Somewhat Like", "Really Like",
    expression(bold("(Training Knowledge)")), "Less Knowledge", "Equal Knowledge", "More Knowledge",
    expression(bold("(Sporting Intent)")), "Purposeful", "Social + Purpose", "Social", expression(bold("(Encouragement)")),
    "Never", "Sometimes", "Always")

# also colors for feature headers
headcol <- c("#999999", rep("black", 3), "#E69F00", rep("black", 3), "#56B4E9", rep("black", 3), "#009E73",
    rep("black", 3))

fshowdf(mm)
outcome statistic feature level estimate std.error z p lower upper
choice mm comparison really likes to compare sports performances -0.07 0 64.46 0 -0.08 -0.06
choice mm comparison somewhat likes to compare sports performances 0.05 0 95.86 0 0.05 0.06
choice mm comparison does not like to compare sports performances 0.02 0 78.99 0 0.01 0.02
choice mm knowledge knows more than you about effective training and the right technique 0.04 0 90.59 0 0.04 0.05
choice mm knowledge knows as much as you about effective training and the right technique 0.05 0 92.26 0 0.04 0.05
choice mm knowledge knows less than you about effective training and the right technique -0.09 0 62.14 0 -0.10 -0.08
choice mm companionship exercises to socially engage 0.00 0 73.76 0 -0.01 0.01
choice mm companionship wants a combination of social interaction and purposeful training 0.12 0 105.71 0 0.11 0.12
choice mm companionship exercises purposefully and seriously -0.12 0 52.96 0 -0.12 -0.11
choice mm encouragement always encourages you 0.07 0 94.89 0 0.06 0.08
choice mm encouragement sometimes encourages you 0.06 0 96.95 0 0.05 0.07
choice mm encouragement never encourages you -0.13 0 51.30 0 -0.14 -0.12
plot(mm, size = 2) + ftheme() + scale_x_continuous(labels = scales::percent) + geom_text(aes(label = sprintf("%0.2f (%0.2f)",
    estimate, std.error)), size = 3, colour = "black", position = position_nudge(y = 0.5)) + scale_y_discrete(labels = rev(newlabels)) +
    scale_color_manual(labels = c("Performance comparison", "Training knowledge", "Sporting intent",
        "Encouragement"), values = cbPalette) + theme(axis.line = element_line(), axis.text.y.left = element_text(color = rev(headcol)),
    legend.position = "none")



4 AMCEs

In our fully randomized design, average marginal component effects (AMCE) (Hainmueller, Hopkins, and Yamamoto 2014) simply represent differences between marginal means at each feature level and the marginal mean in the reference category, ignoring other features.

amce <- cregg::cj(data, f1, id = ~id)
fshowdf(amce)
outcome statistic feature level estimate std.error z p lower upper
choice amce comparison really likes to compare sports performances 0.00 NA NA NA NA NA
choice amce comparison somewhat likes to compare sports performances 0.12 0.01 18.54 0.00 0.11 0.14
choice amce comparison does not like to compare sports performances 0.08 0.01 11.51 0.00 0.07 0.10
choice amce knowledge knows more than you about effective training and the right technique 0.00 NA NA NA NA NA
choice amce knowledge knows as much as you about effective training and the right technique 0.00 0.01 0.45 0.65 -0.01 0.02
choice amce knowledge knows less than you about effective training and the right technique -0.13 0.01 -19.40 0.00 -0.14 -0.12
choice amce companionship exercises to socially engage 0.00 NA NA NA NA NA
choice amce companionship wants a combination of social interaction and purposeful training 0.12 0.01 15.68 0.00 0.10 0.13
choice amce companionship exercises purposefully and seriously -0.11 0.01 -15.52 0.00 -0.13 -0.10
choice amce encouragement always encourages you 0.00 NA NA NA NA NA
choice amce encouragement sometimes encourages you -0.02 0.01 -2.61 0.01 -0.03 0.00
choice amce encouragement never encourages you -0.21 0.01 -29.88 0.00 -0.22 -0.19
# also include coeffients as lables, but leave out the labels for the reference level
amce$showlabel <- ifelse(is.na(amce$std.error), 0, 1)

plot(amce, size = 2) + ftheme() + scale_colour_manual(values = cbPalette) + geom_text(data = subset(amce,
    showlabel == 1), aes(label = sprintf("%0.2f (%0.2f)", estimate, std.error)), size = 3, colour = "black",
    position = position_nudge(y = 0.5)) + scale_y_discrete(labels = rev(newlabels)) + scale_color_manual(labels = c("Performance comparison",
    "Training knowledge", "Sporting intent", "Encouragement"), values = cbPalette) + theme(axis.line = element_line(),
    axis.text.y.left = element_text(color = rev(headcol)), legend.position = "none")



4.1 Reference category diagnostics for AMCEs

amce_diagnostic1 <- cregg::amce_by_reference(data, choice ~ comparison, variable = ~comparison, id = ~id)
plot1 <- plot(amce_diagnostic1, group = "REFERENCE", legend_title = "Ref. cat.") + ftheme() + scale_colour_manual(values = cbPalette)
plot1 + theme(legend.direction = "vertical")

amce_diagnostic2 <- cregg::amce_by_reference(data, choice ~ knowledge, variable = ~knowledge, id = ~id)
plot2 <- plot(amce_diagnostic2, group = "REFERENCE", legend_title = "Ref. cat.") + ftheme() + scale_colour_manual(values = cbPalette)
plot2 + theme(legend.direction = "vertical")

amce_diagnostic3 <- cregg::amce_by_reference(data, choice ~ companionship, variable = ~companionship,
    id = ~id)
plot3 <- plot(amce_diagnostic3, group = "REFERENCE", legend_title = "Ref. cat.") + ftheme() + scale_colour_manual(values = cbPalette)
plot3 + theme(legend.direction = "vertical")

amce_diagnostic4 <- cregg::amce_by_reference(data, choice ~ encouragement, variable = ~encouragement,
    id = ~id)
plot4 <- plot(amce_diagnostic4, group = "REFERENCE", legend_title = "Ref. cat.") + ftheme() + scale_colour_manual(values = cbPalette)
plot4 + theme(legend.direction = "vertical")



5 Subgroup analysis

5.1 Subgroup marginal means

Estimate conditional marginal means and differences between conditional marginal means to describe differences in preference level between subgroups. To formally test for groups differences in preferences toward particular features, I use omnibus nested model comparisons.

data$Sex <- NA_real_
data$Sex[data$gender == "man"] <- 1L
data$Sex[data$gender == "woman"] <- 2L
data$Sex <- factor(data$Sex, 1:2, c("Male", "Female"))

# conditional MM
mm <- cregg::cj(na.omit(data), f1, id = ~id, estimate = "mm", by = ~Sex)
mm <- mm %>%
    arrange(level, feature)

# substract baseline marginal mean
mm$estimate <- mm$estimate - (1/3)
mm$upper <- mm$upper - (1/3)
mm$lower <- mm$lower - (1/3)

# difference between subgroups
diff_mm <- cregg::cj(data, f1, id = ~id, estimate = "mm_diff", by = ~Sex)

# combine plots
mm <- rbind(mm, diff_mm)
mm$BY <- factor(mm$BY, levels = rev(levels(mm$BY)))
mm$showlabel <- ifelse(is.na(mm$std.error), 0, 1)

# plot with grouping
p1 <- plot(mm, group = "BY", feature_headers = TRUE, size = 1) + ggplot2::facet_wrap(~feature, ncol = 1L,
    scales = "free_y", strip.position = "right") + scale_color_manual(values = c("#56B4E9", "#E69F00",
    "#999999"), breaks = c("Female", "Male", "Female - Male")) + scale_x_continuous(labels = scales::percent) +
    labs(x = "") + ftheme() + theme(strip.text.y = element_blank(), strip.background = element_blank(),
    panel.background = element_rect(color = "darkgrey"), axis.text.y = element_blank(), legend.position = "top",
    legend.direction = "vertical")

# test of preference heterogeneity (nested model comparison test)
cregg::cj_anova(na.omit(data), choice ~ comparison, by = ~Sex)
#> Analysis of Deviance Table
#> 
#> Model 1: choice ~ comparison
#> Model 2: choice ~ comparison + Sex + comparison:Sex
#>   Resid. Df Resid. Dev Df Deviance      F    Pr(>F)    
#> 1     18528     4073.0                                 
#> 2     18525     4062.7  3   10.263 15.599 3.947e-10 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cregg::cj_anova(na.omit(data), choice ~ knowledge, by = ~Sex)
#> Analysis of Deviance Table
#> 
#> Model 1: choice ~ knowledge
#> Model 2: choice ~ knowledge + Sex + knowledge:Sex
#>   Resid. Df Resid. Dev Df Deviance      F Pr(>F)
#> 1     18528     4039.9                          
#> 2     18525     4039.7  3  0.27112 0.4144 0.7426
cregg::cj_anova(na.omit(data), choice ~ companionship, by = ~Sex)
#> Analysis of Deviance Table
#> 
#> Model 1: choice ~ companionship
#> Model 2: choice ~ companionship + Sex + companionship:Sex
#>   Resid. Df Resid. Dev Df Deviance      F Pr(>F)
#> 1     18528     3946.1                          
#> 2     18525     3944.8  3   1.2851 2.0116   0.11
cregg::cj_anova(na.omit(data), choice ~ encouragement, by = ~Sex)
#> Analysis of Deviance Table
#> 
#> Model 1: choice ~ encouragement
#> Model 2: choice ~ encouragement + Sex + encouragement:Sex
#>   Resid. Df Resid. Dev Df Deviance      F Pr(>F)
#> 1     18528     3922.8                          
#> 2     18525     3922.1  3  0.76103 1.1982 0.3087
data$Active <- NA_real_
data$Active[data$activeW2 == "yes"] <- 2L
data$Active[data$activeW2 == "no"] <- 1L
data$Active <- factor(data$Active, 1:2, c("Inactive", "Active"))

# conditional MM
mm <- cregg::cj(data, f1, id = ~id, estimate = "mm", by = ~Active)
mm <- mm %>%
    arrange(level, feature)

# substract baseline marginal mean
mm$estimate <- mm$estimate - (1/3)
mm$upper <- mm$upper - (1/3)
mm$lower <- mm$lower - (1/3)

# difference between subgroups
diff_mm <- cregg::cj(data, f1, id = ~id, estimate = "mm_diff", by = ~Active)

# combine plots
mm <- rbind(mm, diff_mm)
mm$BY <- factor(mm$BY, levels = rev(levels(mm$BY)))
mm$showlabel <- ifelse(is.na(mm$std.error), 0, 1)

# custom headers/levels
levels(mm$feature) <- c("**Performance Comparison**", "**Training Knowledge**", "**Sporting Intent**",
    "**Encouragement**")
levels(mm$level) <- c("Really Like", "Somewhat Like", "Dislike", "More Knowledge", "Equal Knowledge",
    "Less Knowledge", "Social", "Social + Purpose", "Purposeful", "Always", "Sometimes", "Never")

# plot with grouping
p2 <- plot(mm, group = "BY", feature_headers = TRUE, size = 1) + ggplot2::facet_wrap(~feature, ncol = 1L,
    scales = "free_y", strip.position = "right") + scale_x_continuous(labels = scales::percent) + scale_color_manual(values = c("#56B4E9",
    "#E69F00", "#999999"), breaks = c("Active", "Inactive", "Active - Inactive")) + labs(x = "") + ftheme() +
    theme(strip.text.y = element_blank(), strip.background = element_blank(), panel.background = element_rect(color = "darkgrey"),
        axis.text.y = element_markdown(), legend.position = "top", legend.direction = "vertical")

# test of preference heterogeneity (nested model comparison test)
cregg::cj_anova(data, choice ~ comparison, by = ~Active)
#> Analysis of Deviance Table
#> 
#> Model 1: choice ~ comparison
#> Model 2: choice ~ comparison + Active + comparison:Active
#>   Resid. Df Resid. Dev Df Deviance     F   Pr(>F)    
#> 1     28851     6335.0                               
#> 2     28848     6331.4  3   3.5963 5.462 0.000947 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cregg::cj_anova(data, choice ~ knowledge, by = ~Active)
#> Analysis of Deviance Table
#> 
#> Model 1: choice ~ knowledge
#> Model 2: choice ~ knowledge + Active + knowledge:Active
#>   Resid. Df Resid. Dev Df Deviance      F Pr(>F)
#> 1     28851     6294.6                          
#> 2     28848     6294.2  3  0.41914 0.6403  0.589
cregg::cj_anova(data, choice ~ companionship, by = ~Active)
#> Analysis of Deviance Table
#> 
#> Model 1: choice ~ companionship
#> Model 2: choice ~ companionship + Active + companionship:Active
#>   Resid. Df Resid. Dev Df Deviance     F    Pr(>F)    
#> 1     28851     6155.0                                
#> 2     28848     6130.3  3    24.64 38.65 < 2.2e-16 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cregg::cj_anova(data, choice ~ encouragement, by = ~Active)
#> Analysis of Deviance Table
#> 
#> Model 1: choice ~ encouragement
#> Model 2: choice ~ encouragement + Active + encouragement:Active
#>   Resid. Df Resid. Dev Df Deviance      F    Pr(>F)    
#> 1     28851     6164.8                                 
#> 2     28848     6159.7  3   5.1754 8.0794 2.238e-05 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# sports_frequency <- ifelse(is.na(data$sportsfreq), 0, as.numeric(data$sportsfreq))
# hist(sports_frequency) ftable(psych::describe(sports_frequency))

# here, focus only on those currently active..
data$Frequency <- NA_real_
data$Frequency[data$sportsfreq < 3] <- 1L
data$Frequency[data$sportsfreq >= 3] <- 2L
data$Frequency <- factor(data$Frequency, 1:2, c("Low", "High"))

# conditional MM
mm <- cregg::cj(data, f1, id = ~id, estimate = "mm", by = ~Frequency)
mm <- mm %>%
    arrange(level, feature)

# substract baseline marginal mean
mm$estimate <- mm$estimate - (1/3)
mm$upper <- mm$upper - (1/3)
mm$lower <- mm$lower - (1/3)

# difference between subgroups
diff_mm <- cregg::cj(data, f1, id = ~id, estimate = "mm_diff", by = ~Frequency)

# combine plots
mm <- rbind(mm, diff_mm)
mm$BY <- factor(mm$BY, levels = rev(levels(mm$BY)))
mm$showlabel <- ifelse(is.na(mm$std.error), 0, 1)

# plot with grouping
p3 <- plot(mm, group = "BY", feature_headers = TRUE, size = 1) + ggplot2::facet_wrap(~feature, ncol = 1L,
    scales = "free_y", strip.position = "right") + scale_color_manual(values = c("#56B4E9", "#E69F00",
    "#999999"), breaks = c("High", "Low", "High - Low")) + scale_x_continuous(labels = scales::percent) +
    ftheme() + theme(strip.text.y = element_blank(), strip.background = element_blank(), panel.background = element_rect(color = "darkgrey"),
    axis.text.y = element_blank(), legend.position = "top", legend.direction = "vertical")

# test of preference heterogeneity (nested model comparison test)
cregg::cj_anova(data[!is.na(data$Frequency), ], choice ~ comparison, by = ~Frequency)
#> Analysis of Deviance Table
#> 
#> Model 1: choice ~ comparison
#> Model 2: choice ~ comparison + Frequency + comparison:Frequency
#>   Resid. Df Resid. Dev Df Deviance      F  Pr(>F)  
#> 1     18600     4088.3                             
#> 2     18597     4086.7  3   1.6084 2.4397 0.06243 .
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cregg::cj_anova(data[!is.na(data$Frequency), ], choice ~ knowledge, by = ~Frequency)
#> Analysis of Deviance Table
#> 
#> Model 1: choice ~ knowledge
#> Model 2: choice ~ knowledge + Frequency + knowledge:Frequency
#>   Resid. Df Resid. Dev Df Deviance      F  Pr(>F)  
#> 1     18600     4056.1                             
#> 2     18597     4053.8  3   2.2615 3.4582 0.01565 *
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cregg::cj_anova(data[!is.na(data$Frequency), ], choice ~ companionship, by = ~Frequency)
#> Analysis of Deviance Table
#> 
#> Model 1: choice ~ companionship
#> Model 2: choice ~ companionship + Frequency + companionship:Frequency
#>   Resid. Df Resid. Dev Df Deviance      F    Pr(>F)    
#> 1     18600     3961.6                                 
#> 2     18597     3949.7  3   11.886 18.656 4.446e-12 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cregg::cj_anova(data[!is.na(data$Frequency), ], choice ~ encouragement, by = ~Frequency)
#> Analysis of Deviance Table
#> 
#> Model 1: choice ~ encouragement
#> Model 2: choice ~ encouragement + Frequency + encouragement:Frequency
#>   Resid. Df Resid. Dev Df Deviance      F Pr(>F)
#> 1     18600     3938.9                          
#> 2     18597     3938.8  3  0.10924 0.1719 0.9154


5.1.3.1 different cut-off high/low

high (> 3) vs. low (≤ 3)

# sports_frequency <- ifelse(is.na(data$sportsfreq), 0, as.numeric(data$sportsfreq))
# hist(sports_frequency) ftable(psych::describe(sports_frequency))

# here, focus only on those currently active..
data$Frequency <- NA_real_
data$Frequency[data$sportsfreq < 4] <- 1L
data$Frequency[data$sportsfreq > 3] <- 2L
data$Frequency <- factor(data$Frequency, 1:2, c("Low", "High"))

# prop.table(table(data$Frequency)) #63 vs 37

# conditional MM
mm <- cregg::cj(data, f1, id = ~id, estimate = "mm", by = ~Frequency)
mm <- mm %>%
    arrange(level, feature)

# substract baseline marginal mean
mm$estimate <- mm$estimate - (1/3)
mm$upper <- mm$upper - (1/3)
mm$lower <- mm$lower - (1/3)

# difference between subgroups
diff_mm <- cregg::cj(data, f1, id = ~id, estimate = "mm_diff", by = ~Frequency)

# combine plots
mm <- rbind(mm, diff_mm)
mm$BY <- factor(mm$BY, levels = rev(levels(mm$BY)))
mm$showlabel <- ifelse(is.na(mm$std.error), 0, 1)

# custom headers/levels
levels(mm$feature) <- c("**Performance Comparison**", "**Training Knowledge**", "**Sporting Intent**",
    "**Encouragement**")
levels(mm$level) <- c("Really Like", "Somewhat Like", "Dislike", "More Knowledge", "Equal Knowledge",
    "Less Knowledge", "Social", "Social + Purpose", "Purposeful", "Always", "Sometimes", "Never")

# plot with grouping
p4 <- plot(mm, group = "BY", feature_headers = TRUE, size = 1) + ggplot2::facet_wrap(~feature, ncol = 1L,
    scales = "free_y", strip.position = "right") + scale_x_continuous(labels = scales::percent) + scale_color_manual(values = c("#56B4E9",
    "#E69F00", "#999999"), breaks = c("High", "Low", "High - Low")) + labs(x = "") + ftheme() + theme(strip.text.y = element_blank(),
    strip.background = element_blank(), panel.background = element_rect(color = "darkgrey"), axis.text.y = element_markdown(),
    legend.position = "top", legend.direction = "vertical")

# test of preference heterogeneity (nested model comparison test)
# cregg::cj_anova(data[!is.na(data$Frequency),], choice ~ comparison, by = ~Frequency)
# cregg::cj_anova(data[!is.na(data$Frequency),], choice ~ knowledge, by = ~Frequency)
# cregg::cj_anova(data[!is.na(data$Frequency),], choice ~ companionship, by = ~Frequency)
# cregg::cj_anova(data[!is.na(data$Frequency),], choice ~ encouragement, by = ~Frequency)

print(p4)

5.2 Plot

(multiplot <- ggpubr::ggarrange(p2, p3, p1, ncol = 3, nrow = 1, widths = c(1.9, 1, 1)))

# ggsave('./figures/cmms.pneg', multiplot)


6 Diagnostics

6.1 Frequencies

Of conjoint features (to ensure equal display frequency):

plot(cregg::cj_freqs(data, choice ~ comparison + knowledge + companionship + encouragement + Sex + Active +
    Frequency, id = ~id)) + ftheme() + scale_colour_manual(values = cbPalette)


References

Hainmueller, Jens, Daniel Hopkins, and Teppei Yamamoto. 2014. Causal Inference in Conjoint Analysis: Understanding Multidimensional Choices via Stated Preference Experiments.” Political Analysis 2 (22): 1–30.
LS0tDQp0aXRsZTogIkFuYWx5c2VzIg0KYmlibGlvZ3JhcGh5OiByZWZlcmVuY2VzLmJpYg0KZGF0ZTogIkxhc3QgY29tcGlsZWQgb24gYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiwgJVknKWAiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIGNzczogdHdlYWtzLmNzcw0KICAgIHRvYzogIHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0b2NfZGVwdGg6IDINCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCi0tLQ0KDQpgYGB7ciwgZ2xvYmFsc2V0dGluZ3MsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkodGlkeXZlcnNlKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0Kb3B0c19jaHVuayRzZXQodGlkeS5vcHRzPWxpc3Qod2lkdGguY3V0b2ZmPTEwMCksdGlkeT1UUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSxjb21tZW50ID0gIiM+IiwgY2FjaGU9VFJVRSwgY2xhc3Muc291cmNlPWMoInRlc3QiKSwgY2xhc3Mub3V0cHV0PWMoInRlc3QzIikpDQpvcHRpb25zKHdpZHRoID0gMTAwKQ0KcmdsOjpzZXR1cEtuaXRyKCkNCg0KY29sb3JpemUgPC0gZnVuY3Rpb24oeCwgY29sb3IpIHtzcHJpbnRmKCI8c3BhbiBzdHlsZT0nY29sb3I6ICVzOyc+JXM8L3NwYW4+IiwgY29sb3IsIHgpIH0NCmBgYA0KDQoNCmBgYHtyIGtsaXBweSwgZWNobz1GQUxTRSwgaW5jbHVkZT1UUlVFfQ0Ka2xpcHB5OjprbGlwcHkocG9zaXRpb24gPSBjKCd0b3AnLCAncmlnaHQnKSkNCiNrbGlwcHk6OmtsaXBweShjb2xvciA9ICdkYXJrcmVkJykNCiNrbGlwcHk6OmtsaXBweSh0b29sdGlwX21lc3NhZ2UgPSAnQ2xpY2sgdG8gY29weScsIHRvb2x0aXBfc3VjY2VzcyA9ICdEb25lJykNCmBgYA0KDQoNCi0tLSAgDQoNCg0KVG8gY29weSB0aGUgY29kZSwgY2xpY2sgdGhlIGJ1dHRvbiBpbiB0aGUgdXBwZXIgcmlnaHQgY29ybmVyIG9mIHRoZSBjb2RlLWNodW5rcy4NCg0KIyBHZXR0aW5nIHN0YXJ0ZWQNCg0KIyMgY2xlYW4gdXANCg0KYGBge3IsIHJlc3VsdHM9J2hpZGUnfQ0Kcm0obGlzdD1scygpKQ0KZ2MoKQ0KYGBgDQoNCjxicj4NCg0KIyMgZ2VuZXJhbCBjdXN0b20gZnVuY3Rpb25zDQoNCi0gYGZwYWNrYWdlLmNoZWNrYDogQ2hlY2sgaWYgcGFja2FnZXMgYXJlIGluc3RhbGxlZCAoYW5kIGluc3RhbGwgaWYgbm90KSBpbiBSDQotIGBmc2F2ZWA6IEZ1bmN0aW9uIHRvIHNhdmUgZGF0YSB3aXRoIHRpbWUgc3RhbXAgaW4gY29ycmVjdCBkaXJlY3RvcnkNCi0gYGZsb2FkYDogRnVuY3Rpb24gdG8gbG9hZCBSLW9iamVjdHMgdW5kZXIgbmV3IG5hbWVzDQotIGBmc2hvd2RmYDogUHJpbnQgb2JqZWN0cyAoYHRpYmJsZWAgLyBgZGF0YS5mcmFtZWApIG5pY2VseSBvbiBzY3JlZW4gaW4gYC5SbWRgDQotIGBmdGhlbWVgOiBwcmV0dHkgZ2dwbG90MiB0aGVtZQ0KDQpgYGB7ciwgZnVufQ0KZnBhY2thZ2UuY2hlY2sgPC0gZnVuY3Rpb24ocGFja2FnZXMpIHsNCiAgICBsYXBwbHkocGFja2FnZXMsIEZVTiA9IGZ1bmN0aW9uKHgpIHsNCiAgICAgICAgaWYgKCFyZXF1aXJlKHgsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkpIHsNCiAgICAgICAgICAgIGluc3RhbGwucGFja2FnZXMoeCwgZGVwZW5kZW5jaWVzID0gVFJVRSkNCiAgICAgICAgICAgIGxpYnJhcnkoeCwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQ0KICAgICAgICB9DQogICAgfSkNCn0NCg0KZnNhdmUgPC0gZnVuY3Rpb24oeCwgZmlsZSwgbG9jYXRpb24gPSAiLi9kYXRhL3Byb2Nlc3NlZC8iLCAuLi4pIHsNCiAgICBpZiAoIWRpci5leGlzdHMobG9jYXRpb24pKQ0KICAgICAgICBkaXIuY3JlYXRlKGxvY2F0aW9uKQ0KICAgIGRhdGVuYW1lIDwtIHN1YnN0cihnc3ViKCJbOi1dIiwgIiIsIFN5cy50aW1lKCkpLCAxLCA4KQ0KICAgIHRvdGFsbmFtZSA8LSBwYXN0ZShsb2NhdGlvbiwgZGF0ZW5hbWUsIGZpbGUsIHNlcCA9ICIiKQ0KICAgIHByaW50KHBhc3RlKCJTQVZFRDogIiwgdG90YWxuYW1lLCBzZXAgPSAiIikpDQogICAgc2F2ZSh4LCBmaWxlID0gdG90YWxuYW1lKQ0KfQ0KDQoNCmZsb2FkICA8LSBmdW5jdGlvbihmaWxlTmFtZSl7DQogIGxvYWQoZmlsZU5hbWUpDQogIGdldChscygpW2xzKCkgIT0gImZpbGVOYW1lIl0pDQp9DQoNCg0KZnNob3dkZiA8LSBmdW5jdGlvbih4LCBjYXB0aW9uID0gTlVMTCwgLi4uKSB7DQogICAga25pdHI6OmthYmxlKHgsIGRpZ2l0cyA9IDIsICJodG1sIiwgY2FwdGlvbiA9IGNhcHRpb24sIC4uLikgJT4lDQogICAgICAgIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIikpICU+JQ0KICAgICAgICBrYWJsZUV4dHJhOjpzY3JvbGxfYm94KHdpZHRoID0gIjEwMCUiLCBoZWlnaHQgPSAiMzAwcHgiKQ0KfQ0KDQpmdGhlbWUgPC0gZnVuY3Rpb24oKSB7DQogICNkb3dubG9hZCBmb250IGF0IGh0dHBzOi8vZm9udHMuZ29vZ2xlLmNvbS9zcGVjaW1lbi9Kb3N0Lw0KICB0aGVtZV9taW5pbWFsKGJhc2VfZmFtaWx5ID0gIkpvc3QiKSArDQogICAgdGhlbWUocGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJKb3N0IiwgZmFjZSA9ICJib2xkIiksDQogICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiSm9zdCBNZWRpdW0iKSwNCiAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwKSwNCiAgICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAxKSwNCiAgICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJKb3N0IiwgZmFjZSA9ICJib2xkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSByZWwoMC43NSksIGhqdXN0ID0gMCksDQogICAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gImdyZXk5MCIsIGNvbG9yID0gTkEpLA0KICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0KfQ0KYGBgDQoNCg0KPGJyPg0KDQojIyBuZWNlc3NhcnkgcGFja2FnZXMNCg0KLSBgdGlkeXZlcnNlYDogZGF0YSB3cmFuZ2xpbmcNCi0gYGNyZWdnYDogY2FsY3VsYXRlIGFuZCB2aXN1YWxpemUgbWFyZ2luYWwgbWVhbnMgYW5kIGF2ZXJhZ2UgbWFyZ2luYWwgY29tcG9uZW50IGVmZmVjdHMNCi0gYGdndGV4dGA6IHRleHQgcmVuZGVyaW5nIGZvciBnZ3Bsb3QyDQotIGBnZ3B1YnJgOiBmb3JtYXQgZ2dwbG90MiBwbG90cw0KDQpgYGB7ciwgZXZhbD1UUlVFLCBtZXNzYWdlcz1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQpwYWNrYWdlcyA9IGMoInRpZHl2ZXJzZSIsICJjcmVnZyIsICJnZ3RleHQiLCAiZ2dwdWJyIikNCmZwYWNrYWdlLmNoZWNrKHBhY2thZ2VzKQ0KYGBgDQoNCmBgYHtyIGZvbnRzLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCiMgaW1wb3J0IGZvbnQgSk9TVA0KI2V4dHJhZm9udDo6Zm9udF9pbXBvcnQocGF0dGVybiA9ICJKb3N0IikNCmV4dHJhZm9udDo6bG9hZGZvbnRzKGRldmljZT0id2luIikNCg0KIyBTZXQgZGVmYXVsdCB0aGVtZSBhbmQgZm9udCBzdHVmZg0KdGhlbWVfc2V0KGZ0aGVtZSgpKQ0KdXBkYXRlX2dlb21fZGVmYXVsdHMoInRleHQiLCBsaXN0KGZhbWlseSA9ICJKb3N0IiwgZm9udGZhY2UgPSAicGxhaW4iKSkNCnVwZGF0ZV9nZW9tX2RlZmF1bHRzKCJsYWJlbCIsIGxpc3QoZmFtaWx5ID0gIkpvc3QiLCBmb250ZmFjZSA9ICJwbGFpbiIpKQ0KYGBgDQoNCi0tLS0NCg0KPGJyPg0KDQojIERhdGEgaW1wb3J0DQoNCkxvYWQgaW4gdGhlIHJlcGxpY2F0ZWQgZGF0YXNldA0KDQpZb3UgbWF5IGFsc28gb2J0YWluIGl0IGJ5IGRvd25sb2FkaW5nOiBgciB4ZnVuOjplbWJlZF9maWxlKCIuL2RhdGEgc2hhcmVkL2NvbmpvaW50LlJkYSIpYC4NCg0KDQpgYGB7cn0NCnRvZGF5IDwtIGdzdWIoIi0iLCAiIiwgU3lzLkRhdGUoKSkNCmRhdGEgPC0gZmxvYWQocGFzdGUwKCIuL2RhdGEvcHJvY2Vzc2VkLyIsIHRvZGF5LCAiY29uam9pbnQuUmRhIikpDQpgYGANCg0KPGJyPg0KDQotLS0NCg0KIyBNTXMNCg0KV2UgcmVwb3J0ICh1bmFkanVzdGVkKSBtYXJnaW5hbCBtZWFucyAoTU1zKSB0byBwcm92aWRlIGEgKmRlc2NyaXB0aXZlKiBzdW1tYXJ5IG9mIHJlc3BvbmRlbnQgcHJlZmVyZW5jZXMsIHJlZmxlY3RpbmcgdGhlIHBlcmNlbnRhZ2Ugb2YsIGhlcmUgc3BvcnRzIHBhcnRuZXJzLCB3aXRoIGEgcGFydGljdWxhciBhdHRyaWJ1dGUtbGV2ZWwsIHRoYXQgaXMgY2hvc2VuIGJ5IHJlc3BvbmRlbnRzLg0KDQpJbiBvdXIgY2hvaWNlIGRlc2lnbiwgcmVzcG9uZGVudHMgd2VyZSBwcmVzZW50ZWQgd2l0aCAzIGFsdGVybmF0aXZlcyBpbiBlYWNoIGNob2ljZS1zZXQsIHJlc3VsdGluZyBpbiBNTXMgdGhhdCBhdmVyYWdlIGF0IGFib3V0IDAuMzMuIFdlIHRoZXJlZm9yZSBzdWJ0cmFjdCB0aGlzIGJhc2VsaW5lIHByb2JhYmlsaXR5IGZyb20gdGhlIG1hcmdpbmFsIG1lYW4sIHN1Y2ggdGhhdCBzY29yZXMgYWJvdmUgKGJlbG93KSB6ZXJvIGluZGljYXRlIGZlYXR1cmUgbGV2ZWxzIHRoYXQgaW5jcmVhc2UgKGRlY3JlYXNlKSBwcm9maWxlIGF0dHJhY3RpdmVuZXNzLiANCg0KYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfSANCmYxIDwtIGNob2ljZSB+IGNvbXBhcmlzb24gKyBrbm93bGVkZ2UgKyBjb21wYW5pb25zaGlwICsgZW5jb3VyYWdlbWVudA0KDQojZXN0aW1hdGUgbWFyZ2luYWwgbWVhbnMNCm1tIDwtIGNyZWdnOjptbShkYXRhLCBmMSwgaWQgPSB+aWQpDQoNCiNzdWJzdHJhY3QgYmFzZWxpbmUgZ3JhbmQgbWVhbg0KbW0kZXN0aW1hdGUgPC0gbW0kZXN0aW1hdGUgLSAoMS8zKQ0KbW0kdXBwZXIgPC0gbW0kdXBwZXIgLSAoMS8zKQ0KbW0kbG93ZXIgPC0gbW0kbG93ZXIgLSAoMS8zKQ0KDQojbmljZSBjb2xvciBwYWxldHRlDQpjYlBhbGV0dGUgPC0gYygiIzk5OTk5OSIsICIjRTY5RjAwIiwgIiM1NkI0RTkiLCAiIzAwOUU3MyIsICIjRjBFNDQyIiwgIiMwMDcyQjIiLCAiI0Q1NUUwMCIsICIjQ0M3OUE3IikNCg0KI2Fsc28gKHNob3J0KSBsYWJlbHMgZm9yIHRoZSBsZXZlbHMgKGluY2x1ZGluZyBib2xkIGhlYWRlcnMpDQpuZXdsYWJlbHMgPC0gYyhleHByZXNzaW9uKGJvbGQoIihQZXJmb3JtYW5jZSBDb21wYXJpc29uKSIpKSwgIkRpc2xpa2UiLCAiU29tZXdoYXQgTGlrZSIsICJSZWFsbHkgTGlrZSIsDQogICAgICAgICAgICBleHByZXNzaW9uKGJvbGQoIihUcmFpbmluZyBLbm93bGVkZ2UpIikpLCAiTGVzcyBLbm93bGVkZ2UiLCAiRXF1YWwgS25vd2xlZGdlIiwgIk1vcmUgS25vd2xlZGdlIiwNCiAgICAgICAgICAgIGV4cHJlc3Npb24oYm9sZCgiKFNwb3J0aW5nIEludGVudCkiKSksICJQdXJwb3NlZnVsIiwgIlNvY2lhbCArIFB1cnBvc2UiLCAiU29jaWFsIiwNCiAgICAgICAgICAgIGV4cHJlc3Npb24oYm9sZCgiKEVuY291cmFnZW1lbnQpIikpLCAiTmV2ZXIiLCAiU29tZXRpbWVzIiwgIkFsd2F5cyIpDQoNCiNhbHNvIGNvbG9ycyBmb3IgZmVhdHVyZSBoZWFkZXJzDQpoZWFkY29sIDwtIGMoIiM5OTk5OTkiLCByZXAoImJsYWNrIiwzKSwiI0U2OUYwMCIsIHJlcCgiYmxhY2siLDMpLCIjNTZCNEU5IiwgcmVwKCJibGFjayIsMyksIiMwMDlFNzMiLCByZXAoImJsYWNrIiwzKSkNCg0KZnNob3dkZihtbSkNCg0KcGxvdChtbSwgc2l6ZSA9IDIpICsgDQogIGZ0aGVtZSgpICsgDQogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsNCiAgZ2VvbV90ZXh0KCBhZXMobGFiZWwgPSBzcHJpbnRmKCIlMC4yZiAoJTAuMmYpIiwgZXN0aW1hdGUsIHN0ZC5lcnJvcikpLCBzaXplID0gMywgY29sb3VyID0gImJsYWNrIiwgcG9zaXRpb24gPSBwb3NpdGlvbl9udWRnZSh5ID0gLjUpKSArICAgDQogIHNjYWxlX3lfZGlzY3JldGUobGFiZWxzID0gcmV2KG5ld2xhYmVscykpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKGxhYmVscyA9IGMoIlBlcmZvcm1hbmNlIGNvbXBhcmlzb24iLCAiVHJhaW5pbmcga25vd2xlZGdlIiwgIlNwb3J0aW5nIGludGVudCIsICJFbmNvdXJhZ2VtZW50IiksDQogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjYlBhbGV0dGUpICsNCiAgdGhlbWUoYXhpcy5saW5lID0gZWxlbWVudF9saW5lKCksDQogICAgICAgIGF4aXMudGV4dC55LmxlZnQgPSBlbGVtZW50X3RleHQoY29sb3IgPSByZXYoaGVhZGNvbCkpLA0KICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQpgYGANCg0KDQpgYGB7ciwgZXZhbD1GQUxTRSwgZWNobz1GQUxTRX0NCmdnc2F2ZSgiLi9maWd1cmVzL21tcy5wbmciLCB3aWR0aD04LCBoZWlnaHQ9My41KQ0KYGBgDQoNCi0tLS0NCg0KPGJyPg0KDQojIEFNQ0VzDQoNCkluIG91ciBmdWxseSByYW5kb21pemVkIGRlc2lnbiwgYXZlcmFnZSBtYXJnaW5hbCBjb21wb25lbnQgZWZmZWN0cyAoQU1DRSkgW0BoYWlubXVlbGxlcjIwMTRdIHNpbXBseSByZXByZXNlbnQgZGlmZmVyZW5jZXMgYmV0d2VlbiBtYXJnaW5hbCBtZWFucyBhdCBlYWNoIGZlYXR1cmUgbGV2ZWwgYW5kIHRoZSBtYXJnaW5hbCBtZWFuIGluIHRoZSByZWZlcmVuY2UgY2F0ZWdvcnksIGlnbm9yaW5nIG90aGVyIGZlYXR1cmVzLg0KDQoNCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0gDQphbWNlIDwtIGNyZWdnOjpjaihkYXRhLCBmMSwgaWQgPSB+aWQpDQpmc2hvd2RmKGFtY2UpDQoNCiNhbHNvIGluY2x1ZGUgY29lZmZpZW50cyBhcyBsYWJsZXMsIGJ1dCBsZWF2ZSBvdXQgdGhlIGxhYmVscyBmb3IgdGhlIHJlZmVyZW5jZSBsZXZlbA0KYW1jZSRzaG93bGFiZWwgPC0gaWZlbHNlKGlzLm5hKGFtY2Ukc3RkLmVycm9yKSwwLDEpDQoNCnBsb3QoYW1jZSwgc2l6ZSA9IDIpICsgDQogIGZ0aGVtZSgpICsgDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gY2JQYWxldHRlKSArDQogIGdlb21fdGV4dChkYXRhID0gc3Vic2V0KGFtY2UsIHNob3dsYWJlbCA9PSAxKSwNCiAgICAgICAgICAgIGFlcyhsYWJlbCA9IHNwcmludGYoIiUwLjJmICglMC4yZikiLCBlc3RpbWF0ZSwgc3RkLmVycm9yKSksDQogICAgICAgICAgICBzaXplID0gMywgDQogICAgICAgICAgICBjb2xvdXIgPSAiYmxhY2siLA0KICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9udWRnZSh5ID0gLjUpKSArDQogIHNjYWxlX3lfZGlzY3JldGUobGFiZWxzID0gcmV2KG5ld2xhYmVscykpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKGxhYmVscyA9IGMoIlBlcmZvcm1hbmNlIGNvbXBhcmlzb24iLCAiVHJhaW5pbmcga25vd2xlZGdlIiwgIlNwb3J0aW5nIGludGVudCIsICJFbmNvdXJhZ2VtZW50IiksDQogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjYlBhbGV0dGUpICsNCiAgdGhlbWUoYXhpcy5saW5lID0gZWxlbWVudF9saW5lKCksDQogICAgICAgIGF4aXMudGV4dC55LmxlZnQgPSBlbGVtZW50X3RleHQoY29sb3IgPSByZXYoaGVhZGNvbCkpLA0KICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQpgYGANCg0KYGBge3IsIGV2YWw9RkFMU0UsZWNobz1GQUxTRX0NCmdnc2F2ZSgiLi9maWd1cmVzL2FtY2VzLnBuZyIsIHdpZHRoPTgsIGhlaWdodD00KQ0KYGBgDQoNCi0tLSANCg0KPGJyPg0KDQoNCiMjIFJlZmVyZW5jZSBjYXRlZ29yeSBkaWFnbm9zdGljcyBmb3IgQU1DRXMgey50YWJzZXQgLnRhYnNldC1mYWRlfQ0KDQojIyMgUGVyZm9ybWFuY2UgY29tcGFyaXNvbg0KDQpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTQsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNsYXNzLnNvdXJjZT0nZm9sZC1oaWRlJ30NCmFtY2VfZGlhZ25vc3RpYzEgPC0gY3JlZ2c6OmFtY2VfYnlfcmVmZXJlbmNlKGRhdGEsIGNob2ljZSB+IGNvbXBhcmlzb24sIHZhcmlhYmxlID0gfmNvbXBhcmlzb24sIGlkID0gfmlkKQ0KcGxvdDEgPC0gcGxvdChhbWNlX2RpYWdub3N0aWMxLCBncm91cCA9ICJSRUZFUkVOQ0UiLCBsZWdlbmRfdGl0bGUgPSAiUmVmLiBjYXQuIikgKyBmdGhlbWUoKSArIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWNiUGFsZXR0ZSkNCnBsb3QxICsgdGhlbWUobGVnZW5kLmRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIpDQpgYGANCg0KIyMjIFRyYWluaW5nIGtub3dsZWRnZQ0KDQpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTQsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNsYXNzLnNvdXJjZT0nZm9sZC1oaWRlJ30NCmFtY2VfZGlhZ25vc3RpYzIgPC0gY3JlZ2c6OmFtY2VfYnlfcmVmZXJlbmNlKGRhdGEsIGNob2ljZSB+IGtub3dsZWRnZSwgdmFyaWFibGUgPSB+a25vd2xlZGdlLCBpZCA9IH5pZCkNCnBsb3QyIDwtIHBsb3QoYW1jZV9kaWFnbm9zdGljMiwgZ3JvdXAgPSAiUkVGRVJFTkNFIiwgbGVnZW5kX3RpdGxlID0gIlJlZi4gY2F0LiIpICsgZnRoZW1lKCkgKyBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jYlBhbGV0dGUpDQpwbG90MiArIHRoZW1lKGxlZ2VuZC5kaXJlY3Rpb24gPSAidmVydGljYWwiKQ0KYGBgDQoNCg0KIyMjIENvbXBhbmlvbnNoaXAgDQoNCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2xhc3Muc291cmNlPSdmb2xkLWhpZGUnfQ0KYW1jZV9kaWFnbm9zdGljMyA8LSBjcmVnZzo6YW1jZV9ieV9yZWZlcmVuY2UoZGF0YSwgY2hvaWNlIH4gY29tcGFuaW9uc2hpcCwgdmFyaWFibGUgPSB+Y29tcGFuaW9uc2hpcCwgaWQgPSB+aWQpDQpwbG90MyA8LSBwbG90KGFtY2VfZGlhZ25vc3RpYzMsIGdyb3VwID0gIlJFRkVSRU5DRSIsIGxlZ2VuZF90aXRsZSA9ICJSZWYuIGNhdC4iKSArIGZ0aGVtZSgpICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y2JQYWxldHRlKQ0KcGxvdDMgKyB0aGVtZShsZWdlbmQuZGlyZWN0aW9uID0gInZlcnRpY2FsIikNCmBgYA0KDQojIyMgRW5jb3VyYWdlbWVudA0KDQpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTQsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNsYXNzLnNvdXJjZT0nZm9sZC1oaWRlJ30NCmFtY2VfZGlhZ25vc3RpYzQgPC0gY3JlZ2c6OmFtY2VfYnlfcmVmZXJlbmNlKGRhdGEsIGNob2ljZSB+IGVuY291cmFnZW1lbnQsIHZhcmlhYmxlID0gfmVuY291cmFnZW1lbnQsIGlkID0gfmlkKQ0KcGxvdDQgPC0gcGxvdChhbWNlX2RpYWdub3N0aWM0LCBncm91cCA9ICJSRUZFUkVOQ0UiLCBsZWdlbmRfdGl0bGUgPSAiUmVmLiBjYXQuIikgKyBmdGhlbWUoKSArIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWNiUGFsZXR0ZSkNCnBsb3Q0ICsgdGhlbWUobGVnZW5kLmRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIpDQpgYGANCg0KDQojIyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQotLS0tDQoNCjxicj4NCg0KIyBTdWJncm91cCBhbmFseXNpcw0KDQojIyBTdWJncm91cCBtYXJnaW5hbCBtZWFucyB7LnRhYnNldCAudGFic2V0LWZhZGV9IA0KDQpFc3RpbWF0ZSBjb25kaXRpb25hbCBtYXJnaW5hbCBtZWFucyBhbmQgZGlmZmVyZW5jZXMgYmV0d2VlbiBjb25kaXRpb25hbCBtYXJnaW5hbCBtZWFucyB0byBkZXNjcmliZSBkaWZmZXJlbmNlcyBpbiBwcmVmZXJlbmNlIGxldmVsIGJldHdlZW4gc3ViZ3JvdXBzLiBUbyBmb3JtYWxseSB0ZXN0IGZvciBncm91cHMgZGlmZmVyZW5jZXMgaW4gcHJlZmVyZW5jZXMgdG93YXJkIHBhcnRpY3VsYXIgZmVhdHVyZXMsIEkgdXNlIG9tbmlidXMgbmVzdGVkIG1vZGVsIGNvbXBhcmlzb25zLiANCg0KIyMjIE1hbGUgdnMgZmVtYWxlDQoNCmBgYHtyLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD04LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfSANCmRhdGEkU2V4IDwtIE5BX3JlYWxfDQpkYXRhJFNleFtkYXRhJGdlbmRlciA9PSAibWFuIl0gPC0gMUwNCmRhdGEkU2V4W2RhdGEkZ2VuZGVyID09ICJ3b21hbiJdIDwtIDJMDQpkYXRhJFNleCA8LSBmYWN0b3IoZGF0YSRTZXgsIDE6MiwgYygiTWFsZSIsICJGZW1hbGUiKSkNCg0KI2NvbmRpdGlvbmFsIE1NDQptbSA8LSBjcmVnZzo6Y2oobmEub21pdChkYXRhKSwgZjEsIGlkID0gfmlkLCBlc3RpbWF0ZSA9ICJtbSIsIGJ5ID0gflNleCkNCm1tIDwtIG1tICU+JSBhcnJhbmdlKGxldmVsLCBmZWF0dXJlKQ0KDQojc3Vic3RyYWN0IGJhc2VsaW5lIG1hcmdpbmFsIG1lYW4NCm1tJGVzdGltYXRlIDwtIG1tJGVzdGltYXRlIC0gKDEvMykNCm1tJHVwcGVyIDwtIG1tJHVwcGVyIC0gKDEvMykNCm1tJGxvd2VyIDwtIG1tJGxvd2VyIC0gKDEvMykNCg0KI2RpZmZlcmVuY2UgYmV0d2VlbiBzdWJncm91cHMNCmRpZmZfbW0gPC0gY3JlZ2c6OmNqKGRhdGEsIGYxLCBpZCA9IH5pZCwgZXN0aW1hdGUgPSAibW1fZGlmZiIsIGJ5ID0gflNleCkNCg0KI2NvbWJpbmUgcGxvdHMNCm1tIDwtIHJiaW5kKG1tLCBkaWZmX21tKQ0KbW0kQlkgPC0gZmFjdG9yKG1tJEJZLCBsZXZlbHMgPSByZXYobGV2ZWxzKG1tJEJZKSkpDQptbSRzaG93bGFiZWwgPC0gaWZlbHNlKGlzLm5hKG1tJHN0ZC5lcnJvciksMCwxKQ0KDQojIHBsb3Qgd2l0aCBncm91cGluZw0KcDEgPC0gcGxvdChtbSwgZ3JvdXAgPSAiQlkiLCBmZWF0dXJlX2hlYWRlcnMgPSBUUlVFLCBzaXplID0gMSkgKyANCiAgZ2dwbG90Mjo6ZmFjZXRfd3JhcCh+ZmVhdHVyZSwgbmNvbCA9IDFMLCBzY2FsZXMgPSAiZnJlZV95Iiwgc3RyaXAucG9zaXRpb24gPSAicmlnaHQiKSArIA0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoIiM1NkI0RTkiLCAiI0U2OUYwMCIsICIjOTk5OTk5IiksIGJyZWFrcyA9IGMoIkZlbWFsZSIsICJNYWxlIiwgIkZlbWFsZSAtIE1hbGUiKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArDQogIGxhYnMoeCA9ICIiKSArDQogIGZ0aGVtZSgpICsNCiAgdGhlbWUoc3RyaXAudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwgDQogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAnZGFya2dyZXknKSwNCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLCBsZWdlbmQuZGlyZWN0aW9uPSJ2ZXJ0aWNhbCIpDQoNCiN0ZXN0IG9mIHByZWZlcmVuY2UgaGV0ZXJvZ2VuZWl0eSAobmVzdGVkIG1vZGVsIGNvbXBhcmlzb24gdGVzdCkNCmNyZWdnOjpjal9hbm92YShuYS5vbWl0KGRhdGEpLCBjaG9pY2UgfiBjb21wYXJpc29uLCBieSA9IH5TZXgpDQpjcmVnZzo6Y2pfYW5vdmEobmEub21pdChkYXRhKSwgY2hvaWNlIH4ga25vd2xlZGdlLCBieSA9IH5TZXgpDQpjcmVnZzo6Y2pfYW5vdmEobmEub21pdChkYXRhKSwgY2hvaWNlIH4gY29tcGFuaW9uc2hpcCwgYnkgPSB+U2V4KQ0KY3JlZ2c6OmNqX2Fub3ZhKG5hLm9taXQoZGF0YSksIGNob2ljZSB+IGVuY291cmFnZW1lbnQsIGJ5ID0gflNleCkNCmBgYA0KDQojIyMgYWN0aXZlIHZzIG5vbi1hY3RpdmUNCg0KYGBge3IsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTgsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9IA0KZGF0YSRBY3RpdmUgPC0gTkFfcmVhbF8NCmRhdGEkQWN0aXZlW2RhdGEkYWN0aXZlVzIgPT0gInllcyJdIDwtIDJMDQpkYXRhJEFjdGl2ZVtkYXRhJGFjdGl2ZVcyID09ICJubyJdIDwtIDFMDQpkYXRhJEFjdGl2ZSA8LSBmYWN0b3IoZGF0YSRBY3RpdmUsIDE6MiwgYygiSW5hY3RpdmUiLCAiQWN0aXZlIikpDQoNCiNjb25kaXRpb25hbCBNTQ0KbW0gPC0gY3JlZ2c6OmNqKGRhdGEsIGYxLCBpZCA9IH5pZCwgZXN0aW1hdGUgPSAibW0iLCBieSA9IH5BY3RpdmUpDQptbSA8LSBtbSAlPiUgYXJyYW5nZShsZXZlbCwgZmVhdHVyZSkNCg0KI3N1YnN0cmFjdCBiYXNlbGluZSBtYXJnaW5hbCBtZWFuDQptbSRlc3RpbWF0ZSA8LSBtbSRlc3RpbWF0ZSAtICgxLzMpDQptbSR1cHBlciA8LSBtbSR1cHBlciAtICgxLzMpDQptbSRsb3dlciA8LSBtbSRsb3dlciAtICgxLzMpDQoNCiNkaWZmZXJlbmNlIGJldHdlZW4gc3ViZ3JvdXBzDQpkaWZmX21tIDwtIGNyZWdnOjpjaihkYXRhLCBmMSwgaWQgPSB+aWQsIGVzdGltYXRlID0gIm1tX2RpZmYiLCBieSA9IH5BY3RpdmUpDQoNCiNjb21iaW5lIHBsb3RzDQptbSA8LSByYmluZChtbSwgZGlmZl9tbSkNCm1tJEJZIDwtIGZhY3RvcihtbSRCWSwgbGV2ZWxzID0gcmV2KGxldmVscyhtbSRCWSkpKQ0KbW0kc2hvd2xhYmVsIDwtIGlmZWxzZShpcy5uYShtbSRzdGQuZXJyb3IpLDAsMSkNCg0KI2N1c3RvbSBoZWFkZXJzL2xldmVscw0KbGV2ZWxzKG1tJGZlYXR1cmUpICA8LSBjKCIqKlBlcmZvcm1hbmNlIENvbXBhcmlzb24qKiIsICIqKlRyYWluaW5nIEtub3dsZWRnZSoqIiwgIioqU3BvcnRpbmcgSW50ZW50KioiLCAiKipFbmNvdXJhZ2VtZW50KioiKQ0KbGV2ZWxzKG1tJGxldmVsKSA8LSBjKCJSZWFsbHkgTGlrZSIsICJTb21ld2hhdCBMaWtlIiwgIkRpc2xpa2UiLCAiTW9yZSBLbm93bGVkZ2UiLCAiRXF1YWwgS25vd2xlZGdlIiwgIkxlc3MgS25vd2xlZGdlIiwgIlNvY2lhbCIsICJTb2NpYWwgKyBQdXJwb3NlIiwgIlB1cnBvc2VmdWwiLCAiQWx3YXlzIiwgIlNvbWV0aW1lcyIsICJOZXZlciIgKQ0KDQojIHBsb3Qgd2l0aCBncm91cGluZw0KcDIgPC0gcGxvdChtbSwgZ3JvdXAgPSAiQlkiLCBmZWF0dXJlX2hlYWRlcnMgPSBUUlVFLCBzaXplID0gMSkgKyANCiAgZ2dwbG90Mjo6ZmFjZXRfd3JhcCh+ZmVhdHVyZSwgbmNvbCA9IDFMLCBzY2FsZXMgPSAiZnJlZV95Iiwgc3RyaXAucG9zaXRpb24gPSAicmlnaHQiKSArIA0KICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzU2QjRFOSIsICIjRTY5RjAwIiwgIiM5OTk5OTkiKSwgYnJlYWtzID0gYygiQWN0aXZlIiwgIkluYWN0aXZlIiwgIkFjdGl2ZSAtIEluYWN0aXZlIikpICsNCiAgbGFicyh4ID0gIiIpICsNCiAgZnRoZW1lKCkgKw0KICB0aGVtZShzdHJpcC50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksIA0KICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvciA9ICdkYXJrZ3JleScpLA0KICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfbWFya2Rvd24oKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIsIGxlZ2VuZC5kaXJlY3Rpb249InZlcnRpY2FsIikNCg0KI3Rlc3Qgb2YgcHJlZmVyZW5jZSBoZXRlcm9nZW5laXR5IChuZXN0ZWQgbW9kZWwgY29tcGFyaXNvbiB0ZXN0KQ0KY3JlZ2c6OmNqX2Fub3ZhKGRhdGEsIGNob2ljZSB+IGNvbXBhcmlzb24sIGJ5ID0gfkFjdGl2ZSkNCmNyZWdnOjpjal9hbm92YShkYXRhLCBjaG9pY2UgfiBrbm93bGVkZ2UsIGJ5ID0gfkFjdGl2ZSkNCmNyZWdnOjpjal9hbm92YShkYXRhLCBjaG9pY2UgfiBjb21wYW5pb25zaGlwLCBieSA9IH5BY3RpdmUpDQpjcmVnZzo6Y2pfYW5vdmEoZGF0YSwgY2hvaWNlIH4gZW5jb3VyYWdlbWVudCwgYnkgPSB+QWN0aXZlKQ0KYGBgDQoNCg0KDQojIyMgSGlnaCB2cyBsb3cgZnJlcXVlbmN5DQoNCmBgYHtyLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD04LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfSANCiNzcG9ydHNfZnJlcXVlbmN5IDwtIGlmZWxzZShpcy5uYShkYXRhJHNwb3J0c2ZyZXEpLCAwLCBhcy5udW1lcmljKGRhdGEkc3BvcnRzZnJlcSkpDQojaGlzdChzcG9ydHNfZnJlcXVlbmN5KQ0KI2Z0YWJsZShwc3ljaDo6ZGVzY3JpYmUoc3BvcnRzX2ZyZXF1ZW5jeSkpDQoNCiNoZXJlLCBmb2N1cyBvbmx5IG9uIHRob3NlIGN1cnJlbnRseSBhY3RpdmUuLg0KZGF0YSRGcmVxdWVuY3kgPC0gTkFfcmVhbF8NCmRhdGEkRnJlcXVlbmN5W2RhdGEkc3BvcnRzZnJlcSA8IDNdIDwtIDFMDQpkYXRhJEZyZXF1ZW5jeVtkYXRhJHNwb3J0c2ZyZXEgPj0gM10gPC0gMkwNCmRhdGEkRnJlcXVlbmN5IDwtIGZhY3RvcihkYXRhJEZyZXF1ZW5jeSwgMToyLCBjKCJMb3ciLCAiSGlnaCIpKQ0KDQojY29uZGl0aW9uYWwgTU0NCm1tIDwtIGNyZWdnOjpjaihkYXRhLCBmMSwgaWQgPSB+aWQsIGVzdGltYXRlID0gIm1tIiwgYnkgPSB+RnJlcXVlbmN5KQ0KbW0gPC0gbW0gJT4lIGFycmFuZ2UobGV2ZWwsIGZlYXR1cmUpDQoNCiNzdWJzdHJhY3QgYmFzZWxpbmUgbWFyZ2luYWwgbWVhbg0KbW0kZXN0aW1hdGUgPC0gbW0kZXN0aW1hdGUgLSAoMS8zKQ0KbW0kdXBwZXIgPC0gbW0kdXBwZXIgLSAoMS8zKQ0KbW0kbG93ZXIgPC0gbW0kbG93ZXIgLSAoMS8zKQ0KDQojZGlmZmVyZW5jZSBiZXR3ZWVuIHN1Ymdyb3Vwcw0KZGlmZl9tbSA8LSBjcmVnZzo6Y2ooZGF0YSwgZjEsIGlkID0gfmlkLCBlc3RpbWF0ZSA9ICJtbV9kaWZmIiwgYnkgPSB+RnJlcXVlbmN5KQ0KDQojY29tYmluZSBwbG90cw0KbW0gPC0gcmJpbmQobW0sIGRpZmZfbW0pDQptbSRCWSA8LSBmYWN0b3IobW0kQlksIGxldmVscyA9IHJldihsZXZlbHMobW0kQlkpKSkNCm1tJHNob3dsYWJlbCA8LSBpZmVsc2UoaXMubmEobW0kc3RkLmVycm9yKSwwLDEpDQoNCiMgcGxvdCB3aXRoIGdyb3VwaW5nDQpwMyA8LSBwbG90KG1tLCBncm91cCA9ICJCWSIsIGZlYXR1cmVfaGVhZGVycyA9IFRSVUUsIHNpemUgPSAxKSArIA0KICBnZ3Bsb3QyOjpmYWNldF93cmFwKH5mZWF0dXJlLCBuY29sID0gMUwsIHNjYWxlcyA9ICJmcmVlX3kiLCBzdHJpcC5wb3NpdGlvbiA9ICJyaWdodCIpICsgDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzU2QjRFOSIsICIjRTY5RjAwIiwgIiM5OTk5OTkiKSwgYnJlYWtzID0gYygiSGlnaCIsICJMb3ciLCAiSGlnaCAtIExvdyIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsNCiAgZnRoZW1lKCkgKw0KICB0aGVtZShzdHJpcC50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksIA0KICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvciA9ICdkYXJrZ3JleScpLA0KICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIsIGxlZ2VuZC5kaXJlY3Rpb249InZlcnRpY2FsIikNCg0KI3Rlc3Qgb2YgcHJlZmVyZW5jZSBoZXRlcm9nZW5laXR5IChuZXN0ZWQgbW9kZWwgY29tcGFyaXNvbiB0ZXN0KQ0KY3JlZ2c6OmNqX2Fub3ZhKGRhdGFbIWlzLm5hKGRhdGEkRnJlcXVlbmN5KSxdLCBjaG9pY2UgfiBjb21wYXJpc29uLCBieSA9IH5GcmVxdWVuY3kpDQpjcmVnZzo6Y2pfYW5vdmEoZGF0YVshaXMubmEoZGF0YSRGcmVxdWVuY3kpLF0sIGNob2ljZSB+IGtub3dsZWRnZSwgYnkgPSB+RnJlcXVlbmN5KQ0KY3JlZ2c6OmNqX2Fub3ZhKGRhdGFbIWlzLm5hKGRhdGEkRnJlcXVlbmN5KSxdLCBjaG9pY2UgfiBjb21wYW5pb25zaGlwLCBieSA9IH5GcmVxdWVuY3kpDQpjcmVnZzo6Y2pfYW5vdmEoZGF0YVshaXMubmEoZGF0YSRGcmVxdWVuY3kpLF0sIGNob2ljZSB+IGVuY291cmFnZW1lbnQsIGJ5ID0gfkZyZXF1ZW5jeSkNCmBgYA0KDQo8YnI+DQoNCiMjIyMgZGlmZmVyZW50IGN1dC1vZmYgaGlnaC9sb3cNCg0KaGlnaCAoPiAzKSB2cy4gbG93ICjiiaQgMykNCg0KYGBge3IsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTgsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9IA0KI3Nwb3J0c19mcmVxdWVuY3kgPC0gaWZlbHNlKGlzLm5hKGRhdGEkc3BvcnRzZnJlcSksIDAsIGFzLm51bWVyaWMoZGF0YSRzcG9ydHNmcmVxKSkNCiNoaXN0KHNwb3J0c19mcmVxdWVuY3kpDQojZnRhYmxlKHBzeWNoOjpkZXNjcmliZShzcG9ydHNfZnJlcXVlbmN5KSkNCg0KI2hlcmUsIGZvY3VzIG9ubHkgb24gdGhvc2UgY3VycmVudGx5IGFjdGl2ZS4uDQpkYXRhJEZyZXF1ZW5jeSA8LSBOQV9yZWFsXw0KZGF0YSRGcmVxdWVuY3lbZGF0YSRzcG9ydHNmcmVxIDwgNF0gPC0gMUwNCmRhdGEkRnJlcXVlbmN5W2RhdGEkc3BvcnRzZnJlcSA+IDNdIDwtIDJMDQpkYXRhJEZyZXF1ZW5jeSA8LSBmYWN0b3IoZGF0YSRGcmVxdWVuY3ksIDE6MiwgYygiTG93IiwgIkhpZ2giKSkNCg0KI3Byb3AudGFibGUodGFibGUoZGF0YSRGcmVxdWVuY3kpKSAjNjMgdnMgMzcNCg0KI2NvbmRpdGlvbmFsIE1NDQptbSA8LSBjcmVnZzo6Y2ooZGF0YSwgZjEsIGlkID0gfmlkLCBlc3RpbWF0ZSA9ICJtbSIsIGJ5ID0gfkZyZXF1ZW5jeSkNCm1tIDwtIG1tICU+JSBhcnJhbmdlKGxldmVsLCBmZWF0dXJlKQ0KDQojc3Vic3RyYWN0IGJhc2VsaW5lIG1hcmdpbmFsIG1lYW4NCm1tJGVzdGltYXRlIDwtIG1tJGVzdGltYXRlIC0gKDEvMykNCm1tJHVwcGVyIDwtIG1tJHVwcGVyIC0gKDEvMykNCm1tJGxvd2VyIDwtIG1tJGxvd2VyIC0gKDEvMykNCg0KI2RpZmZlcmVuY2UgYmV0d2VlbiBzdWJncm91cHMNCmRpZmZfbW0gPC0gY3JlZ2c6OmNqKGRhdGEsIGYxLCBpZCA9IH5pZCwgZXN0aW1hdGUgPSAibW1fZGlmZiIsIGJ5ID0gfkZyZXF1ZW5jeSkNCg0KI2NvbWJpbmUgcGxvdHMNCm1tIDwtIHJiaW5kKG1tLCBkaWZmX21tKQ0KbW0kQlkgPC0gZmFjdG9yKG1tJEJZLCBsZXZlbHMgPSByZXYobGV2ZWxzKG1tJEJZKSkpDQptbSRzaG93bGFiZWwgPC0gaWZlbHNlKGlzLm5hKG1tJHN0ZC5lcnJvciksMCwxKQ0KDQojY3VzdG9tIGhlYWRlcnMvbGV2ZWxzDQpsZXZlbHMobW0kZmVhdHVyZSkgIDwtIGMoIioqUGVyZm9ybWFuY2UgQ29tcGFyaXNvbioqIiwgIioqVHJhaW5pbmcgS25vd2xlZGdlKioiLCAiKipTcG9ydGluZyBJbnRlbnQqKiIsICIqKkVuY291cmFnZW1lbnQqKiIpDQpsZXZlbHMobW0kbGV2ZWwpIDwtIGMoIlJlYWxseSBMaWtlIiwgIlNvbWV3aGF0IExpa2UiLCAiRGlzbGlrZSIsICJNb3JlIEtub3dsZWRnZSIsICJFcXVhbCBLbm93bGVkZ2UiLCAiTGVzcyBLbm93bGVkZ2UiLCAiU29jaWFsIiwgIlNvY2lhbCArIFB1cnBvc2UiLCAiUHVycG9zZWZ1bCIsICJBbHdheXMiLCAiU29tZXRpbWVzIiwgIk5ldmVyIiApDQoNCiMgcGxvdCB3aXRoIGdyb3VwaW5nDQpwNCA8LSBwbG90KG1tLCBncm91cCA9ICJCWSIsIGZlYXR1cmVfaGVhZGVycyA9IFRSVUUsIHNpemUgPSAxKSArIA0KICBnZ3Bsb3QyOjpmYWNldF93cmFwKH5mZWF0dXJlLCBuY29sID0gMUwsIHNjYWxlcyA9ICJmcmVlX3kiLCBzdHJpcC5wb3NpdGlvbiA9ICJyaWdodCIpICsgDQogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCIjNTZCNEU5IiwgIiNFNjlGMDAiLCAiIzk5OTk5OSIpLCBicmVha3MgPSBjKCJIaWdoIiwgIkxvdyIsICJIaWdoIC0gTG93IikpICsNCiAgbGFicyh4ID0gIiIpICsNCiAgZnRoZW1lKCkgKw0KICB0aGVtZShzdHJpcC50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksIA0KICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvciA9ICdkYXJrZ3JleScpLA0KICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfbWFya2Rvd24oKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIsIGxlZ2VuZC5kaXJlY3Rpb249InZlcnRpY2FsIikNCg0KI3Rlc3Qgb2YgcHJlZmVyZW5jZSBoZXRlcm9nZW5laXR5IChuZXN0ZWQgbW9kZWwgY29tcGFyaXNvbiB0ZXN0KQ0KI2NyZWdnOjpjal9hbm92YShkYXRhWyFpcy5uYShkYXRhJEZyZXF1ZW5jeSksXSwgY2hvaWNlIH4gY29tcGFyaXNvbiwgYnkgPSB+RnJlcXVlbmN5KQ0KI2NyZWdnOjpjal9hbm92YShkYXRhWyFpcy5uYShkYXRhJEZyZXF1ZW5jeSksXSwgY2hvaWNlIH4ga25vd2xlZGdlLCBieSA9IH5GcmVxdWVuY3kpDQojY3JlZ2c6OmNqX2Fub3ZhKGRhdGFbIWlzLm5hKGRhdGEkRnJlcXVlbmN5KSxdLCBjaG9pY2UgfiBjb21wYW5pb25zaGlwLCBieSA9IH5GcmVxdWVuY3kpDQojY3JlZ2c6OmNqX2Fub3ZhKGRhdGFbIWlzLm5hKGRhdGEkRnJlcXVlbmN5KSxdLCBjaG9pY2UgfiBlbmNvdXJhZ2VtZW50LCBieSA9IH5GcmVxdWVuY3kpDQoNCnByaW50KHA0KQ0KYGBgDQoNCiMjIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCiMjIFBsb3QNCg0KYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTcsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9IA0KKG11bHRpcGxvdCA8LSBnZ3B1YnI6OmdnYXJyYW5nZShwMiwgcDMsIHAxLCBuY29sID0gMywgbnJvdyA9IDEsIHdpZHRocyA9IGMoMS45LDEsMSkpKQ0KDQojZ2dzYXZlKCIuL2ZpZ3VyZXMvY21tcy5wbmVnIiwgbXVsdGlwbG90KQ0KYGBgDQoNCi0tLS0NCg0KPGJyPg0KDQojIERpYWdub3N0aWNzDQoNCiMjIEZyZXF1ZW5jaWVzDQoNCk9mIGNvbmpvaW50IGZlYXR1cmVzICh0byBlbnN1cmUgZXF1YWwgZGlzcGxheSBmcmVxdWVuY3kpOg0KDQpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KcGxvdChjcmVnZzo6Y2pfZnJlcXMoZGF0YSwgY2hvaWNlIH4gY29tcGFyaXNvbiArIGtub3dsZWRnZSArIGNvbXBhbmlvbnNoaXAgKyBlbmNvdXJhZ2VtZW50ICsgU2V4ICsgQWN0aXZlICsgRnJlcXVlbmN5LCBpZCA9IH5pZCkpICsgZnRoZW1lKCkgKyBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jYlBhbGV0dGUpDQpgYGANCg0KLS0tDQoNCiMgUmVmZXJlbmNlcw0KDQo=


Copyright © 2025 Rob Franken