Skip to content

Commit

Permalink
v0.14.0 (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
j6k4m8 authored May 16, 2023
1 parent 08fd3f7 commit 3493256
Show file tree
Hide file tree
Showing 6 changed files with 931 additions and 23 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

- **0.14.0** (May 16 2023)
- Housekeeping:
- Upgraded the package manager to Poetry, with version-pinning now handled by the `pyproject.toml` file.
- **0.13.0** (October 11 2022)
- Features:
- Introduced propagation of constraints when nodes are explicitly marked as automorphic to one another.
Expand Down
14 changes: 8 additions & 6 deletions dotmotif/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
limitations under the License.
"""

from typing import Union, IO
from typing import List, Optional, Union, IO
import copy
import pickle
import warnings
Expand All @@ -25,12 +25,12 @@
from networkx.algorithms import isomorphism

from .parsers.v2 import ParserV2
from .validators import DisagreeingEdgesValidator
from .validators import DisagreeingEdgesValidator, Validator

from .executors.NetworkXExecutor import NetworkXExecutor
from .executors.GrandIsoExecutor import GrandIsoExecutor

__version__ = "0.13.0"
__version__ = "0.14.0"

DEFAULT_MOTIF_PARSER = ParserV2

Expand All @@ -46,7 +46,7 @@ class Motif:
See __init__ documentation for more details.
"""

def __init__(self, input_motif: str = None, **kwargs):
def __init__(self, input_motif: Optional[str] = None, **kwargs):
"""
Create a new dotmotif object.
Expand Down Expand Up @@ -74,7 +74,9 @@ def __init__(self, input_motif: str = None, **kwargs):
self.pretty_print = kwargs.get("pretty_print", True)
self.parser = kwargs.get("parser", DEFAULT_MOTIF_PARSER)
self.exclude_automorphisms = kwargs.get("exclude_automorphisms", False)
self.validators = kwargs.get("validators", [DisagreeingEdgesValidator()])
self.validators: List[Validator] = kwargs.get(
"validators", [DisagreeingEdgesValidator()]
)
self._g = nx.MultiDiGraph()

self._edge_constraints = {}
Expand Down Expand Up @@ -186,7 +188,7 @@ def _propagate_automorphic_constraints(self):
# Note to future self: We DON'T loop over implicit automorphisms because
# there is no guarantee that the user intends for structural symmetries
# to also be symmetries in the constraint space.
for (u, v) in self._automorphisms:
for u, v in self._automorphisms:
# Add a superset of constraints on the two nodes.
# First add attributes on the nodes themselves:
constraints = _deep_merge_constraint_dicts(
Expand Down
7 changes: 4 additions & 3 deletions dotmotif/executors/GrandIsoExecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
limitations under the License.`
"""
from functools import lru_cache
from typing import Optional
import networkx as nx
from grandiso import find_motifs_iter

Expand All @@ -31,7 +32,7 @@ class GrandIsoExecutor(NetworkXExecutor):
"""

def find(self, motif, limit: int = None):
def find(self, motif, limit: Optional[int] = None):
"""
Find a motif in a larger graph.
Expand All @@ -46,7 +47,7 @@ def find(self, motif, limit: int = None):
# We need to first remove "negative" nodes from the motif, and then
# filter them out later on.

if motif.ignore_direction or not self.graph.is_directed:
if motif.ignore_direction or not self.graph.is_directed: # type: ignore
graph_constructor = nx.Graph
else:
graph_constructor = nx.DiGraph
Expand All @@ -62,7 +63,7 @@ def find(self, motif, limit: int = None):

def _doesnt_have_any_of_motifs_negative_edges(mapping):
for u, v in must_not_exist_edges:
if self.graph.has_edge(mapping[u], mapping[v]):
if self.graph.has_edge(mapping[u], mapping[v]): # type: ignore
return False
return True

Expand Down
28 changes: 14 additions & 14 deletions dotmotif/executors/NetworkXExecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
limitations under the License.`
"""

from typing import TYPE_CHECKING, List, Tuple
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
import copy
import networkx as nx

from .Executor import Executor

if TYPE_CHECKING:
from .. import dotmotif
from .. import dotmotif # type: ignore

_OPERATORS = {
"=": lambda x, y: x == y,
Expand Down Expand Up @@ -124,7 +124,7 @@ def __init__(self, **kwargs) -> None:
"""
if "graph" in kwargs:
self.graph = kwargs.get("graph")
self.graph: nx.Graph = kwargs["graph"]
else:
raise ValueError(
"You must pass a graph to the NetworkXExecutor constructor."
Expand All @@ -143,7 +143,7 @@ def __init__(self, **kwargs) -> None:
), "_multigraph_edge_match must be one of 'all' or 'any'."

def _validate_node_constraints(
self, node_isomorphism_map: dict, graph: nx.DiGraph, constraints: dict
self, node_isomorphism_map: dict, graph: nx.Graph, constraints: dict
) -> bool:
"""
Validate nodes against their isomorphism's constraints in the motif.
Expand All @@ -162,7 +162,7 @@ def _validate_node_constraints(
return True

def _validate_dynamic_node_constraints(
self, node_isomorphism_map: dict, graph: nx.DiGraph, constraints: dict
self, node_isomorphism_map: dict, graph: nx.Graph, constraints: dict
) -> bool:
"""
Validate a graph against its dynamic node constraints.
Expand All @@ -183,7 +183,7 @@ def _validate_dynamic_node_constraints(
this_node = node_isomorphism_map[motif_U]
for this_key, operators in constraint_list.items():
for operator, that_node_list in operators.items():
for (that_node_V, that_key) in that_node_list:
for that_node_V, that_key in that_node_list:
that_node = node_isomorphism_map[that_node_V]
if this_key not in graph.nodes[this_node]:
return False
Expand All @@ -197,7 +197,7 @@ def _validate_dynamic_node_constraints(
return True

def _validate_edge_constraints(
self, node_isomorphism_map: dict, graph: nx.DiGraph, constraints: dict
self, node_isomorphism_map: dict, graph: nx.Graph, constraints: dict
):
"""
Validate all edge constraints on a subgraph.
Expand Down Expand Up @@ -234,15 +234,15 @@ def _validate_edge_constraints(
graph_v = node_isomorphism_map[motif_V]

# Check edge in graph for constraints
edge_attrs = graph.get_edge_data(graph_u, graph_v)
edge_attrs: Dict[Any, Any] = graph.get_edge_data(graph_u, graph_v) # type: ignore

if not _edge_satisfies_constraints(edge_attrs, constraint_list):
# Fail fast
return False
return True

def _validate_dynamic_edge_constraints(
self, node_isomorphism_map: dict, graph: nx.DiGraph, constraints: dict
self, node_isomorphism_map: dict, graph: nx.Graph, constraints: dict
):
"""
Validate all edge constraints on a subgraph.
Expand All @@ -264,7 +264,7 @@ def _validate_dynamic_edge_constraints(
"""
for (motif_U, motif_V), constraint_list in constraints.items():
for (this_attr, ops) in constraint_list.items():
for this_attr, ops in constraint_list.items():
for op, (that_u, that_v, that_attr) in ops.items():
this_graph_u = node_isomorphism_map[motif_U]
this_graph_v = node_isomorphism_map[motif_V]
Expand All @@ -281,7 +281,7 @@ def _validate_dynamic_edge_constraints(
return True

def _validate_multigraph_all_edge_constraints(
self, node_isomorphism_map: dict, graph: nx.DiGraph, constraints: dict
self, node_isomorphism_map: dict, graph: nx.Graph, constraints: dict
):
"""
Reuses logic from the simple _validate_edge_constraints case.
Expand All @@ -304,7 +304,7 @@ def _validate_multigraph_all_edge_constraints(
return True

def _validate_multigraph_any_edge_constraints(
self, node_isomorphism_map: dict, graph: nx.DiGraph, constraints: dict
self, node_isomorphism_map: dict, graph: nx.Graph, constraints: dict
):
"""
Reuses logic from the simple _validate_edge_constraints case.
Expand Down Expand Up @@ -351,15 +351,15 @@ def _validate_multigraph_any_edge_constraints(

return True

def count(self, motif: "dotmotif.Motif", limit: int = None):
def count(self, motif: "dotmotif.Motif", limit: Optional[int] = None):
"""
Count the occurrences of a motif in a graph.
See NetworkXExecutor#find for more documentation.
"""
return len(self.find(motif, limit))

def find(self, motif: "dotmotif.Motif", limit: int = None):
def find(self, motif: "dotmotif.Motif", limit: Optional[int] = None):
"""
Find a motif in a larger graph.
Expand Down
Loading

0 comments on commit 3493256

Please sign in to comment.