Skip to content

Commit

Permalink
GraphNode interface abstraction & Graph refactorings (#919)
Browse files Browse the repository at this point in the history
* Better impl of ordered_subnodes_hier; make it fun, not method

* Remove dupl of node depth omputation

* abstrat desriptive_id, introdue repr(node)

* WIP introdue interfae for GraphNode

* WIP fix str/repr/desriptive_id logic for tests

* WIP simplify disonnet nodes

* WIP minor fixes

* Introduce abstract GraphNode interface

* WIP refator delete_subtree & delete_node

* Revert "WIP refator delete_subtree & delete_node"

This reverts commit 4febfad00e52ec23a6524cc7cfac5c17710103dd.

* Fix graph_id test

* WIP upd usages of GraphNode type to DAGNode where needed

* fixup! WIP simplify disonnet nodes

* fixup! Fix graph_id test

* Add backward compatibility mapping for moved/renamed classes in serializer.py

* Make get_nodes_degrees a funtion, not a method

* Make distane_to_primary_level a funtion, not a method

* Make nodes_from_layer a funtion, not a method

* Move graph utils methods to graph_utils.py

* WIP fix doc comments in graph_utils.py

* Modify dos for GraphNode

* Mode reursive_desriptive_id

* Make GraphNode Hashable

* Rename DAGNode to LinkedGraphNode

* Move LinkedGraphNode to its own file

* Drop usages of `distane_to_primary_level` in favor of node_depth

* Revert "Drop usages of `distane_to_primary_level` in favor of node_depth"

This reverts commit 3bbf80e.

* Revert making graph node Hashable

* Rename GraphOperator to LinkedGraph

* Revert moving reursive_desription_id

* Move distane_to_primary_level

* fixup! Rename GraphOperator to LinkedGraph

* fixup! Revert moving reursive_desription_id

* fix serialize of renamed GraphOperator/LinkedGraph

* fixup! disonnet nodes

* fix type import from typing module

* Drop pipeline.__eq__ that aused failure in hte ase of multiple root_nodes

* review fixes

* review fixes for type in tests

* WIP rebase fixes

* WIP rebase fixes for node desription

* Raise error when Node has no content in `description`

* fix descriptive_id in the case of default graph node (don't print {})

* Add expliit super() to linked_graph_node.py
  • Loading branch information
gkirgizov authored Oct 19, 2022
1 parent f0c69cc commit c66d718
Show file tree
Hide file tree
Showing 34 changed files with 376 additions and 358 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pandas as pd

from fedot.core.adapter import DirectAdapter, register_native
from fedot.core.dag.graph_utils import ordered_subnodes_hierarchy
from fedot.core.dag.verification_rules import has_no_cycle, has_no_self_cycled_nodes
from fedot.core.log import default_log
from fedot.core.optimisers.gp_comp.gp_optimizer import EvoGraphOptimizer
Expand Down Expand Up @@ -59,9 +60,9 @@ def custom_mutation(graph: OptGraph, **kwargs):
random_node = graph.nodes[rid]
other_random_node = graph.nodes[random.choice(range(len(graph.nodes)))]
nodes_not_cycling = (random_node.descriptive_id not in
[n.descriptive_id for n in other_random_node.ordered_subnodes_hierarchy()] and
[n.descriptive_id for n in ordered_subnodes_hierarchy(other_random_node)] and
other_random_node.descriptive_id not in
[n.descriptive_id for n in random_node.ordered_subnodes_hierarchy()])
[n.descriptive_id for n in ordered_subnodes_hierarchy(random_node)])
if nodes_not_cycling:
graph.connect_nodes(random_node, other_random_node)
except Exception as ex:
Expand Down
31 changes: 0 additions & 31 deletions fedot/core/dag/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,6 @@ def delete_subtree(self, subroot: GraphNode):
"""
raise NotImplementedError()

@abstractmethod
def distance_to_root_level(self, node: GraphNode) -> int:
"""Gets distance to the final output node
Args:
node: search starting point
"""
raise NotImplementedError()

@abstractmethod
def nodes_from_layer(self, layer_number: int) -> Sequence[GraphNode]:
"""Gets all the nodes from the chosen layer up to the surface
Args:
layer_number: max height of diving
Returns:
all nodes from the surface to the ``layer_number`` layer
"""
raise NotImplementedError()

@abstractmethod
def node_children(self, node: GraphNode) -> Sequence[Optional[GraphNode]]:
"""Returns all children of the ``node``
Expand Down Expand Up @@ -115,16 +94,6 @@ def disconnect_nodes(self, node_parent: GraphNode, node_child: GraphNode,
"""
raise NotImplementedError()

@abstractmethod
def get_nodes_degrees(self) -> Sequence[int]:
"""Nodes degree as the number of edges the node has:
``degree = #input_edges + #out_edges``
Returns:
nodes degrees ordered according to the nx_graph representation of this graph
"""
raise NotImplementedError()

@abstractmethod
def get_edges(self) -> Sequence[Tuple[GraphNode, GraphNode]]:
"""Gets all available edges in this graph
Expand Down
13 changes: 2 additions & 11 deletions fedot/core/dag/graph_delegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from fedot.core.dag.graph import Graph
from fedot.core.dag.graph_node import GraphNode
from fedot.core.dag.graph_operator import GraphOperator
from fedot.core.dag.linked_graph import LinkedGraph


class GraphDelegate(Graph):
Expand All @@ -15,7 +15,7 @@ class GraphDelegate(Graph):
- hide Graph implementation details from inheritors.
"""

def __init__(self, *args, delegate_cls: Type[Graph] = GraphOperator, **kwargs):
def __init__(self, *args, delegate_cls: Type[Graph] = LinkedGraph, **kwargs):
self.operator = delegate_cls(*args, **kwargs)

def add_node(self, node: GraphNode):
Expand All @@ -33,12 +33,6 @@ def delete_node(self, node: GraphNode):
def delete_subtree(self, subroot: GraphNode):
self.operator.delete_subtree(subroot)

def distance_to_root_level(self, node: GraphNode) -> int:
return self.operator.distance_to_root_level(node=node)

def nodes_from_layer(self, layer_number: int) -> Sequence[GraphNode]:
return self.operator.nodes_from_layer(layer_number=layer_number)

def node_children(self, node: GraphNode) -> Sequence[Optional[GraphNode]]:
return self.operator.node_children(node=node)

Expand All @@ -49,9 +43,6 @@ def disconnect_nodes(self, node_parent: GraphNode, node_child: GraphNode,
clean_up_leftovers: bool = True):
self.operator.disconnect_nodes(node_parent, node_child, clean_up_leftovers)

def get_nodes_degrees(self):
return self.operator.get_nodes_degrees()

def get_edges(self) -> Sequence[Tuple[GraphNode, GraphNode]]:
return self.operator.get_edges()

Expand Down
132 changes: 36 additions & 96 deletions fedot/core/dag/graph_node.py
Original file line number Diff line number Diff line change
@@ -1,137 +1,77 @@
from abc import ABC, abstractmethod
from copy import copy
from typing import List, Optional, Union, Iterable
from uuid import uuid4
from typing import List, Optional, Iterable

from fedot.core.utilities.data_structures import UniqueList

class GraphNode(ABC):
"""Definition of the node in directed graph structure.
MAX_DEPTH = 1000


class GraphNode:
"""Class for node definition in the DAG-based structure
Args:
nodes_from: parent nodes which information comes from
content: ``dict`` for the content in the node
Notes:
The possible parameters are:
- ``name`` - name (str) or object that performs actions in this node
- ``params`` - dictionary with additional information that is used by
the object in the ``name`` field (e.g. hyperparameters values)
Provides interface for getting and modifying the parent nodes
and recursive description based on all preceding nodes.
"""

def __init__(self, content: Union[dict, str],
nodes_from: Optional[List['GraphNode']] = None):
# Wrap string into dict if it is necessary
if isinstance(content, str):
content = {'name': content}

self.content = content
self._nodes_from = UniqueList(nodes_from or ())
self.uid = str(uuid4())

def __str__(self):
"""Returns graph node description
Returns:
text graph node representation
"""

return str(self.content['name'])

def __repr__(self):
"""Does the same as :meth:`__str__`
Returns:
text graph node representation
"""

return self.__str__()

@property
@abstractmethod
def nodes_from(self) -> List['GraphNode']:
"""Gets all parent nodes of this graph node
Returns:
List['GraphNode']: all the parent nodes
"""

return self._nodes_from
pass

@nodes_from.setter
@abstractmethod
def nodes_from(self, nodes: Optional[Iterable['GraphNode']]):
"""Changes value of parent nodes of this graph node
Returns:
Union['GraphNode', None]: new sequence of parent nodes
Args:
nodes: new sequence of parent nodes
"""
pass

self._nodes_from = UniqueList(nodes)

@property
def descriptive_id(self) -> str:
"""Returns verbal identificator of the node
@abstractmethod
def __str__(self) -> str:
"""Returns short node type description
Returns:
str: text description of the content in the node and its parameters
str: text graph node representation
"""
return _descriptive_id_recursive(self)

def ordered_subnodes_hierarchy(self, visited: Optional[List['GraphNode']] = None) -> List['GraphNode']:
"""Gets hierarchical subnodes representation of the graph starting from the bounded node
pass

Args:
visited: already visited nodes not to be included to the resulting hierarchical list
def __repr__(self) -> str:
"""Returns full node description
Returns:
List['GraphNode']: hierarchical subnodes list starting from the bounded node
str: text graph node representation
"""
if visited is None:
visited = []
return self.__str__()

if len(visited) > MAX_DEPTH:
raise ValueError('Graph has cycle')
nodes = [self]
for parent in self.nodes_from:
if parent not in visited:
visited.append(parent)
nodes.extend(parent.ordered_subnodes_hierarchy(visited))
def description(self) -> str:
"""Returns full node description
for use in recursive id.
return nodes
Returns:
str: text graph node representation
"""

return self.__str__()

@property
def distance_to_primary_level(self) -> int:
"""Returns max depth from bounded node to graphs primary level
def descriptive_id(self) -> str:
"""Returns structural identifier of the subgraph starting at this node
Returns:
int: max depth to the primary level
str: text description of the content in the node and its parameters
"""
if not self.nodes_from:
return 0
else:
return 1 + max([next_node.distance_to_primary_level for next_node in self.nodes_from])
return descriptive_id_recursive(self)


def _descriptive_id_recursive(current_node: GraphNode, visited_nodes: Optional[List[GraphNode]] = None) -> str:
"""Method returns verbal description of the content in the node
and its parameters
"""

def descriptive_id_recursive(current_node: GraphNode, visited_nodes=None) -> str:
if visited_nodes is None:
visited_nodes = []

node_operation = current_node.content['name']
params = current_node.content.get('params')
if isinstance(node_operation, str):
# If there is a string: name of operation (as in json repository)
node_label = str(node_operation)
if params:
node_label = f'n_{node_label}_{params}'
else:
# If instance of Operation is placed in 'name'
node_label = node_operation.description(params)
node_label = current_node.description()

full_path_items = []
if current_node in visited_nodes:
Expand All @@ -140,7 +80,7 @@ def _descriptive_id_recursive(current_node: GraphNode, visited_nodes: Optional[L
if current_node.nodes_from:
previous_items = []
for parent_node in current_node.nodes_from:
previous_items.append(f'{_descriptive_id_recursive(copy(parent_node), copy(visited_nodes))};')
previous_items.append(f'{descriptive_id_recursive(parent_node, copy(visited_nodes))};')
previous_items.sort()
previous_items_str = ';'.join(previous_items)

Expand Down
Loading

0 comments on commit c66d718

Please sign in to comment.