You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
*[networkx](https://networkx.org/) : FreExGraph is a layer on top of networkx
30
30
*[tqdm](https://github.com/tqdm/tqdm) : provide a nice way to display progression on visitation
31
31
32
+
---
33
+
32
34
# Documentation
33
35
34
36
The goal of this library is to provide a standardized way to represent an execution graph and to visit it via visitor. Some visitors are provided by FreExGraph but the more important thing is the ability to very easily provide its own visitor and it's own node type.
@@ -39,7 +41,7 @@ The goal of this library is to provide a standardized way to represent an execut
39
41
40
42
In order to use FreExGraph properly, it is required to provide your own node type. A node is a class that inherit from FreExNode class. It provides the following interface:
@@ -97,7 +100,7 @@ class MyBaseVisitor(AbstractVisitor):
97
100
# My actual Visitor implementation, this one just need a specific action
98
101
# to be made on MyNodeGamma node type. The rest has a default behaviour.
99
102
100
-
classCustomVisitor(MyBaseVisitor)
103
+
classCustomVisitor(MyBaseVisitor):
101
104
defvisit_default(self, node: FreExNode):
102
105
print("This is the behavior for Alpha and Beta nodes")
103
106
returnTrue
@@ -114,19 +117,98 @@ class CustomVisitor(MyBaseVisitor)
114
117
115
118
After creating node types (see above), we can create an execution graph that will use those nodes.
116
119
120
+
A graph is created with the class `FreExGraph`, which doesn't require any parameter, as follow:
121
+
```python
122
+
from freexgraph import FreExGraph
123
+
124
+
execution_graph = FreExGraph()
125
+
```
126
+
117
127
> it is important to note that the character ':' is forbidden in node id, as it is used for internal node (fork uniqueness and/or root_node)
118
128
119
-
**add_node:**
129
+
When the graph instance is done, you need to add nodes to them. The two following methods makes it possible.
120
130
121
-
**add_nodes:**
131
+
**add_node:** Of the method of signature `add_node(self, node: FreExNode)`. This method will add a node to the graph. If no parents are set in the given node,
132
+
the node will be linked with the root node that is automatically generated by the FreExGraph. If parents are set, edges will be made.
133
+
* Node id HAS to not be present in the graph
134
+
* A node's parent HAS to already be present in the graph.
135
+
* The id of the graph cannot contains ':' which is a forbidden character.
136
+
* No infinite looping of the node are accepted.
122
137
138
+
In the case any of those rules is not respected. An Assertion error is thrown.
**add_nodes:**: It can be cumbersome to ensure the ordering of the nodes you want to add (with the parents order). In order to avoid this issue, you can use add_nodes with the signature `add_nodes(self, nodes: List[AnyFreExNode])`.
169
+
This method is going to re-order the nodes depending on their parents in order to add them (calling add_node internally) properly.
170
+
`add_node` being called. All the rules applicable on add_node has to be respected with add_nodes (unicity of id and so on...)
171
+
172
+
173
+
example
174
+
```python
175
+
# we want to make the same graph as the one tested above for add_node
It is possible to embed a graph into another thanks to a graph node. Any visitation going through a graph node is going to be propagated to the inner graph.
127
190
128
-
< TODO >
191
+
To make one you need to create a FreExGraph (that will be in the GraphNode).
@@ -170,27 +252,35 @@ Obviously if you want the node id4::f1 (which is of type FreExNode as you asked)
170
252
171
253
It is also possible to provide a join node. It will be a node used as join for the fork in order to not have to fork the whole graph from the source of the fork. It is usefull if you have to multiply a big chunk of execution graph because one node has to change some internal values (in experimentation fields, it can be useful for parameter explorations).
172
254
173
-
> **IN CASE OF MAP REDUCE CASES DO NOT USE FORKS**. It is possible to do so with a join_id.. But it is prefered to do manually your map reduce (with add_node / add_nodes) than to use fork.
255
+
> **Try avoiding forks** : This is a mecanism that can be useful in certain cases (the main one would be parameter exploration on an experimentation) But when it comes to map reduce for example, it is advised to manually fo the nodes you want instead (improve readibility of what you are doing when making your graph). A chaining of fork can start being very hard to understand for the user.
174
256
175
-
Join is do-able by adding the
257
+
But if you want to do a map reduce with a fork, it is do-able by setting the join_id to the `fork_from_node` method. The join_id has to be an existing node on which, for every parents that are part of the fork has only this join node as child.
258
+
See [test using this mechanism](https://github.com/FreeYourSoul/FreExGraph/blob/ae707cf0fcb8486bde783cd0c7fe67217a56b3d2/test/fork_test.py#L41-L66) for more details
176
259
177
260
## Visitors
178
261
179
262
### Abstract Visitor hooks
180
263
181
264
Abstract visitor provide some default hooks that can be overridden from custom visitors in order to implement more complex logic depending on the graph visit.
182
-
*`hook_start()` : This hook is called when the visitation of the graph start (an interesting way to use this hook could be to reinitialize your visitor in case of re-use).
183
-
*`hook_end()` : This hook is called when the visitation of the graph end.
184
-
*`hook_fork_started(n: FreExNode, fork_id: str)` : This hook is called when a fork has been entered (when visiting the first node of a fork).
265
+
*`hook_start()`: This hook is called when the visitation of the graph start (an interesting way to use this hook could be to reinitialize your visitor in case of re-use).
266
+
*`hook_end()`: This hook is called when the visitation of the graph end.
185
267
*`hook_start_graph_node(gn: GraphNode)` : This hook is called when a graphnode recursion start (graph node given as parameter of the hook).
186
268
*`hook_end_graph_node(gn: GraphNode)` : This hook is called when a graphnode visitation end (graph node given as parameter of the hook).
187
269
188
-
Custom hooks can be implemented if you must trigger a specific action that depend on the business data stored in your node.
189
-
To do so, in the `__init__` of your custom visitor, use the method `register_custom_hook(predicate: Callable, hook: Callable)` : This method will trigger the provided hook if the given predicate return true for a node.
270
+
Custom hooks can be implemented on any visitor if you want to trigger a specific action that depend on the business data stored in your node.
271
+
272
+
To do so, on a visitor instance, use the method `register_custom_hook(predicate: Callable, hook: Callable)` : This method will trigger the provided hook if the given predicate return true for a node. Custom hooks are all executed just before the visitation of each node.
@@ -243,13 +333,28 @@ v = ValidateGraphIntegrity()
243
333
# visitation does assertion to verify if the graph is correct
244
334
v.visit(graph_above.root())
245
335
```
246
-
Those visitor implement the start hook that reinitialize their state as if they were new freshly created visitor. Which is why an instance of a standard visitor can be re-used. To implement this kind of behaviour in your own visitors, check out [visitor hooks](#abstract-visitor-hooks).
336
+
Those visitors implement the start hook that reinitialize their state as if they were new freshly created visitor. Which is why an instance of a standard visitor can be re-used. To implement this kind of behaviour in your own visitors, check out [visitor hooks](#abstract-visitor-hooks).
337
+
338
+
### Progression visit
339
+
340
+
tqdm is implemented in AbstractVisitor.
341
+
It makes possible to have a progress bar while visiting your graph. To do so, when instantiating the AbstractVisitor from `super()` within your custom visitor.
342
+
Just set the boolean parameter `with_progress_bar` in the constructor to True.
343
+
```python
344
+
classMyCustomVisitor(AbstractVisitor):
345
+
def__init__(self):
346
+
super().__init__(with_progress_bar=True)
347
+
348
+
# rest of your custom visitor implementation
349
+
...
350
+
```
247
351
248
352
### Reverse visit
249
-
It is possible to revert the order of visitation of your visitor by setting its attribute `is_reversed` of your visitor (works with any kind of visitor), example :
353
+
It is possible to revert the order of visitation of any visitor by setting its attribute `is_reversed` of the visitor (works with any type of visitor inheriting from AbstractVisitor).
354
+
example :
250
355
```python
251
356
# we assume a graph called `execution_graph` that represent the following graph:
0 commit comments