From f9c601d9e61b831e680fefaa9298b4c43d918f03 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Sun, 16 Aug 2026 17:40:20 -0700 Subject: [PATCH 1/9] src/falco_utils.hcpp: added a replacement for std::views::enumerate for compilers that don't support it (macOS clang). Changed high_resolution_clock to system_clock to satisfy macOS, and it won't matter for use in falco. Other workarounds for missing features in apple clang compiler --- src/falco_utils.cpp | 17 ++++++++++++----- src/falco_utils.hpp | 41 +++++++++++++++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/falco_utils.cpp b/src/falco_utils.cpp index 0959ca1..b4a026c 100644 --- a/src/falco_utils.cpp +++ b/src/falco_utils.cpp @@ -7,6 +7,7 @@ #include #include // for std::localtime #include +#include // for std::put_time #include #include #include @@ -56,7 +57,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 +100,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 +154,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..980a5b8 100644 --- a/src/falco_utils.hpp +++ b/src/falco_utils.hpp @@ -58,6 +58,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,7 +114,7 @@ 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 = @@ -245,7 +266,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 +319,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 +343,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; From 3134ccf62d076c14d357118863a25b4c9bced5ad Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Sun, 16 Aug 2026 17:41:40 -0700 Subject: [PATCH 2/9] src/falco_config.hpp: forward declaration as class not struct because apple clang cares about this consistency --- src/falco_config.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From d2717724d997fa357af2779bca122d414df75f1b Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Sun, 16 Aug 2026 17:45:10 -0700 Subject: [PATCH 3/9] multiple sources: changed std::views::enumerate to falco::views::enumerate for when views enumerate is not present testing __cpp_lib_ranges_enumerate. Also using feature tests and workarounds for __cpp_lib_ranges_zip and __cpp_lib_ranges_slide when the corresponding functions are not available. None of these are relevant for performance, mostly related to formatting results or postprocessing --- src/adapter_matcher.cpp | 20 +++++++++++++++++--- src/base_groups.cpp | 13 +++++++++++-- src/base_groups.hpp | 4 ++-- src/contaminants.cpp | 3 ++- src/duplication_results.cpp | 8 ++++---- src/falco.cpp | 2 +- src/falco_analyzer.cpp | 2 +- src/html.cpp | 29 +++++++++++++++++++++-------- src/kmer_counter.cpp | 2 +- src/report.cpp | 6 +++--- src/tile_processor.cpp | 10 ++++++++-- 11 files changed, 71 insertions(+), 28 deletions(-) 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/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..25bd691 100644 --- a/src/duplication_results.cpp +++ b/src/duplication_results.cpp @@ -179,7 +179,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 +229,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 +255,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/falco.cpp b/src/falco.cpp index 934daec..fbfaac4 100644 --- a/src/falco.cpp +++ b/src/falco.cpp @@ -141,7 +141,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..6c247da 100644 --- a/src/falco_analyzer.cpp +++ b/src/falco_analyzer.cpp @@ -48,7 +48,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/html.cpp b/src/html.cpp index 3168d16..809e4dd 100644 --- a/src/html.cpp +++ b/src/html.cpp @@ -196,7 +196,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 +245,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 +334,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 +386,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 +446,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"(