From ce5bc77c767351c57ce4f7872c827316cb3b1040 Mon Sep 17 00:00:00 2001
From: Arun Isaac
Date: Fri, 14 Aug 2026 21:42:46 +0100
Subject: Add domagi overlap.
---
doc/domagi-overlap.dbk | 20 ++++++++++++++++++++
doc/domagi.dbk | 1 +
domagi/domagi.py | 18 ++++++++++++++++++
domagi/overlap.sql | 20 ++++++++++++++++++++
meson.build | 1 +
test-data/expected-output/test1-overlap | 3 +++
test-data/expected-output/test2-overlap | 3 +++
test-data/expected-output/test3-overlap | 3 +++
tests/test_domagi.py | 32 ++++++++++++++++++++++++++++++++
9 files changed, 101 insertions(+)
create mode 100644 doc/domagi-overlap.dbk
create mode 100644 domagi/overlap.sql
create mode 100644 test-data/expected-output/test1-overlap
create mode 100644 test-data/expected-output/test2-overlap
create mode 100644 test-data/expected-output/test3-overlap
diff --git a/doc/domagi-overlap.dbk b/doc/domagi-overlap.dbk
new file mode 100644
index 0000000..e8e40e7
--- /dev/null
+++ b/doc/domagi-overlap.dbk
@@ -0,0 +1,20 @@
+
+
+
+
+ Description
+ Find the paths touched by the specified paths. The output is in a four-column tab-delimited format with the following columns—the name of the specified path, its start coordinate, its end coordinate, and the name of the path that touches it. The start and end coordinates are always 0 and the length of the specified path.
+
+ Options
+
+
+
+
+ Find paths touched by PATH. This argument may be specified more than once to find paths touched by more than one path.
+
+
+
+
+
+
diff --git a/doc/domagi.dbk b/doc/domagi.dbk
index 0079fa2..53f25e2 100644
--- a/doc/domagi.dbk
+++ b/doc/domagi.dbk
@@ -12,6 +12,7 @@
+
diff --git a/domagi/domagi.py b/domagi/domagi.py
index 4cc5137..faa7429 100644
--- a/domagi/domagi.py
+++ b/domagi/domagi.py
@@ -256,6 +256,24 @@ def matrix(con, threads):
print(segment_count, segment_count, df.shape[0])
df.to_csv(sys.stdout, sep=" ", header=False, index=False)
+@main.command(short_help="Find paths touched by given input paths")
+@click.option("-i", "--db", "--idx", "con",
+ type=DuckDBParamType(),
+ required=True,
+ help="pangenome duckdb database")
+@click.option("-r", "--path", "paths",
+ # We deviate a little from odgi and allow -r to be specified
+ # several times.
+ multiple=True,
+ metavar="PATH",
+ help="find paths touched by PATH")
+@common_options
+def overlap(con, paths, threads):
+ set_duckdb_threads(con, threads)
+ (con.execute(read_sql("overlap.sql"), [paths])
+ .fetchdf()
+ .to_csv(sys.stdout, sep="\t", index=False))
+
@main.command(short_help="Interrogate paths")
@click.option("-i", "--db", "--idx", "con",
type=DuckDBParamType(),
diff --git a/domagi/overlap.sql b/domagi/overlap.sql
new file mode 100644
index 0000000..2ffb35b
--- /dev/null
+++ b/domagi/overlap.sql
@@ -0,0 +1,20 @@
+WITH path_overlap AS (
+ SELECT path1.id AS path1_id, path1.name AS path1_name, path2.name AS path2_name
+ FROM path path1
+ INNER JOIN path path2
+ ON -- Avoid matching paths to themselves.
+ path1.id != path2.id
+ -- Check if there is any segment in common between the
+ -- two paths.
+ AND EXISTS(SELECT segment_id
+ FROM path_segment path_segment1
+ WHERE path_segment1.path_id=path1.id
+ INTERSECT
+ SELECT segment_id
+ FROM path_segment path_segment2
+ WHERE path_segment2.path_id=path2.id)
+ WHERE path1.name IN (SELECT UNNEST(?)))
+ -- Add path coordinates.
+ SELECT path1_name AS "#path", 0 AS start, length AS "end", path2_name AS "path.touched"
+ FROM path_overlap
+ INNER JOIN path_length ON path_length.path_id=path1_id
diff --git a/meson.build b/meson.build
index 7d85388..7b86e87 100644
--- a/meson.build
+++ b/meson.build
@@ -12,6 +12,7 @@ py.install_sources('domagi/domagi.py',
install_data('domagi/matrix.sql',
'domagi/schema.sql',
'domagi/bed-depth.sql',
+ 'domagi/overlap.sql',
'domagi/path-depth.sql',
'domagi/pre-import.sql',
'domagi/post-import.sql',
diff --git a/test-data/expected-output/test1-overlap b/test-data/expected-output/test1-overlap
new file mode 100644
index 0000000..ee0297f
--- /dev/null
+++ b/test-data/expected-output/test1-overlap
@@ -0,0 +1,3 @@
+#path start end path.touched
+x 0 50 y
+y 0 50 x
diff --git a/test-data/expected-output/test2-overlap b/test-data/expected-output/test2-overlap
new file mode 100644
index 0000000..81dfa10
--- /dev/null
+++ b/test-data/expected-output/test2-overlap
@@ -0,0 +1,3 @@
+#path start end path.touched
+5+ 0 13 5-
+5- 0 13 5+
diff --git a/test-data/expected-output/test3-overlap b/test-data/expected-output/test3-overlap
new file mode 100644
index 0000000..bc22807
--- /dev/null
+++ b/test-data/expected-output/test3-overlap
@@ -0,0 +1,3 @@
+#path start end path.touched
+5+ 0 20 5-
+5- 0 13 5+
diff --git a/tests/test_domagi.py b/tests/test_domagi.py
index c65b87e..1ce5fc0 100644
--- a/tests/test_domagi.py
+++ b/tests/test_domagi.py
@@ -19,6 +19,7 @@
import io
from click.testing import CliRunner
+import duckdb
import pandas as pd
from pandas.testing import assert_frame_equal
from pathlib import Path
@@ -183,6 +184,37 @@ def test_domagi_matrix(tmp_path, test_data_file, expected_output):
assert expected_header == actual_header
assert expected_lines == actual_lines
+@pytest.mark.parametrize("test_data_file, expected_output",
+ [(Path("test-data/test1.gfa"),
+ Path("test-data/expected-output/test1-overlap")),
+ (Path("test-data/test2.gfa"),
+ Path("test-data/expected-output/test2-overlap")),
+ (Path("test-data/test3.gfa"),
+ Path("test-data/expected-output/test3-overlap"))])
+def test_domagi_overlap(tmp_path, test_data_file, expected_output):
+ duckdb_path = tmp_path / f"{test_data_file.stem}.db"
+ runner = CliRunner()
+ result = runner.invoke(main, ["build",
+ "--gfa", test_data_file,
+ "--out", duckdb_path])
+ assert result.exit_code == 0
+ paths = [path for path, in (duckdb.connect(duckdb_path, True)
+ .execute("SELECT name FROM path")
+ .fetchall())]
+ result = runner.invoke(main, ["overlap",
+ "--db", duckdb_path,
+ *sum([["--path", path] for path in paths],
+ [])])
+ assert result.exit_code == 0
+ assert_frame_equal(pd.read_csv(expected_output, sep="\t")
+ .sort_values(by=["#path", "path.touched"],
+ ignore_index=True),
+ pd.read_csv(io.StringIO(result.stdout),
+ sep="\t")
+ .sort_values(by=["#path", "path.touched"],
+ ignore_index=True),
+ check_dtype=False)
+
@pytest.mark.parametrize("test_data_file, expected_output",
[(Path("test-data/test1.gfa"),
Path("test-data/expected-output/test1-paths")),
--
cgit 1.4.1