Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
689769e
src/get_binary_dir.cpp: added a warning message in case the config di…
andrewdavidsmith Oct 14, 2026
83b7c7c
src/original_duplicates.cpp: removing unneeded maybe_unused
andrewdavidsmith Oct 14, 2026
6e2478e
src/falco.cpp: adding a note to the detailed help warning users about…
andrewdavidsmith Oct 14, 2026
522c9d0
src/run_mode.hpp: adding option for robustness to iupac degen symbols
andrewdavidsmith Oct 21, 2026
059b46b
src/results_summary.hpp: forgot to turn clang-format back on
andrewdavidsmith Oct 21, 2026
9776897
src/falco_utils.hpp: adding basic functions for anlayzing sequences t…
andrewdavidsmith Oct 21, 2026
fe21db3
src/falco.cpp: adding args for robustness to iupac symbols
andrewdavidsmith Oct 22, 2026
8c54686
src/results_collector.hpp: adding iupac mode via template
andrewdavidsmith Oct 22, 2026
1548098
src/falco_analyzer.cpp: wrapping the analyze implementation for switc…
andrewdavidsmith Oct 22, 2026
d0404f4
cmake/tests.cmake: adding a test for iupac robustness
andrewdavidsmith Oct 22, 2026
896db77
data/test_data/md5sum.txt: adding hash for iupac test
andrewdavidsmith Oct 22, 2026
5967c1d
data/test_scripts/fastq_iupac.sh: adding test for robustness to iupac…
andrewdavidsmith Oct 22, 2026
cdfde1a
src/run_mode.cpp: added more info related to the do_iupac run mode
andrewdavidsmith Oct 22, 2026
096303a
src/falco.cpp: some cleanup and better info
andrewdavidsmith Oct 22, 2026
6075942
src/falco_analyzer.cpp: static analysis fixes
andrewdavidsmith Oct 22, 2026
104a8d5
src/bamrec.cpp: modifying how nucleotides are assigned from BAM codes…
andrewdavidsmith Oct 25, 2026
d8d28ee
src/samrec.cpp: modifying how nucleotides are assigned from BAM codes…
andrewdavidsmith Oct 25, 2026
2de92b5
src/run_mode.hcpp: undoing the changes related to iupac mode
andrewdavidsmith Oct 25, 2026
b6a5dae
src/fqrec.hpp: (1) assuming memory pointed to by 'cursor' is writable…
andrewdavidsmith Oct 25, 2026
6218cad
src/falco_task.hpp: adding the fq_gz_task_t task type which allows fo…
andrewdavidsmith Oct 25, 2026
14f60f0
src/results_collector.hpp: mostly reverting templated results_collect…
andrewdavidsmith Oct 25, 2026
536e85f
src/falco.cpp: reverting iupac options and modifying the comment in d…
andrewdavidsmith Oct 25, 2026
630c930
src/falco_utils.hpp: reverting iupac code and adding a comment relate…
andrewdavidsmith Oct 25, 2026
6bcb1e3
src/falco_analyzer.cpp: reverting iupac-specific (a template on analy…
andrewdavidsmith Oct 25, 2026
b8eef27
src/fastq_gz_file.cpp and src/fastq_bgzf_file.cpp: changing the task …
andrewdavidsmith Oct 25, 2026
3a444a7
data/test_data/md5sum.txt: updating test hash for iupac test falco no…
andrewdavidsmith Oct 25, 2026
826de0a
data/test_scripts/fastq_iupac.sh: updating test script iupac test as …
andrewdavidsmith Oct 25, 2026
d06cb36
src/fqrec.hpp: adding a size function to fqrec
andrewdavidsmith Oct 26, 2026
adabede
src/results_collector.hpp: modifying process_reads overload for fq_ta…
andrewdavidsmith Oct 26, 2026
4d0381f
src/falco_analyzer.cpp: adding fq_buffer to hold thread local data fo…
andrewdavidsmith Oct 26, 2026
97f927e
src/fqrec.hpp: simplifying loop
andrewdavidsmith Oct 27, 2026
734a197
src/fastq_file.hcpp: merging the code for mmap fastq with the code fo…
andrewdavidsmith Oct 27, 2026
a73911d
src/falco_analyzer.cpp: using a preprocessor guard to distinguish to …
andrewdavidsmith Oct 27, 2026
dee9042
src/results_collector.hpp: using a preprocessor guard to distinguish …
andrewdavidsmith Oct 27, 2026
9f013e5
README.md: adding explanation about building falco to use memory mapp…
andrewdavidsmith Oct 27, 2026
6c78116
CMakeLists.txt: adding configure argument that will turn on USE_MMAP_…
andrewdavidsmith Oct 27, 2026
9e1e895
src/fastq_file.hcpp: adding posix_advise sequential for non-macos and…
andrewdavidsmith Oct 28, 2026
3c11c97
src/fastq_file.cpp: fixing prepro guard around fcntl.h on apple
andrewdavidsmith Oct 28, 2026
6e6e398
src/falco.cpp: attempting to make the message about ACGTN more clear
andrewdavidsmith Oct 28, 2026
0836bf1
src/fastq_file.cpp: apparently fcntl.h is needed on systems besides m…
andrewdavidsmith Oct 28, 2026
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
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ if(PACKAGE)
include(cmake/package.cmake)
endif()

option(USE_MMAP_FOR_PLAIN_FASTQ
"Enabling memory mapping for plain FASTQ files"
off
)
if(USE_MMAP_FOR_PLAIN_FASTQ)
add_compile_definitions(USE_MMAP_FOR_PLAIN_FASTQ)
endif()

find_package(LIBDEFLATE REQUIRED)
find_package(HTSLIB 1.18 REQUIRED)
find_package(ZLIB REQUIRED)
Expand Down
34 changes: 28 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ forward.
You will be able to find binaries for Linux and macOS with the releases.

Example:
```
```console
falco -o output input.fq
```
This generates 3 files in the directory named `output` (creating it if needed):
Expand All @@ -35,7 +35,7 @@ This generates 3 files in the directory named `output` (creating it if needed):
If you use [anaconda](https://anaconda.org) to manage your packages,
and the `conda` binary is in your path, you can install the most
recent release of `falco` by running
```
```console
conda install -c bioconda falco
```

Expand All @@ -56,7 +56,7 @@ conda install -c bioconda falco
you need: `.github/workflows/linux-release.yml` and `.github/workflows/macos-release.yml`

From the root of the repo if you have all dependencies:
```
```console
cmake -B build -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Release
cmake --build build -j8 # use -j for more cores when building
```
Expand All @@ -70,11 +70,11 @@ specified directly.
### Linux detailed instructions

I'm explaining this via a clean Ubuntu instance in docker:
```
```console
docker run -it ubuntu:latest bash
```
Inside the docker:
```
```console
export DEBIAN_FRONTEND=noninteractive &&
apt-get update &&
apt-get install -y --no-install-recommends \
Expand Down Expand Up @@ -102,7 +102,7 @@ you can find them here: `.github/workflows/linux-release.yml`
I don't have the same ability to test with clean OS images for macOS
(suggestions welcome). The best I can do is use the GitHub macOS runners, which
already have some of the dependencies installed. Here is what works:
```
```console
brew install libdeflate htslib samtools && # samtools for testing
git clone https://github.com/smithlabcode/falco.git &&
cd falco &&
Expand All @@ -117,6 +117,28 @@ GitHub's macOS look
Note: ISA-L was designed for Intel hardware. It works on Apple silicon, but only
gives 1-2% speedup in my tests.

### Potential speedups for plain FASTQ file inputs

For uncompressed FASTQ files, run times can be improved by building Falco to
memory map the input files. This only helps for *uncompressed FASTQ* files. The
speedup depends on whether your input files remain "hot" during your analysis.
This is determined by your pipeline, hardware, OS settings, etc. One reason a
file might stay "hot" is if it's stil in virtual memory from a previous step in
your pipeline. The speed difference can be almost 2x on some systems, but on
other systems you might see reduced performance. If you use Falco frequently on
plain FASTQ files, this might be worth trying. To enable memory mapped plain
FASTQ files, configure the build with `-DUSE_MMAP_FOR_PLAIN_FASTQ=on`:

```console
cmake -B build -DCMAKE_CXX_COMPILER=g++ -DUSE_MMAP_FOR_PLAIN_FASTQ=on -DCMAKE_BUILD_TYPE=Release
```

If you build Falco from source, there's no harm in testing both ways and seeing
what works best for your pipeline. Just make sure to test using your entire
pipeline and with both orders of testing the two configurations. Otherwise you
risk seeing better performance for whichever is tested second. You can use
`vmtouch` (available through conda) to load or evict files while testing.

## Changes in Falco v2.0

### Tiles results
Expand Down
1 change: 1 addition & 0 deletions cmake/tests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ add_test(NAME "Preseq output" COMMAND bash test_scripts/preseq.sh)
add_test(NAME "FASTQ from stdin" COMMAND bash test_scripts/fastq_stdin.sh)
add_test(NAME "SAM from stdin" COMMAND bash test_scripts/sam_stdin.sh)
add_test(NAME "Orig GC content" COMMAND bash test_scripts/fastq_orig_gc.sh)
add_test(NAME "IUPAC symbols" COMMAND bash test_scripts/fastq_iupac.sh)
1 change: 1 addition & 0 deletions data/test_data/md5sum.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ e786bd4d9e6065904ab7b45021e7190b groups_out/bam_1/fastqc_data.txt
22573e9861f6c2fa3951712a73e4db4c sam_out/sam_1/fastqc_data.txt
bcea1e9f86f0917496b14ae81b6ecbc5 sam_stdin_out/sam_stdin/fastqc_data.txt
088c04f706f340f058b663d6e0209dca fastq_orig_gc_out/fastq_bgzip_1/fastqc_data.txt
e71cef09fd4b6f4019f8fdb82ee2eaf3 fastq_iupac_out/fastq_1_iupac/fastqc_data.txt
23 changes: 23 additions & 0 deletions data/test_scripts/fastq_iupac.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MIT

prog=./falco
infile1=test_data/fastq_bgzip_1.fq.gz
infile2=fastq_1_iupac.fq
outdir=fastq_iupac_out
if [[ -e "${infile1}" ]]; then
mkdir -p ${outdir}
# ADS: on the first sequence line, modify all C to K
gunzip -c ${infile1}| sed -e "2s/C/K/g" > ${outdir}/${infile2}
${prog} -o ${outdir} ${outdir}/${infile2}
x=$(md5sum --ignore-missing -c test_data/md5sum.txt | \
grep "${outdir}" | \
grep -c "OK$")
if [[ "${x}" != "1" ]]; then
exit 1;
fi
rm -r ${outdir}
else
echo "${infile} not found; skipping remaining tests";
exit 77;
fi
9 changes: 3 additions & 6 deletions src/bamrec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,20 @@ bamrec::to_string() const -> std::string {
template <class bidir_itr_t, class output_itr_t>
static inline constexpr output_itr_t
assign_sequence_revcomp(bidir_itr_t first, auto last, output_itr_t d_first) {
static constexpr std::span seq_nt16_str = "=ACMGRSVTWYHKDBN";
constexpr auto complem = [](const auto x) {
return "TNGNNNCNNNNNNNNNNNNA"[x - 'A'];
};
static constexpr std::span seq_nt16_str_complem = "NTGNCNNNANNNNNNN";
constexpr auto bam_seqi = [](const auto s, const auto i) -> int {
constexpr auto low_nibble_on = 0xf;
return s[i >> 1] >> ((~i & 1) << 2) & low_nibble_on;
};
for (auto j = last; j != 0; ++d_first)
*d_first = complem(seq_nt16_str[bam_seqi(first, --j)]);
*d_first = seq_nt16_str_complem[bam_seqi(first, --j)];
return d_first;
}

template <class bidir_itr_t, class output_itr_t>
static inline constexpr output_itr_t
assign_sequence(bidir_itr_t first, auto last, output_itr_t d_first) {
static constexpr std::span seq_nt16_str = "=ACMGRSVTWYHKDBN";
static constexpr std::span seq_nt16_str = "NACNGNNNTNNNNNNN";
constexpr auto bam_seqi = [](const auto s, const auto i) -> int {
constexpr auto low_nibble_on = 0xf;
return s[i >> 1] >> ((~i & 1) << 2) & low_nibble_on;
Expand Down
45 changes: 24 additions & 21 deletions src/falco.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,39 @@
// clang-format off
static constexpr auto about = R"(Falco v{})";
static constexpr auto description =
R"(Note: Always use bgzip when compressing to gz format. It comes with samtools.
R"(NOTES:

- Prefer bgzip over gzip when compressing to gz format. Get it with
these packages: tabix (apt), bgzip (pip and dnf) or htslib (brew and conda).
- Falco assumes nucleotides symbols ACGT and N (upper or lowercase). Any other
symbols are counted as N.

EXAMPLES:

Use all defaults making output files in a dir named 'results/SRX081761_1':
Use all defaults settings:
$ falco -o results SRX081761_1.fastq
Output files will be:
results/SRX081761_1/summary.txt
results/SRX081761_1/fastqc_data.txt
results/SRX081761_1/fastqc_results.html

Use 8 cores and analyze all files in 'project' with the fq suffix, and results
for each input file in their own subdirectory of 'results':
Multiple cores for multiple input files:
$ falco -t 8 -o results project/*.fq

Use 64 cores on multiple BAM files in the current directory:
$ falco -t 64 -o results sample*.bam

Skip adapter content and tile-specific analysis:
No adapter or tile-specific analysis:
$ falco --no-adap --no-tiles -o results SRX081761.bam

Use configuration settings from 'my_limits.txt':
Custom configuration file 'my_limits.txt':
$ falco --config my_limits.txt -o results project_sample2.fq.gz

Generate a file with duplication info for preseq analysis:
Generate file for preseq analysis:
$ falco --preseq -o results SRX081761_1.fq.gz
Output files include:
results/SRX081761_1/preseq_hist.txt
Output will include: results/SRX081761_1/preseq_hist.txt

Take input from a pipe (accepts FASTQ or SAM):
$ samtools view SRX081761_1.bam | falco --stdin sam -o results SRX081761
Output files will be created in "results/SRX081761". Reading from stdin
Output files will be created in 'results/SRX081761'. Reading from stdin
disables tile analysis. To re-enable it, specify either 4 or 6 to indicate
position of the tile id in read names. Example: "--stdin fq:4"

Expand Down Expand Up @@ -264,12 +265,13 @@ make_outdirs(const auto &ins, const auto &outdir, const bool keep_extn = false)

// ADS: in functions below, the input_record_multiplier is the relative space
// for a record: ideally for FASTQ it would be 2x the (max) size of the read
// name, and 2x the (max) sequence length. For BAM it would be 0.5x the sequence
// length, the name length and the length of the quality score sequence, plus
// all other BAM fields. The buffer_multiplier is how much total buffer space is
// needed for a given record, which is 3 for BAM and BGZF because one for
// decompression, and then two inflated buffers that are swapped so once can be
// analyzed while the other is the target of other inflation work.
// name, and 2x the (max) sequence length. For BAM it would be 0.5x the
// sequence length, the name length and the length of the quality score
// sequence, plus all other BAM fields. The buffer_multiplier is how much
// total buffer space is needed for a given record, which is 3 for BAM and
// BGZF because one for decompression, and then two inflated buffers that are
// swapped so once can be analyzed while the other is the target of other
// inflation work.

[[nodiscard]] static auto
get_max_read_length(const auto buffer_size) {
Expand Down Expand Up @@ -336,6 +338,7 @@ main(int argc, char *argv[]) {
struct FormatWithoutFlagDefaults : public CLI::Formatter {
FormatWithoutFlagDefaults() : Formatter() {
CLI::FormatterBase::enable_default_flag_values_ = false;
CLI::FormatterBase::enable_footer_formatting_ = false;
}
};

Expand All @@ -350,8 +353,8 @@ main(int argc, char *argv[]) {
};

CLI::App app{std::format(about, VERSION)};
const auto fmt = std::make_shared<FormatWithoutFlagDefaults>();
app.formatter(fmt);
const auto app_fmt = std::make_shared<FormatWithoutFlagDefaults>();
app.formatter(app_fmt);

argv = app.ensure_utf8(argv);
app.usage(
Expand Down Expand Up @@ -379,7 +382,7 @@ main(int argc, char *argv[]) {
->check(CLI::ExistingFile, "file_check");
app.add_option("-o,--output", outdir, "Output directory (required)")
->required()
->option_text("DIR");
->option_text("OUTDIR");
app.add_option("-t,--threads", n_threads,
std::format("Threads for analysis (this machine supports: {})",
std::thread::hardware_concurrency()))
Expand Down
16 changes: 15 additions & 1 deletion src/falco_analyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ analyze(const std::uint32_t n_threads,
for (const auto th_id : std::views::iota(0u, n_threads))
workers.emplace_back([&, n_threads, th_id] {
try {
#ifdef USE_MMAP_FOR_PLAIN_FASTQ
/// ADS: the 'fq_buffer' is used to make a thread-local copy of mmap
/// data so it can be modified without invalidating all
/// memories. Declare it here so we have one per thread.
std::vector<char> fq_buffer;
#endif // USE_MMAP_FOR_PLAIN_FASTQ
auto &res = results[th_id];
for (const auto [file_id, info] : falco::views::enumerate(infos))
res[file_id].init(mode, info, dups_init[file_id]);
Expand All @@ -63,8 +69,16 @@ analyze(const std::uint32_t n_threads,
if (tq.is_finished())
return;
auto [file_id, task] = tq.pop_and_release_lock(tq_lock);
// clang-format off
if (std::holds_alternative<fq_task_t>(task))
process_reads(res[file_id], std::get<fq_task_t>(task));
process_reads(res[file_id], std::get<fq_task_t>(task)
#ifdef USE_MMAP_FOR_PLAIN_FASTQ
, fq_buffer
#endif // USE_MMAP_FOR_PLAIN_FASTQ
);
// clang-format on
else if (std::holds_alternative<fq_gz_task_t>(task))
process_reads(res[file_id], std::get<fq_gz_task_t>(task));
else if (std::holds_alternative<bam_task_t>(task))
process_reads(res[file_id], std::get<bam_task_t>(task));
else if (std::holds_alternative<sam_task_t>(task))
Expand Down
8 changes: 6 additions & 2 deletions src/falco_task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@

#include <variant>

using task_t =
std::variant<std::monostate, fq_task_t, bam_task_t, sam_task_t, bgzf_block_t>;
using task_t = std::variant<std::monostate,
fq_task_t,
fq_gz_task_t,
bam_task_t,
sam_task_t,
bgzf_block_t>;

#endif // SRC_FALCO_TASK_HPP_
4 changes: 4 additions & 0 deletions src/falco_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include <type_traits>
#include <vector>

/// ADS: (2026-10-24) intention currently is to be robust to IUPAC degenerate
/// nucleotide symbols, but current assumption is that this is taken care of
/// when constructing each record type (i.e., FASTQ, SAM, BAM, etc.)

// N (78)10 = (1001110)2 => 11 => 3 (same as G)
// A (65)10 = (1000001)2 => 00 => 0
// C (67)10 = (1000011)2 => 01 => 1
Expand Down
2 changes: 1 addition & 1 deletion src/fastq_bgzf_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ fastq_bgzf_file::get_chunks(std::int64_t n_chunks,
}
++n_tasks;
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
tq.push(file_id, fq_task_t(data + chunk_beg, data + chunk_end));
tq.push(file_id, fq_gz_task_t(data + chunk_beg, data + chunk_end));
start_pos = stop_pos;
}
output_cursor = chunk_end;
Expand Down
Loading
Loading