Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 2 additions & 4 deletions cpp/include/seqwin/graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ struct Kmer {
/**
* @brief Minimizer graph node for one unique minimizer hash.
*
* The `[start, stop)` range is a half-open interval into the parallel
* `Graph.kmers` and `Graph.idx` arrays for minimizers with this hash.
* The `[start, stop)` range is a half-open interval into `Graph.kmers`
* for minimizers with this hash.
*/
struct Node {
/** Hash value of the minimizers represented by this node. */
Expand Down Expand Up @@ -58,8 +58,6 @@ struct Edge {
struct Graph {
/** Minimizer occurrences in all assemblies, grouped and sorted by hash. */
NoInitArray<Kmer> kmers;
/** Parallel to `kmers` and stores each minimizer's original generation index, ordered by genomic position. */
NoInitArray<std::size_t> idx;
/** Sorted by hash. */
NoInitArray<Node> nodes;
/** Sorted by hash. */
Expand Down
21 changes: 5 additions & 16 deletions cpp/include/seqwin/helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ class ThreadPool;
/**
* @brief Thread-local minimizer graph node.
*
* `start` and `count` define a range in `ThreadGraph.idx`.
* Values in `ThreadGraph.idx` index into `ThreadGraph.kmers`.
* `start` and `count` define a range in `ThreadGraph.kmers`.
*/
struct ThreadNode {
/** Hash value of the minimizers represented by this node. */
std::uint64_t hash;
/** Start of entries in `ThreadGraph.idx` for this node. */
/** Start of entries in `ThreadGraph.kmers` for this node. */
std::size_t start;
/** Number of entries in `ThreadGraph.idx` for this node. */
/** Number of entries in `ThreadGraph.kmers` for this node. */
std::size_t count;
/** Number of target assemblies containing this minimizer hash. */
std::uint32_t n_tar;
Expand All @@ -36,17 +35,8 @@ struct ThreadNode {
* @brief Partial minimizer graph built by one worker thread.
*/
struct ThreadGraph {
/**
* Minimizer occurrences generated by this worker, stored in genomic position order.
* Uses `std::vector` for `push_back()` support (exact kmer count is not known).
*/
std::vector<Kmer> kmers;
/**
* Indices into `kmers`, grouped by minimizer hash.
* The order is stable within each hash group, preserving the original genomic
* position order of minimizers with the same hash.
*/
NoInitArray<std::size_t> idx;
/** Minimizer occurrences generated by this worker, grouped by hash. */
NoInitArray<Kmer> kmers;
/** Unsorted. */
NoInitArray<ThreadNode> nodes;
/** Unsorted. */
Expand Down Expand Up @@ -88,7 +78,6 @@ Graph merge_thread_graphs(

Graph filter_kmers(
const Kmer* kmers,
const std::size_t* idx,
const Node* nodes,
std::size_t n_nodes,
std::vector<std::uint64_t> used_hashes
Expand Down
11 changes: 2 additions & 9 deletions cpp/src/bindings/python_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ PYBIND11_MODULE(_core, m) {
}

auto kmers = array_to_numpy(std::move(graph.kmers));
auto idx = array_to_numpy(std::move(graph.idx));
auto nodes = array_to_numpy(std::move(graph.nodes));
auto edges = array_to_numpy(std::move(graph.edges));
auto record_offsets = array_to_numpy(std::move(graph.record_offsets));
Expand All @@ -79,7 +78,7 @@ PYBIND11_MODULE(_core, m) {
ids_by_assembly.append(ids_tuple);
}

return py::make_tuple(kmers, idx, nodes, edges, record_offsets, ids_by_assembly);
return py::make_tuple(kmers, nodes, edges, record_offsets, ids_by_assembly);
},
py::arg("assembly_paths"),
py::arg("kmerlen"),
Expand All @@ -90,16 +89,13 @@ PYBIND11_MODULE(_core, m) {

m.def("_filter_kmers_native",
[](py::array_t<seqwin::Kmer, py::array::c_style> kmers,
py::array_t<std::size_t, py::array::c_style> idx,
py::array_t<seqwin::Node, py::array::c_style> nodes,
const std::vector<std::uint64_t>& used_hashes
) {
auto kmers_buf = kmers.request();
auto idx_buf = idx.request();
auto nodes_buf = nodes.request();

const auto* kmers_ptr = static_cast<const seqwin::Kmer*>(kmers_buf.ptr);
const auto* idx_ptr = static_cast<const std::size_t*>(idx_buf.ptr);
const auto* nodes_ptr = static_cast<const seqwin::Node*>(nodes_buf.ptr);
const auto n_nodes = static_cast<std::size_t>(nodes_buf.shape[0]);

Expand All @@ -108,21 +104,18 @@ PYBIND11_MODULE(_core, m) {
py::gil_scoped_release release;
graph = seqwin::filter_kmers(
kmers_ptr,
idx_ptr,
nodes_ptr,
n_nodes,
used_hashes
);
}

auto kmers_new = array_to_numpy(std::move(graph.kmers));
auto idx_new = array_to_numpy(std::move(graph.idx));
auto nodes_new = array_to_numpy(std::move(graph.nodes));

return py::make_tuple(kmers_new, idx_new, nodes_new);
return py::make_tuple(kmers_new, nodes_new);
},
py::arg("kmers").noconvert(),
py::arg("idx").noconvert(),
py::arg("nodes").noconvert(),
py::arg("used_hashes")
);
Expand Down
34 changes: 19 additions & 15 deletions cpp/src/seqwin/graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ namespace {

constexpr std::size_t map_reserve_divisor = 100;

struct RawKmer {
std::uint64_t hash;
Kmer kmer;
};

struct NodeState {
std::size_t count = 0;
std::size_t start = 0;
Expand Down Expand Up @@ -60,17 +65,15 @@ ThreadGraph build_worker(
),
windowsize
);
std::vector<RawKmer> raw_kmers;
raw_kmers.reserve(n_kmers_est);

ThreadGraph graph;
graph.kmers.reserve(n_kmers_est);
graph.record_offsets.reserve(end_assembly - start_assembly + 1);
graph.record_offsets.push_back(0);
graph.ids_by_assembly.reserve(end_assembly - start_assembly);
graph.start_assembly = start_assembly;

std::vector<std::uint64_t> hashes; // Used to build ThreadGraph.idx
hashes.reserve(n_kmers_est);

// Reserving for unordered_map will actually allocate physical memory
std::unordered_map<std::uint64_t, NodeState> node_map;
std::unordered_map<EdgeKey, EdgeState, EdgeKeyHash> edge_map;
Expand Down Expand Up @@ -99,11 +102,13 @@ ThreadGraph build_worker(
const auto mins = btllib::minimize_sequence(record.sequence, kmerlen, windowsize);

for (const auto& m : mins) {
graph.kmers.push_back(Kmer{
static_cast<std::uint32_t>(m.pos),
static_cast<std::uint32_t>(record_idx)
raw_kmers.push_back(RawKmer{
m.out_hash,
Kmer{
static_cast<std::uint32_t>(m.pos),
static_cast<std::uint32_t>(record_idx)
}
});
hashes.push_back(m.out_hash);

// Add this minimizer to an existing node, or create a new node
auto [node_it, node_inserted] = node_map.try_emplace(m.out_hash);
Expand Down Expand Up @@ -151,21 +156,20 @@ ThreadGraph build_worker(
}
std::unordered_map<EdgeKey, EdgeState, EdgeKeyHash>().swap(edge_map);

// Build ThreadGraph.idx (grouped by minimizer hash)
graph.idx = NoInitArray<std::size_t>(graph.n_kmers);
// Build ThreadGraph.kmers (grouped by hash)
graph.kmers = NoInitArray<Kmer>(graph.n_kmers);
std::size_t cursor = 0;
for (auto& [hash, state] : node_map) {
(void)hash;
state.start = cursor;
state.cursor = cursor;
cursor += state.count;
}
for (std::size_t i = 0; i < graph.n_kmers; ++i) {
const auto hash = hashes[i];
auto node_it = node_map.find(hash);
graph.idx[node_it->second.cursor++] = i;
for (const auto& rk : raw_kmers) {
auto node_it = node_map.find(rk.hash);
graph.kmers[node_it->second.cursor++] = rk.kmer;
}
std::vector<std::uint64_t>().swap(hashes);
std::vector<RawKmer>().swap(raw_kmers);

graph.nodes = NoInitArray<ThreadNode>(node_map.size());
std::size_t node_i = 0;
Expand Down
Loading
Loading