From 87a5b83d4009da27c2165ed190b55174ef20e484 Mon Sep 17 00:00:00 2001 From: mxwang66 <54554363+mxwang66@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:49:03 -0500 Subject: [PATCH 1/3] Reorganize build internals --- cpp/CMakeLists.txt | 6 +- cpp/include/seqwin/build.hpp | 34 +++++++ .../{helpers.hpp => build_internals.hpp} | 20 +--- cpp/include/seqwin/fasta_reader.hpp | 12 --- cpp/include/seqwin/filter.hpp | 18 ++++ cpp/include/seqwin/graph.hpp | 23 ----- cpp/include/seqwin/utils.hpp | 20 ++++ cpp/src/bindings/python_bindings.cpp | 3 +- cpp/src/seqwin/{graph.cpp => build.cpp} | 61 +++++++++--- .../{helpers.cpp => build_internals.cpp} | 94 +------------------ cpp/src/seqwin/fasta_reader.cpp | 23 ----- cpp/src/seqwin/filter.cpp | 74 +++++++++++++++ cpp/src/seqwin/utils.cpp | 32 +++++++ 13 files changed, 240 insertions(+), 180 deletions(-) create mode 100644 cpp/include/seqwin/build.hpp rename cpp/include/seqwin/{helpers.hpp => build_internals.hpp} (87%) create mode 100644 cpp/include/seqwin/filter.hpp create mode 100644 cpp/include/seqwin/utils.hpp rename cpp/src/seqwin/{graph.cpp => build.cpp} (86%) rename cpp/src/seqwin/{helpers.cpp => build_internals.cpp} (82%) create mode 100644 cpp/src/seqwin/filter.cpp create mode 100644 cpp/src/seqwin/utils.cpp diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index b7d21e7..163a9dc 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -41,8 +41,10 @@ endif() Python_add_library(_core MODULE WITH_SOABI src/bindings/python_bindings.cpp - src/seqwin/graph.cpp - src/seqwin/helpers.cpp + src/seqwin/build.cpp + src/seqwin/build_internals.cpp + src/seqwin/filter.cpp + src/seqwin/utils.cpp vendor/btllib/minimizer.cpp src/seqwin/fasta_reader.cpp vendor/btllib/status.cpp diff --git a/cpp/include/seqwin/build.hpp b/cpp/include/seqwin/build.hpp new file mode 100644 index 0000000..f3218cd --- /dev/null +++ b/cpp/include/seqwin/build.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +#include "seqwin/graph.hpp" + +namespace seqwin { + +/** + * @brief Build a minimizer graph from assembly FASTA files. + * + * `assembly_paths` and `is_targets` are parallel lists. + * + * @param assembly_paths Paths to input assemblies in FASTA format (plain or gzipped). + * @param kmerlen K-mer length for minimizer sketch. + * @param windowsize Window size for minimizer sketch. + * @param is_targets Whether each assembly is a target assembly. + * @param n_cpu Number of worker threads to use. + * @param low_memory Recompute minimizers in a second pass to reduce peak memory. + * @return Minimizer graph. + * @throws `std::runtime_error` If input sizes are inconsistent or counts exceed supported ranges. + */ +Graph build( + const std::vector& assembly_paths, + std::size_t kmerlen, + std::size_t windowsize, + const std::vector& is_targets, + std::size_t n_cpu = 1, + bool low_memory = false +); + +} // namespace seqwin diff --git a/cpp/include/seqwin/helpers.hpp b/cpp/include/seqwin/build_internals.hpp similarity index 87% rename from cpp/include/seqwin/helpers.hpp rename to cpp/include/seqwin/build_internals.hpp index ac5f895..d930867 100644 --- a/cpp/include/seqwin/helpers.hpp +++ b/cpp/include/seqwin/build_internals.hpp @@ -13,6 +13,8 @@ namespace seqwin { class ThreadPool; +namespace internal { + /** * @brief Thread-local minimizer graph node. * @@ -65,17 +67,6 @@ struct ThreadGraph { */ using KmerMaps = std::vector>; -/** - * @brief Emit a message through Python's logging module. - * - * @param message Message to log. - * @param level Logging level: `debug`, `info`, `warning`, `error`, or `critical`. - */ -void log_python( - const std::string& message, - const std::string& level = "info" -); - /** * @brief Merge thread-local minimizer graphs into a single graph. * @@ -92,11 +83,6 @@ std::pair merge_thread_graphs( bool low_memory ); -Graph filter_kmers( - const Kmer* kmers, - const Node* nodes, - std::size_t n_nodes, - std::vector used_hashes -); +} // namespace internal } // namespace seqwin diff --git a/cpp/include/seqwin/fasta_reader.hpp b/cpp/include/seqwin/fasta_reader.hpp index 9db3b3c..f93f245 100644 --- a/cpp/include/seqwin/fasta_reader.hpp +++ b/cpp/include/seqwin/fasta_reader.hpp @@ -20,16 +20,4 @@ struct FastaRecord { */ std::vector read_fasta(const std::string& assembly_path); -/** - * @brief Estimate the number of minimizers based on the size of assembly files. - * - * @param assembly_paths Paths to assembly FASTA files (plain or gzipped). - * @param windowsize Minimizer window size used for the estimate. - * @return Estimated total number of minimizers for all assembly files. - */ -std::size_t est_kmer_number( - const std::vector& assembly_paths, - std::size_t windowsize -); - } // namespace seqwin diff --git a/cpp/include/seqwin/filter.hpp b/cpp/include/seqwin/filter.hpp new file mode 100644 index 0000000..b2a4405 --- /dev/null +++ b/cpp/include/seqwin/filter.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +#include "seqwin/graph.hpp" + +namespace seqwin { + +Graph filter_kmers( + const Kmer* kmers, + const Node* nodes, + std::size_t n_nodes, + std::vector used_hashes +); + +} // namespace seqwin diff --git a/cpp/include/seqwin/graph.hpp b/cpp/include/seqwin/graph.hpp index 422b204..c0ec30f 100644 --- a/cpp/include/seqwin/graph.hpp +++ b/cpp/include/seqwin/graph.hpp @@ -68,27 +68,4 @@ struct Graph { std::vector> ids_by_assembly; }; -/** - * @brief Build a minimizer graph from assembly FASTA files. - * - * `assembly_paths` and `is_targets` are parallel lists. - * - * @param assembly_paths Paths to input assemblies in FASTA format (plain or gzipped). - * @param kmerlen K-mer length for minimizer sketch. - * @param windowsize Window size for minimizer sketch. - * @param is_targets Whether each assembly is a target assembly. - * @param n_cpu Number of worker threads to use. - * @param low_memory Recompute minimizers in a second pass to reduce peak memory. - * @return Minimizer graph. - * @throws `std::runtime_error` If input sizes are inconsistent or counts exceed supported ranges. - */ -Graph build( - const std::vector& assembly_paths, - std::size_t kmerlen, - std::size_t windowsize, - const std::vector& is_targets, - std::size_t n_cpu = 1, - bool low_memory = false -); - } // namespace seqwin diff --git a/cpp/include/seqwin/utils.hpp b/cpp/include/seqwin/utils.hpp new file mode 100644 index 0000000..59e4822 --- /dev/null +++ b/cpp/include/seqwin/utils.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace seqwin { +namespace internal { + +/** + * @brief Emit a message through Python's logging module. + * + * @param message Message to log. + * @param level Logging level: `debug`, `info`, `warning`, `error`, or `critical`. + */ +void log_python( + const std::string& message, + const std::string& level = "info" +); + +} // namespace internal +} // namespace seqwin diff --git a/cpp/src/bindings/python_bindings.cpp b/cpp/src/bindings/python_bindings.cpp index 6d45028..db443e6 100644 --- a/cpp/src/bindings/python_bindings.cpp +++ b/cpp/src/bindings/python_bindings.cpp @@ -9,8 +9,9 @@ #include #include +#include "seqwin/build.hpp" +#include "seqwin/filter.hpp" #include "seqwin/graph.hpp" -#include "seqwin/helpers.hpp" namespace py = pybind11; diff --git a/cpp/src/seqwin/graph.cpp b/cpp/src/seqwin/build.cpp similarity index 86% rename from cpp/src/seqwin/graph.cpp rename to cpp/src/seqwin/build.cpp index e0f2b40..3fd26cd 100644 --- a/cpp/src/seqwin/graph.cpp +++ b/cpp/src/seqwin/build.cpp @@ -1,25 +1,63 @@ -#include "seqwin/graph.hpp" +#include "seqwin/build.hpp" #include #include -#include +#include #include #include #include +#include #include #include #include #include "btllib/minimizer.hpp" #include "seqwin/fasta_reader.hpp" -#include "seqwin/helpers.hpp" +#include "seqwin/build_internals.hpp" +#include "seqwin/utils.hpp" #include "seqwin/thread_pool.hpp" namespace seqwin { namespace { +using internal::KmerMaps; +using internal::ThreadGraph; +using internal::ThreadNode; + constexpr std::size_t map_reserve_divisor = 100; +constexpr std::size_t plain_fasta_seq_len_per_byte = 1; +constexpr std::size_t gz_fasta_seq_len_per_byte = 4; + +bool ends_with(std::string_view text, std::string_view suffix) +{ + return text.size() >= suffix.size() && + text.substr(text.size() - suffix.size()) == suffix; +} + +std::size_t estimate_minimizer_count( + const std::vector& assembly_paths, + std::size_t start_assembly, + std::size_t end_assembly, + std::size_t windowsize +) { + // Reserve-only heuristic, not correctness-critical + std::size_t est_total_seq_len = 0; + for (std::size_t assembly_i = start_assembly; assembly_i < end_assembly; ++assembly_i) { + const auto& assembly_path = assembly_paths[assembly_i]; + const std::size_t seq_len_per_byte = ends_with(assembly_path, ".gz") + ? gz_fasta_seq_len_per_byte + : plain_fasta_seq_len_per_byte; + std::error_code ec; + const auto file_bytes = std::filesystem::file_size(assembly_path, ec); + if (ec) { + continue; + } + est_total_seq_len += static_cast(file_bytes * seq_len_per_byte); + } + return (2 * est_total_seq_len) / (windowsize + 1); +} + struct RawKmer { std::uint64_t hash; Kmer kmer; @@ -67,16 +105,15 @@ ThreadGraph build_worker( bool low_memory ) { // Estimate total minimizer count in all assemblies - const auto n_kmers_est = seqwin::est_kmer_number( - std::vector( - assembly_paths.begin() + static_cast(start_assembly), - assembly_paths.begin() + static_cast(end_assembly) - ), + const auto n_minimizers_est = estimate_minimizer_count( + assembly_paths, + start_assembly, + end_assembly, windowsize ); std::vector raw_kmers; if (!low_memory) { - raw_kmers.reserve(n_kmers_est); + raw_kmers.reserve(n_minimizers_est); } ThreadGraph graph; @@ -89,7 +126,7 @@ ThreadGraph build_worker( // Reserving for unordered_map will actually allocate physical memory std::unordered_map node_map; std::unordered_map edge_map; - const auto n_map_entries_est = n_kmers_est / map_reserve_divisor; + const auto n_map_entries_est = n_minimizers_est / map_reserve_divisor; node_map.reserve(n_map_entries_est); edge_map.reserve(n_map_entries_est); @@ -317,14 +354,14 @@ Graph build( } }); - auto [graph, kmer_maps] = merge_thread_graphs( + auto [graph, kmer_maps] = internal::merge_thread_graphs( graphs, n_assemblies, pool, low_memory ); if (low_memory) { - log_python(" - Recomputing minimizers for low-memory mode..."); + internal::log_python(" - Recomputing minimizers for low-memory mode..."); graph.kmers = recompute_kmers( assembly_paths, kmerlen, diff --git a/cpp/src/seqwin/helpers.cpp b/cpp/src/seqwin/build_internals.cpp similarity index 82% rename from cpp/src/seqwin/helpers.cpp rename to cpp/src/seqwin/build_internals.cpp index cc0a28c..1d41fcd 100644 --- a/cpp/src/seqwin/helpers.cpp +++ b/cpp/src/seqwin/build_internals.cpp @@ -1,4 +1,5 @@ -#include "seqwin/helpers.hpp" +#include "seqwin/build_internals.hpp" +#include "seqwin/utils.hpp" #include "seqwin/thread_pool.hpp" #include @@ -9,11 +10,9 @@ #include #include -#include - -namespace py = pybind11; namespace seqwin { +namespace internal { namespace { /** @@ -298,28 +297,6 @@ static void merge_edges(NoInitArray& edges, ThreadPool& pool) } // namespace -void log_python(const std::string& message, const std::string& level) -{ - py::gil_scoped_acquire acquire; - - py::object logging = py::module_::import("logging"); - py::object logger = logging.attr("getLogger")(); - - if (level == "debug") { - logger.attr("debug")(message); - } else if (level == "info") { - logger.attr("info")(message); - } else if (level == "warning" || level == "warn") { - logger.attr("warning")(message); - } else if (level == "error") { - logger.attr("error")(message); - } else if (level == "critical") { - logger.attr("critical")(message); - } else { - logger.attr("info")(message); - } -} - std::pair merge_thread_graphs( std::vector& graphs, std::size_t n_assemblies, @@ -423,68 +400,5 @@ std::pair merge_thread_graphs( }; } -Graph filter_kmers( - const Kmer* kmers, - const Node* nodes, - std::size_t n_nodes, - std::vector used_hashes -) { - std::sort(used_hashes.begin(), used_hashes.end()); - - Graph graph; - - std::vector used_node_indices; - used_node_indices.reserve(used_hashes.size()); - - std::size_t n_kmers = 0; - std::size_t node_i = 0; - std::size_t used_i = 0; - while (node_i < n_nodes && used_i < used_hashes.size()) { - const auto node_hash = nodes[node_i].hash; - const auto used_hash = used_hashes[used_i]; - - if (node_hash < used_hash) { - ++node_i; - continue; - } - if (used_hash < node_hash) { - ++used_i; - continue; - } - - used_node_indices.push_back(node_i); - n_kmers += nodes[node_i].stop - nodes[node_i].start; - ++node_i; - ++used_i; - } - - graph.nodes = NoInitArray(used_node_indices.size()); - graph.kmers = NoInitArray(n_kmers); - - std::size_t new_start = 0; - for (std::size_t out_node_i = 0; out_node_i < used_node_indices.size(); ++out_node_i) { - const auto in_node_i = used_node_indices[out_node_i]; - const Node& old_node = nodes[in_node_i]; - - const auto old_start = old_node.start; - const auto old_stop = old_node.stop; - const auto size = old_stop - old_start; - - Node new_node = old_node; - new_node.start = new_start; - new_node.stop = new_start + size; - graph.nodes[out_node_i] = new_node; - - for (std::size_t k = 0; k < size; ++k) { - const auto out_i = new_start + k; - const auto in_i = old_start + k; - graph.kmers[out_i] = kmers[in_i]; - } - - new_start += size; - } - - return graph; -} - +} // namespace internal } // namespace seqwin diff --git a/cpp/src/seqwin/fasta_reader.cpp b/cpp/src/seqwin/fasta_reader.cpp index 720dc32..d67f73d 100644 --- a/cpp/src/seqwin/fasta_reader.cpp +++ b/cpp/src/seqwin/fasta_reader.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -16,8 +15,6 @@ namespace seqwin { namespace { -constexpr std::size_t plain_fasta_seq_len_per_byte = 1; -constexpr std::size_t gz_fasta_seq_len_per_byte = 4; constexpr std::size_t gz_read_buf_size = 1U << 16; bool ends_with(std::string_view text, std::string_view suffix) @@ -215,24 +212,4 @@ std::vector read_fasta(const std::string& assembly_path) return read_plain_fasta(assembly_path); } -std::size_t est_kmer_number( - const std::vector& assembly_paths, - std::size_t windowsize -) { - // Reserve-only heuristic, not correctness-critical. - std::size_t est_total_seq_len = 0; - for (const auto& assembly_path : assembly_paths) { - const std::size_t seq_len_per_byte = ends_with(assembly_path, ".gz") - ? gz_fasta_seq_len_per_byte - : plain_fasta_seq_len_per_byte; - std::error_code ec; - const auto file_bytes = std::filesystem::file_size(assembly_path, ec); - if (ec) { - continue; - } - est_total_seq_len += static_cast(file_bytes * seq_len_per_byte); - } - return (2 * est_total_seq_len) / (windowsize + 1); -} - } // namespace seqwin diff --git a/cpp/src/seqwin/filter.cpp b/cpp/src/seqwin/filter.cpp new file mode 100644 index 0000000..fbbc0fa --- /dev/null +++ b/cpp/src/seqwin/filter.cpp @@ -0,0 +1,74 @@ +#include "seqwin/filter.hpp" + +#include +#include +#include +#include + +namespace seqwin { + +Graph filter_kmers( + const Kmer* kmers, + const Node* nodes, + std::size_t n_nodes, + std::vector used_hashes +) { + std::sort(used_hashes.begin(), used_hashes.end()); + + Graph graph; + + std::vector used_node_indices; + used_node_indices.reserve(used_hashes.size()); + + std::size_t n_kmers = 0; + std::size_t node_i = 0; + std::size_t used_i = 0; + while (node_i < n_nodes && used_i < used_hashes.size()) { + const auto node_hash = nodes[node_i].hash; + const auto used_hash = used_hashes[used_i]; + + if (node_hash < used_hash) { + ++node_i; + continue; + } + if (used_hash < node_hash) { + ++used_i; + continue; + } + + used_node_indices.push_back(node_i); + n_kmers += nodes[node_i].stop - nodes[node_i].start; + ++node_i; + ++used_i; + } + + graph.nodes = NoInitArray(used_node_indices.size()); + graph.kmers = NoInitArray(n_kmers); + + std::size_t new_start = 0; + for (std::size_t out_node_i = 0; out_node_i < used_node_indices.size(); ++out_node_i) { + const auto in_node_i = used_node_indices[out_node_i]; + const Node& old_node = nodes[in_node_i]; + + const auto old_start = old_node.start; + const auto old_stop = old_node.stop; + const auto size = old_stop - old_start; + + Node new_node = old_node; + new_node.start = new_start; + new_node.stop = new_start + size; + graph.nodes[out_node_i] = new_node; + + for (std::size_t k = 0; k < size; ++k) { + const auto out_i = new_start + k; + const auto in_i = old_start + k; + graph.kmers[out_i] = kmers[in_i]; + } + + new_start += size; + } + + return graph; +} + +} // namespace seqwin diff --git a/cpp/src/seqwin/utils.cpp b/cpp/src/seqwin/utils.cpp new file mode 100644 index 0000000..e667119 --- /dev/null +++ b/cpp/src/seqwin/utils.cpp @@ -0,0 +1,32 @@ +#include "seqwin/utils.hpp" +#include + +namespace py = pybind11; + +namespace seqwin { +namespace internal { + +void log_python(const std::string& message, const std::string& level) +{ + py::gil_scoped_acquire acquire; + + py::object logging = py::module_::import("logging"); + py::object logger = logging.attr("getLogger")(); + + if (level == "debug") { + logger.attr("debug")(message); + } else if (level == "info") { + logger.attr("info")(message); + } else if (level == "warning" || level == "warn") { + logger.attr("warning")(message); + } else if (level == "error") { + logger.attr("error")(message); + } else if (level == "critical") { + logger.attr("critical")(message); + } else { + logger.attr("info")(message); + } +} + +} // namespace internal +} // namespace seqwin From dc0bf6732e94979edfd171d6ad0e615531b6a8c4 Mon Sep 17 00:00:00 2001 From: mxwang66 <54554363+mxwang66@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:15:47 -0500 Subject: [PATCH 2/3] Reorganize private implementation files --- cpp/CMakeLists.txt | 6 ++--- cpp/src/seqwin/build.cpp | 25 +++++++++---------- cpp/src/seqwin/build_internals.cpp | 10 +++----- .../seqwin/build_internals.hpp | 8 ++---- cpp/src/{seqwin => utils}/fasta_reader.cpp | 8 +++--- .../seqwin => src/utils}/fasta_reader.hpp | 4 +-- .../{seqwin/utils.cpp => utils/logging.cpp} | 9 +++---- .../utils.hpp => src/utils/logging.hpp} | 6 ++--- .../seqwin => src/utils}/thread_pool.hpp | 4 +-- 9 files changed, 35 insertions(+), 45 deletions(-) rename cpp/{include => src}/seqwin/build_internals.hpp (96%) rename cpp/src/{seqwin => utils}/fasta_reader.cpp (98%) rename cpp/{include/seqwin => src/utils}/fasta_reader.hpp (89%) rename cpp/src/{seqwin/utils.cpp => utils/logging.cpp} (86%) rename cpp/{include/seqwin/utils.hpp => src/utils/logging.hpp} (78%) rename cpp/{include/seqwin => src/utils}/thread_pool.hpp (98%) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 163a9dc..943358d 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -44,13 +44,13 @@ Python_add_library(_core MODULE WITH_SOABI src/seqwin/build.cpp src/seqwin/build_internals.cpp src/seqwin/filter.cpp - src/seqwin/utils.cpp + src/utils/fasta_reader.cpp + src/utils/logging.cpp vendor/btllib/minimizer.cpp - src/seqwin/fasta_reader.cpp vendor/btllib/status.cpp ) -target_include_directories(_core PRIVATE include vendor "${PYBIND11_INCLUDE}") +target_include_directories(_core PRIVATE include src vendor "${PYBIND11_INCLUDE}") target_link_libraries(_core PRIVATE Python::Module ZLIB::ZLIBSTATIC) # GCC 8's libstdc++ keeps std::filesystem symbols in a separate library. diff --git a/cpp/src/seqwin/build.cpp b/cpp/src/seqwin/build.cpp index 3fd26cd..cd2a27a 100644 --- a/cpp/src/seqwin/build.cpp +++ b/cpp/src/seqwin/build.cpp @@ -12,18 +12,16 @@ #include #include "btllib/minimizer.hpp" -#include "seqwin/fasta_reader.hpp" + #include "seqwin/build_internals.hpp" -#include "seqwin/utils.hpp" -#include "seqwin/thread_pool.hpp" +#include "utils/fasta_reader.hpp" +#include "utils/logging.hpp" +#include "utils/thread_pool.hpp" namespace seqwin { +namespace internal { namespace { -using internal::KmerMaps; -using internal::ThreadGraph; -using internal::ThreadNode; - constexpr std::size_t map_reserve_divisor = 100; constexpr std::size_t plain_fasta_seq_len_per_byte = 1; @@ -133,7 +131,7 @@ ThreadGraph build_worker( for (std::size_t assembly_i = start_assembly; assembly_i < end_assembly; ++assembly_i) { const bool is_target = is_targets[assembly_i]; - auto records = seqwin::read_fasta(assembly_paths[assembly_i]); + auto records = read_fasta(assembly_paths[assembly_i]); std::vector record_ids; record_ids.reserve(records.size()); @@ -270,7 +268,7 @@ NoInitArray recompute_kmers( for (std::size_t assembly_i = graph.start_assembly; assembly_i < graph.end_assembly; ++assembly_i) { - auto records = seqwin::read_fasta(assembly_paths[assembly_i]); + auto records = read_fasta(assembly_paths[assembly_i]); for (std::size_t record_i = 0; record_i < records.size(); ++record_i) { const auto record_idx = record_offsets[assembly_i] + record_i; @@ -309,6 +307,7 @@ NoInitArray recompute_kmers( } } // namespace +} // namespace internal Graph build( const std::vector& assembly_paths, @@ -331,8 +330,8 @@ Graph build( n_workers = std::min(n_workers, n_assemblies); } - ThreadPool pool(n_workers); // Avoid spawning threads every time - std::vector graphs(n_workers); + internal::ThreadPool pool(n_workers); // Avoid spawning threads every time + std::vector graphs(n_workers); const std::size_t base = n_assemblies / n_workers; const std::size_t rem = n_assemblies % n_workers; @@ -341,7 +340,7 @@ Graph build( for (std::size_t thread_id = start; thread_id < end; ++thread_id) { std::size_t start_assembly = thread_id * base + std::min(thread_id, rem); std::size_t end_assembly = start_assembly + base + (thread_id < rem ? 1 : 0); - graphs[thread_id] = build_worker( + graphs[thread_id] = internal::build_worker( assembly_paths, kmerlen, windowsize, @@ -362,7 +361,7 @@ Graph build( ); if (low_memory) { internal::log_python(" - Recomputing minimizers for low-memory mode..."); - graph.kmers = recompute_kmers( + graph.kmers = internal::recompute_kmers( assembly_paths, kmerlen, windowsize, diff --git a/cpp/src/seqwin/build_internals.cpp b/cpp/src/seqwin/build_internals.cpp index 1d41fcd..27cf0e7 100644 --- a/cpp/src/seqwin/build_internals.cpp +++ b/cpp/src/seqwin/build_internals.cpp @@ -1,6 +1,4 @@ #include "seqwin/build_internals.hpp" -#include "seqwin/utils.hpp" -#include "seqwin/thread_pool.hpp" #include #include @@ -10,9 +8,10 @@ #include #include +#include "utils/logging.hpp" +#include "utils/thread_pool.hpp" -namespace seqwin { -namespace internal { +namespace seqwin::internal { namespace { /** @@ -400,5 +399,4 @@ std::pair merge_thread_graphs( }; } -} // namespace internal -} // namespace seqwin +} // namespace seqwin::internal diff --git a/cpp/include/seqwin/build_internals.hpp b/cpp/src/seqwin/build_internals.hpp similarity index 96% rename from cpp/include/seqwin/build_internals.hpp rename to cpp/src/seqwin/build_internals.hpp index d930867..aea68ce 100644 --- a/cpp/include/seqwin/build_internals.hpp +++ b/cpp/src/seqwin/build_internals.hpp @@ -9,12 +9,10 @@ #include "seqwin/graph.hpp" -namespace seqwin { +namespace seqwin::internal { class ThreadPool; -namespace internal { - /** * @brief Thread-local minimizer graph node. * @@ -83,6 +81,4 @@ std::pair merge_thread_graphs( bool low_memory ); - -} // namespace internal -} // namespace seqwin +} // namespace seqwin::internal diff --git a/cpp/src/seqwin/fasta_reader.cpp b/cpp/src/utils/fasta_reader.cpp similarity index 98% rename from cpp/src/seqwin/fasta_reader.cpp rename to cpp/src/utils/fasta_reader.cpp index d67f73d..90b4ee0 100644 --- a/cpp/src/seqwin/fasta_reader.cpp +++ b/cpp/src/utils/fasta_reader.cpp @@ -1,7 +1,7 @@ -#include "seqwin/fasta_reader.hpp" +#include "utils/fasta_reader.hpp" -#include #include +#include #include #include #include @@ -12,7 +12,7 @@ #include -namespace seqwin { +namespace seqwin::internal { namespace { constexpr std::size_t gz_read_buf_size = 1U << 16; @@ -212,4 +212,4 @@ std::vector read_fasta(const std::string& assembly_path) return read_plain_fasta(assembly_path); } -} // namespace seqwin +} // namespace seqwin::internal diff --git a/cpp/include/seqwin/fasta_reader.hpp b/cpp/src/utils/fasta_reader.hpp similarity index 89% rename from cpp/include/seqwin/fasta_reader.hpp rename to cpp/src/utils/fasta_reader.hpp index f93f245..ef4934e 100644 --- a/cpp/include/seqwin/fasta_reader.hpp +++ b/cpp/src/utils/fasta_reader.hpp @@ -4,7 +4,7 @@ #include #include -namespace seqwin { +namespace seqwin::internal { struct FastaRecord { std::string id; @@ -20,4 +20,4 @@ struct FastaRecord { */ std::vector read_fasta(const std::string& assembly_path); -} // namespace seqwin +} // namespace seqwin::internal diff --git a/cpp/src/seqwin/utils.cpp b/cpp/src/utils/logging.cpp similarity index 86% rename from cpp/src/seqwin/utils.cpp rename to cpp/src/utils/logging.cpp index e667119..62cba8b 100644 --- a/cpp/src/seqwin/utils.cpp +++ b/cpp/src/utils/logging.cpp @@ -1,10 +1,10 @@ -#include "seqwin/utils.hpp" +#include "utils/logging.hpp" + #include namespace py = pybind11; -namespace seqwin { -namespace internal { +namespace seqwin::internal { void log_python(const std::string& message, const std::string& level) { @@ -28,5 +28,4 @@ void log_python(const std::string& message, const std::string& level) } } -} // namespace internal -} // namespace seqwin +} // namespace seqwin::internal diff --git a/cpp/include/seqwin/utils.hpp b/cpp/src/utils/logging.hpp similarity index 78% rename from cpp/include/seqwin/utils.hpp rename to cpp/src/utils/logging.hpp index 59e4822..2421f8b 100644 --- a/cpp/include/seqwin/utils.hpp +++ b/cpp/src/utils/logging.hpp @@ -2,8 +2,7 @@ #include -namespace seqwin { -namespace internal { +namespace seqwin::internal { /** * @brief Emit a message through Python's logging module. @@ -16,5 +15,4 @@ void log_python( const std::string& level = "info" ); -} // namespace internal -} // namespace seqwin +} // namespace seqwin::internal diff --git a/cpp/include/seqwin/thread_pool.hpp b/cpp/src/utils/thread_pool.hpp similarity index 98% rename from cpp/include/seqwin/thread_pool.hpp rename to cpp/src/utils/thread_pool.hpp index dba4672..9a0d3aa 100644 --- a/cpp/include/seqwin/thread_pool.hpp +++ b/cpp/src/utils/thread_pool.hpp @@ -11,7 +11,7 @@ #include #include -namespace seqwin { +namespace seqwin::internal { /** * @brief Simple fixed-size worker pool for parallel range processing. @@ -155,4 +155,4 @@ class ThreadPool { bool stopping_ = false; }; -} // namespace seqwin +} // namespace seqwin::internal From 892f47fe97c0c119b739cf5e6d7b95ee90b198e8 Mon Sep 17 00:00:00 2001 From: mxwang66 <54554363+mxwang66@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:56:48 -0500 Subject: [PATCH 3/3] Remove redundant `NodeState.start` --- cpp/src/seqwin/build.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/src/seqwin/build.cpp b/cpp/src/seqwin/build.cpp index cd2a27a..622d7c5 100644 --- a/cpp/src/seqwin/build.cpp +++ b/cpp/src/seqwin/build.cpp @@ -63,7 +63,6 @@ struct RawKmer { struct NodeState { std::size_t count = 0; - std::size_t start = 0; std::size_t cursor = 0; std::uint32_t n_tar = 0; std::uint32_t n_neg = 0; @@ -211,7 +210,6 @@ ThreadGraph build_worker( std::size_t cursor = 0; for (auto& [hash, state] : node_map) { (void)hash; - state.start = cursor; state.cursor = cursor; cursor += state.count; } @@ -226,9 +224,11 @@ ThreadGraph build_worker( graph.nodes = NoInitArray(node_map.size()); std::size_t node_i = 0; for (const auto& [hash, state] : node_map) { + const std::size_t start = low_memory ? 0 : state.cursor - state.count; + graph.nodes[node_i++] = ThreadNode{ hash, - state.start, + start, state.count, state.n_tar, state.n_neg,