From d20229b0bce2ce568f31164e87eefe354553378f Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:18:06 +0200 Subject: [PATCH 01/10] Add deterministic canonical-core block-jackknife cluster sensitivity --- shinyRam/R/atlas-robustness.R | 142 ++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 shinyRam/R/atlas-robustness.R diff --git a/shinyRam/R/atlas-robustness.R b/shinyRam/R/atlas-robustness.R new file mode 100644 index 0000000..db68ad5 --- /dev/null +++ b/shinyRam/R/atlas-robustness.R @@ -0,0 +1,142 @@ +# Sensitivity of exploratory Atlas clusters to canonical-core position choice. +# Deterministic contiguous-block deletion of the SAME common, sampled +# UniProt-position matrix. This is not a biological bootstrap and does not +# establish experimental independence or state probabilities. + +ram_atlas_cluster_robustness <- function(matrices, ids, positions, + baseline_labels, cluster_mode=c("automatic","manual"), cutoff=1.5, + blocks=10L) { + cluster_mode <- match.arg(cluster_mode) + ids <- as.character(ids) + n <- length(ids) + if(n<2L || n>12L || anyNA(ids) || any(!nzchar(ids)) || + anyDuplicated(ids) || !is.list(matrices) || length(matrices)!=n) + stop("Invalid verified experimental matrices for sensitivity analysis.", + call.=FALSE) + positions <- as.integer(positions) + count <- length(positions) + if(count<30L || anyNA(positions) || anyDuplicated(positions) || + is.unsorted(positions,strictly=TRUE)) + stop("Sensitivity analysis requires a sorted exact canonical core.", + call.=FALSE) + clean <- lapply(matrices,function(x) { + x <- as.matrix(x) + if(!is.numeric(x) || nrow(x)!=count || ncol(x)!=3L || + any(!is.finite(x))) + stop("Sensitivity matrices need complete, aligned C-alpha XYZ.", + call.=FALSE) + x + }) + baseline <- as.character(baseline_labels) + if(length(baseline)!=n || anyNA(baseline) || + any(!nzchar(baseline))) + stop("Sensitivity analysis requires one baseline group per entity.", + call.=FALSE) + if(!is.null(names(baseline_labels))) { + if(!setequal(names(baseline_labels),ids)) + stop("Baseline groups must name the same verified entities.", + call.=FALSE) + baseline <- as.character(baseline_labels[ids]) + } + names(baseline) <- ids + cutoff <- as.numeric(cutoff) + if(length(cutoff)!=1L || !is.finite(cutoff) || + cutoff<=0 || cutoff>10) + stop("Invalid manual grouping cutoff.",call.=FALSE) + blocks <- as.integer(blocks) + if(length(blocks)!=1L || is.na(blocks) || + blocks<2L || blocks>20L) + stop("Use between 2 and 20 position-deletion blocks.",call.=FALSE) + + group_ids <- unique(baseline) + group_members <- lapply(group_ids,function(g) ids[baseline==g]) + names(group_members) <- group_ids + pairs <- utils::combn(seq_len(n),2L) + same_group <- baseline[pairs[1L,]]==baseline[pairs[2L,]] + same_entry <- substr(ids[pairs[1L,]],1L,4L)== + substr(ids[pairs[2L,]],1L,4L) + shared_entry_total <- sum(same_entry) + + # With two entries an automatic split is intentionally forbidden, and + # leave-position-out agreement would be trivially reassuring. + if(n<3L) return(list(status="insufficient_structures", + reason="Two experimental entities cannot establish cluster robustness.", + iterations=0L,replicates=data.frame(),groups=data.frame(), + pairs=data.frame(),fraction_unchanged=NA_real_, + shared_pdb_pairs=shared_entry_total, + method="Contiguous-block jackknife on exact observed UniProt positions")) + + k <- min(blocks,count %/% 3L) + # cut() gives contiguous blocks of sorted, sampled UniProt positions, + # including when the observed canonical sequence contains gaps. + deleted_block <- cut(seq_len(count),breaks=k,labels=FALSE) + runs <- vector("list",k) + replay <- vector("list",k) + for(b in seq_len(k)) { + drop <- which(deleted_block==b) + keep <- which(deleted_block!=b) + pair_dist <- lapply(clean,function(coords) + as.numeric(stats::dist(coords[keep,,drop=FALSE]))) + D <- matrix(0,nrow=n,ncol=n,dimnames=list(ids,ids)) + for(i in seq_len(n-1L)) for(j in seq.int(i+1L,n)) { + value <- sqrt(mean((pair_dist[[i]]-pair_dist[[j]])^2)) + D[i,j] <- D[j,i] <- value + } + hc <- stats::hclust(stats::as.dist(D),method="average") + labels <- if(identical(cluster_mode,"automatic")) + ram_atlas_auto_clusters(D,hc)$assignment + else stats::cutree(hc,h=cutoff) + labels <- as.character(labels) + together <- labels[pairs[1L,]]==labels[pairs[2L,]] + replay[[b]] <- list(labels=labels,pairs=together) + runs[[b]] <- data.frame( + deleted_block=b,removed_start=positions[min(drop)], + removed_end=positions[max(drop)],removed_positions=length(drop), + remaining_positions=length(keep), + resulting_groups=length(unique(labels)), + baseline_partition_reproduced=all(together==same_group), + pair_agreement=mean(together==same_group), + stringsAsFactors=FALSE) + } + runs <- do.call(rbind,runs) + group_table <- do.call(rbind,lapply(seq_along(group_members),function(i) { + members <- group_members[[i]] + unchanged <- vapply(replay,function(run) { + # Compare exact member sets, never numeric cluster label values. + any(vapply(unique(run$labels),function(label) + setequal(ids[run$labels==label],members),logical(1L))) + },logical(1L)) + index <- which(ids %in% members) + group_pairs <- which(pairs[1L,] %in% index & + pairs[2L,] %in% index) + data.frame(geometry_group=names(group_members)[[i]], + size=length(members),members=paste(members,collapse=", "), + exact_group_recovered=sum(unchanged), + iterations=k,exact_group_fraction=mean(unchanged), + within_group_same_pdb_pairs=sum(same_entry[group_pairs]), + stringsAsFactors=FALSE) + })) + pair_table <- data.frame(entity_a=ids[pairs[1L,]], + entity_b=ids[pairs[2L,]], + baseline_same_group=as.logical(same_group), + leave_block_out_same_group=vapply(seq_len(ncol(pairs)),function(i) + sum(vapply(replay,function(run)run$pairs[[i]],logical(1L))), + integer(1L)), + iterations=k,same_group_fraction=vapply(seq_len(ncol(pairs)),function(i) + mean(vapply(replay,function(run)run$pairs[[i]],logical(1L))), + numeric(1L)), + same_pdb_entry=as.logical(same_entry),stringsAsFactors=FALSE) + list(status="ok",reason=sprintf(paste0( + "%d/%d contiguous-block omissions reproduced the full ", + "geometry partition."),sum(runs$baseline_partition_reproduced),k), + iterations=k,replicates=runs,groups=group_table,pairs=pair_table, + fraction_unchanged=mean(runs$baseline_partition_reproduced), + shared_pdb_pairs=shared_entry_total, + method=paste( + "Deterministic leave-one-contiguous-block-out of sorted sampled", + "exact UniProt C-alpha coordinates; recomputed distance-map RMSD,", + "average linkage and original automatic or manual grouping settings.", + "Partition comparison ignores arbitrary cluster label numbers.", + "This is sensitivity to sequence-position selection, not statistical", + "confidence, a biological state probability, or independent replication.")) +} From fe4a0fed5bf9bd6f49ce649645f828cb2aa4a6f2 Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:19:36 +0200 Subject: [PATCH 02/10] Attach canonical block-deletion sensitivity to Atlas groups --- shinyRam/R/atlas-geometry.R | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shinyRam/R/atlas-geometry.R b/shinyRam/R/atlas-geometry.R index 721b837..d8e3d49 100644 --- a/shinyRam/R/atlas-geometry.R +++ b/shinyRam/R/atlas-geometry.R @@ -230,12 +230,17 @@ ram_atlas_geometry_groups <- function(verified,accession,selected, for(j in seq.int(i+1L,n)) D[i,j] <- D[j,i] <- sqrt(mean((dists[[i]]-dists[[j]])^2)) hc <- stats::hclust(stats::as.dist(D),method="average") + requested_cutoff <- cutoff automatic <- if(identical(cluster_mode,"automatic")) ram_atlas_auto_clusters(D,hc) else NULL if(!is.null(automatic)) { labels <- stats::setNames(automatic$assignment,ids) cutoff <- automatic$cutoff } else labels <- stats::cutree(hc,h=cutoff) + robustness <- ram_atlas_cluster_robustness( + matrices=matrices,ids=ids,positions=sampled, + baseline_labels=stats::setNames(labels[ids],ids), + cluster_mode=cluster_mode,cutoff=requested_cutoff,blocks=10L) groups <- split(ids,labels) representatives <- vapply(groups,function(members) { idx <- match(members,ids) @@ -254,6 +259,7 @@ ram_atlas_geometry_groups <- function(verified,accession,selected, stringsAsFactors=FALSE), representatives=representatives,cutoff=cutoff, cluster_mode=cluster_mode,auto_cluster=automatic, + robustness=robustness, distance_method="C-alpha intrachain distance-map RMSD (Å), common canonical UniProt positions; average-linkage hierarchical clustering") } From aa437cdbc6bd3e7205559b2bc17ee5952b4c01c6 Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:20:04 +0200 Subject: [PATCH 03/10] Show Atlas cluster robustness and shared-deposition warnings with CSV exports --- shinyRam/app.R | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/shinyRam/app.R b/shinyRam/app.R index da10fb4..5a8cba3 100644 --- a/shinyRam/app.R +++ b/shinyRam/app.R @@ -32,6 +32,7 @@ source(file.path("R", "canonical.R"), local = TRUE) source(file.path("R", "atlas-sifts.R"), local = TRUE) source(file.path("R", "atlas-cohort.R"), local = TRUE) source(file.path("R", "atlas-geometry.R"), local = TRUE) +source(file.path("R", "atlas-robustness.R"), local = TRUE) source(file.path("R", "atlas-construct.R"), local = TRUE) source(file.path("R", "atlas-switch.R"), local = TRUE) source(file.path("R", "atlas.R"), local = TRUE) @@ -2265,6 +2266,7 @@ server <- function(input, output, session) { plotOutput("atlasGeometryPlot",height="260px"), tableOutput("atlasGeometryTable"), uiOutput("atlasAutoQuality"), + uiOutput("atlasRobustnessPanel"), uiOutput("atlasGroupsHandoffPanel")) }) @@ -2489,6 +2491,86 @@ server <- function(input, output, session) { ) }) + output$atlasRobustnessPanel <- renderUI({ + result <- atlas_geometry_result() + if(is.null(result) || !is.null(result$error) || + is.null(result$robustness)) return(NULL) + stability <- result$robustness + if(identical(stability$status,"insufficient_structures")) + return(tags$p(class="ram-field-hint", + "Cluster robustness: two experimental structures cannot provide a meaningful sensitivity estimate. You can still inspect their measured structural difference.")) + tags$details(class="ram-details ram-atlas-robustness", + tags$summary("Cluster robustness and shared-entry cautions"), + tags$p(class="ram-field-hint", + sprintf("%d of %d leave-block-out analyses reproduced the full geometric grouping.", + sum(stability$replicates$baseline_partition_reproduced), + stability$iterations)), + tags$p(class="ram-field-hint", + paste("Contiguous blocks of sampled canonical UniProt positions are", + "omitted one at a time. Atlas recalculates C-alpha distance maps", + "and reruns the same automatic or manual clustering.", + "A stable partition shows robustness to this particular position", + "selection, not statistical confidence or biological states.")), + if(stability$shared_pdb_pairs>0L) + tags$p(class="ram-confidence-warning", + sprintf(paste("%d selected entity pair(s) originate in the same", + "PDB entry. Polymer chains from a single deposition cannot be", + "treated as independent experimental replicates."), + stability$shared_pdb_pairs)), + tags$h5("Exact group recovery"), + tableOutput("atlasRobustnessGroups"), + tags$h5("Pairwise grouping under position deletion"), + DT::DTOutput("atlasRobustnessPairs"), + tags$div(class="ram-ensemble-actions", + downloadButton("downloadAtlasRobustnessGroups", + "Export group robustness CSV"), + downloadButton("downloadAtlasRobustnessPairs", + "Export pair robustness CSV"), + downloadButton("downloadAtlasRobustnessRuns", + "Export omitted-block runs CSV")), + tags$p(class="ram-field-hint", + "Group recovery requires the exact same member set, regardless of numeric group labels. Pairwise co-assignment is the fraction of block omissions keeping two entries together. It is not a transition probability.") + ) + }) + output$atlasRobustnessGroups <- renderTable({ + result <- req(atlas_geometry_result()) + req(is.null(result$error),identical(result$robustness$status,"ok")) + shown <- result$robustness$groups + shown$exact_group_fraction <- sprintf("%.0f%%", + 100*shown$exact_group_fraction) + names(shown) <- c("Group","Size","PDB entities","Recovered", + "Runs","Recovery","Same-PDB pairs") + shown + },striped=TRUE,spacing="xs",rownames=FALSE) + output$atlasRobustnessPairs <- DT::renderDT({ + result <- req(atlas_geometry_result()) + req(is.null(result$error),identical(result$robustness$status,"ok")) + shown <- result$robustness$pairs + shown$same_group_fraction <- round(100*shown$same_group_fraction,1L) + shown$baseline_same_group <- ifelse(shown$baseline_same_group,"Yes","No") + shown$same_pdb_entry <- ifelse(shown$same_pdb_entry,"Yes","No") + DT::datatable(shown,rownames=FALSE, + colnames=c("Entry A","Entry B","Same baseline group", + "Together after omissions","Runs","Together (%)","Same PDB"), + options=list(pageLength=8,scrollX=TRUE,dom="ftip"), + class="compact stripe") + },server=FALSE) + output$downloadAtlasRobustnessGroups <- downloadHandler( + filename=function() "ramplotr_atlas_cluster_group_robustness.csv", + content=function(file) utils::write.csv( + req(atlas_geometry_result())$robustness$groups, + file,row.names=FALSE,na="")) + output$downloadAtlasRobustnessPairs <- downloadHandler( + filename=function() "ramplotr_atlas_cluster_pair_robustness.csv", + content=function(file) utils::write.csv( + req(atlas_geometry_result())$robustness$pairs, + file,row.names=FALSE,na="")) + output$downloadAtlasRobustnessRuns <- downloadHandler( + filename=function() "ramplotr_atlas_cluster_position_omissions.csv", + content=function(file) utils::write.csv( + req(atlas_geometry_result())$robustness$replicates, + file,row.names=FALSE,na="")) + output$atlasGeometryPlot <- renderPlot({ result <- atlas_geometry_result() req(!is.null(result),is.null(result$error)) From 042c82f3dac42e442ea9a77ed05882b9ee0b5e2f Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:20:32 +0200 Subject: [PATCH 04/10] Cover manual, automatic, no-split and shared-PDB cluster robustness cases --- tests/atlas-geometry.R | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/atlas-geometry.R b/tests/atlas-geometry.R index e2c00a4..0559dc2 100644 --- a/tests/atlas-geometry.R +++ b/tests/atlas-geometry.R @@ -1,6 +1,7 @@ source(file.path("shinyRam","R","canonical.R")) source(file.path("shinyRam","R","atlas-sifts.R")) source(file.path("shinyRam","R","atlas-geometry.R")) +source(file.path("shinyRam","R","atlas-robustness.R")) assert <- function(x,message) if(!isTRUE(x)) stop(message,call.=FALSE) positions <- 1:40 @@ -41,10 +42,26 @@ assert(groups$assignment$geometric_group[1] == groups$assignment$geometric_group[1] != groups$assignment$geometric_group[3], "Exploratory clustering must separate shifted internal geometries.") +assert(identical(groups$robustness$status,"ok") && + groups$robustness$iterations==10L && + nrow(groups$robustness$pairs)==3L && + nrow(groups$robustness$groups)==2L && + nrow(groups$robustness$replicates)==10L, + "Manual clustering should include exact-core position sensitivity.") +assert(identical(groups$robustness$replicates, + ram_atlas_geometry_groups(verified,"P12345",names(verified), + min_core=30,min_fraction=.6,cutoff=1.5)$robustness$replicates), + "Repeated manual grouping must give identical position-deletion results.") +assert(!any(groups$robustness$pairs$same_pdb_entry), + "Different PDB entries must not be labelled as shared experiments.") assert(length(groups$representatives)==2L, "Each geometric group should have one observed representative.") auto <- ram_atlas_geometry_groups(verified,"P12345",names(verified), min_core=30,min_fraction=.6,cluster_mode="automatic") +assert(identical(auto$robustness$status,"ok") && + all(auto$robustness$replicates$pair_agreement>=0 & + auto$robustness$replicates$pair_agreement<=1), + "Automatic clustering must report bounded position sensitivity.") assert(identical(auto$cluster_mode,"automatic") && auto$auto_cluster$k==2L && length(auto$representatives)==2L, @@ -74,6 +91,9 @@ assert(close_split$k==1L, "Small numerical coordinate differences cannot force biological-looking groups.") two_auto <- ram_atlas_geometry_groups(verified,"P12345", c("1ABC_1","3ABC_1"),cluster_mode="automatic") +assert(identical(two_auto$robustness$status,"insufficient_structures") && + two_auto$robustness$iterations==0L, + "Two entries must never receive a misleading cluster stability score.") assert(two_auto$auto_cluster$k==1L && grepl("Fewer than three",two_auto$auto_cluster$reason), "Two PDBs cannot provide within-cluster support for an automatic two-group recommendation.") @@ -133,4 +153,19 @@ grDevices::dev.off() assert(file.exists(pdf_path) && file.info(pdf_path)$size>100L, "Geometry charts must render for pairs and multi-entity cohorts.") unlink(pdf_path) +# Two entities in the same PDB deposition cannot count as independent +# experimental replicates even when their coordinates are different. +within_deposit <- verified +names(within_deposit) <- c("1ABC_1","1ABC_2","3ABC_1") +with_pdb_warning <- ram_atlas_geometry_groups(within_deposit,"P12345", + names(within_deposit),cluster_mode="automatic") +assert(with_pdb_warning$robustness$shared_pdb_pairs==1L && + sum(with_pdb_warning$robustness$pairs$same_pdb_entry)==1L, + "Same-deposition entities must be flagged regardless of geometry.") +# Malformed canonical input is rejected instead of guessed or recycled. +fake <- lapply(seq_along(verified),function(i) matrix(1,40,3)) +assert(inherits(try(ram_atlas_cluster_robustness(fake, + names(verified),rev(positions),c(1L,1L,2L)), + silent=TRUE),"try-error"), + "Jackknife must reject unsorted canonical positions.") message("Experimental Atlas distance-map grouping tests passed.") From 6256ee4795cac28b67fe3d4343949e25b93ca81b Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:20:47 +0200 Subject: [PATCH 05/10] Test explicit two-structure robustness limitation in live Atlas browser --- tests/atlas-browser.cjs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/atlas-browser.cjs b/tests/atlas-browser.cjs index 3c09ca0..2eee04c 100644 --- a/tests/atlas-browser.cjs +++ b/tests/atlas-browser.cjs @@ -269,6 +269,11 @@ ${atoms} "2 exploratory geometry group(s)") && panel.textContent.includes("40 common observed UniProt"); },{timeout:25000}); + await page.waitForFunction(() => { + const notice = document.getElementById("atlasRobustnessPanel"); + return notice && notice.textContent.includes( + "two experimental structures cannot provide a meaningful sensitivity estimate"); + }, {timeout:12000}); const geometryTable=await page.$eval("#atlasGeometryTable", el=>el.textContent); assert.match(geometryTable,/1CRN_1/); From aa9500c34b82e0e4ee7cf1e49b42d36b202b3348 Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:21:50 +0200 Subject: [PATCH 06/10] Document Atlas cluster sensitivity method and scientific limitations --- docs/atlas-cluster-robustness.md | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 docs/atlas-cluster-robustness.md diff --git a/docs/atlas-cluster-robustness.md b/docs/atlas-cluster-robustness.md new file mode 100644 index 0000000..e3b87bb --- /dev/null +++ b/docs/atlas-cluster-robustness.md @@ -0,0 +1,74 @@ +# Experimental Atlas cluster sensitivity + +Atlas groups experimental structures using exact PDBe SIFTS/UniProt-mapped +first-model C-alpha coordinates. A suggested geometric grouping can change if +part of the shared sequence is excluded. This check reports how sensitive an +existing grouping is to **which backbone positions are used**. + +## How it works + +For a selected cohort of at least three structures, Atlas: + +1. Starts with the same common UniProt core used in the main distance-map + RMSD and average-linkage dendrogram. Large cores are already subsampled + deterministically to at most 300 ordered positions. +2. Divides the ordered sampled positions into ten consecutive blocks. When + there are gaps in observed residues, a block follows the sampled sequence + order, not an invented continuous sequence interval. +3. Omits each block once. For each omission, recalculates the entire cohort + distance matrix from internal C-alpha pair distances on the remaining + common positions. +4. Reruns the selected **automatic cluster suggestion** or the exact + user-selected **manual distance cutoff**. In automatic mode, the candidate + group count can change across omissions. +5. Compares partitions by their member pairs. Arbitrary numeric group names + are ignored. + +There is no random seed and no new sequence alignment or network download. +Repeating the same cohort with identical parameters reproduces the same +sensitivity table. + +## Reading the results + +- **Whole-partition recovery**: number of ten omissions that reproduce every + original same/different-group relationship. +- **Exact group recovery**: number of omissions where a particular original + group reappears with exactly the same members, independent of its label. +- **Pair co-assignment**: number/fraction of omissions where a specific pair + of structures is grouped together. It can be high for pairs originally + separated or low for originally co-grouped pairs. +- **Same PDB entry**: flags structures whose entity identifiers share the + same four-character PDB deposition ID. These are not independent + experiments just because they are separate chains or polymer entities. +- **Omitted-block runs**: start/end UniProt positions, omitted/retained count, + resulting group count and agreement with the original partition. + +All three tables are downloadable as CSV. The grouping itself and the cluster +representatives are unchanged by this diagnostic. + +## Limitations + +This is **not** a bootstrap confidence interval, sampling probability, +likelihood of an experimental state, or evidence of functional-state identity. +Adjacent canonical positions and multiple polymer entities from the same +deposition are not independent biological replicates. The check cannot test +unobserved regions, missing conformational states, ligand annotations, +experimental method biases or construct equivalence. A stable single-cluster +result may simply reflect conservative automatic cut safeguards. For a cohort +of two structures, the sensitivity score is deliberately unavailable. + +Construct and monomer-chemistry compatibility is separately assessed before +clustering and still requires explicit acknowledgement when chemistry differs. +Do not apply arbitrary robustness thresholds to declare functional states. + +## Implementation and testing + +- Algorithm: `shinyRam/R/atlas-robustness.R` +- Integration: `ram_atlas_geometry_groups()` with its current verified + canonical-core matrices; both manual and automatic modes are supported. +- Deterministic, shared-deposition, no-split and invalid-input controls: + `tests/atlas-geometry.R` +- Browser notice for a two-entry cohort: `tests/atlas-browser.cjs` + +The current algorithm is intended for interactive cohorts of 2–12 verified +polymer entities, not a large archive-wide bootstrap study. From a8f262c0d81e1400bddd30885ba84dc3eae88c54 Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:21:53 +0200 Subject: [PATCH 07/10] Explain Atlas cluster sensitivity in guide and worklog --- shinyRam/R/guide.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shinyRam/R/guide.R b/shinyRam/R/guide.R index 9ec69a7..c095e36 100644 --- a/shinyRam/R/guide.R +++ b/shinyRam/R/guide.R @@ -158,10 +158,12 @@ ram_guide_ui <- function() { "For the selected structures, inspect pairwise observed UniProt core coverage and the monomer identities encoded in the mmCIF polymer scheme. Missing or unmatched positions do not establish equivalence. Any confirmed residue-chemistry differences require your explicit acknowledgement before exploratory grouping; export the review CSV for provenance."), step(6, "Let Atlas suggest geometric clusters", "Select at least two exact-verified experimental entities, review construct chemistry and choose Cluster experimental structures. Automatic mode searches for coherent groupings using Cα distance-map separation and mean silhouette, and can recommend no split when evidence is weak. Its numeric checks are exploratory, not calibrated functional-state labels."), + step(7, "Check cluster robustness", + "Expand Cluster robustness and shared-entry cautions to see how often the original groups survive omission of contiguous blocks of canonical backbone positions. Download the group, pair and omitted-block CSVs. This measures sensitivity to residue selection only, not functional-state confidence. Entries from one PDB deposition are not independent experiments."), step(7, "Review or adjust cluster membership", "Examine the dendrogram, group sizes and the Why did Atlas suggest these groups? quality details. You can switch to a manual distance cutoff or edit the suggested Group A/Group B memberships. Use these entries in Compare Groups passes verified experimental backbone torsions directly without file uploads."), step(8, "Investigate local differences", - "Choose two geometric-group representatives, then select Find local backbone changes. Click a canonical residue to see paired φ/ψ and exact PDB residue identifiers."), + "Choose any two verified experimental structures, then select Find local backbone changes. Click a canonical residue to see paired φ/ψ and exact PDB residue identifiers."), step(9, "Inspect both representative structures in 2D/3D", "Click Inspect both structures in 2D/3D. Compare now uses shared, observed UniProt positions from the exact verified SIFTS mappings, even when author numbering or constructs differ. Unmapped residues are excluded and the compared coverage is shown. A missing or ambiguous mapping is never replaced by a guessed match.") ), From ffe0c57e734cd2213b6a7fbff22b55abba3b568b Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:21:57 +0200 Subject: [PATCH 08/10] Explain Atlas cluster sensitivity in guide and worklog --- docs/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README.md b/docs/README.md index 8b730b2..b154466 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,3 +16,5 @@ Choose a guide by the research task you want to perform. The [main README](../RE ## Development records [Implementation and verification history](development/README.md) is kept separately from the user guides. Its historical notes describe what changed during development, not different current application modes. + +- [Atlas cluster sensitivity](atlas-cluster-robustness.md): deterministic position-deletion checks, shared-PDB warnings, CSV evidence and interpretation limits. From aeb7400139cef6c17890bf681e0e1070302e9417 Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:22:01 +0200 Subject: [PATCH 09/10] Explain Atlas cluster sensitivity in guide and worklog --- docs/development-worklog.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/development-worklog.md b/docs/development-worklog.md index 13b24c1..ddf6a31 100644 --- a/docs/development-worklog.md +++ b/docs/development-worklog.md @@ -367,3 +367,23 @@ branch, and do not reuse historical feature branches. mismatched missing-angle patterns and complete absence of paired residues. - This is a scientific correctness fix, not a classifier or new validation threshold. CI runs only when the pull request becomes ready for review. + +## October 9: sensitivity of experimental Atlas cluster suggestions + +- Branch `feature/atlas-cluster-jackknife` originates from merged + `main` at `519c9257063ca33a0f1e391e8f84af4a79af3805`; + the `arxiv-preprint` PR #20 is not modified. +- Uses deterministic leave-one-contiguous-block-out sensitivity of the same + exact-mapped, sampled canonical C-alpha coordinate matrices as the Atlas + dendrogram. Recomputes structural distances and automatic/manual + average-linkage grouping with unchanged settings. +- Reports whole-partition reproduction, exact membership recovery, pairwise + co-assignment, the exact omitted residue spans and duplicated PDB entry IDs. + Two-structure cohorts receive no potentially misleading stability score. +- The diagnostic is not a bootstrap confidence measure, biological-state + assignment or measure of structural sampling probability. The manual group + cutoff, construct-chemistry audit and researcher-defined Group A/B remain + unchanged. +- Added collapsed Atlas UI, three CSV exports, help text, scientific tests, + and browser smoke coverage. CI runs when a new PR becomes ready, not on + every commit. Keep this feature on its own PR until validation is green. From 584efa810a24cfa21b0c981d500916f5d348c789 Mon Sep 17 00:00:00 2001 From: spomlol <77454330+CedricHermansBIT@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:22:36 +0200 Subject: [PATCH 10/10] Detect fragile geometry splits driven by one local canonical segment --- tests/atlas-geometry.R | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/atlas-geometry.R b/tests/atlas-geometry.R index 0559dc2..c8cd34b 100644 --- a/tests/atlas-geometry.R +++ b/tests/atlas-geometry.R @@ -162,6 +162,21 @@ with_pdb_warning <- ram_atlas_geometry_groups(within_deposit,"P12345", assert(with_pdb_warning$robustness$shared_pdb_pairs==1L && sum(with_pdb_warning$robustness$pairs$same_pdb_entry)==1L, "Same-deposition entities must be flagged regardless of geometry.") +# A short, geometry-driving segment should reduce robustness when omitted. +# The first two structures are rigid-motion equivalents, while the third has +# a pronounced rearrangement restricted to one deletion block. +localized <- coords +localized[17:20,2] <- localized[17:20,2]+12 +localized_set <- list( + "1ABC_1"=fixture("1ABC",coords), + "2XYZ_1"=fixture("2XYZ",rotated), + "3ABC_1"=fixture("3ABC",localized)) +sensitive <- ram_atlas_geometry_groups(localized_set,"P12345", + names(localized_set),cutoff=1.5) +assert(sensitive$distance_matrix[1L,3L]>1.5 && + length(unique(sensitive$assignment$geometric_group))==2L && + any(!sensitive$robustness$replicates$baseline_partition_reproduced), + "A localized group-defining segment must be detected as position-sensitive.") # Malformed canonical input is rejected instead of guessed or recycled. fake <- lapply(seq_along(verified),function(i) matrix(1,40,3)) assert(inherits(try(ram_atlas_cluster_robustness(fake,