-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #65 from geoneric/gh56
Fix horizontal spread when multiple actions continue from a single tipping point
- Loading branch information
Showing
9 changed files
with
511 additions
and
414 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 103 additions & 0 deletions
103
source/package/adaptation_pathways/graph/directed_graph.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import typing | ||
|
||
import networkx as nx | ||
|
||
|
||
class DirectedGraph: | ||
""" | ||
Base class for specialized directed graphs. | ||
""" | ||
|
||
_graph: nx.DiGraph | ||
|
||
def __init__(self) -> None: | ||
self._graph = nx.DiGraph() | ||
|
||
def __str__(self) -> str: | ||
return "\n".join(nx.generate_network_text(self._graph)) | ||
|
||
@property | ||
def graph(self) -> nx.DiGraph: | ||
""" | ||
:return: The layered directed graph instance | ||
Try not to use it -- it should be an implementation detail as much as possible. | ||
""" | ||
return self._graph | ||
|
||
# def is_empty(self) -> bool: | ||
# """ | ||
# :return: Whether or not the tree is empty | ||
# """ | ||
# return nx.is_empty(self._graph) | ||
|
||
def nr_nodes(self) -> int: | ||
""" | ||
:return: Number of nodes | ||
""" | ||
return len(self._graph.nodes) | ||
|
||
def nr_edges(self) -> int: | ||
""" | ||
:return: Number of edges | ||
""" | ||
return len(self._graph.edges) | ||
|
||
def all_to_nodes(self, from_node): | ||
# Use shortest_path to find all nodes reachable from the node passed in | ||
graph = self._graph.subgraph(nx.shortest_path(self._graph, from_node)) | ||
|
||
# Remove the from_node itself before returning the result | ||
result = list(graph.nodes) | ||
result.remove(from_node) | ||
|
||
return result | ||
|
||
def to_nodes(self, from_node) -> list[typing.Any]: | ||
""" | ||
:return: Collection of nodes that start at the node passed in | ||
""" | ||
return list(self._graph.adj[from_node]) | ||
|
||
def from_nodes(self, to_node): | ||
""" | ||
:return: Collection of nodes that end at the node passed in | ||
""" | ||
# TODO Can this be done more efficiently? | ||
return [node for node in self._graph.nodes() if to_node in self.to_nodes(node)] | ||
|
||
def leaf_nodes(self) -> typing.Iterable[typing.Any]: | ||
""" | ||
:Return: Iterable for iterating over all leaf nodes | ||
""" | ||
return [ | ||
node | ||
for node in self._graph.nodes() | ||
if self._graph.in_degree(node) != 0 and self._graph.out_degree(node) == 0 | ||
] | ||
|
||
def all_paths(self) -> list[list[typing.Any]]: | ||
result = [] | ||
|
||
if self.nr_nodes() > 0: | ||
graph = self._graph | ||
|
||
root_nodes = [ | ||
node for node, degree in self._graph.in_degree() if degree == 0 | ||
] | ||
leaf_nodes = self.leaf_nodes() | ||
|
||
for root_node in root_nodes: | ||
cutoff = None | ||
result += nx.all_simple_paths(graph, root_node, leaf_nodes, cutoff) | ||
|
||
return result | ||
|
||
def set_attribute(self, name: str, value: typing.Any) -> None: | ||
""" | ||
Add / update attribute value to / of the graph | ||
:param name: Name of attribute to set | ||
:param value: Value of the attribute to set | ||
""" | ||
self.graph.graph[name] = value |
22 changes: 22 additions & 0 deletions
22
source/package/adaptation_pathways/graph/multi_rooted_graph.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from .directed_graph import DirectedGraph | ||
|
||
|
||
class MultiRootedGraph(DirectedGraph): | ||
""" | ||
Class for directed out-graphs in which there can be multiple graphs rooted at independent nodes. | ||
""" | ||
|
||
@property | ||
def root_nodes(self): | ||
""" | ||
:return: The root nodes | ||
""" | ||
|
||
root_nodes = [node for node, degree in self._graph.in_degree() if degree == 0] | ||
|
||
nr_root_nodes = len(root_nodes) | ||
|
||
if nr_root_nodes == 0: | ||
raise LookupError("Graph is empty") | ||
|
||
return root_nodes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.