Overview
This R Markdown performs FGSEA-based pathway characterization of
malignant CD4+ T-cell clusters using positive markers
only (Version A).
Rationale: for cluster annotation, the relevant question is “what
biological programs define this cluster?”, not “which pathways are
increased or decreased relative to other clusters?”. Using only genes
enriched within each cluster
(FindAllMarkers(only.pos = TRUE)), ranked by an unsigned
statistic combining effect size and significance, keeps FGSEA focused on
defining/up-regulated programs and substantially reduces the “unbalanced
positive/negative gene-level statistic” warnings that occur when a
signed ranking is dominated by one-sided pathways
(e.g. interferon-stimulated genes, which are almost universally positive
in an IFN-high cluster).
The workflow:
- Identify cluster-specific positive markers using Seurat
FindAllMarkers(only.pos = TRUE), retaining all tested
positive genes (no top-N filtering, since FGSEA requires a full ranked
list).
- Rank genes per cluster using: \[
Score = avg\_log2FC \times \sqrt{-log10(p_{adj})}
\] with an explicit floor on
p_val_adj to avoid
infinite ranks.
- Run FGSEA against Hallmark, Reactome, KEGG, and GO:BP gene sets.
- Export enrichment tables including NES, p-value, adjusted p-value,
and leading-edge genes – the genes actually driving each enrichment
signal.
- Sanity-check known clusters (13 = IFN, 7 = G2M) and visualize top
pathways.
The resulting evidence chain for cluster naming is:
FindAllMarkers(only.pos=TRUE) -> defining marker genes
-> FGSEA on those genes -> defining pathways -> leading-edge
genes link the pathway back to the specific markers driving it. This is
the primary evidence to cite in the manuscript’s cluster annotation
table.
1. Load packages
library(Seurat)
library(dplyr)
library(fgsea)
library(msigdbr)
library(ggplot2)
library(tibble)
library(stringr)
set.seed(123)
2. Load Seurat object
All_samples_Merged <- readRDS("../../../1-Seurat_RDS_OBJECT_FINAL/All_samples_Merged_with_Renamed_Clusters_Cell_state-03-12-2025.rds.rds")
All_samples_Merged
DefaultAssay(All_samples_Merged)
Idents(All_samples_Merged) <- "seurat_clusters"
3. Differential expression analysis (positive markers only)
All positive genes are retained for FGSEA – no top-N filtering, since
FGSEA requires a full ranked list rather than a short marker set.
markers_pos <- FindAllMarkers(
object = All_samples_Merged,
only.pos = TRUE,
min.pct = 0.1,
logfc.threshold = 0
)
head(markers_pos)
4. Generate ranking statistic
The ranking combines expression effect size and statistical
confidence, without a sign term since only positive markers are
retained:
\[
Score = avg\_log2FC \times \sqrt{-log10(p_{adj})}
\]
Fix applied: in large single-cell datasets,
p_val_adj is frequently exactly 0 due to very large cell
numbers, which makes -log10(p_val_adj) = Inf for many genes
simultaneously. A numerical floor is applied to p_val_adj
before the log transform, preserving relative differences
between highly significant genes rather than tying them all to one
collapsed maximum value.
p_floor <- min(markers_pos$p_val_adj[markers_pos$p_val_adj > 0], na.rm = TRUE) / 10
markers_pos <- markers_pos %>%
mutate(
p_val_adj_floored = ifelse(p_val_adj == 0, p_floor, p_val_adj),
fgsea_rank = avg_log2FC * sqrt(-log10(p_val_adj_floored))
)
sum(is.infinite(markers_pos$fgsea_rank))
[1] 0
sum(markers_pos$p_val_adj == 0)
[1] 5175
5. Prepare pathway databases
library(msigdbr)
msigdbr_species <- "Homo sapiens"
# Hallmark
hallmark_sets <- msigdbr(species = msigdbr_species, collection = "H")
hallmark_sets <- split(hallmark_sets$gene_symbol, hallmark_sets$gs_name)
# Reactome
reactome_sets <- msigdbr(species = msigdbr_species, collection = "C2",
subcollection = "CP:REACTOME")
reactome_sets <- split(reactome_sets$gene_symbol, reactome_sets$gs_name)
# KEGG (legacy, equivalent to the old "CP:KEGG")
kegg_sets <- msigdbr(species = msigdbr_species, collection = "C2",
subcollection = "CP:KEGG_LEGACY")
kegg_sets <- split(kegg_sets$gene_symbol, kegg_sets$gs_name)
# GO Biological Process
go_sets <- msigdbr(species = msigdbr_species, collection = "C5",
subcollection = "GO:BP")
go_sets <- split(go_sets$gene_symbol, go_sets$gs_name)
length(hallmark_sets)
[1] 50
[1] 1839
[1] 186
[1] 7538
Note: if your installed msigdbr version is < 10.0,
replace collection = with category = and
subcollection = with subcategory = throughout
this chunk, and use "CP:KEGG" instead of
"CP:KEGG_LEGACY".
6. FGSEA function
run_fgsea_cluster <- function(cluster_id, markers, pathways) {
message("Running cluster: ", cluster_id)
cluster_markers <- markers %>% dplyr::filter(cluster == cluster_id)
ranks <- cluster_markers$fgsea_rank
names(ranks) <- cluster_markers$gene
ranks <- ranks[!duplicated(names(ranks))]
ranks <- ranks[!is.na(ranks) & !is.infinite(ranks)]
ranks <- sort(ranks, decreasing = TRUE)
fgsea_result <- fgsea(
pathways = pathways,
stats = ranks,
minSize = 15,
maxSize = 500,
eps = 0,
nPermSimple = 10000
)
fgsea_result <- as.data.frame(fgsea_result)
fgsea_result$leadingEdge_genes <- sapply(
fgsea_result$leadingEdge,
function(x) paste(x, collapse = ", ")
)
fgsea_result$cluster <- cluster_id
fgsea_result$n_genes_ranked <- length(ranks)
return(fgsea_result)
}
7. Define clusters
clusters <- sort(unique(markers_pos$cluster))
clusters
[1] 0 1 2 3 4 5 6 7 8 9 10 11 12 13
Levels: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
8. Run FGSEA across all four databases
hallmark_results <- bind_rows(lapply(clusters, run_fgsea_cluster,
markers = markers_pos, pathways = hallmark_sets))
hallmark_results$database <- "Hallmark"
reactome_results <- bind_rows(lapply(clusters, run_fgsea_cluster,
markers = markers_pos, pathways = reactome_sets))
reactome_results$database <- "Reactome"
kegg_results <- bind_rows(lapply(clusters, run_fgsea_cluster,
markers = markers_pos, pathways = kegg_sets))
kegg_results$database <- "KEGG"
go_results <- bind_rows(lapply(clusters, run_fgsea_cluster,
markers = markers_pos, pathways = go_sets))
go_results$database <- "GO_BP"
9. Combine all databases
all_fgsea_results <- bind_rows(hallmark_results, reactome_results, kegg_results, go_results)
all_fgsea_results <- all_fgsea_results %>%
dplyr::select(cluster, database, pathway, NES, pval, padj, size,
n_genes_ranked, leadingEdge_genes) %>%
dplyr::arrange(cluster, padj)
head(all_fgsea_results)
10. Export complete FGSEA table
write.csv(all_fgsea_results, "FGSEA_PositiveMarkers_All_Pathways_with_LeadingEdge_Genes.csv",
row.names = FALSE)
11. Significant pathways
significant_fgsea <- all_fgsea_results %>% dplyr::filter(padj < 0.05)
write.csv(significant_fgsea, "FGSEA_PositiveMarkers_Significant_Pathways.csv", row.names = FALSE)
12. Top pathways per cluster
top_pathways <- all_fgsea_results %>%
dplyr::group_by(cluster) %>%
dplyr::arrange(padj) %>%
dplyr::slice_head(n = 10)
write.csv(top_pathways, "FGSEA_PositiveMarkers_Top10_Pathways_Per_Cluster.csv", row.names = FALSE)
top_pathways
13. Sanity check on known clusters
Expected: Cluster 13 should show Hallmark INTERFERON_ALPHA_RESPONSE /
INTERFERON_GAMMA_RESPONSE with leading-edge genes IFIT2, OASL, ISG15,
IFIT3, MX1; Cluster 7 should show G2M_CHECKPOINT, E2F_TARGETS,
MITOTIC_SPINDLE.
hallmark_results %>%
dplyr::filter(cluster == 13) %>%
dplyr::arrange(padj) %>%
dplyr::select(pathway, NES, padj, leadingEdge_genes) %>%
head(10)
hallmark_results %>%
dplyr::filter(cluster == 7) %>%
dplyr::arrange(padj) %>%
dplyr::select(pathway, NES, padj, leadingEdge_genes) %>%
head(10)
14. Plot Hallmark enrichment per cluster
plot_data <- hallmark_results %>%
dplyr::filter(padj < 0.05) %>%
dplyr::group_by(cluster) %>%
dplyr::slice_max(order_by = NES, n = 5)
if (nrow(plot_data) > 0) {
ggplot(plot_data, aes(x = reorder(pathway, NES), y = NES, fill = NES > 0)) +
geom_col() +
facet_wrap(~cluster, scales = "free") +
coord_flip() +
scale_fill_manual(values = c("TRUE" = "#B2182B", "FALSE" = "#4393C3"), guide = "none") +
labs(x = NULL, y = "Normalized Enrichment Score (NES)",
title = "FGSEA on positive markers only (Version A)") +
theme_classic(base_size = 10)
} else {
cat("No Hallmark pathways passed padj < 0.05 in any cluster.\n")
}

15. Save R objects
save(markers_pos, all_fgsea_results,
hallmark_results, reactome_results, kegg_results, go_results,
file = "FGSEA_SS_Malignant_CD4_PositiveMarkers_results.RData")
16. Notes on using this output for cluster naming justification
FGSEA_PositiveMarkers_All_Pathways_with_LeadingEdge_Genes.csv
is the primary file to cite from, since it links each pathway’s NES/FDR
directly to the specific leading-edge genes driving the signal
(e.g. “driven by IFIT2, IFIT3, OASL”).
- Report NES magnitude alongside FDR, not significance alone: a larger
NES means the pathway’s genes are more strongly concentrated toward the
top of that cluster’s positive-marker ranking.
- This positive-marker FGSEA approach and your existing ORA
(
enrichGO/enrichKEGG/enrichPathway)
approach answer closely related but distinct questions and should be
reported as complementary: ORA asks “are my top-N marker genes
overrepresented in a pathway?”; FGSEA asks “is a pathway’s gene set
skewed toward the top of my full positive-marker ranking for this
cluster?” Agreement between the two provides stronger support for a
cluster name than either method alone.
- The evidence chain to report per cluster: marker genes (from
FindAllMarkers(only.pos=TRUE)) -> defining pathway (from
FGSEA) -> leading-edge genes (confirming which markers actually drive
the pathway signal). Example: Cluster 13 marker genes IFIT2/OASL/ISG15
-> Hallmark INTERFERON_ALPHA_RESPONSE/Reactome interferon signaling
-> annotation “IFN-stimulated malignant state”.
LS0tCnRpdGxlOiAiRkdTRUEgQW5hbHlzaXMgb2YgTWFsaWduYW50IENENCBULWNlbGwgU3RhdGVzIChQb3NpdGl2ZSBNYXJrZXJzIE9ubHkpIGluIFNlemFyeSBTeW5kcm9tZSIKYXV0aG9yOiAiTmFzaXIgTWFobW9vZCBBYmJhc2kiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIHRvY19jb2xsYXBzZWQ6IHllcwogICAgdGhlbWU6IGZsYXRseQogICAgaGlnaGxpZ2h0OiB0YW5nbwogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAogIHdvcmRfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogeWVzCi0tLQoKIyBPdmVydmlldwoKVGhpcyBSIE1hcmtkb3duIHBlcmZvcm1zIEZHU0VBLWJhc2VkIHBhdGh3YXkgY2hhcmFjdGVyaXphdGlvbiBvZiBtYWxpZ25hbnQgQ0Q0KwpULWNlbGwgY2x1c3RlcnMgdXNpbmcgKipwb3NpdGl2ZSBtYXJrZXJzIG9ubHkqKiAoVmVyc2lvbiBBKS4KClJhdGlvbmFsZTogZm9yIGNsdXN0ZXIgYW5ub3RhdGlvbiwgdGhlIHJlbGV2YW50IHF1ZXN0aW9uIGlzICJ3aGF0IGJpb2xvZ2ljYWwKcHJvZ3JhbXMgZGVmaW5lIHRoaXMgY2x1c3Rlcj8iLCBub3QgIndoaWNoIHBhdGh3YXlzIGFyZSBpbmNyZWFzZWQgb3IgZGVjcmVhc2VkCnJlbGF0aXZlIHRvIG90aGVyIGNsdXN0ZXJzPyIuIFVzaW5nIG9ubHkgZ2VuZXMgZW5yaWNoZWQgd2l0aGluIGVhY2ggY2x1c3RlcgooYEZpbmRBbGxNYXJrZXJzKG9ubHkucG9zID0gVFJVRSlgKSwgcmFua2VkIGJ5IGFuIHVuc2lnbmVkIHN0YXRpc3RpYyBjb21iaW5pbmcKZWZmZWN0IHNpemUgYW5kIHNpZ25pZmljYW5jZSwga2VlcHMgRkdTRUEgZm9jdXNlZCBvbiBkZWZpbmluZy91cC1yZWd1bGF0ZWQKcHJvZ3JhbXMgYW5kIHN1YnN0YW50aWFsbHkgcmVkdWNlcyB0aGUgInVuYmFsYW5jZWQgcG9zaXRpdmUvbmVnYXRpdmUgZ2VuZS1sZXZlbApzdGF0aXN0aWMiIHdhcm5pbmdzIHRoYXQgb2NjdXIgd2hlbiBhIHNpZ25lZCByYW5raW5nIGlzIGRvbWluYXRlZCBieSBvbmUtc2lkZWQKcGF0aHdheXMgKGUuZy4gaW50ZXJmZXJvbi1zdGltdWxhdGVkIGdlbmVzLCB3aGljaCBhcmUgYWxtb3N0IHVuaXZlcnNhbGx5IHBvc2l0aXZlCmluIGFuIElGTi1oaWdoIGNsdXN0ZXIpLgoKVGhlIHdvcmtmbG93OgoKMS4gSWRlbnRpZnkgY2x1c3Rlci1zcGVjaWZpYyBwb3NpdGl2ZSBtYXJrZXJzIHVzaW5nIFNldXJhdAogICBgRmluZEFsbE1hcmtlcnMob25seS5wb3MgPSBUUlVFKWAsIHJldGFpbmluZyBhbGwgdGVzdGVkIHBvc2l0aXZlIGdlbmVzCiAgIChubyB0b3AtTiBmaWx0ZXJpbmcsIHNpbmNlIEZHU0VBIHJlcXVpcmVzIGEgZnVsbCByYW5rZWQgbGlzdCkuCjIuIFJhbmsgZ2VuZXMgcGVyIGNsdXN0ZXIgdXNpbmc6CiAgIFxbCiAgIFNjb3JlID0gYXZnXF9sb2cyRkMgXHRpbWVzIFxzcXJ0ey1sb2cxMChwX3thZGp9KX0KICAgXF0KICAgd2l0aCBhbiBleHBsaWNpdCBmbG9vciBvbiBgcF92YWxfYWRqYCB0byBhdm9pZCBpbmZpbml0ZSByYW5rcy4KMy4gUnVuIEZHU0VBIGFnYWluc3QgSGFsbG1hcmssIFJlYWN0b21lLCBLRUdHLCBhbmQgR086QlAgZ2VuZSBzZXRzLgo0LiBFeHBvcnQgZW5yaWNobWVudCB0YWJsZXMgaW5jbHVkaW5nIE5FUywgcC12YWx1ZSwgYWRqdXN0ZWQgcC12YWx1ZSwgYW5kCiAgIGxlYWRpbmctZWRnZSBnZW5lcyAtLSB0aGUgZ2VuZXMgYWN0dWFsbHkgZHJpdmluZyBlYWNoIGVucmljaG1lbnQgc2lnbmFsLgo1LiBTYW5pdHktY2hlY2sga25vd24gY2x1c3RlcnMgKDEzID0gSUZOLCA3ID0gRzJNKSBhbmQgdmlzdWFsaXplIHRvcCBwYXRod2F5cy4KClRoZSByZXN1bHRpbmcgZXZpZGVuY2UgY2hhaW4gZm9yIGNsdXN0ZXIgbmFtaW5nIGlzOgpgRmluZEFsbE1hcmtlcnMob25seS5wb3M9VFJVRSlgIC0+IGRlZmluaW5nIG1hcmtlciBnZW5lcyAtPiBGR1NFQSBvbiB0aG9zZSBnZW5lcwotPiBkZWZpbmluZyBwYXRod2F5cyAtPiBsZWFkaW5nLWVkZ2UgZ2VuZXMgbGluayB0aGUgcGF0aHdheSBiYWNrIHRvIHRoZSBzcGVjaWZpYwptYXJrZXJzIGRyaXZpbmcgaXQuIFRoaXMgaXMgdGhlIHByaW1hcnkgZXZpZGVuY2UgdG8gY2l0ZSBpbiB0aGUgbWFudXNjcmlwdCdzCmNsdXN0ZXIgYW5ub3RhdGlvbiB0YWJsZS4KCiMgMS4gTG9hZCBwYWNrYWdlcwoKYGBge3Igc2V0dXAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGZnc2VhKQpsaWJyYXJ5KG1zaWdkYnIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeSh0aWJibGUpCmxpYnJhcnkoc3RyaW5ncikKCnNldC5zZWVkKDEyMykKYGBgCgojIDIuIExvYWQgU2V1cmF0IG9iamVjdAoKYGBge3IgbG9hZC1vYmplY3R9CkFsbF9zYW1wbGVzX01lcmdlZCA8LSByZWFkUkRTKCIuLi8uLi8uLi8xLVNldXJhdF9SRFNfT0JKRUNUX0ZJTkFML0FsbF9zYW1wbGVzX01lcmdlZF93aXRoX1JlbmFtZWRfQ2x1c3RlcnNfQ2VsbF9zdGF0ZS0wMy0xMi0yMDI1LnJkcy5yZHMiKQoKQWxsX3NhbXBsZXNfTWVyZ2VkCkRlZmF1bHRBc3NheShBbGxfc2FtcGxlc19NZXJnZWQpCgpJZGVudHMoQWxsX3NhbXBsZXNfTWVyZ2VkKSA8LSAic2V1cmF0X2NsdXN0ZXJzIgoKYGBgCgojIDMuIERpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzIChwb3NpdGl2ZSBtYXJrZXJzIG9ubHkpCgpBbGwgcG9zaXRpdmUgZ2VuZXMgYXJlIHJldGFpbmVkIGZvciBGR1NFQSAtLSBubyB0b3AtTiBmaWx0ZXJpbmcsIHNpbmNlIEZHU0VBCnJlcXVpcmVzIGEgZnVsbCByYW5rZWQgbGlzdCByYXRoZXIgdGhhbiBhIHNob3J0IG1hcmtlciBzZXQuCgpgYGB7ciBmaW5kLWFsbC1tYXJrZXJzfQptYXJrZXJzX3BvcyA8LSBGaW5kQWxsTWFya2VycygKICBvYmplY3QgPSBBbGxfc2FtcGxlc19NZXJnZWQsCiAgb25seS5wb3MgPSBUUlVFLAogIG1pbi5wY3QgPSAwLjEsCiAgbG9nZmMudGhyZXNob2xkID0gMAopCgpoZWFkKG1hcmtlcnNfcG9zKQpgYGAKCiMgNC4gR2VuZXJhdGUgcmFua2luZyBzdGF0aXN0aWMKClRoZSByYW5raW5nIGNvbWJpbmVzIGV4cHJlc3Npb24gZWZmZWN0IHNpemUgYW5kIHN0YXRpc3RpY2FsIGNvbmZpZGVuY2UsIHdpdGhvdXQKYSBzaWduIHRlcm0gc2luY2Ugb25seSBwb3NpdGl2ZSBtYXJrZXJzIGFyZSByZXRhaW5lZDoKClxbClNjb3JlID0gYXZnXF9sb2cyRkMgXHRpbWVzIFxzcXJ0ey1sb2cxMChwX3thZGp9KX0KXF0KCioqRml4IGFwcGxpZWQ6KiogaW4gbGFyZ2Ugc2luZ2xlLWNlbGwgZGF0YXNldHMsIGBwX3ZhbF9hZGpgIGlzIGZyZXF1ZW50bHkgZXhhY3RseQowIGR1ZSB0byB2ZXJ5IGxhcmdlIGNlbGwgbnVtYmVycywgd2hpY2ggbWFrZXMgYC1sb2cxMChwX3ZhbF9hZGopID0gSW5mYCBmb3IgbWFueQpnZW5lcyBzaW11bHRhbmVvdXNseS4gQSBudW1lcmljYWwgZmxvb3IgaXMgYXBwbGllZCB0byBgcF92YWxfYWRqYCAqYmVmb3JlKiB0aGUgbG9nCnRyYW5zZm9ybSwgcHJlc2VydmluZyByZWxhdGl2ZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGhpZ2hseSBzaWduaWZpY2FudCBnZW5lcwpyYXRoZXIgdGhhbiB0eWluZyB0aGVtIGFsbCB0byBvbmUgY29sbGFwc2VkIG1heGltdW0gdmFsdWUuCgpgYGB7ciByYW5raW5nLXN0YXRpc3RpY30KcF9mbG9vciA8LSBtaW4obWFya2Vyc19wb3MkcF92YWxfYWRqW21hcmtlcnNfcG9zJHBfdmFsX2FkaiA+IDBdLCBuYS5ybSA9IFRSVUUpIC8gMTAKCm1hcmtlcnNfcG9zIDwtIG1hcmtlcnNfcG9zICU+JQogIG11dGF0ZSgKICAgIHBfdmFsX2Fkal9mbG9vcmVkID0gaWZlbHNlKHBfdmFsX2FkaiA9PSAwLCBwX2Zsb29yLCBwX3ZhbF9hZGopLAogICAgZmdzZWFfcmFuayA9IGF2Z19sb2cyRkMgKiBzcXJ0KC1sb2cxMChwX3ZhbF9hZGpfZmxvb3JlZCkpCiAgKQoKc3VtKGlzLmluZmluaXRlKG1hcmtlcnNfcG9zJGZnc2VhX3JhbmspKQpzdW0obWFya2Vyc19wb3MkcF92YWxfYWRqID09IDApCmBgYAoKIyA1LiBQcmVwYXJlIHBhdGh3YXkgZGF0YWJhc2VzCgpgYGB7ciBwYXRod2F5LWRhdGFiYXNlc30KbGlicmFyeShtc2lnZGJyKQoKbXNpZ2Ricl9zcGVjaWVzIDwtICJIb21vIHNhcGllbnMiCgojIEhhbGxtYXJrCmhhbGxtYXJrX3NldHMgPC0gbXNpZ2RicihzcGVjaWVzID0gbXNpZ2Ricl9zcGVjaWVzLCBjb2xsZWN0aW9uID0gIkgiKQpoYWxsbWFya19zZXRzIDwtIHNwbGl0KGhhbGxtYXJrX3NldHMkZ2VuZV9zeW1ib2wsIGhhbGxtYXJrX3NldHMkZ3NfbmFtZSkKCiMgUmVhY3RvbWUKcmVhY3RvbWVfc2V0cyA8LSBtc2lnZGJyKHNwZWNpZXMgPSBtc2lnZGJyX3NwZWNpZXMsIGNvbGxlY3Rpb24gPSAiQzIiLAogICAgICAgICAgICAgICAgICAgICAgICAgIHN1YmNvbGxlY3Rpb24gPSAiQ1A6UkVBQ1RPTUUiKQpyZWFjdG9tZV9zZXRzIDwtIHNwbGl0KHJlYWN0b21lX3NldHMkZ2VuZV9zeW1ib2wsIHJlYWN0b21lX3NldHMkZ3NfbmFtZSkKCiMgS0VHRyAobGVnYWN5LCBlcXVpdmFsZW50IHRvIHRoZSBvbGQgIkNQOktFR0ciKQprZWdnX3NldHMgPC0gbXNpZ2RicihzcGVjaWVzID0gbXNpZ2Ricl9zcGVjaWVzLCBjb2xsZWN0aW9uID0gIkMyIiwKICAgICAgICAgICAgICAgICAgICAgIHN1YmNvbGxlY3Rpb24gPSAiQ1A6S0VHR19MRUdBQ1kiKQprZWdnX3NldHMgPC0gc3BsaXQoa2VnZ19zZXRzJGdlbmVfc3ltYm9sLCBrZWdnX3NldHMkZ3NfbmFtZSkKCiMgR08gQmlvbG9naWNhbCBQcm9jZXNzCmdvX3NldHMgPC0gbXNpZ2RicihzcGVjaWVzID0gbXNpZ2Ricl9zcGVjaWVzLCBjb2xsZWN0aW9uID0gIkM1IiwKICAgICAgICAgICAgICAgICAgICBzdWJjb2xsZWN0aW9uID0gIkdPOkJQIikKZ29fc2V0cyA8LSBzcGxpdChnb19zZXRzJGdlbmVfc3ltYm9sLCBnb19zZXRzJGdzX25hbWUpCgpsZW5ndGgoaGFsbG1hcmtfc2V0cykKbGVuZ3RoKHJlYWN0b21lX3NldHMpCmxlbmd0aChrZWdnX3NldHMpCmxlbmd0aChnb19zZXRzKQpgYGAKCk5vdGU6IGlmIHlvdXIgaW5zdGFsbGVkIGBtc2lnZGJyYCB2ZXJzaW9uIGlzIDwgMTAuMCwgcmVwbGFjZSBgY29sbGVjdGlvbiA9YCB3aXRoCmBjYXRlZ29yeSA9YCBhbmQgYHN1YmNvbGxlY3Rpb24gPWAgd2l0aCBgc3ViY2F0ZWdvcnkgPWAgdGhyb3VnaG91dCB0aGlzIGNodW5rLAphbmQgdXNlIGAiQ1A6S0VHRyJgIGluc3RlYWQgb2YgYCJDUDpLRUdHX0xFR0FDWSJgLgoKIyA2LiBGR1NFQSBmdW5jdGlvbgoKYGBge3IgZmdzZWEtZnVuY3Rpb259CnJ1bl9mZ3NlYV9jbHVzdGVyIDwtIGZ1bmN0aW9uKGNsdXN0ZXJfaWQsIG1hcmtlcnMsIHBhdGh3YXlzKSB7CgogIG1lc3NhZ2UoIlJ1bm5pbmcgY2x1c3RlcjogIiwgY2x1c3Rlcl9pZCkKCiAgY2x1c3Rlcl9tYXJrZXJzIDwtIG1hcmtlcnMgJT4lIGRwbHlyOjpmaWx0ZXIoY2x1c3RlciA9PSBjbHVzdGVyX2lkKQoKICByYW5rcyA8LSBjbHVzdGVyX21hcmtlcnMkZmdzZWFfcmFuawogIG5hbWVzKHJhbmtzKSA8LSBjbHVzdGVyX21hcmtlcnMkZ2VuZQoKICByYW5rcyA8LSByYW5rc1shZHVwbGljYXRlZChuYW1lcyhyYW5rcykpXQogIHJhbmtzIDwtIHJhbmtzWyFpcy5uYShyYW5rcykgJiAhaXMuaW5maW5pdGUocmFua3MpXQogIHJhbmtzIDwtIHNvcnQocmFua3MsIGRlY3JlYXNpbmcgPSBUUlVFKQoKICBmZ3NlYV9yZXN1bHQgPC0gZmdzZWEoCiAgICBwYXRod2F5cyA9IHBhdGh3YXlzLAogICAgc3RhdHMgPSByYW5rcywKICAgIG1pblNpemUgPSAxNSwKICAgIG1heFNpemUgPSA1MDAsCiAgICBlcHMgPSAwLAogICAgblBlcm1TaW1wbGUgPSAxMDAwMAogICkKCiAgZmdzZWFfcmVzdWx0IDwtIGFzLmRhdGEuZnJhbWUoZmdzZWFfcmVzdWx0KQoKICBmZ3NlYV9yZXN1bHQkbGVhZGluZ0VkZ2VfZ2VuZXMgPC0gc2FwcGx5KAogICAgZmdzZWFfcmVzdWx0JGxlYWRpbmdFZGdlLAogICAgZnVuY3Rpb24oeCkgcGFzdGUoeCwgY29sbGFwc2UgPSAiLCAiKQogICkKCiAgZmdzZWFfcmVzdWx0JGNsdXN0ZXIgPC0gY2x1c3Rlcl9pZAogIGZnc2VhX3Jlc3VsdCRuX2dlbmVzX3JhbmtlZCA8LSBsZW5ndGgocmFua3MpCgogIHJldHVybihmZ3NlYV9yZXN1bHQpCn0KYGBgCgojIDcuIERlZmluZSBjbHVzdGVycwoKYGBge3IgZGVmaW5lLWNsdXN0ZXJzfQpjbHVzdGVycyA8LSBzb3J0KHVuaXF1ZShtYXJrZXJzX3BvcyRjbHVzdGVyKSkKY2x1c3RlcnMKYGBgCgojIDguIFJ1biBGR1NFQSBhY3Jvc3MgYWxsIGZvdXIgZGF0YWJhc2VzCgpgYGB7ciBydW4tZmdzZWEtYWxsfQpoYWxsbWFya19yZXN1bHRzIDwtIGJpbmRfcm93cyhsYXBwbHkoY2x1c3RlcnMsIHJ1bl9mZ3NlYV9jbHVzdGVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmtlcnMgPSBtYXJrZXJzX3BvcywgcGF0aHdheXMgPSBoYWxsbWFya19zZXRzKSkKaGFsbG1hcmtfcmVzdWx0cyRkYXRhYmFzZSA8LSAiSGFsbG1hcmsiCgpyZWFjdG9tZV9yZXN1bHRzIDwtIGJpbmRfcm93cyhsYXBwbHkoY2x1c3RlcnMsIHJ1bl9mZ3NlYV9jbHVzdGVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmtlcnMgPSBtYXJrZXJzX3BvcywgcGF0aHdheXMgPSByZWFjdG9tZV9zZXRzKSkKcmVhY3RvbWVfcmVzdWx0cyRkYXRhYmFzZSA8LSAiUmVhY3RvbWUiCgprZWdnX3Jlc3VsdHMgPC0gYmluZF9yb3dzKGxhcHBseShjbHVzdGVycywgcnVuX2Znc2VhX2NsdXN0ZXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJrZXJzID0gbWFya2Vyc19wb3MsIHBhdGh3YXlzID0ga2VnZ19zZXRzKSkKa2VnZ19yZXN1bHRzJGRhdGFiYXNlIDwtICJLRUdHIgoKZ29fcmVzdWx0cyA8LSBiaW5kX3Jvd3MobGFwcGx5KGNsdXN0ZXJzLCBydW5fZmdzZWFfY2x1c3RlciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJrZXJzID0gbWFya2Vyc19wb3MsIHBhdGh3YXlzID0gZ29fc2V0cykpCmdvX3Jlc3VsdHMkZGF0YWJhc2UgPC0gIkdPX0JQIgpgYGAKCiMgOS4gQ29tYmluZSBhbGwgZGF0YWJhc2VzCgpgYGB7ciBjb21iaW5lLXJlc3VsdHN9CmFsbF9mZ3NlYV9yZXN1bHRzIDwtIGJpbmRfcm93cyhoYWxsbWFya19yZXN1bHRzLCByZWFjdG9tZV9yZXN1bHRzLCBrZWdnX3Jlc3VsdHMsIGdvX3Jlc3VsdHMpCgphbGxfZmdzZWFfcmVzdWx0cyA8LSBhbGxfZmdzZWFfcmVzdWx0cyAlPiUKICBkcGx5cjo6c2VsZWN0KGNsdXN0ZXIsIGRhdGFiYXNlLCBwYXRod2F5LCBORVMsIHB2YWwsIHBhZGosIHNpemUsCiAgICAgICAgICAgICAgICBuX2dlbmVzX3JhbmtlZCwgbGVhZGluZ0VkZ2VfZ2VuZXMpICU+JQogIGRwbHlyOjphcnJhbmdlKGNsdXN0ZXIsIHBhZGopCgpoZWFkKGFsbF9mZ3NlYV9yZXN1bHRzKQpgYGAKCiMgMTAuIEV4cG9ydCBjb21wbGV0ZSBGR1NFQSB0YWJsZQoKYGBge3IgZXhwb3J0LWFsbH0Kd3JpdGUuY3N2KGFsbF9mZ3NlYV9yZXN1bHRzLCAiRkdTRUFfUG9zaXRpdmVNYXJrZXJzX0FsbF9QYXRod2F5c193aXRoX0xlYWRpbmdFZGdlX0dlbmVzLmNzdiIsCiAgICAgICAgICByb3cubmFtZXMgPSBGQUxTRSkKYGBgCgojIDExLiBTaWduaWZpY2FudCBwYXRod2F5cwoKYGBge3IgZXhwb3J0LXNpZ25pZmljYW50fQpzaWduaWZpY2FudF9mZ3NlYSA8LSBhbGxfZmdzZWFfcmVzdWx0cyAlPiUgZHBseXI6OmZpbHRlcihwYWRqIDwgMC4wNSkKd3JpdGUuY3N2KHNpZ25pZmljYW50X2Znc2VhLCAiRkdTRUFfUG9zaXRpdmVNYXJrZXJzX1NpZ25pZmljYW50X1BhdGh3YXlzLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKCiMgMTIuIFRvcCBwYXRod2F5cyBwZXIgY2x1c3RlcgoKYGBge3IgdG9wLXBhdGh3YXlzfQp0b3BfcGF0aHdheXMgPC0gYWxsX2Znc2VhX3Jlc3VsdHMgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGNsdXN0ZXIpICU+JQogIGRwbHlyOjphcnJhbmdlKHBhZGopICU+JQogIGRwbHlyOjpzbGljZV9oZWFkKG4gPSAxMCkKCndyaXRlLmNzdih0b3BfcGF0aHdheXMsICJGR1NFQV9Qb3NpdGl2ZU1hcmtlcnNfVG9wMTBfUGF0aHdheXNfUGVyX0NsdXN0ZXIuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpCnRvcF9wYXRod2F5cwpgYGAKCiMgMTMuIFNhbml0eSBjaGVjayBvbiBrbm93biBjbHVzdGVycwoKRXhwZWN0ZWQ6IENsdXN0ZXIgMTMgc2hvdWxkIHNob3cgSGFsbG1hcmsgSU5URVJGRVJPTl9BTFBIQV9SRVNQT05TRSAvCklOVEVSRkVST05fR0FNTUFfUkVTUE9OU0Ugd2l0aCBsZWFkaW5nLWVkZ2UgZ2VuZXMgSUZJVDIsIE9BU0wsIElTRzE1LCBJRklUMywgTVgxOwpDbHVzdGVyIDcgc2hvdWxkIHNob3cgRzJNX0NIRUNLUE9JTlQsIEUyRl9UQVJHRVRTLCBNSVRPVElDX1NQSU5ETEUuCgpgYGB7ciBzYW5pdHktY2hlY2stMTN9CmhhbGxtYXJrX3Jlc3VsdHMgJT4lCiAgZHBseXI6OmZpbHRlcihjbHVzdGVyID09IDEzKSAlPiUKICBkcGx5cjo6YXJyYW5nZShwYWRqKSAlPiUKICBkcGx5cjo6c2VsZWN0KHBhdGh3YXksIE5FUywgcGFkaiwgbGVhZGluZ0VkZ2VfZ2VuZXMpICU+JQogIGhlYWQoMTApCmBgYAoKYGBge3Igc2FuaXR5LWNoZWNrLTd9CmhhbGxtYXJrX3Jlc3VsdHMgJT4lCiAgZHBseXI6OmZpbHRlcihjbHVzdGVyID09IDcpICU+JQogIGRwbHlyOjphcnJhbmdlKHBhZGopICU+JQogIGRwbHlyOjpzZWxlY3QocGF0aHdheSwgTkVTLCBwYWRqLCBsZWFkaW5nRWRnZV9nZW5lcykgJT4lCiAgaGVhZCgxMCkKYGBgCgojIDE0LiBQbG90IEhhbGxtYXJrIGVucmljaG1lbnQgcGVyIGNsdXN0ZXIKCmBgYHtyIHBsb3QtaGFsbG1hcmssIGZpZy53aWR0aD0xOCwgZmlnLmhlaWdodD04fQpwbG90X2RhdGEgPC0gaGFsbG1hcmtfcmVzdWx0cyAlPiUKICBkcGx5cjo6ZmlsdGVyKHBhZGogPCAwLjA1KSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoY2x1c3RlcikgJT4lCiAgZHBseXI6OnNsaWNlX21heChvcmRlcl9ieSA9IE5FUywgbiA9IDUpCgppZiAobnJvdyhwbG90X2RhdGEpID4gMCkgewogIGdncGxvdChwbG90X2RhdGEsIGFlcyh4ID0gcmVvcmRlcihwYXRod2F5LCBORVMpLCB5ID0gTkVTLCBmaWxsID0gTkVTID4gMCkpICsKICAgIGdlb21fY29sKCkgKwogICAgZmFjZXRfd3JhcCh+Y2x1c3Rlciwgc2NhbGVzID0gImZyZWUiKSArCiAgICBjb29yZF9mbGlwKCkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiVFJVRSIgPSAiI0IyMTgyQiIsICJGQUxTRSIgPSAiIzQzOTNDMyIpLCBndWlkZSA9ICJub25lIikgKwogICAgbGFicyh4ID0gTlVMTCwgeSA9ICJOb3JtYWxpemVkIEVucmljaG1lbnQgU2NvcmUgKE5FUykiLAogICAgICAgICB0aXRsZSA9ICJGR1NFQSBvbiBwb3NpdGl2ZSBtYXJrZXJzIG9ubHkgKFZlcnNpb24gQSkiKSArCiAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDEwKQp9IGVsc2UgewogIGNhdCgiTm8gSGFsbG1hcmsgcGF0aHdheXMgcGFzc2VkIHBhZGogPCAwLjA1IGluIGFueSBjbHVzdGVyLlxuIikKfQpgYGAKCiMgMTUuIFNhdmUgUiBvYmplY3RzCgpgYGB7ciBzYXZlLW9iamVjdHN9CnNhdmUobWFya2Vyc19wb3MsIGFsbF9mZ3NlYV9yZXN1bHRzLAogICAgIGhhbGxtYXJrX3Jlc3VsdHMsIHJlYWN0b21lX3Jlc3VsdHMsIGtlZ2dfcmVzdWx0cywgZ29fcmVzdWx0cywKICAgICBmaWxlID0gIkZHU0VBX1NTX01hbGlnbmFudF9DRDRfUG9zaXRpdmVNYXJrZXJzX3Jlc3VsdHMuUkRhdGEiKQpgYGAKCiMgMTYuIE5vdGVzIG9uIHVzaW5nIHRoaXMgb3V0cHV0IGZvciBjbHVzdGVyIG5hbWluZyBqdXN0aWZpY2F0aW9uCgotIGBGR1NFQV9Qb3NpdGl2ZU1hcmtlcnNfQWxsX1BhdGh3YXlzX3dpdGhfTGVhZGluZ0VkZ2VfR2VuZXMuY3N2YCBpcyB0aGUgcHJpbWFyeQogIGZpbGUgdG8gY2l0ZSBmcm9tLCBzaW5jZSBpdCBsaW5rcyBlYWNoIHBhdGh3YXkncyBORVMvRkRSIGRpcmVjdGx5IHRvIHRoZQogIHNwZWNpZmljIGxlYWRpbmctZWRnZSBnZW5lcyBkcml2aW5nIHRoZSBzaWduYWwgKGUuZy4gImRyaXZlbiBieSBJRklUMiwgSUZJVDMsCiAgT0FTTCIpLgotIFJlcG9ydCBORVMgbWFnbml0dWRlIGFsb25nc2lkZSBGRFIsIG5vdCBzaWduaWZpY2FuY2UgYWxvbmU6IGEgbGFyZ2VyIE5FUyBtZWFucwogIHRoZSBwYXRod2F5J3MgZ2VuZXMgYXJlIG1vcmUgc3Ryb25nbHkgY29uY2VudHJhdGVkIHRvd2FyZCB0aGUgdG9wIG9mIHRoYXQKICBjbHVzdGVyJ3MgcG9zaXRpdmUtbWFya2VyIHJhbmtpbmcuCi0gVGhpcyBwb3NpdGl2ZS1tYXJrZXIgRkdTRUEgYXBwcm9hY2ggYW5kIHlvdXIgZXhpc3RpbmcgT1JBCiAgKGBlbnJpY2hHT2AvYGVucmljaEtFR0dgL2BlbnJpY2hQYXRod2F5YCkgYXBwcm9hY2ggYW5zd2VyIGNsb3NlbHkgcmVsYXRlZCBidXQKICBkaXN0aW5jdCBxdWVzdGlvbnMgYW5kIHNob3VsZCBiZSByZXBvcnRlZCBhcyBjb21wbGVtZW50YXJ5OiBPUkEgYXNrcyAiYXJlIG15CiAgdG9wLU4gbWFya2VyIGdlbmVzIG92ZXJyZXByZXNlbnRlZCBpbiBhIHBhdGh3YXk/IjsgRkdTRUEgYXNrcyAiaXMgYSBwYXRod2F5J3MKICBnZW5lIHNldCBza2V3ZWQgdG93YXJkIHRoZSB0b3Agb2YgbXkgZnVsbCBwb3NpdGl2ZS1tYXJrZXIgcmFua2luZyBmb3IgdGhpcwogIGNsdXN0ZXI/IiBBZ3JlZW1lbnQgYmV0d2VlbiB0aGUgdHdvIHByb3ZpZGVzIHN0cm9uZ2VyIHN1cHBvcnQgZm9yIGEgY2x1c3RlcgogIG5hbWUgdGhhbiBlaXRoZXIgbWV0aG9kIGFsb25lLgotIFRoZSBldmlkZW5jZSBjaGFpbiB0byByZXBvcnQgcGVyIGNsdXN0ZXI6IG1hcmtlciBnZW5lcyAoZnJvbQogIGBGaW5kQWxsTWFya2Vycyhvbmx5LnBvcz1UUlVFKWApIC0+IGRlZmluaW5nIHBhdGh3YXkgKGZyb20gRkdTRUEpIC0+IGxlYWRpbmctZWRnZQogIGdlbmVzIChjb25maXJtaW5nIHdoaWNoIG1hcmtlcnMgYWN0dWFsbHkgZHJpdmUgdGhlIHBhdGh3YXkgc2lnbmFsKS4gRXhhbXBsZToKICBDbHVzdGVyIDEzIG1hcmtlciBnZW5lcyBJRklUMi9PQVNML0lTRzE1IC0+IEhhbGxtYXJrCiAgSU5URVJGRVJPTl9BTFBIQV9SRVNQT05TRS9SZWFjdG9tZSBpbnRlcmZlcm9uIHNpZ25hbGluZyAtPiBhbm5vdGF0aW9uCiAgIklGTi1zdGltdWxhdGVkIG1hbGlnbmFudCBzdGF0ZSIuCgojIDE3LiBTZXNzaW9uIEluZm8KCmBgYHtyIHNlc3Npb24taW5mb30Kc2Vzc2lvbkluZm8oKQpgYGA=