diff --git a/src/adapter_matcher.cpp b/src/adapter_matcher.cpp index 59139ba..0ac539a 100644 --- a/src/adapter_matcher.cpp +++ b/src/adapter_matcher.cpp @@ -84,8 +84,15 @@ adapter_matcher::report(const std::uint64_t n_reads, r += '\n'; auto cumulative = adap_counts; +#if __cpp_lib_ranges_zip for (auto [prev, curr] : cumulative | std::views::pairwise) std::ranges::transform(curr, prev, std::begin(curr), std::plus{}); +#else + for (auto prev = 0LU, curr = 1LU; curr < std::size(cumulative); + ++prev, ++curr) + std::ranges::transform(cumulative[curr], cumulative[prev], + std::begin(cumulative[curr]), std::plus{}); +#endif const auto n_pos = max_read_len + 1 >= adapter_size ? max_read_len - adapter_size + 1 @@ -96,7 +103,7 @@ adapter_matcher::report(const std::uint64_t n_reads, const auto fmt_pct_of_reads = [n_reads](const auto c) { return std::format("\t{:.6g}", pct(as_frac(c, n_reads))); }; - for (const auto [idx, cumul] : std::views::enumerate(cumulative)) + for (const auto [idx, cumul] : falco::views::enumerate(cumulative)) r += std::format("{}{}\n", make_group_tag(groups[idx]), to_flat(cumul, fmt_pct_of_reads)); return r + end_module_tag; @@ -130,9 +137,15 @@ name: "{}", return make_group_tag(g); // ADS: purposely unquoted }); auto cumulative = adap_counts; +#if __cpp_lib_ranges_zip for (auto [prev, curr] : cumulative | std::views::pairwise) std::ranges::transform(curr, prev, std::begin(curr), std::plus{}); - +#else + for (auto prev = 0LU, curr = 1LU; curr < std::size(cumulative); + ++prev, ++curr) + std::ranges::transform(cumulative[curr], cumulative[prev], + std::begin(cumulative[curr]), std::plus{}); +#endif const auto n_pos = max_read_len + 1 >= adapter_size ? max_read_len - adapter_size + 1 : max_read_len; @@ -145,7 +158,8 @@ name: "{}", }; std::vector html_by_adapter; const auto adapter_names = adapter_set::instance().adapter_names; - for (const auto [adap_id, adap_name] : std::views::enumerate(adapter_names)) { + for (const auto [adap_id, adap_name] : + falco::views::enumerate(adapter_names)) { const auto make_y = [&](const auto &cumul) { return pct_of_reads(cumul[adap_id]); }; diff --git a/src/adapter_set.cpp b/src/adapter_set.cpp index 9d33107..bf94c70 100644 --- a/src/adapter_set.cpp +++ b/src/adapter_set.cpp @@ -74,10 +74,9 @@ adapter_set::adapter_set(const run_mode &m, const std::string &filename) { [[nodiscard]] auto adapter_set::validate() const -> std::tuple { - const auto &adapters = instance().adapters; - if (adapters.empty()) + if (instance().adapters.empty()) return {true, std::string{}}; - const auto adap_len = std::size(adapters.front()); + const auto adap_len = std::size(instance().adapters.front()); if (!std::ranges::all_of(instance().adapters, [&](const auto &a) { return std::size(a) == adap_len; })) diff --git a/src/adapter_set.hpp b/src/adapter_set.hpp index a476425..f090a5e 100644 --- a/src/adapter_set.hpp +++ b/src/adapter_set.hpp @@ -12,6 +12,8 @@ #include #include +// ADS: all this code assumes all adapters for a given run of falco have the +// same length struct adapter_set { static constexpr auto min_adapter_size = 6; static constexpr auto max_adapter_size = 16; diff --git a/src/base_groups.cpp b/src/base_groups.cpp index a10afc5..ab09bbc 100644 --- a/src/base_groups.cpp +++ b/src/base_groups.cpp @@ -44,8 +44,17 @@ make_base_groups(const std::uint64_t n_bases, const std::uint64_t n_initial, static constexpr auto make_groups = [](const auto n, const auto offset, const auto scale) { const auto f = [scale, offset](const auto x) { return offset + x * scale; }; +#if __cpp_lib_ranges_zip return std::views::transform(std::views::iota(0LU, n + 1), f) | std::views::adjacent_transform<2>(make_one_group); +#else + const auto x = std::views::transform(std::views::iota(0LU, n + 1), f) | + std::ranges::to(); + std::vector r(n); + for (auto i = 0U; i < n; ++i) + r[i] = make_one_group(x[i], x[i + 1]); + return r; +#endif }; if (n_bases <= n_initial + n_groups_target) return make_groups(n_bases, 0, 1) | std::ranges::to(); @@ -63,8 +72,8 @@ make_base_groups(const std::uint64_t n_bases, const std::uint64_t n_initial, } [[nodiscard]] auto -get_default_base_groups(const std::uint64_t n_bases, - const bool make_groups) -> base_group_vec { +get_default_base_groups(const std::uint64_t n_bases, const bool make_groups) + -> base_group_vec { static constexpr auto default_n_initial = 9UL; static constexpr auto default_n_groups_target = 75UL - default_n_initial; return make_groups ? make_base_groups(n_bases, default_n_initial, diff --git a/src/base_groups.hpp b/src/base_groups.hpp index d79d573..75a561d 100644 --- a/src/base_groups.hpp +++ b/src/base_groups.hpp @@ -38,7 +38,7 @@ apply_base_groups(const base_group_vec &groups, auto &rows) { auto group_itr = std::cbegin(groups); auto current_row = 0U; for (const auto [idx, row] : - std::views::enumerate(rows) | std::views::drop(1)) { + falco::views::enumerate(rows) | std::views::drop(1)) { if (static_cast(idx) < group_itr->second) add(rows[current_row], row); else { @@ -56,7 +56,7 @@ apply_base_groups(const base_group_vec &groups, auto &rows, const auto &adder) { auto group_itr = std::cbegin(groups); auto current_row = 0U; for (const auto [idx, row] : - std::views::enumerate(rows) | std::views::drop(1)) { + falco::views::enumerate(rows) | std::views::drop(1)) { if (static_cast(idx) < group_itr->second) adder(rows[current_row], row); else { diff --git a/src/contaminants.cpp b/src/contaminants.cpp index f688b57..356879d 100644 --- a/src/contaminants.cpp +++ b/src/contaminants.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT; Copyright 2026 Andrew D Smith #include "contaminants.hpp" +#include "falco_utils.hpp" #include #include @@ -244,7 +245,7 @@ match_contaminant(const std::string &query) -> std::int64_t { auto best_match = 0L; auto best_match_len = 0L; for (const auto &[idx, seq] : - std::views::enumerate(std::views::elements<1>(contaminants))) { + falco::views::enumerate(std::views::elements<1>(contaminants))) { const auto n_match = std::max(get_overlap(query, seq), get_overlap(seq, query)); if (n_match > best_match) { diff --git a/src/duplication_results.cpp b/src/duplication_results.cpp index ee813ef..f2842d7 100644 --- a/src/duplication_results.cpp +++ b/src/duplication_results.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -179,7 +180,7 @@ overrepresented_report(const std::vector &overrep, make_bins(const auto &breaks, const auto &hist) { std::vector binned(std::size(breaks), 0); auto b_itr = std::cbegin(breaks); - for (const auto [i, h] : std::views::enumerate(hist)) { + for (const auto [i, h] : falco::views::enumerate(hist)) { // ADS: clang-tidy false positive? // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) b_itr += (b_itr < std::cend(breaks) && i >= *b_itr); @@ -229,12 +230,12 @@ duplication_results::get_dups_summary(const std::uint64_t n_reads) const std::vector hist_dedup(max_dup + 1); for (const auto n_copies : std::views::values(dups)) ++hist_dedup[n_copies]; - for (auto [idx, val] : std::views::enumerate(hist_dedup)) + for (auto [idx, val] : falco::views::enumerate(hist_dedup)) val = static_cast( get_corrected_count(count_at_limit, n_reads, idx, val)); auto hist_mass = std::views::transform( - std::views::enumerate(hist_dedup), + falco::views::enumerate(hist_dedup), [](const auto x) { return std::get<0>(x) * std::get<1>(x); }) | std::ranges::to(); return dup_summary_t{ @@ -255,7 +256,7 @@ duplication_results::get_dups_summary() const -> dup_summary_t { ++hist_dedup[n_copies]; auto hist_mass = std::views::transform( - std::views::enumerate(hist_dedup), + falco::views::enumerate(hist_dedup), [](const auto x) { return std::get<0>(x) * std::get<1>(x); }) | std::ranges::to(); return dup_summary_t{ diff --git a/src/duplication_results.hpp b/src/duplication_results.hpp index ab04d5a..a86afc4 100644 --- a/src/duplication_results.hpp +++ b/src/duplication_results.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include class run_mode; diff --git a/src/falco.cpp b/src/falco.cpp index 934daec..7150b38 100644 --- a/src/falco.cpp +++ b/src/falco.cpp @@ -41,6 +41,7 @@ Use these as templates. Copy and modify them to customize your analysis. #include "adapter_set.hpp" #include "bam_file.hpp" #include "contaminants.hpp" +#include "duplication_results.hpp" #include "falco_analyzer.hpp" #include "falco_config.hpp" #include "falco_file_format.hpp" @@ -49,12 +50,12 @@ Use these as templates. Copy and modify them to customize your analysis. #include "fastq_bgzf_file.hpp" #include "fastq_file.hpp" #include "fastq_gz_file.hpp" +#include "file_info.hpp" #include "get_binary_dir.hpp" #include "original_duplicates.hpp" -#include "quality_score.hpp" #include "reads_file.hpp" // IWYU pragma: keep -#include "results_collector.hpp" #include "results_summary.hpp" +#include "run_mode.hpp" #include "sam_file.hpp" #include "tile_processor.hpp" @@ -73,6 +74,7 @@ Use these as templates. Copy and modify them to customize your analysis. #include #include #include +#include #include #include #include @@ -80,10 +82,12 @@ Use these as templates. Copy and modify them to customize your analysis. #include #include #include -#include +#include #include #include +struct results_collector; + static auto write_file(const auto &filename, const auto &data) { std::ofstream out(filename); @@ -141,7 +145,7 @@ make_reads_files(const std::vector &infos, [[nodiscard]] static auto get_file_info(const auto &infiles) { std::vector infos; - for (const auto [file_id, infile] : std::views::enumerate(infiles)) { + for (const auto [file_id, infile] : falco::views::enumerate(infiles)) { const auto [input_format, format_description] = get_file_format(infile); const auto tile_id_position = get_tile_info(infile); const bool has_tiles = (tile_id_position != 0); diff --git a/src/falco_analyzer.cpp b/src/falco_analyzer.cpp index 549f9db..145a0ec 100644 --- a/src/falco_analyzer.cpp +++ b/src/falco_analyzer.cpp @@ -5,6 +5,7 @@ #include "bamrec.hpp" #include "bgzf_block.hpp" #include "duplication_results.hpp" +#include "falco_utils.hpp" #include "file_info.hpp" #include "fqrec.hpp" #include "reads_file.hpp" @@ -48,7 +49,7 @@ analyze(const std::uint32_t n_threads, const run_mode &mode, for (const auto th_id : std::views::iota(0u, n_threads)) workers.emplace_back([&, n_threads, th_id] { auto &res = results[th_id]; - for (const auto [file_id, info] : std::views::enumerate(infos)) + for (const auto [file_id, info] : falco::views::enumerate(infos)) res[file_id].init(mode, info, dups_init[file_id]); while (true) { auto tq_lock = tq.wait_and_acquire_lock(); diff --git a/src/falco_config.cpp b/src/falco_config.cpp index 6b3c538..f5a3a38 100644 --- a/src/falco_config.cpp +++ b/src/falco_config.cpp @@ -9,15 +9,17 @@ #include #include +#include #include +#include #include #include -#include #include +#include #include #include +#include #include -#include #include [[nodiscard]] auto diff --git a/src/falco_config.hpp b/src/falco_config.hpp index be193b8..c65276e 100644 --- a/src/falco_config.hpp +++ b/src/falco_config.hpp @@ -5,7 +5,7 @@ #include -struct run_mode; +class run_mode; // ADS: run mode is an out-param because it might be partially set already auto diff --git a/src/falco_utils.cpp b/src/falco_utils.cpp index 0959ca1..926dc39 100644 --- a/src/falco_utils.cpp +++ b/src/falco_utils.cpp @@ -7,7 +7,9 @@ #include #include // for std::localtime #include +#include // for std::put_time #include +#include #include #include #include @@ -56,7 +58,8 @@ get_theoretical_distribution(const std::vector &gc, const auto sd_term = [&](const auto &x) { return cntr_sq(std::get<0>(x)) * std::get<1>(x); }; - const auto id_gc = std::views::enumerate(gc) | std::views::transform(sd_term); + const auto id_gc = + falco::views::enumerate(gc) | std::views::transform(sd_term); const auto sd = std::sqrt(as_frac( std::reduce(std::cbegin(id_gc), std::cend(id_gc)), total_count - 1)); const auto to_normal = [&](const auto val) { @@ -98,9 +101,16 @@ smooth_gc_content(const std::vector &data, for (auto w = 1; w < (window_size + 1) / 2; ++w) smoothed.push_back(get_mean( std::ranges::subrange(std::cbegin(data), std::cbegin(data) + w))); +#if __cpp_lib_ranges_slide for (const auto &window : data | std::views::slide(window_size)) // cppcheck-suppress useStlAlgorithm smoothed.push_back(get_mean(window)); +#else + for (auto i = 0LU; i < std::size(data); ++i) + // cppcheck-suppress useStlAlgorithm + smoothed.push_back(get_mean(std::ranges::subrange( + std::cbegin(data) + i, std::cbegin(data) + i + window_size))); +#endif for (auto w = (window_size + 1) / 2; w > 1; --w) smoothed.push_back(get_mean( std::ranges::subrange(std::cend(data) - w + 1, std::cend(data)))); @@ -145,10 +155,8 @@ combine_gc_content_for_lengths(const std::vector &gcs) } [[nodiscard]] auto -get_program_start_time() - -> std::chrono::time_point { - static const auto start_time = std::chrono::high_resolution_clock::now(); +get_program_start_time() -> std::chrono::time_point { + static const auto start_time = std::chrono::system_clock::now(); return start_time; } diff --git a/src/falco_utils.hpp b/src/falco_utils.hpp index 67ff487..85b60fe 100644 --- a/src/falco_utils.hpp +++ b/src/falco_utils.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,27 @@ static constexpr auto gc_content_array_max_lim = 250; static constexpr auto gc_content_array_max_size = gc_content_array_max_lim + 1; using nuc_array = std::array; using gc_content_array = std::vector; + +// ADS: this is because I wrote falco v2 using std::views::enumerate in many +// places, but macOS apple-clang still doesn't have this c++20 feature. Not +// using full 'std::views::enumerate'. Assuming std::ranges::sized_range +namespace views { +#if __cpp_lib_ranges_enumerate +using std::views::enumerate; +#else +// ADS: from https://brevzin.github.io/c++/2022/12/05/enumerate +struct enumerate_t : std::ranges::range_adaptor_closure { + template + requires std::ranges::sized_range + [[nodiscard]] constexpr auto + operator()(R &&r) const { + const auto d = std::ranges::distance(r); + return std::views::zip(std::views::iota(0, d), static_cast(r)); + } +}; +inline constexpr enumerate_t enumerate; +#endif +} // namespace views } // namespace falco static constexpr std::int64_t gigabytes = 1024 * 1024 * 1024; @@ -93,13 +115,19 @@ sum_deviation_from_normal(const std::vector &gc) -> double; [[nodiscard]] auto get_run_duration(const auto start_time) { using namespace std::literals::chrono_literals; - const auto d = std::chrono::high_resolution_clock::now() - start_time; + const auto d = std::chrono::system_clock::now() - start_time; const auto d_ms = std::chrono::floor(d); if (d < 1min) { const auto ds = std::chrono::duration_cast>(d).count(); return std::format("0:{:05.2f}", ds); } + if (d < 1h) { + const auto dm = std::chrono::floor(d); + const auto ds = + std::chrono::duration_cast>(d - dm); + return std::format("{}:{:05.2f}", dm.count(), ds.count()); + } return std::format("{}", std::chrono::hh_mm_ss{d_ms}); } @@ -245,7 +273,7 @@ count_gc(auto seq_itr, const auto seq_end) { [[nodiscard]] inline auto tabular_dot(const auto &a) { auto total = static_cast::value_type>(0); - for (const auto [i, x] : std::views::enumerate(a)) + for (const auto [i, x] : falco::views::enumerate(a)) total += i * x; return total; } @@ -298,8 +326,8 @@ five_quants(const auto &a) -> std::array { // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) [[nodiscard]] auto -size_to_units(const std::int64_t s, - const std::string &suffix = "iB") -> std::string; +size_to_units(const std::int64_t s, const std::string &suffix = "iB") + -> std::string; [[nodiscard]] inline auto get_max_size(const auto &x) { @@ -322,18 +350,22 @@ estimate_read_length_fastq_chunk(const auto &data, const auto n) { if (std::size(lines) < fastq_lines_per_read) return 1ul; auto total = 0ul; - for (const auto l : lines | std::views::adjacent) +#if __cpp_lib_ranges_zip + for (const auto l : lines | std::views::adjacent) { +#else + for (auto i = 0U; i + fastq_lines_per_read < std::size(lines) + 1; ++i) { + std::tuple l{i, i + 1, i + 2}; +#endif if (data[std::get<0>(l)] == '@' && data[std::get<2>(l)] == '+' && valid(data[std::get<1>(l)])) // cppcheck-suppress useStlAlgorithm total += (std::get<2>(l) - std::get<1>(l)) - 1; + } return total / (std::size(lines) / fastq_lines_per_read); } [[nodiscard]] auto -get_program_start_time() - -> std::chrono::time_point; +get_program_start_time() -> std::chrono::time_point; [[nodiscard]] auto format_program_start_date_and_time() -> std::string; diff --git a/src/html.cpp b/src/html.cpp index 3168d16..fb88c8e 100644 --- a/src/html.cpp +++ b/src/html.cpp @@ -20,16 +20,12 @@ #include #include #include -#include #include -#include // for std::localtime #include -#include #include #include #include #include -#include // to format time with timezone #include #include #include // IWYU pragma: keep @@ -196,7 +192,13 @@ line: {{color: "{}"}} const auto pct_for_pos = [idx](const auto &nucs_for_pos, const auto tot) { return pct(as_frac(nucs_for_pos[idx], tot)); }; +#if __cpp_lib_ranges_zip const auto y = std::views::zip_transform(pct_for_pos, nucs, total_by_pos); +#else + std::vector y; + for (const auto [nuc, tot] : std::views::zip(nucs, total_by_pos)) + y.push_back(pct_for_pos(nuc, tot)); +#endif r.emplace_back(fmt::format(per_base_fmt, fmt::join(x, ","), fmt::join(y, ","), bases[idx], base_colors_for_html[idx])); @@ -239,11 +241,18 @@ yaxis: {{title: "% N", range: [0, 100]}}, assert(std::size(nucs) == std::size(groups)); const auto grade = grades.grade(label); const auto title = grades.get_title(label); +#if __cpp_lib_ranges_zip + const auto y = std::views::zip_transform(pct_for_pos, nucs, n_counts); +#else + std::vector y; + for (const auto [nuc, n_count] : std::views::zip(nucs, n_counts)) + y.push_back(pct_for_pos(nuc, n_count)); +#endif return fmt::format( html_module_fmt, grade, label, title, grade, - fmt::format( - plot_fmt, fmt::join(groups | std::views::transform(make_tag), ","), - fmt::join(std::views::zip_transform(pct_for_pos, nucs, n_counts), ","))); + fmt::format(plot_fmt, + fmt::join(groups | std::views::transform(make_tag), ","), + fmt::join(y, ","))); } [[nodiscard]] auto @@ -321,7 +330,7 @@ yaxis: {{title: "Phred quality", rangemode: "tozero"}}, return "green"; }; std::vector lines; - for (const auto [idx, q] : std::views::enumerate(qual)) { + for (const auto [idx, q] : falco::views::enumerate(qual)) { const auto fq = five_quants(q); lines.emplace_back(fmt::format(row_fmt, fmt::join(fq, ", "), // ADS: unquoted on purpose @@ -373,8 +382,8 @@ basic_stats_html(const file_info &info, const std::uint64_t n_reads, [[nodiscard]] auto tile_html(const tile_processor::tiles_centered_t ¢ered, - const std::vector &groups, - const file_grades &grades) -> std::string { + const std::vector &groups, const file_grades &grades) + -> std::string { static constexpr auto label = "tile"; static constexpr auto n_quants = 20.0; // ADS: ??? (-10: red, 0: light blue, +10: dark blue) @@ -433,8 +442,8 @@ yaxis: {{title: "tile", type: "category"}}, } [[nodiscard]] auto -kmer_html(const std::vector &results, - const file_grades &grades) -> std::string { +kmer_html(const std::vector &results, const file_grades &grades) + -> std::string { static constexpr auto label = "kmer"; static constexpr auto plot_format = R"(