Skip to content

Commit 8e8ddb7

Browse files
authored
Add initial working multigraph edge validator plus tests (#107)
* 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
1 parent c60ba4e commit 8e8ddb7

26 files changed

+468
-205
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
- **0.10.0**
4+
- Features:
5+
- `GrandIsoExecutor` and `NetworkXExecutor`: Support for `networkx.MultiGraph` and `networkx.MultiDiGraph` search through the use of the `multigraph_edge_match` executor argument (#107).
6+
- Deprecations:
7+
- Removed `dotmotif.dotmotif` method-style API (#107).
38
- **0.9.2** (May 28 2021)
49
- Features:
510
- `GrandIsoExecutor`: Utilizes the node attribute matching flow available in grandiso≥2.0.0 to reduce complexity of attribute-heavy searches (#104)

docs/reference/dotmotif/dotmotif.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Create a new dotmotif object.
2323
> - **parser** (`dotmotif.parsers.Parser`: `DEFAULT_MOTIF_PARSER`): The parser
2424
to use to parse the document. Defaults to the v2 parser.
2525
> - **exclude_automorphisms** (`bool`: `False`): Whether to exclude automorphism
26-
variants of the motif when rturning results.
26+
variants of the motif when returning results.
2727
> - **validators** (`List[Validator]`: `None`): A list of dotmotif.Validators to use
2828
when verifying the motif for correctness and executability.
2929

docs/reference/dotmotif/utils.py.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Draw a dotmotif motif object.
55

66
### Arguments
7-
> - **dm** (`None`: `None`): dotmotif.DotMotif
7+
> - **dm** (`None`: `None`): dotmotif.Motif
88
> - **negative_edge_color** (`str`: `r`): Color used to represent negative edges
99
> - **pos** (`dict`: `None`): The position to use. If unset, uses nx.spring_layout
1010
Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
1+
## *Class* `GrandIsoExecutor(NetworkXExecutor)`
2+
3+
4+
A DotMotif executor that uses grandiso for subgraph monomorphism.
5+
6+
This executor is dramatically fast than the NetworkX search, and is still a pure-Python implementation.
7+
8+
[GrandIso](https://github.com/aplbrain/grandiso-networkx)
9+
10+
11+
112
## *Function* `find(self, motif, limit: int = None)`
213

314

415
Find a motif in a larger graph.
516

617
### Arguments
7-
motif (dotmotif.dotmotif)
18+
motif (dotmotif.Motif)
19+
> - **int** (`None`: `None`): None)
20+
21+
### Returns
22+
List[dict]
823

docs/reference/executors/Neo4jExecutor.py.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,21 +72,21 @@ You should usually ignore this, and use .find() instead.
7272

7373

7474

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

7777

7878
Count a motif in a larger graph.
7979

8080
### Arguments
81-
motif (dotmotif.dotmotif)
81+
motif (dotmotif.Motif)
8282

8383

8484

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

8787

8888
Find a motif in a larger graph.
8989

9090
### Arguments
91-
motif (dotmotif.dotmotif)
91+
motif (dotmotif.Motif)
9292

docs/reference/executors/NetworkXExecutor.py.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
Check if a single edge satisfies the constraints.
55

66

7+
## *Function* `_edge_satisfies_many_constraints_for_muligraph_any_edges(edge_attributes: dict, constraints: dict) -> List[Tuple[str, str, str]]`
8+
9+
10+
Returns a subset of constraints that this edge matches, in the form (key, op, val).
11+
12+
713
## *Function* `_node_satisfies_constraints(node_attributes: dict, constraints: dict) -> bool`
814

915

@@ -32,19 +38,19 @@ Create a new NetworkXExecutor.
3238

3339

3440

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

3743

3844
Count the occurrences of a motif in a graph.
3945

4046
See NetworkXExecutor#find for more documentation.
4147

4248

43-
## *Function* `find(self, motif: "dotmotif", limit: int = None)`
49+
## *Function* `find(self, motif: "dotmotif.Motif", limit: int = None)`
4450

4551

4652
Find a motif in a larger graph.
4753

4854
### Arguments
49-
motif (dotmotif.dotmotif)
55+
motif (dotmotif.Motif)
5056

docs/reference/executors/NeuPrintExecutor.py.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,26 +42,26 @@ You should usually ignore this, and use .find() instead.
4242

4343

4444

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

4747

4848
Count a motif in a larger graph.
4949

5050
### Arguments
51-
> - **motif** (`dotmotif.dotmotif`: `None`): The motif to search for
51+
> - **motif** (`dotmotif.Motif`: `None`): The motif to search for
5252
5353
### Returns
5454
> - **int** (`None`: `None`): The count of this motif in the host graph
5555
5656

5757

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

6060

6161
Find a motif in a larger graph.
6262

6363
### Arguments
64-
> - **motif** (`dotmotif.dotmotif`: `None`): The motif to search for
64+
> - **motif** (`dotmotif.Motif`: `None`): The motif to search for
6565
6666
### Returns
6767
> - **pd.DataFrame** (`None`: `None`): The results of the search

docs/reference/ingest/ingest.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ An abstract base class for import to the NetworkX format.
55

66

77

8-
## *Class* `CSVEdgelistConverter(NetworkXConverter)`
8+
## *Class* `EdgelistConverter(NetworkXConverter)`
99

1010

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

1313

1414

docs/reference/tests/tests.md

Whitespace-only changes.

dotmotif/__init__.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def from_motif(self, cmd: str):
119119

120120
return self
121121

122-
def from_nx(self, graph: nx.DiGraph) -> "dotmotif":
122+
def from_nx(self, graph: nx.DiGraph) -> "Motif":
123123
"""
124124
Ingest directly from a graph.
125125
@@ -197,7 +197,7 @@ def save(self, fname: Union[str, IO[bytes]]) -> Union[str, IO[bytes]]:
197197
return fname
198198

199199
@staticmethod
200-
def load(fname: Union[str, IO[bytes]]) -> "dotmotif":
200+
def load(fname: Union[str, IO[bytes]]) -> "Motif":
201201
"""
202202
Load the motif from a file on disk.
203203
@@ -215,6 +215,3 @@ def load(fname: Union[str, IO[bytes]]) -> "dotmotif":
215215
result = pickle.load(f)
216216
f.close()
217217
return result
218-
219-
220-
dotmotif = Motif

dotmotif/executors/GrandIsoExecutor.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,19 @@ def _node_attr_match_fn(
8383
is_node_attr_match=_node_attr_match_fn,
8484
)
8585

86+
_edge_constraint_validator = (
87+
self._validate_edge_constraints if not self._host_is_multigraph
88+
else (
89+
self._validate_multigraph_all_edge_constraints
90+
if self._multigraph_edge_match == "all"
91+
else self._validate_multigraph_any_edge_constraints
92+
)
93+
)
94+
8695
results = []
8796
for r in graph_matches:
8897
if _doesnt_have_any_of_motifs_negative_edges(r) and (
89-
self._validate_edge_constraints(
98+
_edge_constraint_validator(
9099
r, self.graph, motif.list_edge_constraints()
91100
)
92101
# and self._validate_node_constraints(

dotmotif/executors/Neo4jExecutor.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Copyright 2020 The Johns Hopkins University Applied Physics Laboratory.
2+
Copyright 2021 The Johns Hopkins University Applied Physics Laboratory.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -280,12 +280,12 @@ def run(self, cypher: str, cursor=True):
280280
return self.G.run(cypher).to_table()
281281
return self.G.run(cypher)
282282

283-
def count(self, motif: "dotmotif", limit=None) -> int:
283+
def count(self, motif: "dotmotif.Motif", limit=None) -> int:
284284
"""
285285
Count a motif in a larger graph.
286286
287287
Arguments:
288-
motif (dotmotif.dotmotif)
288+
motif (dotmotif.Motif)
289289
290290
"""
291291
qry = self.motif_to_cypher(
@@ -295,12 +295,12 @@ def count(self, motif: "dotmotif", limit=None) -> int:
295295
qry += f" LIMIT {limit}"
296296
return int(self.G.run(qry).to_ndarray())
297297

298-
def find(self, motif: "dotmotif", limit=None, cursor=True):
298+
def find(self, motif: "dotmotif.Motif", limit=None, cursor=True):
299299
"""
300300
Find a motif in a larger graph.
301301
302302
Arguments:
303-
motif (dotmotif.dotmotif)
303+
motif (dotmotif.Motif)
304304
305305
"""
306306
qry = self.motif_to_cypher(motif, static_entity_labels=self._entity_labels)
@@ -312,7 +312,7 @@ def find(self, motif: "dotmotif", limit=None, cursor=True):
312312

313313
@staticmethod
314314
def motif_to_cypher(
315-
motif: "dotmotif", count_only: bool = False, static_entity_labels: dict = None,
315+
motif: "dotmotif.Motif", count_only: bool = False, static_entity_labels: dict = None,
316316
) -> str:
317317
"""
318318
Output a query suitable for Cypher-compatible engines (e.g. Neo4j).

0 commit comments

Comments
 (0)