Grove#

The universal Grove (grove<genomic_coordinate, json>) and the point-key groves NumericGrove / KmerGrove, together with the key and result wrappers they return. See the User Guide for the conceptual model, insertion modes, and the graph overlay.

Groves#

class pygenogrove.Grove#

Bases: pybind11_object

A B+ tree container for efficient genomic interval storage and querying.

The grove supports multi-index operations, where each index (e.g., chromosome) maintains its own B+ tree structure.

Parameters:

order (int, optional) – Maximum branching factor of the B+ tree (default: 3, minimum: 3). Controls the maximum number of keys per node (order - 1).

add_edge(*args, **kwargs)#

Overloaded function.

  1. add_edge(self: pygenogrove.Grove, source: pygenogrove.Key, target: pygenogrove.Key) -> None

    Add a directed edge from source to target.

    source and target must be Keys belonging to this Grove (returned by insert(), add_external_key(), or yielded by a QueryResult). Raises TypeError if either is None.

  2. add_edge(self: pygenogrove.Grove, source: pygenogrove.Key, target: pygenogrove.Key, data: object) -> None

    add_edge(source, target, data) -> None

    Add a directed edge from source to target carrying a metadata payload (any JSON-serializable value on the universal Grove). This is an overload of add_edge(source, target); the two-argument form attaches a None payload. Raises TypeError if either key is None.

add_external_key(self: pygenogrove.Grove, key: pygenogrove.GenomicCoordinate, data: object = None) pygenogrove.Key#

Add a key (coordinate + data) that lives outside the B+ tree index but can participate in the graph overlay.

Both the key and the data are copied into the Grove. Returns a stable Key that remains valid as long as the Grove is alive. External keys are not returned by intersect() queries. On the universal Grove the data defaults to None.

clear_graph(self: pygenogrove.Grove) None#

Remove all edges from the graph overlay. The keys themselves are left intact.

compact(self: pygenogrove.Grove) None#

Reclaim the dead storage slots left by remove_key() (storage shrinks to exactly the live key count).

WARNING: this INVALIDATES every Key previously returned for this grove’s indexed keys (by insert(), insert_bulk(), or yielded from intersect()/flanking()) — they become dangling and must NOT be used afterward (doing so is undefined behaviour). After compact(), re-discover keys via a fresh intersect()/flanking() query. Keys from add_external_key() are NOT affected.

static deserialize(path: str) pygenogrove.Grove#

Load a Grove previously written with serialize(). Returns a new Grove with the same intervals, associated data, and graph edges.

edge_count(self: pygenogrove.Grove) int#

Total number of directed edges in the graph overlay.

external_vertex_count(self: pygenogrove.Grove) int#

Number of external (graph-only) keys — those added with add_external_key(), not indexed in any B+ tree.

flanking(*args, **kwargs)#

Overloaded function.

  1. flanking(self: pygenogrove.Grove, query: pygenogrove.GenomicCoordinate, index: str) -> pygenogrove.FlankingResult

    Find the nearest non-overlapping keys on either side of the query within an index (the predecessor and successor).

    Returns a FlankingResult with .predecessor / .successor, each a Key or None. Keys that overlap the query are excluded; for nested intervals the predecessor is the one with the largest end (smallest gap), not the sort-order maximum. Compute the gap distance from the returned key, e.g. query.start - result.predecessor.value.end - 1.

  2. flanking(self: pygenogrove.Grove, query: pygenogrove.GenomicCoordinate, index: str, is_compatible: Callable[[pygenogrove.GenomicCoordinate, pygenogrove.GenomicCoordinate], bool]) -> pygenogrove.FlankingResult

    flanking(query, index, is_compatible) -> FlankingResult

    Predicate-filtered flanking: like flanking(query, index), but only candidate keys for which is_compatible(candidate, query) returns True are considered as neighbours. candidate and query are key values (e.g. GenomicCoordinate); the predicate is applied at every leaf candidate before the overlap/distance checks.

    The canonical use is strand-aware neighbours on a GenomicCoordinateGrove — the nearest non-overlapping key on the same strand:

    g.flanking(q, “chr1”,

    lambda cand, q: cand.strand == q.strand)

    (Internal-node pruning ignores the predicate, so subtrees holding only incompatible keys are still traversed and filtered at the leaves — correct, just not pruned. Exceptions raised by the predicate propagate out.)

get_edge_list(self: object, source: pygenogrove.Key) list#

get_edge_list(source) -> list[tuple[Key, object]]

The outgoing edges from source as (target Key, metadata) pairs — i.e. the zip of get_neighbors(source) and get_edges(source). Edges added without a payload yield None metadata. Each returned Key keeps this Grove alive.

get_edges(self: pygenogrove.Grove, source: pygenogrove.Key) list[object]#

get_edges(source) -> list

The metadata payloads of all outgoing edges from source, in edge order (parallel to get_neighbors(source)). Edges added without a payload yield None.

get_neighbors(self: object, source: pygenogrove.Key) list#

Return the list of target Keys directly reachable from source.

The returned Keys point into this Grove’s storage and remain valid only while the Grove is alive.

get_neighbors_if(self: object, source: pygenogrove.Key, predicate: Callable[[object], bool]) list#

get_neighbors_if(source, predicate) -> list[Key]

Target Keys of the outgoing edges from source whose edge metadata satisfies predicate(metadata). The predicate receives the decoded payload (e.g. the dict you stored). The returned Keys point into this Grove’s storage and are valid only while it is alive.

get_order(self: pygenogrove.Grove) int#

Get the order (branching factor) of the B+ tree

graph_empty(self: pygenogrove.Grove) bool#

Return True if the graph overlay holds no edges.

has_edge(self: pygenogrove.Grove, source: pygenogrove.Key, target: pygenogrove.Key) bool#

Return True if a directed edge from source to target exists.

indexed_vertex_count(self: pygenogrove.Grove) int#

Number of indexed intervals (B+ tree leaf keys)

insert(self: pygenogrove.Grove, index: str, key: pygenogrove.GenomicCoordinate, data: object = None) pygenogrove.Key#

Insert a key with an associated data payload at the given index.

Parameters:
  • index (str) – The index name (e.g., chromosome name like “chr1”)

  • key (GenomicCoordinate) – The key (copied into the grove). Drives B+ tree ordering — do not mutate it after insertion.

  • data (object) – The associated data payload (copied into the grove). On the universal Grove this is any JSON-serializable value (dict / list / scalar / None) and defaults to None.

Returns:

Stable reference to the inserted key.

Return type:

Key

insert_bulk(self: object, index: str, items: list[tuple[pygenogrove.GenomicCoordinate, object]], presorted: bool = False) list#

Bulk-insert many (interval, data) records at once. 10-20x faster than repeated insert() for large datasets — an empty index is built bottom-up in O(n); a non-empty index appends.

Parameters:
  • index (str) – The index name (e.g., chromosome name).

  • items (list[tuple[Interval, object]]) – The (interval, data) records to insert.

  • presorted (bool, optional) – If False (default) the records are sorted by interval first. If True, they are assumed already sorted ascending (skips the sort — fastest).

Returns:

  • list[Key] – Stable key handles, in insertion (sorted) order.

  • PRECONDITION (appending to a non-empty index) (every inserted)

  • interval must be greater than every existing interval in the

  • index. Violating this corrupts B+ tree ordering.

insert_sorted(self: pygenogrove.Grove, index: str, interval: pygenogrove.GenomicCoordinate, data: object) pygenogrove.Key#

Insert one (interval, data) record on the optimized sorted path (rightmost-append, no tree traversal).

PRECONDITION: the interval must be greater than every interval already present in this index. Insert in ascending order. Violating this corrupts B+ tree ordering (queries silently return wrong results). Use plain insert() if unsure.

intersect(*args, **kwargs)#

Overloaded function.

  1. intersect(self: pygenogrove.Grove, query: pygenogrove.GenomicCoordinate) -> pygenogrove.QueryResult

    Find all intervals that overlap with the query across all indices.

  2. intersect(self: pygenogrove.Grove, query: pygenogrove.GenomicCoordinate, index: str) -> pygenogrove.QueryResult

    Find all intervals that overlap with the query in a specific index.

key_storage_size(self: pygenogrove.Grove) int#

Total slots in the indexed-key storage: live leaf data keys + internal B+ tree separator keys + dead slots left behind by remove_key(). Grows across insert/remove cycles until compact() reclaims the dead slots — so it is >= indexed_vertex_count().

link_if(keys, predicate) -> None

Add an (unlabelled) directed edge between each adjacent pair (keys[i], keys[i+1]) for which predicate(keys[i], keys[i+1]) returns True. keys is typically the list returned by a bulk insert; the predicate receives two Keys. Use link_with() to attach edge metadata.

link_with(keys, predicate) -> None

Like link_if(), but the predicate returns the edge metadata to attach, or None to skip: predicate(keys[i], keys[i+1]) -> Optional[object], applied to each adjacent pair. Use this to label edges built over a bulk-inserted run of keys.

out_degree(self: pygenogrove.Grove, source: pygenogrove.Key) int#

Number of outgoing edges from source.

remove_all_edges(self: pygenogrove.Grove, key: pygenogrove.Key) int#

Remove every edge touching key, incoming and outgoing. Returns the total number removed.

remove_edge(self: pygenogrove.Grove, source: pygenogrove.Key, target: pygenogrove.Key) bool#

Remove the directed edge from source to target. Returns True if an edge was removed, False if it did not exist.

remove_edges_from(self: pygenogrove.Grove, source: pygenogrove.Key) int#

Remove all outgoing edges from source. Returns the number removed.

remove_edges_if(self: pygenogrove.Grove, predicate: Callable[[pygenogrove.Key, object], bool]) int#

remove_edges_if(predicate) -> int

Remove every edge for which predicate(target: Key, metadata: object) -> bool returns True, returning the number removed. The predicate receives the target Key and the decoded edge metadata (the value passed to add_edge).

remove_edges_to(self: pygenogrove.Grove, target: pygenogrove.Key) int#

Remove all incoming edges to target (O(E) scan over the graph). Returns the number removed.

remove_key(self: pygenogrove.Grove, index: str, key: pygenogrove.Key) bool#

Remove a key from the index’s B+ tree, rebalancing as needed.

Returns True if the key was found and removed, False otherwise (including a None key or an unknown index). All graph edges touching the key (incoming and outgoing) are also removed. The key remains in storage as a dead slot (not freed) — other Keys keep their pointers; only compact() reclaims the slot (and invalidates pointers — see its warning).

serialize(self: pygenogrove.Grove, path: str) None#

Serialize the Grove (intervals + associated data + graph overlay) to a zlib-compressed binary file at the given path.

size(self: pygenogrove.Grove) int#

Number of indexed intervals across all indices (alias of len)

to_sif(self: pygenogrove.Grove, path: str) None#

Write the grove to a SIF (Simple Interaction Format) text file for graph visualization (e.g. Cytoscape). Emits the B+ tree structure (nodelink / leaflink lines) and the graph-overlay edges (keylink lines) as tab-separated interactions; an empty grove writes an empty file.

Note: line and index iteration order are not stable across runs.

vertex_count(self: pygenogrove.Grove) int#

Total number of keys in the grove: indexed (B+ tree) plus external (graph-only) keys, including isolated ones with no edges.

vertex_count_with_edges(self: pygenogrove.Grove) int#

Number of keys that have at least one outgoing edge.

class pygenogrove.NumericGrove#

Bases: pybind11_object

A B+ tree container for efficient genomic interval storage and querying.

The grove supports multi-index operations, where each index (e.g., chromosome) maintains its own B+ tree structure.

Parameters:

order (int, optional) – Maximum branching factor of the B+ tree (default: 3, minimum: 3). Controls the maximum number of keys per node (order - 1).

add_edge(*args, **kwargs)#

Overloaded function.

  1. add_edge(self: pygenogrove.NumericGrove, source: pygenogrove.NumericKey, target: pygenogrove.NumericKey) -> None

    Add a directed edge from source to target.

    source and target must be Keys belonging to this Grove (returned by insert(), add_external_key(), or yielded by a QueryResult). Raises TypeError if either is None.

  2. add_edge(self: pygenogrove.NumericGrove, source: pygenogrove.NumericKey, target: pygenogrove.NumericKey, data: object) -> None

    add_edge(source, target, data) -> None

    Add a directed edge from source to target carrying a metadata payload (any JSON-serializable value on the universal Grove). This is an overload of add_edge(source, target); the two-argument form attaches a None payload. Raises TypeError if either key is None.

add_external_key(self: pygenogrove.NumericGrove, key: pygenogrove.Numeric, data: object = None) pygenogrove.NumericKey#

Add a key (coordinate + data) that lives outside the B+ tree index but can participate in the graph overlay.

Both the key and the data are copied into the Grove. Returns a stable Key that remains valid as long as the Grove is alive. External keys are not returned by intersect() queries. On the universal Grove the data defaults to None.

clear_graph(self: pygenogrove.NumericGrove) None#

Remove all edges from the graph overlay. The keys themselves are left intact.

compact(self: pygenogrove.NumericGrove) None#

Reclaim the dead storage slots left by remove_key() (storage shrinks to exactly the live key count).

WARNING: this INVALIDATES every Key previously returned for this grove’s indexed keys (by insert(), insert_bulk(), or yielded from intersect()/flanking()) — they become dangling and must NOT be used afterward (doing so is undefined behaviour). After compact(), re-discover keys via a fresh intersect()/flanking() query. Keys from add_external_key() are NOT affected.

static deserialize(path: str) pygenogrove.NumericGrove#

Load a Grove previously written with serialize(). Returns a new Grove with the same intervals, associated data, and graph edges.

edge_count(self: pygenogrove.NumericGrove) int#

Total number of directed edges in the graph overlay.

external_vertex_count(self: pygenogrove.NumericGrove) int#

Number of external (graph-only) keys — those added with add_external_key(), not indexed in any B+ tree.

flanking(*args, **kwargs)#

Overloaded function.

  1. flanking(self: pygenogrove.NumericGrove, query: pygenogrove.Numeric, index: str) -> pygenogrove.NumericFlankingResult

    Find the nearest non-overlapping keys on either side of the query within an index (the predecessor and successor).

    Returns a FlankingResult with .predecessor / .successor, each a Key or None. Keys that overlap the query are excluded; for nested intervals the predecessor is the one with the largest end (smallest gap), not the sort-order maximum. Compute the gap distance from the returned key, e.g. query.start - result.predecessor.value.end - 1.

  2. flanking(self: pygenogrove.NumericGrove, query: pygenogrove.Numeric, index: str, is_compatible: Callable[[pygenogrove.Numeric, pygenogrove.Numeric], bool]) -> pygenogrove.NumericFlankingResult

    flanking(query, index, is_compatible) -> FlankingResult

    Predicate-filtered flanking: like flanking(query, index), but only candidate keys for which is_compatible(candidate, query) returns True are considered as neighbours. candidate and query are key values (e.g. GenomicCoordinate); the predicate is applied at every leaf candidate before the overlap/distance checks.

    The canonical use is strand-aware neighbours on a GenomicCoordinateGrove — the nearest non-overlapping key on the same strand:

    g.flanking(q, “chr1”,

    lambda cand, q: cand.strand == q.strand)

    (Internal-node pruning ignores the predicate, so subtrees holding only incompatible keys are still traversed and filtered at the leaves — correct, just not pruned. Exceptions raised by the predicate propagate out.)

get_edge_list(self: object, source: pygenogrove.NumericKey) list#

get_edge_list(source) -> list[tuple[Key, object]]

The outgoing edges from source as (target Key, metadata) pairs — i.e. the zip of get_neighbors(source) and get_edges(source). Edges added without a payload yield None metadata. Each returned Key keeps this Grove alive.

get_edges(self: pygenogrove.NumericGrove, source: pygenogrove.NumericKey) list[object]#

get_edges(source) -> list

The metadata payloads of all outgoing edges from source, in edge order (parallel to get_neighbors(source)). Edges added without a payload yield None.

get_neighbors(self: object, source: pygenogrove.NumericKey) list#

Return the list of target Keys directly reachable from source.

The returned Keys point into this Grove’s storage and remain valid only while the Grove is alive.

get_neighbors_if(self: object, source: pygenogrove.NumericKey, predicate: Callable[[object], bool]) list#

get_neighbors_if(source, predicate) -> list[Key]

Target Keys of the outgoing edges from source whose edge metadata satisfies predicate(metadata). The predicate receives the decoded payload (e.g. the dict you stored). The returned Keys point into this Grove’s storage and are valid only while it is alive.

get_order(self: pygenogrove.NumericGrove) int#

Get the order (branching factor) of the B+ tree

graph_empty(self: pygenogrove.NumericGrove) bool#

Return True if the graph overlay holds no edges.

has_edge(self: pygenogrove.NumericGrove, source: pygenogrove.NumericKey, target: pygenogrove.NumericKey) bool#

Return True if a directed edge from source to target exists.

indexed_vertex_count(self: pygenogrove.NumericGrove) int#

Number of indexed intervals (B+ tree leaf keys)

insert(self: pygenogrove.NumericGrove, index: str, key: pygenogrove.Numeric, data: object = None) pygenogrove.NumericKey#

Insert a key with an associated data payload at the given index.

Parameters:
  • index (str) – The index name (e.g., chromosome name like “chr1”)

  • key (GenomicCoordinate) – The key (copied into the grove). Drives B+ tree ordering — do not mutate it after insertion.

  • data (object) – The associated data payload (copied into the grove). On the universal Grove this is any JSON-serializable value (dict / list / scalar / None) and defaults to None.

Returns:

Stable reference to the inserted key.

Return type:

Key

insert_bulk(self: object, index: str, items: list[tuple[pygenogrove.Numeric, object]], presorted: bool = False) list#

Bulk-insert many (interval, data) records at once. 10-20x faster than repeated insert() for large datasets — an empty index is built bottom-up in O(n); a non-empty index appends.

Parameters:
  • index (str) – The index name (e.g., chromosome name).

  • items (list[tuple[Interval, object]]) – The (interval, data) records to insert.

  • presorted (bool, optional) – If False (default) the records are sorted by interval first. If True, they are assumed already sorted ascending (skips the sort — fastest).

Returns:

  • list[Key] – Stable key handles, in insertion (sorted) order.

  • PRECONDITION (appending to a non-empty index) (every inserted)

  • interval must be greater than every existing interval in the

  • index. Violating this corrupts B+ tree ordering.

insert_sorted(self: pygenogrove.NumericGrove, index: str, interval: pygenogrove.Numeric, data: object) pygenogrove.NumericKey#

Insert one (interval, data) record on the optimized sorted path (rightmost-append, no tree traversal).

PRECONDITION: the interval must be greater than every interval already present in this index. Insert in ascending order. Violating this corrupts B+ tree ordering (queries silently return wrong results). Use plain insert() if unsure.

intersect(*args, **kwargs)#

Overloaded function.

  1. intersect(self: pygenogrove.NumericGrove, query: pygenogrove.Numeric) -> pygenogrove.NumericQueryResult

    Find all intervals that overlap with the query across all indices.

  2. intersect(self: pygenogrove.NumericGrove, query: pygenogrove.Numeric, index: str) -> pygenogrove.NumericQueryResult

    Find all intervals that overlap with the query in a specific index.

key_storage_size(self: pygenogrove.NumericGrove) int#

Total slots in the indexed-key storage: live leaf data keys + internal B+ tree separator keys + dead slots left behind by remove_key(). Grows across insert/remove cycles until compact() reclaims the dead slots — so it is >= indexed_vertex_count().

link_if(keys, predicate) -> None

Add an (unlabelled) directed edge between each adjacent pair (keys[i], keys[i+1]) for which predicate(keys[i], keys[i+1]) returns True. keys is typically the list returned by a bulk insert; the predicate receives two Keys. Use link_with() to attach edge metadata.

link_with(keys, predicate) -> None

Like link_if(), but the predicate returns the edge metadata to attach, or None to skip: predicate(keys[i], keys[i+1]) -> Optional[object], applied to each adjacent pair. Use this to label edges built over a bulk-inserted run of keys.

out_degree(self: pygenogrove.NumericGrove, source: pygenogrove.NumericKey) int#

Number of outgoing edges from source.

remove_all_edges(self: pygenogrove.NumericGrove, key: pygenogrove.NumericKey) int#

Remove every edge touching key, incoming and outgoing. Returns the total number removed.

remove_edge(self: pygenogrove.NumericGrove, source: pygenogrove.NumericKey, target: pygenogrove.NumericKey) bool#

Remove the directed edge from source to target. Returns True if an edge was removed, False if it did not exist.

remove_edges_from(self: pygenogrove.NumericGrove, source: pygenogrove.NumericKey) int#

Remove all outgoing edges from source. Returns the number removed.

remove_edges_if(self: pygenogrove.NumericGrove, predicate: Callable[[pygenogrove.NumericKey, object], bool]) int#

remove_edges_if(predicate) -> int

Remove every edge for which predicate(target: Key, metadata: object) -> bool returns True, returning the number removed. The predicate receives the target Key and the decoded edge metadata (the value passed to add_edge).

remove_edges_to(self: pygenogrove.NumericGrove, target: pygenogrove.NumericKey) int#

Remove all incoming edges to target (O(E) scan over the graph). Returns the number removed.

remove_key(self: pygenogrove.NumericGrove, index: str, key: pygenogrove.NumericKey) bool#

Remove a key from the index’s B+ tree, rebalancing as needed.

Returns True if the key was found and removed, False otherwise (including a None key or an unknown index). All graph edges touching the key (incoming and outgoing) are also removed. The key remains in storage as a dead slot (not freed) — other Keys keep their pointers; only compact() reclaims the slot (and invalidates pointers — see its warning).

serialize(self: pygenogrove.NumericGrove, path: str) None#

Serialize the Grove (intervals + associated data + graph overlay) to a zlib-compressed binary file at the given path.

size(self: pygenogrove.NumericGrove) int#

Number of indexed intervals across all indices (alias of len)

to_sif(self: pygenogrove.NumericGrove, path: str) None#

Write the grove to a SIF (Simple Interaction Format) text file for graph visualization (e.g. Cytoscape). Emits the B+ tree structure (nodelink / leaflink lines) and the graph-overlay edges (keylink lines) as tab-separated interactions; an empty grove writes an empty file.

Note: line and index iteration order are not stable across runs.

vertex_count(self: pygenogrove.NumericGrove) int#

Total number of keys in the grove: indexed (B+ tree) plus external (graph-only) keys, including isolated ones with no edges.

vertex_count_with_edges(self: pygenogrove.NumericGrove) int#

Number of keys that have at least one outgoing edge.

class pygenogrove.KmerGrove#

Bases: pybind11_object

A B+ tree container for efficient genomic interval storage and querying.

The grove supports multi-index operations, where each index (e.g., chromosome) maintains its own B+ tree structure.

Parameters:

order (int, optional) – Maximum branching factor of the B+ tree (default: 3, minimum: 3). Controls the maximum number of keys per node (order - 1).

add_edge(*args, **kwargs)#

Overloaded function.

  1. add_edge(self: pygenogrove.KmerGrove, source: pygenogrove.KmerKey, target: pygenogrove.KmerKey) -> None

    Add a directed edge from source to target.

    source and target must be Keys belonging to this Grove (returned by insert(), add_external_key(), or yielded by a QueryResult). Raises TypeError if either is None.

  2. add_edge(self: pygenogrove.KmerGrove, source: pygenogrove.KmerKey, target: pygenogrove.KmerKey, data: object) -> None

    add_edge(source, target, data) -> None

    Add a directed edge from source to target carrying a metadata payload (any JSON-serializable value on the universal Grove). This is an overload of add_edge(source, target); the two-argument form attaches a None payload. Raises TypeError if either key is None.

add_external_key(self: pygenogrove.KmerGrove, key: pygenogrove.Kmer, data: object = None) pygenogrove.KmerKey#

Add a key (coordinate + data) that lives outside the B+ tree index but can participate in the graph overlay.

Both the key and the data are copied into the Grove. Returns a stable Key that remains valid as long as the Grove is alive. External keys are not returned by intersect() queries. On the universal Grove the data defaults to None.

clear_graph(self: pygenogrove.KmerGrove) None#

Remove all edges from the graph overlay. The keys themselves are left intact.

compact(self: pygenogrove.KmerGrove) None#

Reclaim the dead storage slots left by remove_key() (storage shrinks to exactly the live key count).

WARNING: this INVALIDATES every Key previously returned for this grove’s indexed keys (by insert(), insert_bulk(), or yielded from intersect()/flanking()) — they become dangling and must NOT be used afterward (doing so is undefined behaviour). After compact(), re-discover keys via a fresh intersect()/flanking() query. Keys from add_external_key() are NOT affected.

static deserialize(path: str) pygenogrove.KmerGrove#

Load a Grove previously written with serialize(). Returns a new Grove with the same intervals, associated data, and graph edges.

edge_count(self: pygenogrove.KmerGrove) int#

Total number of directed edges in the graph overlay.

external_vertex_count(self: pygenogrove.KmerGrove) int#

Number of external (graph-only) keys — those added with add_external_key(), not indexed in any B+ tree.

flanking(*args, **kwargs)#

Overloaded function.

  1. flanking(self: pygenogrove.KmerGrove, query: pygenogrove.Kmer, index: str) -> pygenogrove.KmerFlankingResult

    Find the nearest non-overlapping keys on either side of the query within an index (the predecessor and successor).

    Returns a FlankingResult with .predecessor / .successor, each a Key or None. Keys that overlap the query are excluded; for nested intervals the predecessor is the one with the largest end (smallest gap), not the sort-order maximum. Compute the gap distance from the returned key, e.g. query.start - result.predecessor.value.end - 1.

  2. flanking(self: pygenogrove.KmerGrove, query: pygenogrove.Kmer, index: str, is_compatible: Callable[[pygenogrove.Kmer, pygenogrove.Kmer], bool]) -> pygenogrove.KmerFlankingResult

    flanking(query, index, is_compatible) -> FlankingResult

    Predicate-filtered flanking: like flanking(query, index), but only candidate keys for which is_compatible(candidate, query) returns True are considered as neighbours. candidate and query are key values (e.g. GenomicCoordinate); the predicate is applied at every leaf candidate before the overlap/distance checks.

    The canonical use is strand-aware neighbours on a GenomicCoordinateGrove — the nearest non-overlapping key on the same strand:

    g.flanking(q, “chr1”,

    lambda cand, q: cand.strand == q.strand)

    (Internal-node pruning ignores the predicate, so subtrees holding only incompatible keys are still traversed and filtered at the leaves — correct, just not pruned. Exceptions raised by the predicate propagate out.)

get_edge_list(self: object, source: pygenogrove.KmerKey) list#

get_edge_list(source) -> list[tuple[Key, object]]

The outgoing edges from source as (target Key, metadata) pairs — i.e. the zip of get_neighbors(source) and get_edges(source). Edges added without a payload yield None metadata. Each returned Key keeps this Grove alive.

get_edges(self: pygenogrove.KmerGrove, source: pygenogrove.KmerKey) list[object]#

get_edges(source) -> list

The metadata payloads of all outgoing edges from source, in edge order (parallel to get_neighbors(source)). Edges added without a payload yield None.

get_neighbors(self: object, source: pygenogrove.KmerKey) list#

Return the list of target Keys directly reachable from source.

The returned Keys point into this Grove’s storage and remain valid only while the Grove is alive.

get_neighbors_if(self: object, source: pygenogrove.KmerKey, predicate: Callable[[object], bool]) list#

get_neighbors_if(source, predicate) -> list[Key]

Target Keys of the outgoing edges from source whose edge metadata satisfies predicate(metadata). The predicate receives the decoded payload (e.g. the dict you stored). The returned Keys point into this Grove’s storage and are valid only while it is alive.

get_order(self: pygenogrove.KmerGrove) int#

Get the order (branching factor) of the B+ tree

graph_empty(self: pygenogrove.KmerGrove) bool#

Return True if the graph overlay holds no edges.

has_edge(self: pygenogrove.KmerGrove, source: pygenogrove.KmerKey, target: pygenogrove.KmerKey) bool#

Return True if a directed edge from source to target exists.

indexed_vertex_count(self: pygenogrove.KmerGrove) int#

Number of indexed intervals (B+ tree leaf keys)

insert(self: pygenogrove.KmerGrove, index: str, key: pygenogrove.Kmer, data: object = None) pygenogrove.KmerKey#

Insert a key with an associated data payload at the given index.

Parameters:
  • index (str) – The index name (e.g., chromosome name like “chr1”)

  • key (GenomicCoordinate) – The key (copied into the grove). Drives B+ tree ordering — do not mutate it after insertion.

  • data (object) – The associated data payload (copied into the grove). On the universal Grove this is any JSON-serializable value (dict / list / scalar / None) and defaults to None.

Returns:

Stable reference to the inserted key.

Return type:

Key

insert_bulk(self: object, index: str, items: list[tuple[pygenogrove.Kmer, object]], presorted: bool = False) list#

Bulk-insert many (interval, data) records at once. 10-20x faster than repeated insert() for large datasets — an empty index is built bottom-up in O(n); a non-empty index appends.

Parameters:
  • index (str) – The index name (e.g., chromosome name).

  • items (list[tuple[Interval, object]]) – The (interval, data) records to insert.

  • presorted (bool, optional) – If False (default) the records are sorted by interval first. If True, they are assumed already sorted ascending (skips the sort — fastest).

Returns:

  • list[Key] – Stable key handles, in insertion (sorted) order.

  • PRECONDITION (appending to a non-empty index) (every inserted)

  • interval must be greater than every existing interval in the

  • index. Violating this corrupts B+ tree ordering.

insert_sorted(self: pygenogrove.KmerGrove, index: str, interval: pygenogrove.Kmer, data: object) pygenogrove.KmerKey#

Insert one (interval, data) record on the optimized sorted path (rightmost-append, no tree traversal).

PRECONDITION: the interval must be greater than every interval already present in this index. Insert in ascending order. Violating this corrupts B+ tree ordering (queries silently return wrong results). Use plain insert() if unsure.

intersect(*args, **kwargs)#

Overloaded function.

  1. intersect(self: pygenogrove.KmerGrove, query: pygenogrove.Kmer) -> pygenogrove.KmerQueryResult

    Find all intervals that overlap with the query across all indices.

  2. intersect(self: pygenogrove.KmerGrove, query: pygenogrove.Kmer, index: str) -> pygenogrove.KmerQueryResult

    Find all intervals that overlap with the query in a specific index.

key_storage_size(self: pygenogrove.KmerGrove) int#

Total slots in the indexed-key storage: live leaf data keys + internal B+ tree separator keys + dead slots left behind by remove_key(). Grows across insert/remove cycles until compact() reclaims the dead slots — so it is >= indexed_vertex_count().

link_if(keys, predicate) -> None

Add an (unlabelled) directed edge between each adjacent pair (keys[i], keys[i+1]) for which predicate(keys[i], keys[i+1]) returns True. keys is typically the list returned by a bulk insert; the predicate receives two Keys. Use link_with() to attach edge metadata.

link_with(keys, predicate) -> None

Like link_if(), but the predicate returns the edge metadata to attach, or None to skip: predicate(keys[i], keys[i+1]) -> Optional[object], applied to each adjacent pair. Use this to label edges built over a bulk-inserted run of keys.

out_degree(self: pygenogrove.KmerGrove, source: pygenogrove.KmerKey) int#

Number of outgoing edges from source.

remove_all_edges(self: pygenogrove.KmerGrove, key: pygenogrove.KmerKey) int#

Remove every edge touching key, incoming and outgoing. Returns the total number removed.

remove_edge(self: pygenogrove.KmerGrove, source: pygenogrove.KmerKey, target: pygenogrove.KmerKey) bool#

Remove the directed edge from source to target. Returns True if an edge was removed, False if it did not exist.

remove_edges_from(self: pygenogrove.KmerGrove, source: pygenogrove.KmerKey) int#

Remove all outgoing edges from source. Returns the number removed.

remove_edges_if(self: pygenogrove.KmerGrove, predicate: Callable[[pygenogrove.KmerKey, object], bool]) int#

remove_edges_if(predicate) -> int

Remove every edge for which predicate(target: Key, metadata: object) -> bool returns True, returning the number removed. The predicate receives the target Key and the decoded edge metadata (the value passed to add_edge).

remove_edges_to(self: pygenogrove.KmerGrove, target: pygenogrove.KmerKey) int#

Remove all incoming edges to target (O(E) scan over the graph). Returns the number removed.

remove_key(self: pygenogrove.KmerGrove, index: str, key: pygenogrove.KmerKey) bool#

Remove a key from the index’s B+ tree, rebalancing as needed.

Returns True if the key was found and removed, False otherwise (including a None key or an unknown index). All graph edges touching the key (incoming and outgoing) are also removed. The key remains in storage as a dead slot (not freed) — other Keys keep their pointers; only compact() reclaims the slot (and invalidates pointers — see its warning).

serialize(self: pygenogrove.KmerGrove, path: str) None#

Serialize the Grove (intervals + associated data + graph overlay) to a zlib-compressed binary file at the given path.

size(self: pygenogrove.KmerGrove) int#

Number of indexed intervals across all indices (alias of len)

to_sif(self: pygenogrove.KmerGrove, path: str) None#

Write the grove to a SIF (Simple Interaction Format) text file for graph visualization (e.g. Cytoscape). Emits the B+ tree structure (nodelink / leaflink lines) and the graph-overlay edges (keylink lines) as tab-separated interactions; an empty grove writes an empty file.

Note: line and index iteration order are not stable across runs.

vertex_count(self: pygenogrove.KmerGrove) int#

Total number of keys in the grove: indexed (B+ tree) plus external (graph-only) keys, including isolated ones with no edges.

vertex_count_with_edges(self: pygenogrove.KmerGrove) int#

Number of keys that have at least one outgoing edge.

Grove views (partial random-access readers)#

Read-only, partial readers over a serialized .gg — page in only the blocks a query touches. See the Serialization guide.

class pygenogrove.GroveView#

Bases: pybind11_object

A read-only, partial reader over a serialized (format 0.2) .gg grove.

Unlike Grove.deserialize(), which loads the whole grove into memory, a GroveView reads only the block directory up front, then pages in individual blocks on demand as a query descends the tree — and caches them for the view’s lifetime (no eviction). Use it to query a large on-disk index without loading it whole.

Query-only: it has no insert() or serialize(). Not thread-safe (a query mutates the block cache). The Keys it returns point into the view’s own storage and are valid only while the GroveView is alive.

Create one with GroveView.open(path); a file written by Grove.serialize() is read directly (data_offset=0).

block_count(self: pygenogrove.GroveView) int#

Total number of blocks in the serialized grove (0 for an empty grove).

blocks_loaded(self: pygenogrove.GroveView) int#

Number of blocks paged in so far — proof a query loaded only part of the file (compare with block_count()).

get_neighbors(self: object, source: pygenogrove.Key) list#

Return the target Keys directly reachable from source via graph edges, paging in each target’s block on demand. source must be a Key this GroveView produced (via intersect() or a prior get_neighbors()). The returned Keys are valid only while the view is alive. Raises TypeError if source is None.

intersect(*args, **kwargs)#

Overloaded function.

  1. intersect(self: pygenogrove.GroveView, query: pygenogrove.GenomicCoordinate) -> pygenogrove.QueryResult

    Find all intervals overlapping the query across all indices, loading only the blocks the search touches.

  2. intersect(self: pygenogrove.GroveView, query: pygenogrove.GenomicCoordinate, index: str) -> pygenogrove.QueryResult

    Find all intervals overlapping the query within a single index, loading only the blocks on the descent path and overlapping leaves.

static open(path: str, data_offset: int = 0) pygenogrove.GroveView#

open(path, data_offset=0) -> GroveView

Open a serialized grove for partial reading. path is a file written by Grove.serialize() (a bare grove stream; data_offset=0). Pass a non-zero data_offset only for a .gg embedded after a leading header (e.g. a genogrove CLI index). Raises RuntimeError if the file cannot be opened, the magic is wrong, the source is not seekable, or the directory is malformed.

class pygenogrove.NumericGroveView#

Bases: pybind11_object

A read-only, partial reader over a serialized (format 0.2) .gg grove.

Unlike Grove.deserialize(), which loads the whole grove into memory, a GroveView reads only the block directory up front, then pages in individual blocks on demand as a query descends the tree — and caches them for the view’s lifetime (no eviction). Use it to query a large on-disk index without loading it whole.

Query-only: it has no insert() or serialize(). Not thread-safe (a query mutates the block cache). The Keys it returns point into the view’s own storage and are valid only while the GroveView is alive.

Create one with GroveView.open(path); a file written by Grove.serialize() is read directly (data_offset=0).

block_count(self: pygenogrove.NumericGroveView) int#

Total number of blocks in the serialized grove (0 for an empty grove).

blocks_loaded(self: pygenogrove.NumericGroveView) int#

Number of blocks paged in so far — proof a query loaded only part of the file (compare with block_count()).

get_neighbors(self: object, source: pygenogrove.NumericKey) list#

Return the target Keys directly reachable from source via graph edges, paging in each target’s block on demand. source must be a Key this GroveView produced (via intersect() or a prior get_neighbors()). The returned Keys are valid only while the view is alive. Raises TypeError if source is None.

intersect(*args, **kwargs)#

Overloaded function.

  1. intersect(self: pygenogrove.NumericGroveView, query: pygenogrove.Numeric) -> pygenogrove.NumericQueryResult

    Find all intervals overlapping the query across all indices, loading only the blocks the search touches.

  2. intersect(self: pygenogrove.NumericGroveView, query: pygenogrove.Numeric, index: str) -> pygenogrove.NumericQueryResult

    Find all intervals overlapping the query within a single index, loading only the blocks on the descent path and overlapping leaves.

static open(path: str, data_offset: int = 0) pygenogrove.NumericGroveView#

open(path, data_offset=0) -> GroveView

Open a serialized grove for partial reading. path is a file written by Grove.serialize() (a bare grove stream; data_offset=0). Pass a non-zero data_offset only for a .gg embedded after a leading header (e.g. a genogrove CLI index). Raises RuntimeError if the file cannot be opened, the magic is wrong, the source is not seekable, or the directory is malformed.

class pygenogrove.KmerGroveView#

Bases: pybind11_object

A read-only, partial reader over a serialized (format 0.2) .gg grove.

Unlike Grove.deserialize(), which loads the whole grove into memory, a GroveView reads only the block directory up front, then pages in individual blocks on demand as a query descends the tree — and caches them for the view’s lifetime (no eviction). Use it to query a large on-disk index without loading it whole.

Query-only: it has no insert() or serialize(). Not thread-safe (a query mutates the block cache). The Keys it returns point into the view’s own storage and are valid only while the GroveView is alive.

Create one with GroveView.open(path); a file written by Grove.serialize() is read directly (data_offset=0).

block_count(self: pygenogrove.KmerGroveView) int#

Total number of blocks in the serialized grove (0 for an empty grove).

blocks_loaded(self: pygenogrove.KmerGroveView) int#

Number of blocks paged in so far — proof a query loaded only part of the file (compare with block_count()).

get_neighbors(self: object, source: pygenogrove.KmerKey) list#

Return the target Keys directly reachable from source via graph edges, paging in each target’s block on demand. source must be a Key this GroveView produced (via intersect() or a prior get_neighbors()). The returned Keys are valid only while the view is alive. Raises TypeError if source is None.

intersect(*args, **kwargs)#

Overloaded function.

  1. intersect(self: pygenogrove.KmerGroveView, query: pygenogrove.Kmer) -> pygenogrove.KmerQueryResult

    Find all intervals overlapping the query across all indices, loading only the blocks the search touches.

  2. intersect(self: pygenogrove.KmerGroveView, query: pygenogrove.Kmer, index: str) -> pygenogrove.KmerQueryResult

    Find all intervals overlapping the query within a single index, loading only the blocks on the descent path and overlapping leaves.

static open(path: str, data_offset: int = 0) pygenogrove.KmerGroveView#

open(path, data_offset=0) -> GroveView

Open a serialized grove for partial reading. path is a file written by Grove.serialize() (a bare grove stream; data_offset=0). Pass a non-zero data_offset only for a .gg embedded after a leading header (e.g. a genogrove CLI index). Raises RuntimeError if the file cannot be opened, the magic is wrong, the source is not seekable, or the directory is malformed.

Keys#

class pygenogrove.Key#

Bases: pybind11_object

A key wrapping a key value stored in the grove structure.

Returned by Grove.insert() and yielded by QueryResult iteration. Wraps a pointer into the grove’s storage, so the key remains valid only as long as the originating Grove is alive (the key keeps the Grove alive).

property data#

The associated data payload (not part of B+ tree ordering). On the typed BedKey/GffKey it is a live, mutable reference into grove storage (mutating it in place is safe). On the universal Grove the payload is JSON, so .data returns a freshly decoded copy each access — mutating that copy does not persist; re-insert to change it.

property value#

The key value (returned by value/copy, so mutating it cannot corrupt the grove’s B+ tree ordering)

class pygenogrove.NumericKey#

Bases: pybind11_object

A key wrapping a key value stored in the grove structure.

Returned by Grove.insert() and yielded by QueryResult iteration. Wraps a pointer into the grove’s storage, so the key remains valid only as long as the originating Grove is alive (the key keeps the Grove alive).

property data#

The associated data payload (not part of B+ tree ordering). On the typed BedKey/GffKey it is a live, mutable reference into grove storage (mutating it in place is safe). On the universal Grove the payload is JSON, so .data returns a freshly decoded copy each access — mutating that copy does not persist; re-insert to change it.

property value#

The key value (returned by value/copy, so mutating it cannot corrupt the grove’s B+ tree ordering)

class pygenogrove.KmerKey#

Bases: pybind11_object

A key wrapping a key value stored in the grove structure.

Returned by Grove.insert() and yielded by QueryResult iteration. Wraps a pointer into the grove’s storage, so the key remains valid only as long as the originating Grove is alive (the key keeps the Grove alive).

property data#

The associated data payload (not part of B+ tree ordering). On the typed BedKey/GffKey it is a live, mutable reference into grove storage (mutating it in place is safe). On the universal Grove the payload is JSON, so .data returns a freshly decoded copy each access — mutating that copy does not persist; re-insert to change it.

property value#

The key value (returned by value/copy, so mutating it cannot corrupt the grove’s B+ tree ordering)

Query and flanking results#

class pygenogrove.QueryResult#

Bases: pybind11_object

Result of an intersect() query: the query interval plus the matching keys.

property keys#

List of matching keys; each Key keeps this result (and its Grove) alive.

property query#

The query interval used for this search

class pygenogrove.NumericQueryResult#

Bases: pybind11_object

Result of an intersect() query: the query interval plus the matching keys.

property keys#

List of matching keys; each Key keeps this result (and its Grove) alive.

property query#

The query interval used for this search

class pygenogrove.KmerQueryResult#

Bases: pybind11_object

Result of an intersect() query: the query interval plus the matching keys.

property keys#

List of matching keys; each Key keeps this result (and its Grove) alive.

property query#

The query interval used for this search

class pygenogrove.FlankingResult#

Bases: pybind11_object

Result of a Grove.flanking() query: the nearest non-overlapping keys on either side of the query, in the grove’s sort order.

predecessor is the closest key entirely before the query (largest end with no overlap); successor is the closest key entirely after it (smallest start with no overlap). Either is None if no such key exists. Distance is computed by the caller from the key values, e.g. for a query Q and predecessor P: Q.start - P.value.end - 1 (closed coordinates).

property predecessor#

Nearest non-overlapping key before the query (a Key), or None.

property successor#

Nearest non-overlapping key after the query (a Key), or None.

class pygenogrove.NumericFlankingResult#

Bases: pybind11_object

Result of a Grove.flanking() query: the nearest non-overlapping keys on either side of the query, in the grove’s sort order.

predecessor is the closest key entirely before the query (largest end with no overlap); successor is the closest key entirely after it (smallest start with no overlap). Either is None if no such key exists. Distance is computed by the caller from the key values, e.g. for a query Q and predecessor P: Q.start - P.value.end - 1 (closed coordinates).

property predecessor#

Nearest non-overlapping key before the query (a Key), or None.

property successor#

Nearest non-overlapping key after the query (a Key), or None.

class pygenogrove.KmerFlankingResult#

Bases: pybind11_object

Result of a Grove.flanking() query: the nearest non-overlapping keys on either side of the query, in the grove’s sort order.

predecessor is the closest key entirely before the query (largest end with no overlap); successor is the closest key entirely after it (smallest start with no overlap). Either is None if no such key exists. Distance is computed by the caller from the key values, e.g. for a query Q and predecessor P: Q.start - P.value.end - 1 (closed coordinates).

property predecessor#

Nearest non-overlapping key before the query (a Key), or None.

property successor#

Nearest non-overlapping key after the query (a Key), or None.