Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
^\.github$
^\.lintr$
^Makefile$
^docs$
^gcCorrect_chromosome_coordinates_.*\.txt$
^LICENSE$
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
/doc/
/Meta/
.RData
.Rhistory
.Rprofile
.DS_Store
.Rproj.user
12 changes: 12 additions & 0 deletions .lintr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
linters: linters_with_defaults(
line_length_linter = line_length_linter(200),
object_usage_linter = NULL,
object_name_linter = NULL,
commented_code_linter = NULL,
return_linter = NULL,
indentation_linter = NULL,
object_length_linter = NULL,
pipe_consistency_linter = NULL,
T_and_F_symbol_linter = NULL
)
encoding: "UTF-8"
101 changes: 101 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Changelog: Battenberg Refactor v3.0.2

This document outlines the significant mathematical, technical, and feature changes introduced in the refactored Battenberg pipeline compared to the original implementation.

## 1. Architectural Changes: "Pure R" Pipeline
* **Removal of External Executable Orchestration**:
* **Change**: The pipeline no longer internally manages or invokes external binary executables such as `alleleCounter`, `impute2`, or `java -jar beagle.jar`.
* **Impact**: Responsibilities such as **Allele Counting** and **Phasing/Imputation** have been factored out. The R package now strictly acts as a consumer of standard genomic file formats (BAM outputs, VCFs) generated by upstream workflow managers (e.g., Nextflow, Snakemake). This makes the package significantly lighter, more portable, and easier to containerize, as it no longer requires a complex "fat" environment with legacy binaries.
* **VCF Consumption Model**:
* **Change**: Instead of wrapping the execution of Beagle 5, the pipeline now includes a native VCF parser (`convert_beagle_to_impute`) that digests the output of modern phasing tools.
* **Rationale**: Decouples the statistical copy number calling from the specific version or implementation of the phasing tool.

## 2. Mathematical & Algorithmic Changes

### Geometric Centroid Selection
* **Change**: When multiple valid copy number solutions (optima) are found during the grid search (rho/psi space), the algorithm now calculates the **geometric centroid** of all valid solutions and selects the specific optimum closest to this center.
* **Rationale**: The original implementation often defaulted to the solution with the absolute highest goodness-of-fit. Centroid selection ensures a more robust "central" parameter set is chosen when the solution space is flat, reducing outlier artifacts.

### Recalculated Ploidy (Psi_t)
* **Change**: In `run_clonal_ASCAT`, the ploidy (`psi`) is partially recalculated using *only* high-confidence clonal segments after the initial grid search.
* **Rationale**: Ensures the final ploidy estimate is not skewed by subclonal noise.

### Optimized Integer Copy Number Fitting
* **Change**: The C++ implementation (`ascat_distance.cpp`) explicitly optimizes the integer combination of Major/Minor alleles (checking Floor/Ceiling combinations) for every grid point to minimize the BAF squared error.
* **Rationale**: Provides a rigorously optimized "best fit" for integer copy numbers at every hypothetical grid point.

### LOH/Deletion Constraint Logic
* **Change**: Applied a heuristic in `calculate_solution_fast` that valid solutions typically require at least some Loss-Of-Heterozygosity (LOH) or deletions (CN=0).
* **Rationale**: Filters out high-ploidy artifact solutions that lack biological deletion events.

### Winsorization in Segmentation
* **Change**: Added `copynumber::winsorize` step prior to PCF segmentation (`segmentation.R`).
* **Rationale**: Prevents single-point outliers from distorting segment means.

## 3. Technical & Performance Optimizations

### C++ Acceleration (`Rcpp`)
* **Change**: Core bottlenecks—distance calculations (`calculate_ascat_dist_matrix_cpp`) and segmentation (`pcf_core.cpp`)—ported to C++.
* **Impact**: 10x-100x speedup in grid search and segmentation.

### High-Performance IO & Vectorization
* **Change**:
* **`vroom`**: Used for instant reading/merging of large BAF files (`haplotype.R`).
* **`collapse`**: Replaced base R stats with `collapse::fsum`/`fmean`.
* **Vectorization**: Grid search logic fully vectorized to remove nested R loops.
* **Impact**: Massive reduction in I/O overhead and compute time.

### Workflow Checkpoints & Resume
* **Change**: Added `preprocessed_data_dir` and `phasing_results_dir` arguments to `battenberg()`.
* **Impact**: Allows skipping expensive upstream steps (allele counting, imputation) when re-running segmentation with new parameters.

### Dynamic Thread Budgeting
* **Change**: Explicit calculation of total threads (`chromosomes_in_parallel` × `threads_per_chromosome`) and dynamic setting of `OMP_NUM_THREADS` and `MKL_NUM_THREADS`.
* **Impact**: Prevents system lockups due to thread oversubscription.

### Memory Management
* **Change**:
* **Chunked Back-Transformation**: Processes LRR/BAF vectors in chunks to prevent OOM.
* **Smart Downsampling**: Plotting functions downsample data to ~500k points to prevent graphics device hangs.

## 4. Stability & Robustness

### Critical Probe Misalignment Fix
* **Change**: Explicit logic in `run_ascat_enhanced` to subset vectors using **names** (`lrr[names(baf)]`) rather than position.
* **Impact**: Fixes data corruption caused by upstream filtering offsets.

### Failsafe Grid Search
* **Change**: Automatic fallback to **Full Grid Search** or **Top-N Search** if the optimized local minima search yields no results.

## 5. New Features

### Configurable Grid Search
* **New Arguments**:
* `n_neighbors_search`: Limit search to top N closest points.
* `psi_step` / `rho_step`: Custom grid resolution.
* `local_min_window_size`: Adjustable local minima window.

### Structured Logging
* **Change**: Full migration to **`logger`** package for timestamped, leveled logs (INFO, DEBUG, ERROR).

### Early Termination
* **Change**: `early_termination = TRUE` flag to stop grid search once a high-quality solution is found.

## 6. Code Structure & Dependencies

### Modularization
* **Change**: Split monolithic scripts (`clonal_ascat.R`) into focused modules (`run_clonal_ascat.R`, `clonal_ascat_calc.R`, etc.).

### New Dependencies
* `Rcpp`, `RcppRoll` (Acceleration)
* `collapse`, `vroom`, `tictoc`, `fs` (IO/Perf)
* `logger`, `cli`, `optparse` (Interface)
* `S4Vectors`, `IRanges` (Genomics)

### License
* **Change**: Updated to `AGPL-3` since that is what was listed in the LICENSE file.

## 7. Container & CLI Support
* **Change**: Added `Dockerfile`, `Makefile`, and `cli`/`optparse` support.
* **Impact**: Facilitates robust command-line usage and reproducible containerized deployment.
* **Registry**: A Singularity-compatible Docker image is available at [quay.io/ohsu-comp-bio/battenberg](https://quay.io/repository/ohsu-comp-bio/battenberg?tab=tags).
51 changes: 34 additions & 17 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Package: Battenberg
Maintainer: Stefan Dentro <sd11@sanger.ac.uk>
License: GPL-3
License: AGPL-3
Type: Package
Title: Battenberg subclonal copy number caller
Version: 3.0.0
Version: 3.0.2
Encoding: UTF-8
Authors@R: c(person("David", "Wedge", role=c("aut"), email="dw9@sanger.ac.uk"),
person("Peter", "Van Loo", role=c("aut")),
Expand All @@ -17,15 +17,16 @@ Authors@R: c(person("David", "Wedge", role=c("aut"), email="dw9@sanger.ac.uk"),
person("Mohammed Faizal","Eeman Mootor", role="ctb"),
person("Julio Cesar","Cortes Rios", role="ctb"))
Description: Estimate subclonal copy number from whole genome sequencing or SNP6 data.
Depends:
R (>= 4.3.1),
Depends:
R (>= 4.3.0)
Imports:
stats,
utils,
graphics,
grDevices
Imports:
grDevices,
ASCAT,
copynumber,
RColorBrewer,
ASCAT (>= 3.1.3),
ggplot2,
readr,
gtools,
Expand All @@ -36,18 +37,34 @@ Imports:
splines,
GenomicRanges,
VariantAnnotation,
copynumber,
data.table
data.table,
IRanges,
S4Vectors,
logger,
vroom,
cli,
fs,
methods,
rlang,
SummarizedExperiment,
collapse,
dplyr,
Rcpp,
RcppRoll,
optparse,
tictoc,
devtools
Remotes:
VanLoo-lab/ascat/ASCAT
URL: https://github.com/Wedge-Oxford/battenberg
Crick-CancerGenomics/ascat/ASCAT,
igordot/copynumber
URL: https://github.com/ohsu-comp-bio/battenberg
LinkingTo: Rcpp
LazyLoad: yes
Suggests:
testthat,
lintr,
styler,
knitr,
rmarkdown,
ggplot2,
dplyr
VignetteBuilder:
rmarkdown
VignetteBuilder:
knitr
RoxygenNote: 7.3.2
RoxygenNote: 7.3.3
82 changes: 82 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Stage 1: Build C dependencies
FROM ubuntu:24.04 AS builder
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
make git curl gcc g++ bzip2 zlib1g-dev libbz2-dev liblzma-dev libcurl4-gnutls-dev \
&& rm -rf /var/lib/apt/lists/*

RUN mkdir /tmp/downloads
# Build htslib
RUN curl -sSL -o htslib.tar.bz2 https://github.com/samtools/htslib/releases/download/1.7/htslib-1.7.tar.bz2 && \
mkdir /tmp/htslib && \
tar -C /tmp/htslib --strip-components 1 -xjf htslib.tar.bz2 && \
cd /tmp/htslib && \
./configure && \
make -j$(nproc) && \
make install

# Build alleleCount
RUN curl -sSL -o allelecount.tar.gz https://github.com/cancerit/alleleCount/archive/v4.0.0.tar.gz && \
mkdir /tmp/allelecount && \
tar -C /tmp/allelecount --strip-components 1 -zxf allelecount.tar.gz && \
cd /tmp/allelecount/c && \
mkdir -p bin && \
make bin/alleleCounter && \
cp bin/alleleCounter /usr/local/bin/


# Stage 2: Final image
FROM ubuntu:24.04
ARG DEBIAN_FRONTEND=noninteractive

# 1. Install R and System Dependencies
RUN apt-get update && apt-get install -y \
r-base \
r-base-dev \
openjdk-17-jre-headless \
libcurl4-gnutls-dev \
libxml2-dev \
libssl-dev \
libfontconfig1-dev \
libharfbuzz-dev \
libfribidi-dev \
libfreetype6-dev \
libpng-dev \
libtiff5-dev \
libjpeg-dev \
make \
curl \
git \
&& rm -rf /var/lib/apt/lists/*

# 2. OPTIMIZATION: Configure Posit Binary Repository for Ubuntu Noble
# We do this AFTER R is installed so the directory exists.
RUN mkdir -p /usr/lib/R/etc && \
echo 'options(repos = c(CRAN = "https://packagemanager.posit.co/cran/__linux__/noble/latest"))' >> /usr/lib/R/etc/Rprofile.site && \
echo 'options(HTTPUserAgent = sprintf("R/%s R (%s)", getRversion(), paste(getRversion(), R.version$platform, R.version$arch, R.version$os)))' >> /usr/lib/R/etc/Rprofile.site

# 3. Copy binaries from builder stage
COPY --from=builder /usr/local/bin/alleleCounter /usr/local/bin/
# Impute2 (Static x86_64 binary)
RUN curl -sSL -o tmp.tar.gz https://mathgen.stats.ox.ac.uk/impute/impute_v2.3.2_x86_64_static.tgz && \
tar -C /usr/local/bin --strip-components 1 -zxf tmp.tar.gz && \
rm tmp.tar.gz

# 4. Install pak (improved installation for Linux)
RUN Rscript -e "install.packages('pak', repos = 'https://r-lib.github.io/p/pak/stable')"

WORKDIR /opt/battenberg

# 5. OPTIMIZATION: Cache dependency installation layer
# Copy DESCRIPTION first so that changes to code don't invalidate the dependency cache.
COPY DESCRIPTION .
COPY Makefile .
RUN make deps

# 6. Copy the rest of the code and install the package
COPY . .
RUN rm -rf src/*.o src/*.so
RUN make compile && make docs && make install

WORKDIR /home/ubuntu
CMD ["/bin/bash"]
34 changes: 34 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.PHONY: style lint test deps check install docs pak

docs:
Rscript -e "roxygen2::roxygenise(clean = TRUE)"

compile:
Rscript -e "Rcpp::compileAttributes()"

# Run the auto-formatter (styler)
style:
Rscript -e "styler::style_pkg(transformers = styler::tidyverse_style(strict = TRUE), base_indention = 0)"

# Run the linter
lint:
Rscript -e "lintr::lint_package()"

pak:
@echo "Installing pak and core dependencies..."
RUN Rscript -e "install.packages('pak', repos = 'https://cran.rstudio.com/')"

deps:
@echo "Installing all dependencies listed in DESCRIPTION..."
Rscript -e "if (!requireNamespace('pak', quietly = TRUE)) install.packages('pak', repos = 'https://cloud.r-project.org')"
Rscript -e "pak::pkg_install(c('Crick-CancerGenomics/ascat/ASCAT', 'igordot/copynumber'))"
Rscript -e "pak::repo_add(Bioc = '3.18'); \
pak::local_install_deps(upgrade = FALSE, dependencies = TRUE)"

check:
Rscript -e "devtools::check(error_on = 'warning')"
Rscript -e "devtools::load_all('.'); codetools::checkUsagePackage('Battenberg')"

install:
@echo "Installing Battenberg..."
Rscript -e "pak::local_install('.', upgrade=TRUE, dependencies=TRUE)"
Loading