Skip to content

Commit de6c833

Browse files
bschilderclaude
andcommitted
Add FINEMAP Apple Silicon support, PAINTOR annotation pipeline, POLYFUN fixes
FINEMAP: - Add FINEMAP_setup_dylibs() to auto-build x86_64 libs (zstd, gcc@10, libomp) and rewrite binary paths via install_name_tool for Apple Silicon - Add FINEMAP_check_runnable() for early fail-fast validation - Fix sparse matrix (dsCMatrix) handling in FINEMAP_construct_data PAINTOR: - Add PAINTOR_fetch_annotations() to download 7GB functional annotation library from BOX with SHA1 verification and local caching - Add PAINTOR_list_annotations() to browse available annotation categories - Wire annot_paintor parameter through PAINTOR() pipeline - Rewrite PAINTOR_prepare_annotations in pure R (was Python 2.7) - Fix PAINTOR_is_installed to use file.exists() instead of throwing - Add early checks for missing submodule in PAINTOR_install POLYFUN: - Fix filename pattern mismatch in POLYFUN_import_priors_handler for parametric mode (no chromosome in filename) - Remove hardcoded polyfun_path override in POLYFUN_compute_priors - Improve POLYFUN_install with better error messages and path handling Other: - Fix setup_gcc to support both ARM and Intel Homebrew paths - Update all tests with proper skip guards Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8e0bd7b commit de6c833

38 files changed

+1384
-516
lines changed

NEWS.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,50 @@
1+
# echofinemap 1.0.0
2+
3+
## New features
4+
5+
* `FINEMAP`: Added early runnability check via `FINEMAP_check_runnable()`
6+
validates the binary can execute before preparing data, with platform-specific
7+
error messages for Apple Silicon dylib issues.
8+
* `FINEMAP`: Added automatic x86_64 dynamic library setup on Apple Silicon
9+
via `FINEMAP_setup_dylibs()`. Builds/downloads x86_64 libzstd, libomp,
10+
and gcc runtime libraries, then rewrites binary paths with `install_name_tool`.
11+
* `FINEMAP`: System PATH detection — checks for conda-installed FINEMAP
12+
before downloading from GitHub releases.
13+
* `PAINTOR`: Wired functional annotation pipeline end-to-end.
14+
`PAINTOR_prepare_annotations()` is a pure-R reimplementation of the
15+
original Python 2.7 `AnnotateLocus.py`, converting BED files to binary
16+
annotation matrices and passing annotation names to the PAINTOR executable.
17+
* `PAINTOR`: New `annot_paintor` parameter for downloading the comprehensive
18+
PAINTOR functional annotation library (~7GB, 10,000+ annotations) from BOX.
19+
Annotations are cached locally after first download.
20+
* `PAINTOR`: New `PAINTOR_fetch_annotations()` and
21+
`PAINTOR_list_annotations()` functions for managing the annotation library.
22+
* `POLYFUN`: Fixed parametric mode filename pattern — parametric output
23+
has no chromosome in the filename, so the import now matches correctly.
24+
25+
## Bug fixes
26+
27+
* `FINEMAP`: Fixed sparse matrix (`dsCMatrix`) handling in
28+
`FINEMAP_construct_data()` by converting to dense matrix before `fwrite()`.
29+
* `POLYFUN`: Fixed silent error swallowing in `POLYFUN_import_priors()`
30+
replaced `try({})` with proper exit code checking and output file verification.
31+
* `POLYFUN`: Fixed `requireNamespace("Ckmeans.1d.dp")` to properly error
32+
with an informative message instead of silently continuing.
33+
* `POLYFUN`: Fixed `POLYFUN_install()` blocking non-interactive sessions
34+
by gating `readline()` behind `interactive()` check.
35+
* `POLYFUN`: Removed hardcoded path override in `POLYFUN_compute_priors()`
36+
that was overwriting the properly resolved polyfun path.
37+
* `PAINTOR`: Removed `setup_gcc(version="clang")` call from
38+
`PAINTOR_install()` that required sudo for symlink creation.
39+
* `setup_gcc()`: Rewritten for Apple Silicon support — checks both
40+
`/opt/homebrew` (ARM) and `/usr/local` (Intel) Homebrew paths,
41+
handles multiple gcc installation formats.
42+
* Tests: Split monolithic `test-multifinemap.R` into focused subtests
43+
(BST1, LRRK2 with network skip, POLYFUN_SUSIE with dependency checks).
44+
* Tests: Added proper skip guards for FINEMAP, PAINTOR, and POLYFUN tests
45+
with direct function checks instead of tryCatch wrappers.
46+
* Local R CMD check fixes and compatibility updates.
47+
148
# echofinemap 0.99.7
249

350
## Bug fixes

R/FINEMAP.R

Lines changed: 29 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@
8585
#' @importFrom echodata get_sample_size
8686
#' @importFrom data.table :=
8787
#' @importFrom methods is
88-
#' @examples
89-
#' \dontrun{
88+
#' @examples
9089
#' locus_dir <- file.path(tempdir(),echodata::locus_dir)
9190
#' dat <- echodata::BST1;
9291
#' LD_matrix <- echofinemap::drop_finemap_cols(echodata::BST1_LD_matrix)
@@ -96,8 +95,7 @@
9695
#'
9796
#' dat2 <- echofinemap::FINEMAP(dat=dat,
9897
#' locus_dir=locus_dir,
99-
#' LD_matrix=LD_matrix)
100-
#' }
98+
#' LD_matrix=LD_matrix)
10199
FINEMAP <- function(dat,
102100
locus_dir=tempdir(),
103101
LD_matrix,
@@ -114,15 +112,36 @@ FINEMAP <- function(dat,
114112
args_list=list(),
115113
fillNA=0,
116114
nThread=1,
117-
verbose=TRUE){
118-
# echoverseTemplate:::source_all(packages = "dplyr")
119-
# echoverseTemplate:::args2vars(FINEMAP)
120-
115+
verbose=TRUE){
121116
PP <- NULL;
122117

123118
if(!methods::is(finemap_version,"package_version")){
124119
finemap_version <- package_version(finemap_version)
125-
}
120+
}
121+
#### Early check: ensure FINEMAP binary is available and runnable ####
122+
if(is.null(FINEMAP_path)){
123+
FINEMAP_path <- FINEMAP_find_executable(version = finemap_version,
124+
verbose = verbose)
125+
}
126+
messager("FINEMAP path:", FINEMAP_path, v = verbose)
127+
FINEMAP_check_runnable(FINEMAP_path = FINEMAP_path, verbose = verbose)
128+
finemap_version <- FINEMAP_check_version(FINEMAP_path = FINEMAP_path,
129+
verbose = verbose)
130+
if(length(finemap_version)==0){
131+
warning(paste(
132+
"Could not determine FINEMAP version.",
133+
"If you are using Mac OS, please make sure you have the",
134+
"following software installed using brew:",
135+
"e.g. `brew install zstd libomp gcc`"
136+
))
137+
finemap_version <- package_version("1.3.1")
138+
FINEMAP_path <- FINEMAP_find_executable(version = "1.3.1",
139+
verbose = verbose)
140+
FINEMAP_check_runnable(FINEMAP_path = FINEMAP_path, verbose = verbose)
141+
}
142+
if(finemap_version < "1.4") {
143+
nThread <- 1
144+
}
126145
#### Remove rows with NAs ####
127146
dat <- remove_na_rows(dat=dat,
128147
cols = c("Effect","StdErr","SNP","MAF",
@@ -169,35 +188,6 @@ FINEMAP <- function(dat,
169188
dat = dat,
170189
LD_matrix = LD_matrix,
171190
verbose = verbose)
172-
#### Check FINEMAP exec ####
173-
if(is.null(FINEMAP_path)){
174-
FINEMAP_path <- FINEMAP_find_executable(version = finemap_version,
175-
verbose = verbose)
176-
}
177-
messager("FINEMAP path:",FINEMAP_path, v=verbose)
178-
finemap_version <- FINEMAP_check_version(FINEMAP_path = FINEMAP_path,
179-
verbose = verbose)
180-
dylib_msg <- paste(
181-
"\n*********\n",
182-
"System dependency error detected:",
183-
"If you are using Mac OS, please make sure you have the",
184-
"following software installed using brew:",
185-
"e.g. `brew install zstd libomp gcc`",
186-
187-
"If this error persists,\n",
188-
"please see the main FINEMAP website for additional support\n",
189-
"(http://www.christianbenner.com).",
190-
"\n*********\n\n"
191-
)
192-
if(length(finemap_version)==0){
193-
warning(dylib_msg)
194-
finemap_version <- package_version("1.3.1")
195-
FINEMAP_path <- FINEMAP_find_executable(version = "1.3.1",
196-
verbose = verbose)
197-
}
198-
if(finemap_version<"1.4") {
199-
nThread <- 1
200-
}
201191
#### Run FINEMAP ####
202192
# NOTE: Must cd into the directory first,
203193
# or else FINEMAP won't be able to find the input files.
@@ -210,33 +200,7 @@ FINEMAP <- function(dat,
210200
args_list=args_list,
211201
nThread=nThread,
212202
verbose=verbose)
213-
## Check if FINEMAP is giving an error due to `zstd`
214-
## not being installed.
215-
if(any(attr(msg,"status")==134)){
216-
warning(dylib_msg)
217-
#### Rerun if preferred version of FINEMAP fails ####
218-
FINEMAP_path <- FINEMAP_find_executable(version = "1.3.1",
219-
verbose = FALSE)
220-
finemap_version <- package_version("1.3.1")
221-
messager("Rerunning with FINEMAP v1.3.1.",v=verbose)
222-
msg <- FINEMAP_run(locus_dir=locus_dir,
223-
FINEMAP_path=FINEMAP_path,
224-
model=model,
225-
master_path=master_path,
226-
n_causal=n_causal,
227-
prior_k=prior_k,
228-
## May not have the args that the user
229-
## was expecting due to version differences.
230-
args_list=args_list,
231-
### Must be 1 for older versions of FINEMAP
232-
nThread=1,
233-
verbose=verbose)
234-
## Note!: concatenating this output in rmarkdown
235-
## can accidentally print many many lines.
236-
if(verbose) try({cat(paste(msg, collapse = "\n"))})
237-
} else {
238-
if(verbose) try({cat(paste(msg, collapse = "\n"))})
239-
}
203+
if(verbose) try({cat(paste(msg, collapse = "\n"))})
240204
#### Process results ####
241205
dat <- FINEMAP_process_results(locus_dir = locus_dir,
242206
dat = dat,

R/FINEMAP_check_runnable.R

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#' Check FINEMAP is runnable
2+
#'
3+
#' Test that the FINEMAP binary can actually execute on the current system.
4+
#' On Apple Silicon Macs, the x86_64 FINEMAP binary requires Rosetta 2
5+
#' and x86_64 versions of several dynamic libraries (zstd, gcc, libomp).
6+
#'
7+
#' @param FINEMAP_path Path to the FINEMAP executable.
8+
#' @param verbose Print messages.
9+
#' @returns \code{TRUE} if FINEMAP runs successfully, otherwise stops with
10+
#' an informative error message.
11+
#' @keywords internal
12+
#' @importFrom echodata get_os
13+
FINEMAP_check_runnable <- function(FINEMAP_path,
14+
verbose = TRUE){
15+
16+
messager("Checking FINEMAP is runnable...", v = verbose)
17+
out <- tryCatch({
18+
suppressWarnings(
19+
system(paste(FINEMAP_path, "-h"),
20+
intern = TRUE, ignore.stderr = TRUE)
21+
)
22+
}, error = function(e) e,
23+
warning = function(w) w)
24+
25+
exit_code <- attr(out, "status")
26+
27+
## Success
28+
if(is.null(exit_code) || exit_code == 0){
29+
messager("FINEMAP is runnable.", v = verbose)
30+
return(TRUE)
31+
}
32+
33+
## Exit code 134 = dylib loading failure
34+
if(isTRUE(exit_code == 134)){
35+
## Get detailed error
36+
err_msg <- tryCatch(
37+
system(paste(FINEMAP_path, "-h"), intern = TRUE,
38+
ignore.stdout = TRUE),
39+
warning = function(w) conditionMessage(w),
40+
error = function(e) conditionMessage(e)
41+
)
42+
43+
OS <- echodata::get_os()
44+
arch <- Sys.info()[["machine"]]
45+
46+
if(OS == "osx" && arch == "arm64"){
47+
stp <- paste0(
48+
"FINEMAP binary (x86_64) cannot run on Apple Silicon ",
49+
"without the required x86_64 dynamic libraries.\n\n",
50+
"The FINEMAP binary requires x86_64 versions of: ",
51+
"libzstd, libgfortran, libstdc++, libomp, libgcc_s\n\n",
52+
"To fix this, install Intel Homebrew and the required ",
53+
"libraries:\n",
54+
" 1. Install Intel Homebrew (if not already installed):\n",
55+
" arch -x86_64 /bin/bash -c ",
56+
"\"$(curl -fsSL https://raw.githubusercontent.com/",
57+
"Homebrew/install/HEAD/install.sh)\"\n",
58+
" 2. Install required libraries:\n",
59+
" arch -x86_64 /usr/local/bin/brew install ",
60+
"gcc@10 zstd libomp\n\n",
61+
"Alternatively, you can supply a custom FINEMAP binary ",
62+
"compiled for ARM64 via the FINEMAP_path argument."
63+
)
64+
} else if(OS == "osx"){
65+
stp <- paste0(
66+
"FINEMAP binary failed to run (exit code 134). ",
67+
"This usually means required dynamic libraries are missing.\n",
68+
"Install them with: brew install gcc@10 zstd libomp"
69+
)
70+
} else {
71+
stp <- paste0(
72+
"FINEMAP binary failed to run (exit code ", exit_code, ").\n",
73+
"Please ensure the binary is compatible with your system.\n",
74+
"Error: ", paste(err_msg, collapse = "\n")
75+
)
76+
}
77+
stop(stp)
78+
}
79+
80+
## Other failures
81+
stp <- paste0(
82+
"FINEMAP binary failed to run (exit code ", exit_code, ").\n",
83+
"Path: ", FINEMAP_path, "\n",
84+
"Please check that the binary is compatible with your system."
85+
)
86+
stop(stp)
87+
}

R/FINEMAP_construct_data.R

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,12 @@ FINEMAP_construct_data <- function(dat,
9292
# Sys.chmod(data.z_path, "777", use_umask = FALSE)
9393
# data.ld
9494
data.ld_path <- file.path(locus_dir,"FINEMAP","data.ld")
95+
## Convert sparse matrix to dense if needed
96+
if(methods::is(LD_filt, "sparseMatrix")){
97+
LD_filt <- as.matrix(LD_filt)
98+
}
9599
data.table::fwrite(data.table::as.data.table(LD_filt),
96-
data.ld_path, sep=" ",
100+
data.ld_path, sep=" ",
97101
quote = FALSE, col.names = FALSE,
98102
nThread = nThread)
99103
# Sys.chmod(data.ld_path, "777", use_umask = FALSE)

R/FINEMAP_find_executable.R

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
#' Find FINEMAP executable
2-
#'
1+
#' Find FINEMAP executable
2+
#'
33
#' Retrieve location of \code{FINEMAP} executable.
4+
#' First checks if \code{finemap} is already available on the system PATH
5+
#' (e.g. installed via \code{conda install -c bioconda finemap}),
6+
#' then falls back to downloading the binary from GitHub releases.
7+
#' On Apple Silicon Macs, automatically sets up x86_64 dynamic libraries
8+
#' via \code{\link{FINEMAP_setup_dylibs}}.
49
#' @family FINEMAP
510
#' @importFrom tools R_user_dir
611
#' @importFrom utils untar
@@ -23,27 +28,43 @@ FINEMAP_find_executable <- function(FINEMAP_path=NULL,
2328
),
2429
version=package_version("1.4.1"),
2530
verbose=TRUE){
26-
27-
# echoverseTemplate:::args2vars(FINEMAP_find_executable)
28-
# echoverseTemplate:::source_all()
29-
31+
3032
if(is.null(OS)) OS <- echodata::get_os()
31-
# messager("+ Using FINEMAP",paste0("v",version),v=verbose)
33+
34+
#### Check for user-supplied path ####
35+
if(!is.null(FINEMAP_path) && file.exists(FINEMAP_path)){
36+
Sys.chmod(FINEMAP_path, "0755")
37+
return(FINEMAP_path)
38+
}
39+
40+
#### Check system PATH first ####
41+
## e.g. conda install -c bioconda finemap
42+
system_finemap <- Sys.which("finemap")
43+
if(nchar(system_finemap) > 0){
44+
messager("Found FINEMAP on system PATH:",
45+
system_finemap, v = verbose)
46+
return(unname(system_finemap))
47+
}
48+
49+
#### Download from GitHub releases ####
3250
if(OS=="osx"){
3351
exec <- paste0("finemap_v",version,"_MacOSX")
34-
gcc_df <- setup_gcc(verbose = verbose)
3552
} else{
3653
exec <- paste0("finemap_v",version,"_x86_64")
37-
}
38-
#### (Download and) test executable ###
54+
}
3955
if(is.null(FINEMAP_path)) {
40-
FINEMAP_path <- file.path(save_dir,exec,exec)
56+
FINEMAP_path <- file.path(save_dir,exec,exec)
4157
}
42-
if(!file.exists(FINEMAP_path)){
43-
tgz <- get_data(fname = paste0(exec,".tgz"),
44-
save_dir = save_dir)
58+
if(!file.exists(FINEMAP_path)){
59+
messager("Downloading FINEMAP v", version, "...", v = verbose)
60+
tgz <- get_data(fname = paste0(exec,".tgz"),
61+
save_dir = save_dir)
4562
utils::untar(tarfile = tgz,
46-
exdir = dirname(tgz))
63+
exdir = dirname(tgz))
4764
}
65+
Sys.chmod(FINEMAP_path, "0755")
66+
#### Setup dylibs on Apple Silicon ####
67+
FINEMAP_setup_dylibs(FINEMAP_path = FINEMAP_path,
68+
verbose = verbose)
4869
return(FINEMAP_path)
4970
}

0 commit comments

Comments
 (0)