From 880d138be06e2df199e17da7dd6c39596a0b1dff Mon Sep 17 00:00:00 2001 From: A Wokaty Date: Tue, 28 Apr 2026 09:03:57 -0400 Subject: [PATCH 1/5] bump x.y.z version to even y prior to creation of RELEASE_3_23 branch --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 91ecd43..a235ddf 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: immApex Title: Tools for Adaptive Immune Receptor Sequence-Based Machine and Deep Learning -Version: 1.5.4 +Version: 1.6.0 Authors@R: c( person(given = "Nick", family = "Borcherding", role = c("aut", "cre", "cph"), email = "ncborch@gmail.com"), person(given = "Qile", family = "Yang", role = "ctb", email = "qile.yang@berkeley.edu", comment = c(ORCID = "0009-0005-0148-2499"))) From 92dbbf33ac718a4665523e5ddc144d14b0871f2e Mon Sep 17 00:00:00 2001 From: A Wokaty Date: Tue, 28 Apr 2026 09:03:57 -0400 Subject: [PATCH 2/5] bump x.y.z version to odd y following creation of RELEASE_3_23 branch --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index a235ddf..f6c3f95 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: immApex Title: Tools for Adaptive Immune Receptor Sequence-Based Machine and Deep Learning -Version: 1.6.0 +Version: 1.7.0 Authors@R: c( person(given = "Nick", family = "Borcherding", role = c("aut", "cre", "cph"), email = "ncborch@gmail.com"), person(given = "Qile", family = "Yang", role = "ctb", email = "qile.yang@berkeley.edu", comment = c(ORCID = "0009-0005-0148-2499"))) From 471a00466030e39eb3524045100d8bad97cb2b08 Mon Sep 17 00:00:00 2001 From: theHumanBorch Date: Sat, 27 Jun 2026 12:11:17 -0500 Subject: [PATCH 3/5] Speed up buildNetwork() Rewrite the C++ core (fastEditEdges.cpp) around candidate generation instead of all-pairs comparison. No new dependencies, and the default (clique) output is identical to the previous engine. - Deduplicate to unique (sequence, V, J) representatives so clonal expansion no longer drives quadratic distance computation. - Use length and V/J blocking as an index. Only length-compatible representatives in the same block are compared. - Add SymSpell deletion-neighborhood hashing for Levenshtein (radius k) and Damerau (radius 2k, since a transposition costs 2 in Levenshtein), and pigeonhole segment indexing for Hamming. Both fall back to length-blocked comparison outside their range. - Return integer node indices from the engine and attach labels in R, avoiding millions of barcode strings inside the parallel core. - Add an `expand` argument to buildNetwork(): "clique" (default) keeps exact edge multiplicity, "star" preserves connected components with far fewer edges for a large memory win. Fix a pre-existing banded Levenshtein error that dropped pairs whose only optimal alignment rides the band edge, including identical sequences at a tight normalized threshold. The boundary cell read the wrong diagonal value. The fix only recovers true edges. Also make the normalized-threshold cutoff robust to floating-point rounding so a normalized distance exactly equal to the threshold is kept. Add a set-equality regression harness plus star, Damerau, and Levenshtein-vs-adist correctness tests. Bump version to 1.7.1. --- DESCRIPTION | 2 +- NEWS.md | 19 + R/RcppExports.R | 4 +- R/buildNetwork.R | 57 ++- data-raw/make-buildNetwork-golden.R | 14 + man/buildNetwork.Rd | 12 +- src/RcppExports.cpp | 8 +- src/fastEditEdges.cpp | 452 ++++++++++++++---- .../testthat/fixtures/buildNetwork_golden.rds | Bin 0 -> 35057 bytes .../testthat/helper-buildNetwork-regression.R | 191 ++++++++ tests/testthat/test-buildNetwork-damerau.R | 21 + .../test-buildNetwork-levenshtein-exact.R | 46 ++ tests/testthat/test-buildNetwork-regression.R | 36 ++ tests/testthat/test-buildNetwork-star.R | 52 ++ 14 files changed, 794 insertions(+), 120 deletions(-) create mode 100644 data-raw/make-buildNetwork-golden.R create mode 100644 tests/testthat/fixtures/buildNetwork_golden.rds create mode 100644 tests/testthat/helper-buildNetwork-regression.R create mode 100644 tests/testthat/test-buildNetwork-damerau.R create mode 100644 tests/testthat/test-buildNetwork-levenshtein-exact.R create mode 100644 tests/testthat/test-buildNetwork-regression.R create mode 100644 tests/testthat/test-buildNetwork-star.R diff --git a/DESCRIPTION b/DESCRIPTION index f6c3f95..7b85de4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: immApex Title: Tools for Adaptive Immune Receptor Sequence-Based Machine and Deep Learning -Version: 1.7.0 +Version: 1.7.1 Authors@R: c( person(given = "Nick", family = "Borcherding", role = c("aut", "cre", "cph"), email = "ncborch@gmail.com"), person(given = "Qile", family = "Yang", role = "ctb", email = "qile.yang@berkeley.edu", comment = c(ORCID = "0009-0005-0148-2499"))) diff --git a/NEWS.md b/NEWS.md index 0f35831..a631ee0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,22 @@ +# immApex VERSION 1.7.1 + +## PERFORMANCE +`buildNetwork()` now scales to large, clonally expanded repertoires. The C++ engine was rewritten around candidate generation instead of all-pairs comparison, with no new dependencies and identical edge output for the default settings: + +* Identical sequences collapse to one representative before any distance is computed, so clonal expansion no longer drives quadratic work. +* Length and V/J blocking act as an index. Only length-compatible representatives in the same block are compared. +* Levenshtein and Damerau use deletion-neighborhood (SymSpell) hashing for small edit distances, with Damerau indexed at radius `2k`. Hamming uses pigeonhole segment indexing. Both fall back to length-blocked comparison outside their range. +* Edges accumulate as integer node indices and gain their labels in R, which avoids building millions of barcode strings inside the parallel core. + +Speedups range from roughly 2x on mixed repertoires with many V genes to about 70x when one block holds many unique sequences at a tight threshold. + +## NEW FEATURES +* Added the `expand` argument to `buildNetwork()`. `"clique"` (default) materializes every pairwise edge and reproduces the exact edge multiplicity that community-detection clustering expects. `"star"` links identical sequences through a single hub and connects related groups hub to hub. `"star"` produces far fewer edges and preserves connected components exactly, so it is a large memory win when the downstream step depends on connectivity. + +## BUG FIXES +* Fixed a banded Levenshtein error that dropped pairs whose only optimal alignment rides the band edge, including identical sequences at a tight normalized threshold. The boundary cell read the wrong diagonal value. The fix only recovers true edges and never removes valid ones. +* Made the normalized-threshold cutoff robust to floating-point rounding so a normalized distance exactly equal to the threshold is kept, matching the documented inclusive behavior. + # immApex VERSION 1.5.4 ## BUG FIXES diff --git a/R/RcppExports.R b/R/RcppExports.R index 7188a6c..77d0d71 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -9,7 +9,7 @@ encodeSequences_cpp <- function(sequences, mode = "onehot", alphabet = as.charac .Call(`_immApex_encodeSequences_cpp`, sequences, mode, alphabet, prop_mat_, pad_token, summary, max_len, nthreads) } -fast_edge_list <- function(seqs, thresh = 1.0, v_gene = NULL, j_gene = NULL, match_v = FALSE, match_j = FALSE, ids = NULL, metric = "levenshtein", normalize = "none", subst_matrix = NULL, gap_open = -10L, gap_extend = -1L) { - .Call(`_immApex_fast_edge_list`, seqs, thresh, v_gene, j_gene, match_v, match_j, ids, metric, normalize, subst_matrix, gap_open, gap_extend) +fast_edge_list <- function(seqs, thresh = 1.0, v_gene = NULL, j_gene = NULL, match_v = FALSE, match_j = FALSE, metric = "levenshtein", normalize = "none", subst_matrix = NULL, gap_open = -10L, gap_extend = -1L, expand = "clique") { + .Call(`_immApex_fast_edge_list`, seqs, thresh, v_gene, j_gene, match_v, match_j, metric, normalize, subst_matrix, gap_open, gap_extend, expand) } diff --git a/R/buildNetwork.R b/R/buildNetwork.R index 09d6bdb..69aba55 100644 --- a/R/buildNetwork.R +++ b/R/buildNetwork.R @@ -21,6 +21,14 @@ #' `data.frame` **or** a symmetric `Matrix::dgCMatrix` adjacency matrix. #' @param weight `"dist"` (store the edit distance) **or** `"binary"` #' (all edges get weight 1). Ignored when `output = "edges"`. +#' @param expand How identical sequences (and groups of related sequences) are +#' expanded into edges. `"clique"` (default) materializes every pairwise edge, +#' reproducing the exact edge multiplicity expected by community-detection +#' clustering. `"star"` links each set of identical sequences through a single +#' hub and connects related groups hub-to-hub. `"star"` produces far fewer edges +#' (a large memory win on clonally expanded repertoires) and preserves connected +#' components exactly, but changes edge multiplicity, so use it only when the +#' downstream step depends on connectivity rather than weighted degree. #' @param dist_type Character string specifying the distance metric to use: #' \itemize{ #' \item{`"levenshtein"`} - Standard edit distance (default, backward compatible) @@ -114,11 +122,13 @@ buildNetwork <- function(input.data = NULL, filter.j = FALSE, ids = NULL, output = c("edges", "sparse"), - weight = c("dist", "binary")) { - + weight = c("dist", "binary"), + expand = c("clique", "star")) { + output <- match.arg(output) weight <- match.arg(weight) normalize <- match.arg(normalize) # Validate input + expand <- match.arg(expand) ## 1. Decide where sequences come from if (is.null(input.data)) { @@ -192,35 +202,42 @@ buildNetwork <- function(input.data = NULL, numeric_mat <- .fetch.matrix(dist_mat) } - ## 5. Call the C++ engine - edge_df <- fast_edge_list( + # Node labels: explicit `ids`, else row-names / positional (matches the + # former C++ default of "1".."n" for the bare-sequence path). + if (is.null(ids)) ids <- as.character(seq_len(n)) + + ## 5. Call the C++ engine + raw <- fast_edge_list( seqs = seq_vec, thresh = threshold, v_gene = v_vec, j_gene = j_vec, match_v = filter.v, match_j = filter.j, - ids = ids, metric = dist_type, subst_matrix = numeric_mat, gap_open = gap_open, gap_extend = gap_extend, - normalize = normalize + normalize = normalize, + expand = expand ) - - # Remove Duplicate Edges - if (nrow(edge_df) > 0) { - edge_key <- paste( - pmin(edge_df$from, edge_df$to), - pmax(edge_df$from, edge_df$to), - sep = "\t" - ) - - # Check if there are duplicates - if (anyDuplicated(edge_key)) { - agg_idx <- !duplicated(edge_key) - edge_df <- edge_df[agg_idx, , drop = FALSE] - } + + # The engine returns 0-based node indices; attach the labels here so the + # millions of barcode strings are never materialized inside the parallel core. + edge_df <- data.frame( + from = ids[raw$i + 1L], + to = ids[raw$k + 1L], + dist = raw$dist, + stringsAsFactors = FALSE + ) + + # The engine emits each unordered node pair exactly once, so duplicate edges + # can only appear when non-unique `ids` collapse distinct nodes onto the same + # label. Skip the (otherwise wasteful) key build unless that is the case. + if (nrow(edge_df) > 0 && anyDuplicated(ids)) { + edge_key <- paste(pmin(edge_df$from, edge_df$to), + pmax(edge_df$from, edge_df$to), sep = "\t") + edge_df <- edge_df[!duplicated(edge_key), , drop = FALSE] } if (output == "edges") diff --git a/data-raw/make-buildNetwork-golden.R b/data-raw/make-buildNetwork-golden.R new file mode 100644 index 0000000..89ea189 --- /dev/null +++ b/data-raw/make-buildNetwork-golden.R @@ -0,0 +1,14 @@ +# Regenerate the buildNetwork regression golden snapshot. +# Run deliberately (NOT as part of the rewrite) only when an intended change to +# buildNetwork's output set is being accepted. From the package root: +# Rscript data-raw/make-buildNetwork-golden.R +suppressMessages(devtools::load_all(".", quiet = TRUE)) +source("tests/testthat/helper-buildNetwork-regression.R") +sc <- .bn_scenarios() +extra <- if ("expand" %in% names(formals(buildNetwork))) list(expand = "clique") else list() +golden <- lapply(sc$grid, function(scen) { + set <- suppressMessages(.bn_run(sc$datasets, scen, extra)) + list(data = scen$data, args = scen$args, set = set) +}) +saveRDS(golden, "tests/testthat/fixtures/buildNetwork_golden.rds") +cat("Wrote", length(golden), "scenarios.\n") diff --git a/man/buildNetwork.Rd b/man/buildNetwork.Rd index 1e045cd..3b42ca0 100644 --- a/man/buildNetwork.Rd +++ b/man/buildNetwork.Rd @@ -20,7 +20,8 @@ buildNetwork( filter.j = FALSE, ids = NULL, output = c("edges", "sparse"), - weight = c("dist", "binary") + weight = c("dist", "binary"), + expand = c("clique", "star") ) } \arguments{ @@ -86,6 +87,15 @@ Only used when `metric` is "nw" or "sw".} \item{weight}{`"dist"` (store the edit distance) **or** `"binary"` (all edges get weight 1). Ignored when `output = "edges"`.} + +\item{expand}{How identical sequences (and groups of related sequences) are +expanded into edges. `"clique"` (default) materializes every pairwise edge, +reproducing the exact edge multiplicity expected by community-detection +clustering. `"star"` links each set of identical sequences through a single +hub and connects related groups hub-to-hub. `"star"` produces far fewer edges +(a large memory win on clonally expanded repertoires) and preserves connected +components exactly, but changes edge multiplicity, so use it only when the +downstream step depends on connectivity rather than weighted degree.} } \value{ edge-list `data.frame` **or** sparse adjacency `dgCMatrix` of distances diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 5d2318d..d0412e3 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -44,8 +44,8 @@ BEGIN_RCPP END_RCPP } // fast_edge_list -DataFrame fast_edge_list(CharacterVector seqs, double thresh, Nullable v_gene, Nullable j_gene, bool match_v, bool match_j, Nullable ids, std::string metric, std::string normalize, Nullable subst_matrix, int gap_open, int gap_extend); -RcppExport SEXP _immApex_fast_edge_list(SEXP seqsSEXP, SEXP threshSEXP, SEXP v_geneSEXP, SEXP j_geneSEXP, SEXP match_vSEXP, SEXP match_jSEXP, SEXP idsSEXP, SEXP metricSEXP, SEXP normalizeSEXP, SEXP subst_matrixSEXP, SEXP gap_openSEXP, SEXP gap_extendSEXP) { +DataFrame fast_edge_list(CharacterVector seqs, double thresh, Nullable v_gene, Nullable j_gene, bool match_v, bool match_j, std::string metric, std::string normalize, Nullable subst_matrix, int gap_open, int gap_extend, std::string expand); +RcppExport SEXP _immApex_fast_edge_list(SEXP seqsSEXP, SEXP threshSEXP, SEXP v_geneSEXP, SEXP j_geneSEXP, SEXP match_vSEXP, SEXP match_jSEXP, SEXP metricSEXP, SEXP normalizeSEXP, SEXP subst_matrixSEXP, SEXP gap_openSEXP, SEXP gap_extendSEXP, SEXP expandSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; @@ -55,13 +55,13 @@ BEGIN_RCPP Rcpp::traits::input_parameter< Nullable >::type j_gene(j_geneSEXP); Rcpp::traits::input_parameter< bool >::type match_v(match_vSEXP); Rcpp::traits::input_parameter< bool >::type match_j(match_jSEXP); - Rcpp::traits::input_parameter< Nullable >::type ids(idsSEXP); Rcpp::traits::input_parameter< std::string >::type metric(metricSEXP); Rcpp::traits::input_parameter< std::string >::type normalize(normalizeSEXP); Rcpp::traits::input_parameter< Nullable >::type subst_matrix(subst_matrixSEXP); Rcpp::traits::input_parameter< int >::type gap_open(gap_openSEXP); Rcpp::traits::input_parameter< int >::type gap_extend(gap_extendSEXP); - rcpp_result_gen = Rcpp::wrap(fast_edge_list(seqs, thresh, v_gene, j_gene, match_v, match_j, ids, metric, normalize, subst_matrix, gap_open, gap_extend)); + Rcpp::traits::input_parameter< std::string >::type expand(expandSEXP); + rcpp_result_gen = Rcpp::wrap(fast_edge_list(seqs, thresh, v_gene, j_gene, match_v, match_j, metric, normalize, subst_matrix, gap_open, gap_extend, expand)); return rcpp_result_gen; END_RCPP } diff --git a/src/fastEditEdges.cpp b/src/fastEditEdges.cpp index c856b25..d4d450c 100644 --- a/src/fastEditEdges.cpp +++ b/src/fastEditEdges.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #ifdef _OPENMP #include @@ -11,6 +13,49 @@ using namespace Rcpp; +// Lift floating-point products back over an integer they should have reached: +// thresh * norm_len can land at e.g. 2.9999999996 when the true value is 3, and +// a bare (int) cast would drop a pair whose normalized distance exactly equals +// the threshold (the inclusive `fd <= thresh` boundary). The epsilon dwarfs the +// FP rounding error (~norm_len * 2e-16) while never reaching the next integer. +static const double THRESH_EPS = 1e-9; + +// ============================================================================ +// Deletion-neighborhood (SymSpell) helpers +// ============================================================================ +// All strings reachable by deleting up to `k` characters. For Levenshtein, +// edit(a,b) <= k implies their deletion neighborhoods intersect, so colliding +// reps are exact candidates (no false negatives) verified later by banded DP. +// A variant of length L-d is always reached with exactly d deletions, so the +// "insert-then-recurse only on first sight" prune is loss-free. +static void gen_deletes_rec(const std::string& cur, int rem, + std::unordered_set& out) { + if (rem == 0) return; + for (size_t i = 0; i < cur.size(); ++i) { + std::string nxt; + nxt.reserve(cur.size() - 1); + nxt.append(cur, 0, i); + nxt.append(cur, i + 1, std::string::npos); + if (out.insert(nxt).second) gen_deletes_rec(nxt, rem - 1, out); + } +} +static void gen_deletes(const std::string& s, int k, + std::unordered_set& out) { + out.clear(); + out.insert(s); + if (k > 0) gen_deletes_rec(s, k, out); +} +// Rough upper bound on variants per sequence: sum_{j=0}^{k} C(L, j), capped. +static long deletion_variant_bound(int L, int k) { + long tot = 1, c = 1; + for (int j = 1; j <= k; ++j) { + c = c * (L - j + 1) / j; + tot += c; + if (tot > 1000000L) return tot; + } + return tot; +} + // ============================================================================ // Data Structures & Buffers (For Speed) // ============================================================================ @@ -58,13 +103,18 @@ static inline int levenshtein_opt(const std::string &a, const std::string &b, in std::iota(row.begin(), row.begin() + m + 1, 0); for (int i = 1; i <= n; ++i) { - int prev = row[0]; - row[0] = i; - int row_min = i; - int start_j = std::max(1, i - thr); int end_j = std::min(m, i + thr); - + + // prev must hold the diagonal D[i-1][start_j-1]. When the band starts past + // column 1 this is row[start_j-1] BEFORE it is overwritten by the boundary + // guard - seeding it with D[i-1][0] (the old behaviour) corrupts the first + // banded cell and makes the routine miss pairs whose only optimal alignment + // rides the band edge (e.g. distance == |len diff| == thr). + int prev = row[start_j - 1]; + row[0] = i; + int row_min = i; + // Fill valid band if (start_j > 1) row[start_j - 1] = thr + 2; // Guard boundary @@ -204,13 +254,14 @@ DataFrame fast_edge_list(CharacterVector seqs, Nullable j_gene = R_NilValue, bool match_v = false, bool match_j = false, - Nullable ids = R_NilValue, std::string metric = "levenshtein", std::string normalize = "none", Nullable subst_matrix = R_NilValue, int gap_open = -10, - int gap_extend = -1) + int gap_extend = -1, + std::string expand = "clique") { + const bool star = (expand == "star"); int n = seqs.size(); if (n < 2) stop("Need at least 2 sequences."); @@ -223,16 +274,42 @@ DataFrame fast_edge_list(CharacterVector seqs, if(match_v && v_gene.isNotNull()) v_vec = as>(v_gene); if(match_j && j_gene.isNotNull()) j_vec = as>(j_gene); - std::vector lbl(n); - if(ids.isNotNull()) lbl = as>(ids); - else for(int i=0; i, std::vector> group_map; + // 2. Deduplicate to representatives. + // A representative is a unique (sequence, V-used, J-used) tuple. V/J are + // only part of the key when the corresponding filter is on, so a rep never + // spans more than one (V,J) block. All distance work runs over reps; the + // member node indices are expanded back into edges afterwards. + const char SEP = '\x1f'; // unit separator: cannot occur in seq/gene + std::unordered_map rep_of; + std::vector> rep_members; // rep id -> node indices (ascending) + std::vector rep_seq; + std::vector rep_len; + std::map> block_reps; // block key -> rep ids + + rep_of.reserve(n * 2); for(int i=0; i{i}); + block_reps[block_key].push_back(r); + } else { + rep_members[it->second].push_back(i); + } } - + // 3. Prepare Substitution Matrix SubstMatrix smat; if (metric == "nw" || metric == "sw") { @@ -254,96 +331,287 @@ DataFrame fast_edge_list(CharacterVector seqs, } } - // 4. Output Storage - std::vector out_from, out_to; + // 4. Output Storage (0-based node indices; labels are attached in R) + std::vector out_i, out_k; std::vector out_d; - // 5. Parallel Processing -#ifdef _OPENMP - int nthreads = omp_get_max_threads(); -#else - int nthreads = 1; -#endif - - std::vector> all_groups; - for(auto &kv : group_map) if(kv.second.size() > 1) all_groups.push_back(kv.second); - - for (const auto& grp : all_groups) { - int g_size = grp.size(); - -#pragma omp parallel num_threads(nthreads) + // 5. Per-pair decision (the exact, brute-force-faithful verifier). + // Encodes metric / normalize as ints so the hot path avoids string + // comparisons. Returns true and sets `fd_out` when an edge is kept, + // reproducing the original maxd / length-gate / normalize / post-gate + // logic byte-for-byte. + enum Metric { LEV, HAM, DAM, MNW, MSW }; + enum Norm { N_NONE, N_MAXLEN, N_MEANLEN }; + Metric mcode = (metric == "hamming") ? HAM : + (metric == "damerau") ? DAM : + (metric == "nw") ? MNW : + (metric == "sw") ? MSW : LEV; + Norm ncode = (normalize == "maxlen") ? N_MAXLEN : + (normalize == "length") ? N_MEANLEN : N_NONE; + + auto compute_pair = [&](const std::string& A, int la, + const std::string& B, int lb, + DPWorkspace& ws, double& fd_out) -> bool { + int max_l = la > lb ? la : lb; + if (max_l == 0) return false; // matches original `max_l==0 continue` + int maxd; + if (thresh >= 1.0) { + maxd = (int)thresh; + } else { + double norm_len = (ncode == N_MEANLEN) ? (la + lb) / 2.0 : (double)max_l; + maxd = (int)(thresh * norm_len + THRESH_EPS); + } + if (mcode != MNW && mcode != MSW && std::abs(la - lb) > maxd) return false; + + int d = 0; + switch (mcode) { + case LEV: d = levenshtein_opt(A, B, maxd, ws.v1); break; + case HAM: + if (la != lb) d = maxd + 1; + else { for (int p = 0; p < la; ++p) if (A[p] != B[p]) { if (++d > maxd) break; } } + break; + case DAM: d = damerau_opt(A, B, maxd, ws); break; + case MNW: d = nw_opt(A, B, smat, ws); break; + case MSW: d = sw_opt(A, B, smat, ws); break; + } + if (d > maxd) return false; + + double fd = (double)d; + if (ncode == N_MAXLEN) fd /= max_l; + else if (ncode == N_MEANLEN) fd /= ((la + lb) / 2.0); + if (thresh < 1.0 && ncode != N_NONE && fd > thresh) return false; + + fd_out = fd; + return true; + }; + + // 6. Per-block index: reps sorted by id, length buckets, and a block-level + // integer distance cap k_block. Because every per-pair maxd <= k_block + // (norm_len <= Lmax_block), restricting candidates to length-compatible + // buckets never drops a real edge - the exact per-pair verifier decides. + int R = (int)rep_seq.size(); + struct BlockIdx { + std::vector rep_ids; // ascending rep id + std::map> buckets; // length -> ascending rep ids + int kblock = 0; + int Lmax = 0; + }; + std::vector bidx; + bidx.reserve(block_reps.size()); + std::vector rep_block(R); + { + int b = 0; + for (auto& kv : block_reps) { + BlockIdx bi; + bi.rep_ids = std::move(kv.second); + int Lmax = 0; + for (int r : bi.rep_ids) { + rep_block[r] = b; + bi.buckets[rep_len[r]].push_back(r); + if (rep_len[r] > Lmax) Lmax = rep_len[r]; + } + bi.kblock = (thresh >= 1.0) ? (int)thresh : (int)(thresh * (double)Lmax + THRESH_EPS); + bi.Lmax = Lmax; + bidx.push_back(std::move(bi)); + ++b; + } + } + + // 6b. Hamming pigeonhole index (one per equal-length bucket). Two equal-length + // sequences within Hamming distance k must share at least one of k+1 + // positioned segments, so candidate partners are the reps that collide on + // a segment. Buckets where k >= L (every pair trivially passes) fall back + // to a plain bucket scan. + struct SegBucket { + bool brute = false; + int nseg = 0; + std::vector seg_start, seg_len; + std::unordered_map> index; // pos-tag+seg -> ascending rep ids + }; + std::vector> ham(mcode == HAM ? bidx.size() : 0); + if (mcode == HAM) { + for (size_t b = 0; b < bidx.size(); ++b) { + for (auto& lb : bidx[b].buckets) { + int L = lb.first; + const std::vector& reps = lb.second; + if (L == 0 || reps.size() < 2) continue; // no possible edges + int kseg = (thresh >= 1.0) ? (int)thresh : (int)(thresh * (double)L + THRESH_EPS); + SegBucket sb; + if (kseg >= L) { sb.brute = true; ham[b].emplace(L, std::move(sb)); continue; } + sb.nseg = kseg + 1; + int base = L / sb.nseg, rem = L % sb.nseg, pos = 0; + for (int j = 0; j < sb.nseg; ++j) { + int len = base + (j < rem ? 1 : 0); + sb.seg_start.push_back(pos); + sb.seg_len.push_back(len); + pos += len; + } + sb.index.reserve(reps.size() * sb.nseg); + for (int r : reps) { + const std::string& sq = rep_seq[r]; + for (int j = 0; j < sb.nseg; ++j) { + std::string key; + key.reserve(sb.seg_len[j] + 1); + key += (char)(j + 1); // position tag (letters are >= 'A', no clash) + key.append(sq, sb.seg_start[j], sb.seg_len[j]); + sb.index[key].push_back(r); // reps ascending -> postings ascending + } + } + ham[b].emplace(L, std::move(sb)); + } + } + } + + // 6c. Deletion-neighborhood (SymSpell) index per block for edit-distance + // metrics with a small radius. Levenshtein uses radius = k_block; the + // guard keeps the variant explosion bounded, otherwise a block falls back + // to the length-blocked bucket scan. (Damerau, radius 2*k_block, is added + // alongside in step 8.) + const int K_CAP = 3; + const long MAXVAR = 4096; + struct DelIdx { + bool use = false; + int radius = 0; + std::unordered_map> index; // variant -> ascending rep ids + }; + std::vector del(bidx.size()); + if (mcode == LEV || mcode == DAM) { + std::unordered_set vars; + for (size_t b = 0; b < bidx.size(); ++b) { + int k = bidx[b].kblock; + if (k < 0 || k > K_CAP) continue; + int radius = (mcode == DAM) ? 2 * k : k; // transposition costs 2 in Levenshtein + if (deletion_variant_bound(bidx[b].Lmax, radius) > MAXVAR) continue; + DelIdx& di = del[b]; + di.use = true; + di.radius = radius; + di.index.reserve(bidx[b].rep_ids.size() * 4); + for (int r : bidx[b].rep_ids) { + gen_deletes(rep_seq[r], radius, vars); + for (const std::string& v : vars) di.index[v].push_back(r); + } + } + } + + // 7. Parallel over reps. Each rep emits its identical-member edges (clique or + // star) plus the edges to length-compatible reps with a higher id (so each + // unordered pair is generated exactly once). Expansion maps rep-pairs back + // to member node indices. +#pragma omp parallel { - // Thread-Local Storage - DPWorkspace ws; - std::vector loc_f, loc_t; - std::vector loc_d_vec; - -#pragma omp for schedule(dynamic, 64) - for (int i_idx = 0; i_idx < g_size; ++i_idx) { - int i = grp[i_idx]; - for (int k_idx = i_idx + 1; k_idx < g_size; ++k_idx) { - int k = grp[k_idx]; - - int max_l = std::max(lens[i], lens[k]); - if (max_l == 0) continue; - - int maxd; - if (thresh >= 1.0) { - // Absolute distance threshold - maxd = (int)thresh; - } else { - // Normalized distance threshold - use same length as normalization - double norm_len; - if (normalize == "maxlen") { - norm_len = (double)max_l; - } else if (normalize == "length") { - norm_len = (lens[i] + lens[k]) / 2.0; - } else { - // normalize == "none": interpret threshold as fraction of max_l - norm_len = (double)max_l; + DPWorkspace ws; + std::vector loc_f, loc_t; + std::vector loc_d; + std::vector cand; // reusable candidate-partner buffer + std::unordered_set vars; // reusable deletion-variant buffer + + // verify a rep-pair and, if kept, expand to member node pairs + auto verify_expand = [&](int r, int sv) { + double fd; + if (!compute_pair(rep_seq[r], rep_len[r], rep_seq[sv], rep_len[sv], ws, fd)) return; + const std::vector& mr = rep_members[r]; + const std::vector& ms = rep_members[sv]; + if (star) { + loc_f.push_back(mr[0]); loc_t.push_back(ms[0]); loc_d.push_back(fd); + } else { + for (int a : mr) for (int b : ms) { + loc_f.push_back(a); loc_t.push_back(b); loc_d.push_back(fd); + } + } + }; + // consider partners in an ascending bucket whose rep id exceeds r + auto consider = [&](int r, const std::vector& bucket) { + for (auto it = std::upper_bound(bucket.begin(), bucket.end(), r); + it != bucket.end(); ++it) + verify_expand(r, *it); + }; + +#pragma omp for schedule(dynamic, 32) + for (int r = 0; r < R; ++r) { + // intra-rep edges (identical sequences, mutual distance d_self). + const std::vector& mr = rep_members[r]; + double fd; + if (mr.size() >= 2 && + compute_pair(rep_seq[r], rep_len[r], rep_seq[r], rep_len[r], ws, fd)) { + if (star) { + int hub = mr[0]; + for (size_t a = 1; a < mr.size(); ++a) { + loc_f.push_back(hub); loc_t.push_back(mr[a]); loc_d.push_back(fd); } - maxd = (int)(thresh * norm_len); + } else { + for (size_t a = 0; a < mr.size(); ++a) + for (size_t b = a + 1; b < mr.size(); ++b) { + loc_f.push_back(mr[a]); loc_t.push_back(mr[b]); loc_d.push_back(fd); + } } - // ===================================================================== - - if (metric != "nw" && metric != "sw" && std::abs(lens[i] - lens[k]) > maxd) continue; - - int d = 0; - if (metric == "levenshtein") d = levenshtein_opt(s[i], s[k], maxd, ws.v1); - else if (metric == "hamming") { - if (lens[i] != lens[k]) d = maxd + 1; - else { - for(int p=0; pmaxd) break; } + } + + // inter-rep candidates, restricted by the (V,J,length) index. + const BlockIdx& bi = bidx[rep_block[r]]; + if (mcode == MNW || mcode == MSW) { + consider(r, bi.rep_ids); // alignment scores: no length pruning + } else if (mcode == HAM) { + // Hamming: equal length only, candidates via pigeonhole segments. + const std::unordered_map& hmap = ham[rep_block[r]]; + auto hit = hmap.find(rep_len[r]); + if (hit == hmap.end()) { + // bucket had < 2 reps or empty strings -> no edges + } else if (hit->second.brute) { + auto bit = bi.buckets.find(rep_len[r]); + if (bit != bi.buckets.end()) consider(r, bit->second); + } else { + const SegBucket& sb = hit->second; + const std::string& sq = rep_seq[r]; + cand.clear(); + for (int j = 0; j < sb.nseg; ++j) { + std::string key; + key.reserve(sb.seg_len[j] + 1); + key += (char)(j + 1); + key.append(sq, sb.seg_start[j], sb.seg_len[j]); + auto pit = sb.index.find(key); + if (pit != sb.index.end()) + for (auto p = std::upper_bound(pit->second.begin(), pit->second.end(), r); + p != pit->second.end(); ++p) + cand.push_back(*p); } + std::sort(cand.begin(), cand.end()); + cand.erase(std::unique(cand.begin(), cand.end()), cand.end()); + for (int s : cand) verify_expand(r, s); } - else if (metric == "damerau") d = damerau_opt(s[i], s[k], maxd, ws); - else if (metric == "nw") d = nw_opt(s[i], s[k], smat, ws); - else if (metric == "sw") d = sw_opt(s[i], s[k], smat, ws); - - if (d <= maxd) { - double fd = (double)d; - if (normalize == "maxlen") fd /= max_l; - else if (normalize == "length") fd /= ((lens[i]+lens[k])/2.0); - - if (thresh < 1.0 && normalize != "none" && fd > thresh) { - continue; // Skip edges that exceed normalized threshold + } else { + // edit distance (Levenshtein / Damerau) + const DelIdx& di = del[rep_block[r]]; + if (di.use) { + // SymSpell: candidates are reps sharing a deletion variant + gen_deletes(rep_seq[r], di.radius, vars); + cand.clear(); + for (const std::string& v : vars) { + auto it = di.index.find(v); + if (it != di.index.end()) + for (auto p = std::upper_bound(it->second.begin(), it->second.end(), r); + p != it->second.end(); ++p) + cand.push_back(*p); + } + std::sort(cand.begin(), cand.end()); + cand.erase(std::unique(cand.begin(), cand.end()), cand.end()); + for (int s : cand) verify_expand(r, s); + } else { + // fallback: length-blocked bucket scan, |dL| <= k_block + int L = rep_len[r], k = bi.kblock; + for (int Lp = L - k; Lp <= L + k; ++Lp) { + auto it = bi.buckets.find(Lp); + if (it != bi.buckets.end()) consider(r, it->second); } - // ===================================================================== - - loc_f.push_back(lbl[i]); - loc_t.push_back(lbl[k]); - loc_d_vec.push_back(fd); } } } #pragma omp critical { - out_from.insert(out_from.end(), loc_f.begin(), loc_f.end()); - out_to.insert(out_to.end(), loc_t.begin(), loc_t.end()); - out_d.insert(out_d.end(), loc_d_vec.begin(), loc_d_vec.end()); + out_i.insert(out_i.end(), loc_f.begin(), loc_f.end()); + out_k.insert(out_k.end(), loc_t.begin(), loc_t.end()); + out_d.insert(out_d.end(), loc_d.begin(), loc_d.end()); } } - } - - return DataFrame::create(_["from"] = out_from, _["to"] = out_to, _["dist"] = out_d, _["stringsAsFactors"] = false); + + return DataFrame::create(_["i"] = out_i, _["k"] = out_k, _["dist"] = out_d, _["stringsAsFactors"] = false); } diff --git a/tests/testthat/fixtures/buildNetwork_golden.rds b/tests/testthat/fixtures/buildNetwork_golden.rds new file mode 100644 index 0000000000000000000000000000000000000000..498f7a9d4e34c78e9da9eb811189a60cc08a4cbd GIT binary patch literal 35057 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, args=). +# `args` are passed to buildNetwork() along with seq_col/ids. +.bn_scenarios <- function() { + datasets <- .bn_data() + grid <- list() + add <- function(data_name, ...) { + grid[[length(grid) + 1L]] <<- list(data = data_name, args = list(...)) + } + + filters <- list(c(FALSE, FALSE), c(TRUE, FALSE), + c(FALSE, TRUE), c(TRUE, TRUE)) + + # Levenshtein / Damerau: absolute + normalized thresholds, all filters. + for (dn in c("dup_heavy", "unique_mixed", "edge_cases")) { + for (mt in c("levenshtein", "damerau")) { + for (f in filters) { + add(dn, dist_type = mt, threshold = 1, filter.v = f[1], filter.j = f[2]) + add(dn, dist_type = mt, threshold = 2, filter.v = f[1], filter.j = f[2]) + add(dn, dist_type = mt, threshold = 3, filter.v = f[1], filter.j = f[2]) + add(dn, dist_type = mt, threshold = 0.1, normalize = "maxlen", + filter.v = f[1], filter.j = f[2]) + add(dn, dist_type = mt, threshold = 0.15, normalize = "length", + filter.v = f[1], filter.j = f[2]) + # the loose-then-refilter pattern scRepertoire uses today + add(dn, dist_type = mt, threshold = 0.85, normalize = "length", + filter.v = f[1], filter.j = f[2]) + } + } + } + + # Hamming: equal-length and edge-case data, absolute + normalized. + for (dn in c("equal_len", "edge_cases", "dup_heavy")) { + for (f in filters) { + add(dn, dist_type = "hamming", threshold = 1, filter.v = f[1], filter.j = f[2]) + add(dn, dist_type = "hamming", threshold = 2, filter.v = f[1], filter.j = f[2]) + add(dn, dist_type = "hamming", threshold = 3, filter.v = f[1], filter.j = f[2]) + add(dn, dist_type = "hamming", threshold = 0.15, normalize = "maxlen", + filter.v = f[1], filter.j = f[2]) + } + } + + # Damerau transposition stress. + add("transpose", dist_type = "damerau", threshold = 1) + add("transpose", dist_type = "damerau", threshold = 2) + add("transpose", dist_type = "levenshtein", threshold = 1) + add("transpose", dist_type = "levenshtein", threshold = 2) + + # NW / SW (no pruning path): a few thresholds. + for (dn in c("unique_mixed", "equal_len")) { + add(dn, dist_type = "nw", dist_mat = "BLOSUM62", threshold = 5, filter.v = TRUE) + add(dn, dist_type = "sw", dist_mat = "BLOSUM62", threshold = 5, filter.v = TRUE) + add(dn, dist_type = "nw", dist_mat = "PAM30", threshold = 8, filter.v = TRUE) + } + + # A couple of sparse-output scenarios to lock the matrix path. + add("dup_heavy", dist_type = "levenshtein", threshold = 2, + filter.v = TRUE, output = "sparse", weight = "dist") + add("dup_heavy", dist_type = "levenshtein", threshold = 2, + filter.v = TRUE, output = "sparse", weight = "binary") + + list(datasets = datasets, grid = grid) +} + +# Run one scenario through buildNetwork() and return its canonical set. +.bn_run <- function(datasets, scen, extra = list()) { + d <- datasets[[scen$data]] + args <- modifyList(scen$args, extra) + args$input.data <- d + args$seq_col <- "junction_aa" + args$v_col <- "v_call" + args$j_col <- "j_call" + args$ids <- d$sequence_id + res <- do.call(buildNetwork, args) + .bn_canon(res) +} diff --git a/tests/testthat/test-buildNetwork-damerau.R b/tests/testthat/test-buildNetwork-damerau.R new file mode 100644 index 0000000..2ac9e5c --- /dev/null +++ b/tests/testthat/test-buildNetwork-damerau.R @@ -0,0 +1,21 @@ +# Guards the SymSpell radius trap: a pure adjacent transposition is Damerau +# distance 1 but Levenshtein distance 2, so the deletion index for Damerau must +# use radius 2*k. A radius-k index would silently miss these pairs. + +test_that("Damerau finds adjacent transpositions that Levenshtein does not", { + d <- data.frame( + sequence_id = c("a", "b", "c", "d"), + junction_aa = c("ABCDEFGHIK", "BACDEFGHIK", # swap 1-2 + "MNPQRSTVWY", "MNPQRSTVYW"), # swap 9-10 + v_call = "V1", j_call = "J1", stringsAsFactors = FALSE) + + e_dam <- suppressMessages(buildNetwork(d, seq_col = "junction_aa", ids = d$sequence_id, + dist_type = "damerau", threshold = 1)) + e_lev <- suppressMessages(buildNetwork(d, seq_col = "junction_aa", ids = d$sequence_id, + dist_type = "levenshtein", threshold = 1)) + + key <- function(df) if (nrow(df)) sort(paste(pmin(df$from, df$to), pmax(df$from, df$to))) else character(0) + expect_identical(key(e_dam), c("a b", "c d")) + expect_true(all(e_dam$dist == 1)) + expect_identical(key(e_lev), character(0)) # swaps are Levenshtein distance 2 +}) diff --git a/tests/testthat/test-buildNetwork-levenshtein-exact.R b/tests/testthat/test-buildNetwork-levenshtein-exact.R new file mode 100644 index 0000000..f811473 --- /dev/null +++ b/tests/testthat/test-buildNetwork-levenshtein-exact.R @@ -0,0 +1,46 @@ +# Direct correctness check for the banded Levenshtein engine, independent of the +# golden snapshot. The banded routine previously missed pairs whose only optimal +# alignment rides the band edge (true distance == |length difference| == thr), +# including identical sequences at a tight normalized threshold. This compares +# buildNetwork's edge set against brute-force utils::adist for several +# thresholds and a length-diverse sequence set (which guarantees band edges are +# exercised). + +test_that("Levenshtein edges match brute-force adist (incl. band edges)", { + set.seed(2024) + aa <- c("A","C","D","E","F","G","H","I","K","L","M","N","P","Q","R","S","T","V","W","Y") + # wide length spread so |len diff| frequently equals the threshold + seqs <- vapply(1:120, function(i) paste(sample(aa, sample(8:22, 1), replace = TRUE), collapse = ""), + character(1)) + ids <- paste0("s", seq_along(seqs)) + D <- as.matrix(utils::adist(seqs)) + + for (thr in 0:4) { + # brute-force truth: all i maxd = floor(0.05 * 9) = 0 + e <- buildNetwork(d, seq_col = "junction_aa", ids = d$sequence_id, + threshold = 0.05, normalize = "maxlen", filter.v = TRUE) + expect_equal(nrow(e), 3L) # all three identical pairs + expect_true(all(e$dist == 0)) +}) diff --git a/tests/testthat/test-buildNetwork-regression.R b/tests/testthat/test-buildNetwork-regression.R new file mode 100644 index 0000000..b06fb6c --- /dev/null +++ b/tests/testthat/test-buildNetwork-regression.R @@ -0,0 +1,36 @@ +# Regression: the candidate-indexing rewrite must reproduce, as a SET, the +# exact edges + distances of the original brute-force implementation across the +# full parameter matrix. `expand = "clique"` is the bit-identical mode (star +# changes edge multiplicity by design and is checked separately). +# +# The golden snapshot in fixtures/buildNetwork_golden.rds was generated from the +# pre-rewrite implementation. Regenerate ONLY with intent via +# data-raw/make-buildNetwork-golden.R (never blindly). + +test_that("buildNetwork reproduces frozen golden edge sets (clique mode)", { + golden_path <- test_path("fixtures", "buildNetwork_golden.rds") + skip_if_not(file.exists(golden_path), "golden snapshot missing") + golden <- readRDS(golden_path) + sc <- .bn_scenarios() + + # Pass expand = "clique" only once the argument exists (post-rewrite). + extra <- if ("expand" %in% names(formals(buildNetwork))) list(expand = "clique") else list() + + fails <- character(0) + for (i in seq_along(golden)) { + g <- golden[[i]] + scen <- sc$grid[[i]] + got <- tryCatch( + suppressMessages(.bn_run(sc$datasets, scen, extra)), + error = function(e) paste0("ERROR:", conditionMessage(e))) + if (!identical(got, g$set)) { + lab <- sprintf("[%d] %s {%s}", i, g$data, + paste(names(scen$args), unlist(scen$args), sep = "=", collapse = ", ")) + only_new <- setdiff(got, g$set) + only_old <- setdiff(g$set, got) + fails <- c(fails, sprintf("%s +%d/-%d", lab, length(only_new), length(only_old))) + } + } + expect_identical(fails, character(0), + info = paste0("\n", paste(utils::head(fails, 20), collapse = "\n"))) +}) diff --git a/tests/testthat/test-buildNetwork-star.R b/tests/testthat/test-buildNetwork-star.R new file mode 100644 index 0000000..b458177 --- /dev/null +++ b/tests/testthat/test-buildNetwork-star.R @@ -0,0 +1,52 @@ +# `expand = "star"` is NOT bit-identical to clique (it changes edge +# multiplicity by design). Its contract is: identical connected-components +# membership and identical vertex set. We verify that against clique mode on the +# duplicate-heavy fixtures, which is exactly what scRepertoire's default +# `cluster.method = "components"` relies on. + +test_that("star expansion preserves connected components and vertex set", { + skip_if_not_installed("igraph") + sc <- .bn_scenarios() + datasets <- sc$datasets + + # Each connected component as a sorted vertex-set signature; the set of these + # signatures is the partition, compared order-independently. + component_sets <- function(edge_df) { + if (nrow(edge_df) == 0) return(character(0)) + g <- igraph::graph_from_data_frame(edge_df[, c("from", "to")], directed = FALSE) + memb <- igraph::membership(igraph::components(g, mode = "weak")) + groups <- split(names(memb), as.integer(memb)) + sort(vapply(groups, function(v) paste(sort(v), collapse = "\031"), character(1)), + method = "radix") + } + + cfgs <- list( + list(data = "dup_heavy", args = list(dist_type = "levenshtein", threshold = 2, filter.v = TRUE)), + list(data = "dup_heavy", args = list(dist_type = "levenshtein", threshold = 0.15, normalize = "length", filter.v = TRUE)), + list(data = "equal_len", args = list(dist_type = "hamming", threshold = 2, filter.v = TRUE)), + list(data = "dup_heavy", args = list(dist_type = "damerau", threshold = 2, filter.v = TRUE, filter.j = TRUE)), + list(data = "unique_mixed", args = list(dist_type = "nw", dist_mat = "BLOSUM62", threshold = 5, filter.v = TRUE)) + ) + + for (cfg in cfgs) { + d <- datasets[[cfg$data]] + base_args <- c(cfg$args, list(input.data = d, seq_col = "junction_aa", + v_col = "v_call", j_col = "j_call", + ids = d$sequence_id)) + e_clique <- suppressMessages(do.call(buildNetwork, c(base_args, list(expand = "clique")))) + e_star <- suppressMessages(do.call(buildNetwork, c(base_args, list(expand = "star")))) + + # vertex sets identical + v_clique <- sort(unique(c(e_clique$from, e_clique$to))) + v_star <- sort(unique(c(e_star$from, e_star$to))) + expect_identical(v_star, v_clique, + info = paste(cfg$data, "| vertex set")) + + # connected-component partition identical + expect_identical(component_sets(e_star), component_sets(e_clique), + info = paste(cfg$data, "| component partition")) + + # star is never larger than clique + expect_lte(nrow(e_star), nrow(e_clique)) + } +}) From 72c7a48047b664c64a67292e48683ad07418d437 Mon Sep 17 00:00:00 2001 From: theHumanBorch Date: Sat, 27 Jun 2026 12:15:12 -0500 Subject: [PATCH 4/5] update documentation Roxy 8.0.0 --- DESCRIPTION | 2 +- man/amino.acids.Rd | 5 ----- man/immApex-package.Rd | 5 +++++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7b85de4..28c95bc 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -7,7 +7,6 @@ Authors@R: c( Description: A set of tools to for machine and deep learning in R from amino acid and nucleotide sequences focusing on adaptive immune receptors. The package includes pre-processing of sequences, unifying gene nomenclature usage, encoding sequences, and combining models. This package will serve as the basis of future immune receptor sequence functions/packages/models compatible with the scRepertoire ecosystem. License: MIT + file LICENSE Encoding: UTF-8 -RoxygenNote: 7.3.3 biocViews: Software, ImmunoOncology, SingleCell, Classification, Annotation, Sequencing, MotifAnnotation Depends: R (>= 4.3.0) @@ -43,3 +42,4 @@ VignetteBuilder: knitr Language: en-US URL: https://github.com/BorchLab/immApex/ BugReports: https://github.com/BorchLab/immApex/issues +Config/roxygen2/version: 8.0.0 diff --git a/man/amino.acids.Rd b/man/amino.acids.Rd index 8ccde83..05f04ec 100644 --- a/man/amino.acids.Rd +++ b/man/amino.acids.Rd @@ -1,16 +1,11 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R -\docType{data} \name{amino.acids} \alias{amino.acids} \title{Standard 20 amino acids} -\format{ -An object of class \code{character} of length 20. -} \usage{ amino.acids } \description{ Vector of one-letter codes for the 20 standard amino acids. } -\keyword{datasets} diff --git a/man/immApex-package.Rd b/man/immApex-package.Rd index 69248e2..83fd26b 100644 --- a/man/immApex-package.Rd +++ b/man/immApex-package.Rd @@ -19,6 +19,11 @@ Useful links: \author{ \strong{Maintainer}: Nick Borcherding \email{ncborch@gmail.com} [copyright holder] +Authors: +\itemize{ + \item Nick Borcherding \email{ncborch@gmail.com} [copyright holder] +} + Other contributors: \itemize{ \item Qile Yang \email{qile.yang@berkeley.edu} (\href{https://orcid.org/0009-0005-0148-2499}{ORCID}) [contributor] From f10a4a47d79f82a16b4b601670baa3392b4c4649 Mon Sep 17 00:00:00 2001 From: theHumanBorch Date: Sat, 27 Jun 2026 13:15:10 -0500 Subject: [PATCH 5/5] fix buildnetwork unit tests --- .../testthat/fixtures/buildNetwork_golden.rds | Bin 35057 -> 35050 bytes .../testthat/helper-buildNetwork-regression.R | 8 +++++--- tests/testthat/test-buildNetwork-regression.R | 9 ++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/testthat/fixtures/buildNetwork_golden.rds b/tests/testthat/fixtures/buildNetwork_golden.rds index 498f7a9d4e34c78e9da9eb811189a60cc08a4cbd..90f0f38c5f8278d89f1fa99b878e4d03cefcff81 100644 GIT binary patch literal 35050 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

2i^ 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