Skip to content

Commit

Permalink
Add initial working multigraph edge validator plus tests (#107)
Browse files Browse the repository at this point in the history
* Add initial working model of multigraph edge validator plus tests

* Add example test where multigraph ANY fails

* Add temporary documentation while we improve support

* Add basic multigraph tests

* Working multigraph "ANY" support

* Add failure check in unit tests

* Update formatting in grandiso executor

* Remove dotmotif.dotmotif deprecated API

* Remove dotmotif.dotmotif from tests

* Update documentation

* Update multigraph documentation

* fix dangling dotmotif.dotmotif reference
  • Loading branch information
j6k4m8 authored Sep 30, 2021
1 parent c60ba4e commit 8e8ddb7
Show file tree
Hide file tree
Showing 26 changed files with 468 additions and 205 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

- **0.10.0**
- Features:
- `GrandIsoExecutor` and `NetworkXExecutor`: Support for `networkx.MultiGraph` and `networkx.MultiDiGraph` search through the use of the `multigraph_edge_match` executor argument (#107).
- Deprecations:
- Removed `dotmotif.dotmotif` method-style API (#107).
- **0.9.2** (May 28 2021)
- Features:
- `GrandIsoExecutor`: Utilizes the node attribute matching flow available in grandiso≥2.0.0 to reduce complexity of attribute-heavy searches (#104)
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/dotmotif/dotmotif.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Create a new dotmotif object.
> - **parser** (`dotmotif.parsers.Parser`: `DEFAULT_MOTIF_PARSER`): The parser
to use to parse the document. Defaults to the v2 parser.
> - **exclude_automorphisms** (`bool`: `False`): Whether to exclude automorphism
variants of the motif when rturning results.
variants of the motif when returning results.
> - **validators** (`List[Validator]`: `None`): A list of dotmotif.Validators to use
when verifying the motif for correctness and executability.

Expand Down
2 changes: 1 addition & 1 deletion docs/reference/dotmotif/utils.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Draw a dotmotif motif object.

### Arguments
> - **dm** (`None`: `None`): dotmotif.DotMotif
> - **dm** (`None`: `None`): dotmotif.Motif
> - **negative_edge_color** (`str`: `r`): Color used to represent negative edges
> - **pos** (`dict`: `None`): The position to use. If unset, uses nx.spring_layout
Expand Down
17 changes: 16 additions & 1 deletion docs/reference/executors/GrandIsoExecutor.py.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
## *Class* `GrandIsoExecutor(NetworkXExecutor)`


A DotMotif executor that uses grandiso for subgraph monomorphism.

This executor is dramatically fast than the NetworkX search, and is still a pure-Python implementation.

[GrandIso](https://github.com/aplbrain/grandiso-networkx)



## *Function* `find(self, motif, limit: int = None)`


Find a motif in a larger graph.

### Arguments
motif (dotmotif.dotmotif)
motif (dotmotif.Motif)
> - **int** (`None`: `None`): None)
### Returns
List[dict]

8 changes: 4 additions & 4 deletions docs/reference/executors/Neo4jExecutor.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,21 +72,21 @@ You should usually ignore this, and use .find() instead.



## *Function* `count(self, motif: "dotmotif", limit=None) -> int`
## *Function* `count(self, motif: "dotmotif.Motif", limit=None) -> int`


Count a motif in a larger graph.

### Arguments
motif (dotmotif.dotmotif)
motif (dotmotif.Motif)



## *Function* `find(self, motif: "dotmotif", limit=None, cursor=True)`
## *Function* `find(self, motif: "dotmotif.Motif", limit=None, cursor=True)`


Find a motif in a larger graph.

### Arguments
motif (dotmotif.dotmotif)
motif (dotmotif.Motif)

12 changes: 9 additions & 3 deletions docs/reference/executors/NetworkXExecutor.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
Check if a single edge satisfies the constraints.


## *Function* `_edge_satisfies_many_constraints_for_muligraph_any_edges(edge_attributes: dict, constraints: dict) -> List[Tuple[str, str, str]]`


Returns a subset of constraints that this edge matches, in the form (key, op, val).


## *Function* `_node_satisfies_constraints(node_attributes: dict, constraints: dict) -> bool`


Expand Down Expand Up @@ -32,19 +38,19 @@ Create a new NetworkXExecutor.



## *Function* `count(self, motif: "dotmotif", limit: int = None)`
## *Function* `count(self, motif: "dotmotif.Motif", limit: int = None)`


Count the occurrences of a motif in a graph.

See NetworkXExecutor#find for more documentation.


## *Function* `find(self, motif: "dotmotif", limit: int = None)`
## *Function* `find(self, motif: "dotmotif.Motif", limit: int = None)`


Find a motif in a larger graph.

### Arguments
motif (dotmotif.dotmotif)
motif (dotmotif.Motif)

8 changes: 4 additions & 4 deletions docs/reference/executors/NeuPrintExecutor.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,26 @@ You should usually ignore this, and use .find() instead.



## *Function* `count(self, motif: dotmotif, limit=None) -> int`
## *Function* `count(self, motif: Motif, limit=None) -> int`


Count a motif in a larger graph.

### Arguments
> - **motif** (`dotmotif.dotmotif`: `None`): The motif to search for
> - **motif** (`dotmotif.Motif`: `None`): The motif to search for
### Returns
> - **int** (`None`: `None`): The count of this motif in the host graph


## *Function* `find(self, motif: dotmotif, limit=None) -> pd.DataFrame`
## *Function* `find(self, motif: Motif, limit=None) -> pd.DataFrame`


Find a motif in a larger graph.

### Arguments
> - **motif** (`dotmotif.dotmotif`: `None`): The motif to search for
> - **motif** (`dotmotif.Motif`: `None`): The motif to search for
### Returns
> - **pd.DataFrame** (`None`: `None`): The results of the search
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/ingest/ingest.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ An abstract base class for import to the NetworkX format.



## *Class* `CSVEdgelistConverter(NetworkXConverter)`
## *Class* `EdgelistConverter(NetworkXConverter)`


A converter that takes an arbitrary CSV file on disk and converts it to a graph.
Convert an edgelist dataframe or CSV to a graph.



Expand Down
Empty file added docs/reference/tests/tests.md
Empty file.
7 changes: 2 additions & 5 deletions dotmotif/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def from_motif(self, cmd: str):

return self

def from_nx(self, graph: nx.DiGraph) -> "dotmotif":
def from_nx(self, graph: nx.DiGraph) -> "Motif":
"""
Ingest directly from a graph.
Expand Down Expand Up @@ -197,7 +197,7 @@ def save(self, fname: Union[str, IO[bytes]]) -> Union[str, IO[bytes]]:
return fname

@staticmethod
def load(fname: Union[str, IO[bytes]]) -> "dotmotif":
def load(fname: Union[str, IO[bytes]]) -> "Motif":
"""
Load the motif from a file on disk.
Expand All @@ -215,6 +215,3 @@ def load(fname: Union[str, IO[bytes]]) -> "dotmotif":
result = pickle.load(f)
f.close()
return result


dotmotif = Motif
11 changes: 10 additions & 1 deletion dotmotif/executors/GrandIsoExecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,19 @@ def _node_attr_match_fn(
is_node_attr_match=_node_attr_match_fn,
)

_edge_constraint_validator = (
self._validate_edge_constraints if not self._host_is_multigraph
else (
self._validate_multigraph_all_edge_constraints
if self._multigraph_edge_match == "all"
else self._validate_multigraph_any_edge_constraints
)
)

results = []
for r in graph_matches:
if _doesnt_have_any_of_motifs_negative_edges(r) and (
self._validate_edge_constraints(
_edge_constraint_validator(
r, self.graph, motif.list_edge_constraints()
)
# and self._validate_node_constraints(
Expand Down
12 changes: 6 additions & 6 deletions dotmotif/executors/Neo4jExecutor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright 2020 The Johns Hopkins University Applied Physics Laboratory.
Copyright 2021 The Johns Hopkins University Applied Physics Laboratory.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -280,12 +280,12 @@ def run(self, cypher: str, cursor=True):
return self.G.run(cypher).to_table()
return self.G.run(cypher)

def count(self, motif: "dotmotif", limit=None) -> int:
def count(self, motif: "dotmotif.Motif", limit=None) -> int:
"""
Count a motif in a larger graph.
Arguments:
motif (dotmotif.dotmotif)
motif (dotmotif.Motif)
"""
qry = self.motif_to_cypher(
Expand All @@ -295,12 +295,12 @@ def count(self, motif: "dotmotif", limit=None) -> int:
qry += f" LIMIT {limit}"
return int(self.G.run(qry).to_ndarray())

def find(self, motif: "dotmotif", limit=None, cursor=True):
def find(self, motif: "dotmotif.Motif", limit=None, cursor=True):
"""
Find a motif in a larger graph.
Arguments:
motif (dotmotif.dotmotif)
motif (dotmotif.Motif)
"""
qry = self.motif_to_cypher(motif, static_entity_labels=self._entity_labels)
Expand All @@ -312,7 +312,7 @@ def find(self, motif: "dotmotif", limit=None, cursor=True):

@staticmethod
def motif_to_cypher(
motif: "dotmotif", count_only: bool = False, static_entity_labels: dict = None,
motif: "dotmotif.Motif", count_only: bool = False, static_entity_labels: dict = None,
) -> str:
"""
Output a query suitable for Cypher-compatible engines (e.g. Neo4j).
Expand Down
Loading

0 comments on commit 8e8ddb7

Please sign in to comment.