Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
320 commits
Select commit Hold shift + click to select a range
8710cf5
skip extend if end trim can't be filled
hmusta Oct 26, 2024
878e665
minor
hmusta Oct 26, 2024
7237b95
test
hmusta Oct 27, 2024
4d940b4
minor
hmusta Oct 27, 2024
0442497
per anchor
hmusta Oct 27, 2024
d11e6c0
don't merge anchors after distances have been calculated
hmusta Oct 27, 2024
ebe6dbf
fixes
hmusta Oct 27, 2024
5946b2c
added test
hmusta Oct 27, 2024
15a4de9
minor
hmusta Oct 27, 2024
05031c0
don't extend beyond anchors that have been hit
hmusta Oct 27, 2024
28d9cdb
minor
hmusta Oct 27, 2024
b61d749
fixes for multi-node seeds
hmusta Oct 27, 2024
9b1f21e
silence messages
hmusta Oct 27, 2024
0ac0728
cleanup
hmusta Oct 27, 2024
4028016
change output format
hmusta Oct 27, 2024
50f41bf
testing alignment in cli
hmusta Oct 27, 2024
be63707
always terminate at query end
hmusta Oct 28, 2024
6949413
store partial alignments
hmusta Oct 28, 2024
b41ce64
fixes
hmusta Oct 28, 2024
f92295a
this works
hmusta Oct 28, 2024
34ade4a
suppress messages
hmusta Oct 28, 2024
ef4ab71
logging
hmusta Oct 28, 2024
32c08f9
extra checks
hmusta Oct 28, 2024
6e43489
lots of fixes
hmusta Oct 28, 2024
64cf90a
test
hmusta Oct 28, 2024
1a361b3
fixed
hmusta Oct 28, 2024
93064a9
suppress
hmusta Oct 28, 2024
14ee597
better term condition
hmusta Oct 28, 2024
e6d6783
minor
hmusta Oct 28, 2024
30d9a1e
cut chaining short
hmusta Oct 28, 2024
572404e
fix sub-k seeding
hmusta Oct 29, 2024
0cf2741
minor
hmusta Oct 29, 2024
89d2b70
Merge remote-tracking branch 'origin/master' into align_refactor
hmusta Jul 2, 2025
3f958f1
Merge remote-tracking branch 'origin/master' into align_refactor
hmusta Jul 3, 2025
2ffffba
updates
hmusta Jul 3, 2025
324bc17
test
Jul 5, 2025
c17defb
clang fixes
hmusta Jul 10, 2025
3af3eb6
test
hmusta Jul 11, 2025
9a9e0b7
test
hmusta Jul 15, 2025
6352fec
check2
hmusta Jul 16, 2025
199ef78
fixes
hmusta Jul 17, 2025
9c831c4
Add back inserts
hmusta Jul 17, 2025
017b0b3
fix scoring in a test
hmusta Jul 17, 2025
0995fb8
only one aln per forward
hmusta Jul 17, 2025
e98c602
fixes
hmusta Jul 18, 2025
4a1aca2
Assume that anchors are already merged
hmusta Jul 18, 2025
309674e
test
hmusta Jul 18, 2025
f55ab16
fix
hmusta Jul 18, 2025
447fdd2
test
hmusta Jul 18, 2025
be4090f
corner case
hmusta Jul 18, 2025
a1ca2c9
fix tests
hmusta Jul 20, 2025
af9370f
ttt
hmusta Jul 20, 2025
161dbc5
fix
hmusta Jul 20, 2025
6da803c
more fixes
hmusta Jul 20, 2025
e6a75f6
fix
hmusta Jul 21, 2025
d833cbe
fixes
hmusta Jul 21, 2025
bd5b57b
fix unit test
hmusta Jul 21, 2025
79353dd
fix corner cases
hmusta Jul 21, 2025
6ae6648
fix
hmusta Jul 21, 2025
c6ea34b
addd test
hmusta Jul 21, 2025
ae1f7d3
another test
hmusta Jul 21, 2025
0772134
primary graphs
hmusta Jul 21, 2025
fac1a80
add
hmusta Jul 21, 2025
a9570c3
comments
hmusta Jul 21, 2025
9c69124
comment out debugging messages
hmusta Jul 21, 2025
53c452d
cleanup
hmusta Jul 21, 2025
b37af91
annot redone
hmusta Jul 22, 2025
e161e98
labeled extension
hmusta Jul 29, 2025
8ca28d9
fixes for indels
hmusta Jul 30, 2025
c845085
more fixes
hmusta Jul 30, 2025
d0625b9
don't stop at score 0 if max dist known
hmusta Jul 30, 2025
3b7d8fc
fix traversal dist
hmusta Jul 30, 2025
1bbefd3
gap fixes
hmusta Jul 31, 2025
8bdd4f1
fix gap
hmusta Jul 31, 2025
b916d26
simplification
hmusta Jul 31, 2025
21956ed
fix tests
hmusta Jul 31, 2025
10e4002
don't try to chain through complex regions yet
hmusta Jul 31, 2025
a63446c
fix chaining check
hmusta Jul 31, 2025
16990aa
unit test fixes for non-succinct
hmusta Jul 31, 2025
a37ca4c
fix test
hmusta Jul 31, 2025
ac1bf52
cleanup, easier traversal
hmusta Jul 31, 2025
0de88e5
minor
hmusta Jul 31, 2025
fe8f0ae
test
hmusta Jul 31, 2025
e7fb79d
consider labels when chaining
hmusta Jul 31, 2025
a106bca
fix CanonicalDBG suffix seeding
hmusta Jul 31, 2025
1245348
fix suffix seeding for CanonicalDBG
hmusta Jul 31, 2025
536a6cc
fix unit test
hmusta Jul 31, 2025
f06e939
disable debug messages
hmusta Jul 31, 2025
96c8d53
test alignments without actual alignment
hmusta Aug 1, 2025
35f9ef9
cleanup
hmusta Aug 1, 2025
5c2a58b
cli
hmusta Aug 1, 2025
12ca162
seeding and align-free connection revisions
hmusta Aug 1, 2025
9a2ad21
don't bother traversing in noalign mode
hmusta Aug 1, 2025
ecda0a7
test
hmusta Aug 1, 2025
c1b183a
early cutoff
hmusta Aug 1, 2025
5ddfb1b
work with existing spelling
hmusta Aug 2, 2025
4c5aa13
store deleted chars
hmusta Aug 2, 2025
19f35b5
no path spelling when connecting without alignment
hmusta Aug 2, 2025
5fcf346
cleanup
hmusta Aug 2, 2025
75afb5e
flags for disabling anchor connection and extension
hmusta Aug 2, 2025
65948e0
move logging messages
hmusta Aug 2, 2025
67a0069
fix unit tests
hmusta Aug 3, 2025
0468e9d
fix insert detection
hmusta Aug 3, 2025
fce5428
skeleton for SSHash subk seeding
hmusta Aug 3, 2025
2185eb1
cleanup
hmusta Aug 3, 2025
1880c4d
better early cutoff
hmusta Aug 3, 2025
0ac6a62
fix backwards traversal
hmusta Aug 3, 2025
8272e66
avoid unnecessary traversal
hmusta Aug 3, 2025
1f7d9f7
test
hmusta Aug 3, 2025
a0419ce
handle dels differently
hmusta Aug 3, 2025
533c2f5
fix unit test
hmusta Aug 3, 2025
aabe2c0
cleanup
hmusta Aug 3, 2025
10ea638
don't bother aligning to extra exact matching nodes in a seed
hmusta Aug 3, 2025
eeed06f
more messages
hmusta Aug 3, 2025
a908fe4
alignment merging for longer seeds
hmusta Aug 3, 2025
e0d0a20
another early cutoff
hmusta Aug 3, 2025
1a24002
throw exception if failed to connect
hmusta Aug 3, 2025
e605059
another chaining check
hmusta Aug 3, 2025
1218d3c
insert check. disable unit tests with too much exploration
hmusta Aug 3, 2025
6550d19
don't force a seed to fully match when connecting anchors
hmusta Aug 3, 2025
d310b4c
more logging
hmusta Aug 3, 2025
0560c26
add back checks
hmusta Aug 3, 2025
079b621
more logging
hmusta Aug 3, 2025
c2f7f68
test
hmusta Aug 3, 2025
4069156
more logging
hmusta Aug 3, 2025
86449d7
extra inserted
hmusta Aug 4, 2025
39f2d5e
less BFS
hmusta Aug 4, 2025
30b4f06
heap for traversal
hmusta Aug 4, 2025
ff5f7b5
don't forward create if we're done
hmusta Aug 4, 2025
81f81de
more robust unit test
hmusta Aug 4, 2025
19a187d
less redundant matching
hmusta Aug 4, 2025
dceae4b
better mismatch scoring
hmusta Aug 4, 2025
34bfa67
more messages
hmusta Aug 4, 2025
3df1fcf
fix oob
hmusta Aug 4, 2025
246c29c
skip anchors that will obviously be discarded
hmusta Aug 4, 2025
bed9913
don't store scores with matches. don't create redundant anchors
hmusta Aug 5, 2025
4b217d8
less traversal
hmusta Aug 5, 2025
4baadec
much less redundant seeding
hmusta Aug 5, 2025
7f1ed00
less backwards traversal
hmusta Aug 5, 2025
d843f6a
cleanup
hmusta Aug 5, 2025
16787e6
more logging
hmusta Aug 5, 2025
bc3fb20
test for long gaps
hmusta Aug 5, 2025
dcd430b
minor
hmusta Aug 5, 2025
3656970
use std::string_view in traverse. add traverse_back
hmusta Aug 6, 2025
10e01fc
use string-based traversal functions
hmusta Aug 6, 2025
d79b098
pretraverse, then fetch annotations
hmusta Aug 6, 2025
2f49143
more logging
hmusta Aug 6, 2025
76bd6bf
print labels
hmusta Aug 6, 2025
ae15110
more logging
hmusta Aug 6, 2025
c6b843b
print coords
hmusta Aug 6, 2025
78eac2a
don't include dummy nodes as anchors
hmusta Aug 6, 2025
2f8f691
simplify annotation buffer fetch code
hmusta Aug 6, 2025
56ea4d8
only queue and fetch annotations if not previously found
hmusta Aug 6, 2025
a0bcd35
test
hmusta Aug 6, 2025
920f3f5
fix path queueing
hmusta Aug 6, 2025
6ed6678
discard anchors and labeled anchors if their coverage is too low
hmusta Aug 6, 2025
21ba642
fix creation of empty anchors
hmusta Aug 7, 2025
70856dc
don't check labels for DeBruijnGraph::npos
hmusta Aug 7, 2025
0d89505
fix traversal to avoid dummy nodes
hmusta Aug 7, 2025
9d08399
minor
hmusta Aug 7, 2025
56cdad9
Merge remote-tracking branch 'origin/master' into align_refactor
hmusta Aug 7, 2025
55617db
build aln_graph on the fly
hmusta Aug 7, 2025
ecdc4b9
handle label interesection
hmusta Aug 7, 2025
f3ffa0e
store labels
hmusta Aug 7, 2025
2e42592
handle label loss during alignment
hmusta Aug 7, 2025
fcea5f9
name variables used to store traversed distance
hmusta Aug 7, 2025
96002b0
refactor
hmusta Aug 7, 2025
59dfe78
store coords in alignment
hmusta Aug 7, 2025
054d5a0
handle coordinates
hmusta Aug 7, 2025
219a3ca
mark coords in alignments
hmusta Aug 8, 2025
03f3938
track coordinates, pt2
hmusta Aug 8, 2025
27534e7
simple alignment wrapper
hmusta Aug 8, 2025
b4e5963
preliminary sshash subk seeding
hmusta Aug 8, 2025
bda39f8
test
hmusta Aug 9, 2025
40f496a
fixes
hmusta Aug 9, 2025
842aa72
t
hmusta Aug 9, 2025
1e00dd8
add labeled tests
hmusta Aug 9, 2025
c5c1e69
fix labeled unit tests
hmusta Aug 11, 2025
13727fd
Merge remote-tracking branch 'origin/master' into align_refactor
hmusta Aug 11, 2025
9aa9448
update sshash
hmusta Aug 11, 2025
106235f
minor
hmusta Aug 11, 2025
429e9ef
support getting contig IDs
hmusta Aug 12, 2025
95d33e7
minor
hmusta Aug 12, 2025
db74289
traversal distances when in same contig
hmusta Aug 12, 2025
44c2d3f
fix contig id calculation at unitig boundaries
hmusta Aug 12, 2025
5b01711
use coords when available for chaining
hmusta Aug 12, 2025
8bcd4ad
pass tmp dir to SSHash constructor
hmusta Aug 13, 2025
699df28
minor
hmusta Aug 13, 2025
9ee73e0
support sending annotations to different files by header
hmusta Aug 13, 2025
087b3ab
check files before loading and serializing
hmusta Aug 13, 2025
9693ab8
fix for fwd extensions that cdon't complete the alignment the startin…
hmusta Aug 13, 2025
382011f
reverse complement all alignments in unit tests
hmusta Aug 27, 2025
a45d353
terminate branch if goes out of bounds
hmusta Aug 28, 2025
1585b1c
extra checks
hmusta Aug 28, 2025
9550593
fixes
hmusta Aug 28, 2025
8a1401c
end trim fix
hmusta Aug 28, 2025
33fe3fe
fix check
hmusta Aug 28, 2025
ddedb05
fix CanonicalDBG::in_graph
hmusta Aug 28, 2025
8cde47c
fix
hmusta Aug 28, 2025
243d87d
more pessimistic gap scoring
hmusta Aug 28, 2025
32cc15f
update reverse_complement_seq_path
hmusta Aug 28, 2025
3e4e32c
don't connect anchors if the target anchor would go out of the query …
hmusta Aug 28, 2025
b63f776
discard RC seeds for canonical graphs
hmusta Aug 28, 2025
bd33d30
don't add RC seeds for canonical graphs
hmusta Aug 28, 2025
c498af3
merge chains shared by labels
hmusta Aug 28, 2025
04dda5e
better merging
hmusta Aug 28, 2025
c62a1d2
handle corner case: big gap in query, both seeds have end trimming, n…
hmusta Aug 29, 2025
fb99df1
avoid redundant map_to_node
hmusta Aug 30, 2025
50e501f
less redundant sshash calculations
hmusta Aug 30, 2025
ad90135
don't fetch annotations if the traversal will not be used anyway
hmusta Aug 31, 2025
9a2dbad
fewer comparisons
hmusta Aug 31, 2025
931d5a3
check both orientations in sshash
hmusta Sep 1, 2025
3fd58a4
look backwards in window
hmusta Sep 1, 2025
8556a72
Merge remote-tracking branch 'origin/master' into align_refactor
hmusta Nov 13, 2025
ebe5b2e
update server to use new alignment code
hmusta Nov 13, 2025
73113c2
fixes
hmusta Nov 20, 2025
d3ee863
more fixes
hmusta Nov 20, 2025
2d17347
pass seed length to aligner
hmusta Nov 20, 2025
a993f57
Merge Harun's align_refactor tip 2d1734717 as opt-in engine (local tr…
adamant-pwn Oct 27, 2026
841f1a8
Fix dangling-pointer segfault in LabeledSeeder::get_anchors()
sayangoswami Jul 6, 2026
b327104
Build the opt-in aligner against master's SSHash pin
adamant-pwn Oct 27, 2026
b3e0c39
Skip the canonical-graph alignment test for protein
adamant-pwn Oct 27, 2026
9fbe0a0
Add Sayan's pymetagraph binding on the opt-in aligner
adamant-pwn Oct 27, 2026
ddadfe3
Fix parallel SSHash test builds and alignment review findings
adamant-pwn Oct 27, 2026
9e37abd
Map SSHash reverse-complement paths through the generic lookup
adamant-pwn Oct 27, 2026
ead1ea8
Share the aligner configuration between both engines
adamant-pwn Oct 28, 2026
efdb479
Map oriented nodes to annotation rows in one place
adamant-pwn Oct 28, 2026
93666f0
Merge the shared annotation key mapper (#683)
adamant-pwn Oct 28, 2026
eeb3254
Map the new engine's annotation rows through AnnotationKeyMapper
adamant-pwn Oct 28, 2026
bf8b00f
Test primary graphs as the test helper builds them
adamant-pwn Oct 28, 2026
ed9ebaa
Merge the mapper test fix from #683
adamant-pwn Oct 28, 2026
13684d8
Test primary graphs only for DNA
adamant-pwn Oct 28, 2026
98c3751
Merge the protein test guard from #683
adamant-pwn Oct 28, 2026
1e3a976
Merge master: SSHash v6 (#684)
adamant-pwn Oct 28, 2026
65d93e9
Merge master: AnnotationKeyMapper (#683)
adamant-pwn Oct 28, 2026
3c67d24
Fix query reverse complement and connect_anchors forwarding
adamant-pwn Oct 28, 2026
26ff7c4
Report failed reads as unmapped in pymetagraph query_batch
adamant-pwn Oct 28, 2026
641ee9d
Merge master: SSHash input validation (#682)
adamant-pwn Oct 28, 2026
305a060
Qualify the SSHash index conversion in the duplicate check
adamant-pwn Oct 28, 2026
58da9a1
Test labels of seeds shorter than k in both aligners
adamant-pwn Oct 28, 2026
bca751a
Bound sub-k seeding by the mapped k-mers and correct the label tests
adamant-pwn Oct 28, 2026
19c2080
Remove dead code from the redone engine and its tests
adamant-pwn Oct 28, 2026
57c8578
Tighten the sub-k label tests
adamant-pwn Oct 28, 2026
ea3f4f0
Reuse align::spell_path and drop unused hash specialisations
adamant-pwn Oct 28, 2026
a1b98dd
Keep the unitig lookup on DBGSSHash
adamant-pwn Oct 28, 2026
db46f9c
Read connect_anchors and extend_chains in the binding only
adamant-pwn Oct 28, 2026
fe87445
Describe how spell_path spells npos nodes
adamant-pwn Oct 28, 2026
ac3e226
Honour the node budget and seeds-per-locus cap in the redone engine
adamant-pwn Oct 28, 2026
e9677f2
Apply the node budget to costlier entries at the search front
adamant-pwn Oct 28, 2026
51cf399
Fix assertion failures in Debug builds of the redone engine
adamant-pwn 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ database-cli
*._*
metagraph/build*
metagraph/**/build*
metagraph/api/python/pymetagraph/pymetagraph/_pymetagraph_core*
*.egg-info
.*.swp
.ipynb_checkpoints
*lsf.o*
Expand Down
80 changes: 76 additions & 4 deletions metagraph/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ include(CheckFunctionExists)
include(CheckLibraryExists)
include(CMakeListsHelpers.txt)

# Building the pymetagraph Python extension requires every static library in the
# link chain (including vendored deps and HTSLIB) to be position-independent.
# This is opt-in so the default static `metagraph` binary build is unaffected.
option(BUILD_PYTHON_BINDINGS "Build the pymetagraph pybind11 extension" OFF)
if(BUILD_PYTHON_BINDINGS)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# jemalloc can exhaust static TLS when the extension is imported.
# Disable it for this build directory; ordinary builds retain their allocator.
set(CMAKE_DISABLE_FIND_PACKAGE_Jemalloc ON)
endif()

enable_testing()
include(GoogleTest)

Expand Down Expand Up @@ -202,6 +214,7 @@ configure_file(CMakeListsKMC.txt.in KMC/CMakeLists.txt @ONLY)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}"
-DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER}
-DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER}
-DCMAKE_POSITION_INDEPENDENT_CODE=${CMAKE_POSITION_INDEPENDENT_CODE}
.
RESULT_VARIABLE result
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/KMC
Expand Down Expand Up @@ -313,7 +326,12 @@ if(APPLE)
get_filename_component(ICU_DATA_LIBRARY_PATH ${ICU_DATA_LIBRARY} DIRECTORY)
endif()

set(Boost_USE_STATIC_LIBS ON)
# Distribution static Boost libraries may lack PIC.
if(BUILD_PYTHON_BINDINGS AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(Boost_USE_STATIC_LIBS OFF)
else()
set(Boost_USE_STATIC_LIBS ON)
endif()
find_package(Boost REQUIRED COMPONENTS iostreams)
find_library(LIBDEFLATE_LIBRARY NAMES deflate HINTS ENV LIBRARY_PATH
PATHS ${HOMEBREW_DIR}/lib REQUIRED)
Expand Down Expand Up @@ -386,17 +404,29 @@ else()
endif()

include(ExternalProject)
# HTSLIB is built via autoconf/make, outside CMAKE_POSITION_INDEPENDENT_CODE's reach,
# so -fPIC has to be injected into its CFLAGS/CPPFLAGS by hand when building the
# Python extension (see BUILD_PYTHON_BINDINGS above).
set(HTSLIB_PIC_FLAG "")
if(BUILD_PYTHON_BINDINGS)
set(HTSLIB_PIC_FLAG "-fPIC ")
endif()
if(CMAKE_GENERATOR MATCHES "Makefiles")
set(HTSLIB_MAKE "$(MAKE)")
else()
find_program(HTSLIB_MAKE NAMES gmake make REQUIRED)
endif()
ExternalProject_Add(HTSLIB
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external-libraries/htslib
PREFIX "${PROJECT_BINARY_DIR}/HTSLIB-build"
CONFIGURE_COMMAND
${CMAKE_COMMAND} -E env
"CC=${HTSLIB_CC}"
"CPPFLAGS=${OSX_ARCH_FLAGS}\ -I${CMAKE_CURRENT_SOURCE_DIR}/external-libraries/zlib"
"CFLAGS=${OSX_ARCH_FLAGS}\ -I${CMAKE_CURRENT_SOURCE_DIR}/external-libraries/zlib"
"CPPFLAGS=${HTSLIB_PIC_FLAG}${OSX_ARCH_FLAGS}\ -I${CMAKE_CURRENT_SOURCE_DIR}/external-libraries/zlib"
"CFLAGS=${HTSLIB_PIC_FLAG}${OSX_ARCH_FLAGS}\ -I${CMAKE_CURRENT_SOURCE_DIR}/external-libraries/zlib"
"LDFLAGS=${OSX_ARCH_FLAGS}\ -L${PROJECT_BINARY_DIR}/external-libraries/zlib"
/bin/sh -c "autoreconf -i ${CMAKE_CURRENT_SOURCE_DIR}/external-libraries/htslib && exec ${CMAKE_CURRENT_SOURCE_DIR}/external-libraries/htslib/configure --disable-libcurl --disable-bz2 --disable-lzma"
BUILD_COMMAND $(MAKE) -j${N} lib-static
BUILD_COMMAND ${HTSLIB_MAKE} -j${N} lib-static
#INSTALL_COMMAND $(MAKE) install prefix=${HTSLIB_INSTALL} # this installs both static and dynamic
# Install only static lib to avoid the dependency
INSTALL_COMMAND
Expand Down Expand Up @@ -434,6 +464,8 @@ list(REVERSE metagraph_core_files) # for compiling faster
list(FILTER metagraph_core_files EXCLUDE REGEX ".*\\._.*")
list(FILTER metagraph_core_files EXCLUDE REGEX ".*main.cpp")
list(FILTER metagraph_core_files EXCLUDE REGEX ".*/cli/.*")
list(FILTER metagraph_core_files EXCLUDE REGEX ".*/pybindings/.*")

add_library(metagraph-core STATIC ${metagraph_core_files})

# Set up include directories for metagraph-core
Expand Down Expand Up @@ -577,6 +609,46 @@ if(METAGRAPH_BUILD_EXECUTABLE)
add_dependencies(metagraph link_target)
endif()

#-------------------
# pymetagraph Python extension
#-------------------
if(BUILD_PYTHON_BINDINGS)
find_package(Python 3.9 REQUIRED COMPONENTS Interpreter Development.Module)
execute_process(
COMMAND ${Python_EXECUTABLE} -m pybind11 --cmakedir
OUTPUT_VARIABLE pybind11_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)
find_package(pybind11 CONFIG REQUIRED)

pybind11_add_module(_pymetagraph_core src/pybindings/pymetagraph_bindings.cpp)
target_link_libraries(_pymetagraph_core PRIVATE metagraph-cli metagraph-core)
add_dependencies(_pymetagraph_core HTSLIB)
install(TARGETS _pymetagraph_core DESTINATION pymetagraph COMPONENT _pymetagraph_core)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# Use an inherited RPATH so Boost's own shared Boost dependencies also
# resolve from the wheel, without relying on a matching system Boost.
target_link_options(_pymetagraph_core PRIVATE "LINKER:--disable-new-dtags")
set_target_properties(_pymetagraph_core PROPERTIES INSTALL_RPATH "$ORIGIN/.libs")
install(CODE "
set(boost_library \"$<TARGET_FILE:Boost::iostreams>\")
file(GET_RUNTIME_DEPENDENCIES
LIBRARIES \"\${boost_library}\"
RESOLVED_DEPENDENCIES_VAR boost_dependencies
UNRESOLVED_DEPENDENCIES_VAR missing_boost_dependencies
PRE_INCLUDE_REGEXES \"^libboost_\"
PRE_EXCLUDE_REGEXES \".*\")
if(missing_boost_dependencies)
message(FATAL_ERROR \"Unresolved Boost runtime libraries: \${missing_boost_dependencies}\")
endif()
file(INSTALL \"\${boost_library}\" \${boost_dependencies}
DESTINATION \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/pymetagraph/.libs\"
TYPE SHARED_LIBRARY FOLLOW_SYMLINK_CHAIN)
" COMPONENT _pymetagraph_core)
endif()
endif()


#-------------------
# install library
Expand Down
121 changes: 121 additions & 0 deletions metagraph/api/python/pymetagraph/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# pymetagraph

A [Readfish](https://github.com/LooseLab/readfish) `Aligner` plugin backed by a prebuilt
metagraph graph + annotation index. By default (`method="query"`) it reports label-presence hits
(which reference sequence(s) a read's k-mers match, above a discovery/presence-fraction threshold)
rather than real base-level alignments, so `r_st`/`r_en`/`strand` on the returned alignment
objects are placeholder values. Set `method="align"` for real seed-and-extend alignment with
actual coordinates and orientation instead — see [Usage](#usage) below.

## Building

Unlike a typical small pybind11 project, this is not an instant pip install: metagraph needs its
git submodules checked out and takes a while to compile from scratch. But it *is* a plain
`pip install`-driven build — pip's normal build isolation fetches `scikit-build-core` and
`pybind11` (this project's `[build-system].requires`) into a throwaway env on its own, so you
don't need to pre-install anything Python-side yourself. Recommended workflow:

1. From the repo root, make sure submodules are initialized (see the top-level README).
2. `sdsl-lite` is pre-built separately (via its own `install.sh`, not by the main CMake project)
and the resulting `external-libraries/sdsl-lite/lib/libsdsl.a` is **not** position-independent
by default, which breaks the final link into a `.so` even with `BUILD_PYTHON_BINDINGS=ON`. It
only needs to be rebuilt once (PIC static objects link fine into the normal static `metagraph`
binary too, so this doesn't need to be undone for the default build):
```
cd metagraph/external-libraries/sdsl-lite/build
./clean.sh
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_INSTALL_PREFIX=$(cd .. && pwd) ..
make -j$(nproc) && make install
```
3. Build and install — this is the only step most users need, and it never requires running
`cmake` yourself:
```
cd metagraph/api/python/pymetagraph
pip install -e .
```
Under the hood, pip's `scikit-build-core` build backend invokes CMake for you (with
`-DBUILD_PYTHON_BINDINGS=ON` already wired in via this project's `pyproject.toml`) inside a
throwaway build environment, so you don't need CMake installed in your active environment or
any familiarity with its flags.

If you're in a conda environment that ships its own (shared-only) Boost, CMake may pick that up
ahead of the system Boost metagraph needs (static libs), failing with `boost_iostreams_FOUND to
FALSE`. Since pip's build backend is the one invoking CMake here, pass the override through the
standard `CMAKE_ARGS` environment variable, which `scikit-build-core` forwards to it:
```
CMAKE_ARGS="-DBoost_DIR=/usr/lib/x86_64-linux-gnu/cmake/Boost-1.83.0" pip install -e .
```
(adjust the path/version to whatever `find /usr/lib -name 'BoostConfig.cmake'` reports on your
system).

### Faster iteration on the C++ side (advanced, driving CMake directly)

The steps above are a *different, independent path* from what follows: `pip install -e .` never
invokes `cmake` on your command line, it happens inside pip's own build backend. The workflow
below is only for developers iterating on the C++ binding itself, who want incremental rebuilds
without re-running pip's build backend (and its temp-env setup) on every change. It talks to CMake
directly, so **it needs `-D` flags spelled out explicitly** — none of `pyproject.toml`'s settings
or pip's `CMAKE_ARGS` env var apply here, since pip isn't involved:
```
cmake -S metagraph -B metagraph/api/python/pymetagraph/build -DBUILD_PYTHON_BINDINGS=ON
# add -DBoost_DIR=... too if you hit the same conda/Boost conflict as above
cmake --build metagraph/api/python/pymetagraph/build --target _pymetagraph_core -j$(nproc)
cd metagraph/api/python/pymetagraph
pip install -e . --no-build-isolation # requires scikit-build-core + pybind11 pre-installed
```

### Note: jemalloc is disabled for this extension

`BUILD_PYTHON_BINDINGS=ON` also sets `CMAKE_DISABLE_FIND_PACKAGE_Jemalloc=ON`, so
`_pymetagraph_core` is built without jemalloc even if it's installed on the system. This is
deliberate: dlopen()-ing a library linked against jemalloc can fail at import time with `cannot
allocate memory in static TLS block` (a glibc static-TLS-space limitation), which would otherwise
require every user of this plugin to remember `LD_PRELOAD=libjemalloc.so.2` before launching
Readfish. Since this plugin is aimed at non-power-users, it trades away folly's jemalloc-backed
allocator optimizations for an extension that just imports cleanly with no extra steps. This only
affects `_pymetagraph_core` — the regular static `metagraph` CLI binary still picks up jemalloc
normally when built without `BUILD_PYTHON_BINDINGS`.

## Usage

Two unrelated query methods are available via the `method` setting:

- `"query"` (default): label-presence hits via `AnnotatedDBG::get_top_labels` — the same
primitive backing the CLI's `query` command. Fast and tolerant of sequencing errors, but
`r_st`/`r_en`/`strand` are dummy values (`0`, `len(seq)`, `+1`); only `ctg` (the matched label)
is meaningful.
```toml
[mapper_settings.pymetagraph]
input = "/path/to/graph.dbg"
annotator = "/path/to/graph.column.annodbg"
threads = 4
method = "query"
num_top_labels = 1
discovery_fraction = .4
presence_fraction = .1
```
- `"align"`: real seed-and-extend alignment via metagraph's alignment_redone module. Neither the
`metagraph align` CLI command nor the server's `/align` endpoint uses this engine. Slower, but `r_st`/`r_en`/`strand` reflect the
actual matched region and orientation of the read.
```toml
[mapper_settings.pymetagraph]
input = "/path/to/graph.dbg"
annotator = "/path/to/graph.column.annodbg"
threads = 4
method = "align"
seed_length = 19
max_num_nodes_per_seq_char = 10.0
min_exact_match = .7
connect_anchors = true
extend_chains = true
```

## Known limitations

- `r_st`/`r_en`/`strand` are always dummy values (`0`, `len(seq)`, `+1`) under `method="query"`;
only `ctg` (the matched label) is meaningful there. `method="align"` reports real values.
- Exactly one annotator file is supported.
- Malformed configuration may, in rare cases, abort the process rather than raise a Python
exception, since metagraph's underlying CLI config parser calls `exit()` on some invalid inputs.
`Aligner.validate()` checks the common cases (missing files) up front.
7 changes: 7 additions & 0 deletions metagraph/api/python/pymetagraph/pymetagraph/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-

"""Readfish-compatible Aligner backed by a metagraph index."""

from .aligner import Aligner

__all__ = ["Aligner"]
127 changes: 127 additions & 0 deletions metagraph/api/python/pymetagraph/pymetagraph/aligner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
from __future__ import annotations

import sys
from pathlib import Path
from typing import Dict, Iterable, List, Optional

from readfish._config import Barcode, Region
from readfish.plugins.abc import AlignerABC
from readfish.plugins.utils import Result

from . import _pymetagraph_core


class Aligner(AlignerABC):
"""Readfish Aligner plugin backed by a metagraph graph + annotation index.

Two unrelated query methods are available, picked via ``method``:

- ``"query"`` (default): label-presence hits via
``AnnotatedDBG::get_top_labels``. ``Alignment.r_st``/``r_en``/``strand``
are placeholder values (``0``, ``len(seq)``, ``+1``) since only the
matched label is meaningful for accept/reject decisions in this mode.
- ``"align"``: real seed-and-extend alignment via metagraph's
alignment_redone module. Neither the ``metagraph align`` CLI command
nor the server's ``/align`` endpoint uses this engine.
``r_st``/``r_en``/``strand`` reflect
the actual matched region and orientation of the read.

:param debug_log: Filename (or ``"stdout"``/``"stderr"``) for debug logging.
:keyword input: Path to the metagraph graph file (``.dbg``).
:keyword annotator: Path to the metagraph annotation file. Required for
``method="query"``; optional for ``method="align"`` (omitting it runs
plain graph alignment with no label lookups).
:keyword threads: Number of threads to use for querying (default: 1).
:keyword method: Either ``"query"`` (default) or ``"align"``.
:keyword num_top_labels: Max number of labels to consider per query.
Only used by ``method="query"``.
:keyword discovery_fraction: Min fraction of k-mers required to be present
in a label for it to count as a hit (default: 0.7). Only used by
``method="query"``.
:keyword presence_fraction: Min fraction of k-mers required to be present
in the graph at all before querying labels (default: 0.0). Only used
by ``method="query"``.
:keyword seed_length: Minimum seed length. Only used by ``method="align"``.
:keyword max_num_nodes_per_seq_char: Max graph nodes to explore per query
character in each extension or gap fill (default: 5.0). Only used by
``method="align"``.
:keyword max_num_seeds_per_locus: Drop the seeds shorter than k at a
query position that has more than this many (default: 1000). Only
used by ``method="align"``.
:keyword min_exact_match: Min fraction of nucleotides covered by seeds
required to align (default: 0.7). Only used by ``method="align"``.
:keyword connect_anchors: If ``True`` (default), traverse the graph to
align query regions falling between anchors. Only used by
``method="align"``.
:keyword extend_chains: If ``True`` (default), perform ends-free
extension from the first/last anchors in a chain. Only used by
``method="align"``.
"""

def __init__(self, debug_log: Optional[str] = None, **kwargs):
if debug_log:
if debug_log == "stdout":
self.logfile = sys.stdout
elif debug_log == "stderr":
self.logfile = sys.stderr
else:
self.logfile = open(debug_log, "w")
else:
self.logfile = None

if "input" not in kwargs:
raise AttributeError('Required argument "input" not found.')

self.kwargs = kwargs
self.validate()
self.index = _pymetagraph_core.Index(**kwargs)

def validate(self) -> None:
index_path = Path(self.kwargs["input"])
if not index_path.is_file():
raise FileNotFoundError(f"{index_path} does not exist")

# "query" (the default) needs an annotation to have any labels to
# report; "align" can run against the bare graph (see _pymetagraph_core.Index).
method = self.kwargs.get("method", "query")
annotator = self.kwargs.get("annotator")
if not annotator:
if method != "align":
raise AttributeError('Required argument "annotator" not found.')
return
if not Path(annotator).is_file():
raise FileNotFoundError(f"{annotator} does not exist")

@property
def initialised(self) -> bool:
return True

def describe(self, regions: List[Region], barcodes: Dict[str, Barcode]) -> str:
return (
f"Using the pymetagraph plugin. Graph: {self.kwargs['input']}, "
f"annotator: {self.kwargs.get('annotator', 'none')}."
)

def map_reads(self, basecall_results: Iterable[Result]) -> Iterable[Result]:
skipped = []
kept = []
for result in basecall_results:
if result.seq:
kept.append(result)
else:
skipped.append(result)

if kept:
alignments = self.index.query_batch([r.seq for r in kept])
for result, alignment in zip(kept, alignments):
result.alignment_data = [] if alignment.ctg == "*" else [alignment]
yield result

for result in skipped:
result.alignment_data = []
yield result

def disconnect(self) -> None:
self.index = None
if self.logfile and self.logfile not in (sys.stdout, sys.stderr):
self.logfile.close()
Loading
Loading