Skip to content

Commit

Permalink
Add limit argument (#13)
Browse files Browse the repository at this point in the history
* Add `limit` argument to short-circuit and break early

* Update documentation for limit argument
  • Loading branch information
j6k4m8 authored Feb 26, 2021
1 parent 28e2f97 commit 3e7affa
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 15 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
# CHANGELOG

## v1.1.0 (Unreleased)

- Features
- `limit` argument: Pass an integer limit of results to return in order to short-circuit and return early from long-running jobs.

## v1.0.0 (January 11 2021)

> This version adds map hinting in order to let a user specify partial matches in the `find_motifs` call.
- Features
- `hints` argument: Pass a list of partial starting maps in order to condition the search results.

## v0.1.0

> This original release adds support for `find_motifs` and motif-counting with the `count_only=True` argument.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ For very large graphs, you may use a good chunk of RAM not only on the queue of
| `profile` | `bool` | `False` | Whether to slow down execution but give you a better idea of where your RAM usage is going. This is better ignored unless you're debugging something particularly nuanced. |
| `isomorphisms_only` | `bool` | `False` | Whether to search only for isomorphisms. In other words, whether to search for edges that exist in the node-induced subgraph. |
| `hints` | `List[Dict[Hashable, Hashable]]` | A list of valid candidate mappings to use as the starting seeds for new maps. See _Using Hints_, below. |
| `limit` | `int` | An optional integer limit of results to return. If the limit is reached, the search will return early. |

## Using Hints

Expand Down Expand Up @@ -133,15 +134,14 @@ coverage run --source=grandiso -m pytest
If this tool is helpful to your research, please consider citing it with:

```bibtex
# https://www.biorxiv.org/content/10.1101/2020.06.08.140533v1
# https://doi.org/10.1101/2020.06.08.140533
@article{matelsky_2020_dotmotif,
doi = {10.1101/2020.06.08.140533},
url = {https://www.biorxiv.org/content/10.1101/2020.06.08.140533v2},
year = 2020,
month = {june},
publisher = {BiorXiv},
author = {Matelsky, Jordan K. and Reilly, Elizabeth P. and Johnson,Erik C. and Wester, Brock A. and Gray-Roncal, William},
title = {{Connectome subgraph isomorphisms and graph queries with DotMotif}},
journal = {BiorXiv}
title={{DotMotif: An open-source tool for connectome subgraph isomorphism search and graph queries}},
url={http://dx.doi.org/10.1101/2020.06.08.140533},
DOI={10.1101/2020.06.08.140533},
publisher={Cold Spring Harbor Laboratory},
author={Matelsky, Jordan K. and Reilly, Elizabeth P. and Johnson, Erik C. and Stiso, Jennifer and Bassett, Danielle S. and Wester, Brock A. and Gray-Roncal, William},
year={2020},
month={Jun}
}
```
36 changes: 30 additions & 6 deletions grandiso/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@
- Delete the backbone queue
- Delete the results table (after collection)
"""
from typing import Dict, Hashable, List, Union
from typing import Any, Dict, Hashable, List, Optional, Tuple, Union

import itertools
import time
import queue

import networkx as nx

__version__ = "1.0.0"
__version__ = "1.1.0"

"""
In this process, we consider the following operations to be fast:
Expand Down Expand Up @@ -157,7 +157,7 @@ def get_next_backbone_candidates(
]

else:
_node_with_greatest_backbone_count = None
_node_with_greatest_backbone_count: Optional[str] = None
_greatest_backbone_count = 0
for motif_node_id in motif.nodes():
if motif_node_id in backbone:
Expand Down Expand Up @@ -239,9 +239,12 @@ def get_next_backbone_candidates(
if source is not None:
# this is a "from" edge:
candidate_nodes_from_this_edge = list(host.adj[backbone[source]])
elif target is not None:
# elif target is not None:
else: # target is not None:
# this is a "from" edge:
candidate_nodes_from_this_edge = list(host.pred[backbone[target]])
# else:
# raise AssertionError("Encountered an impossible condition: At least one of source or target must be defined.")
else:
candidate_nodes_from_this_edge = list(host.adj[backbone[target]])
if len(candidate_nodes_set) == 0:
Expand Down Expand Up @@ -354,7 +357,8 @@ def find_motifs(
profile: bool = False,
isomorphisms_only: bool = False,
hints: List[Dict[Hashable, Hashable]] = None,
) -> List[dict]:
limit: int = None,
) -> Union[int, List[dict], Tuple[Union[int, List[dict]], Any]]:
"""
Get a list of mappings from motif node IDs to host graph IDs.
Expand All @@ -376,12 +380,20 @@ def find_motifs(
profile (bool: False): SLOWER! Whether to include additional metrics
in addition to results. Note that you should only ever use this to
debug or understand your results, not for use in production.
hints (dict): A dictionary of initial starting mappings. By default,
searches for all instances. You can constrain a node by passing a
list with a single dict item: `[{motifId: hostId}]`.
limit (int: None): A limit to place on the number of returned mappings.
The search will terminate once the limit is reached.
isomorphisms_only (bool: False): Whether to return isomorphisms (the
default is monomorphisms).
Returns:
List[dict]: A list of mappings from motif node IDs to host graph IDs
int, List[dict], Tuple[List[dict], queue.Queue]
int: If `count_only` is True, return the length of the List.
List[dict]: A list of mappings from motif node IDs to host graph IDs
Tuple[List[dict], queue.Queue]: If `profile` is true. Also includes the
queue that was used to perform the search.
"""
interestingness = interestingness or uniform_node_interestingness(motif)
Expand Down Expand Up @@ -424,8 +436,20 @@ def find_motifs(
if len(candidate) == len(motif):
if count_only:
results_count += 1
if limit and results_count >= limit:
# perform return logic
if profile:
return results_count, q
else:
return results_count
else:
results.append(candidate)
if limit and len(results) >= limit:
# perform return logic
if profile:
return results, q
return results

else:
q.put(candidate)

Expand Down
22 changes: 22 additions & 0 deletions grandiso/test_grandiso.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,3 +421,25 @@ def test_basic_hints(self):
find_motifs(motif, host, count_only=True, hints=[{"a": "A"}, {"b": "A"}])
== 2
)


class TestLimits:
def test_zero_limit(self):
host = nx.complete_graph(8)
motif = nx.complete_graph(3)
assert find_motifs(motif, host, count_only=True, limit=0) == 336

def test_limit_one(self):
host = nx.complete_graph(8)
motif = nx.complete_graph(3)
assert find_motifs(motif, host, count_only=True, limit=1) == 1

def test_limit_eq_answer(self):
host = nx.complete_graph(8)
motif = nx.complete_graph(3)
assert find_motifs(motif, host, count_only=True, limit=336) == 336

def test_limit_gt_answer(self):
host = nx.complete_graph(8)
motif = nx.complete_graph(3)
assert find_motifs(motif, host, count_only=True, limit=338) == 336

0 comments on commit 3e7affa

Please sign in to comment.