pytransitions / transitions

A lightweight, object-oriented finite state machine implementation in Python with many extensions
MIT License
5.5k stars 525 forks source link

HierarchicalGraphMachine bug: transitions defined in 'parallel' or 'children' cannot be displayed in the graph, but it works #413

Closed xiaohuihui1024 closed 4 years ago

xiaohuihui1024 commented 4 years ago

Using: Windows 10 64 bit Python 3.7.4 (Anaconda) transitions 0.8.0 Graphviz 2.38.0 (binary) graphviz 0.13.2 (python package)

The following code is from README.md and changes HierarchicalMachine to HierarchicalGraphMachine because I want to visualize parallel HSM

from transitions.extensions.factory import HierarchicalGraphMachine as Machine
import logging
states = ['A', 'B', {'name': 'C', 'parallel': [{'name': '1', 'children': ['a', 'b', 'c'], 'initial': 'a',
                                                'transitions': [['go', 'a', 'b']]},
                                               {'name': '2', 'children': ['x', 'y', 'z'], 'initial': 'z'}],
                      'transitions': [['go', '2_z', '2_x']]}]

transitions = [['reset', 'C_1_b', 'B']]
logging.basicConfig(level=logging.INFO)
extra_args = dict(show_conditions=True, show_state_attributes=True, use_pygraphviz=False)
machine = Machine(states=states, transitions=transitions, initial='A',**extra_args)
machine.get_graph().draw('my_state_diagram', format='png')

This produces the graph like this:

my_state_diagram

Note: The transition from C_1_a to C_1_b is not shown, but it is defined in states

then I try the following triggers:

machine.to_C()
INFO:transitions.extensions.nesting:Exited state A
INFO:transitions.extensions.nesting:Entered state C
INFO:transitions.extensions.nesting:Entered state C_1
INFO:transitions.extensions.nesting:Entered state C_2
INFO:transitions.extensions.nesting:Entered state C_1_a
INFO:transitions.extensions.nesting:Entered state C_2_z
machine.go()
machine.get_graph()
INFO:transitions.extensions.nesting:Exited state C_1_a
INFO:transitions.extensions.nesting:Entered state C_1_b
INFO:transitions.extensions.nesting:Exited state C_2_z
INFO:transitions.extensions.nesting:Entered state C_2_x

my_state_diagram1

It can be found from the log output that C_1_a-> C_1_b did occur, but it cannot be displayed in the graph.

Changing the states definition in the above code to the following also happens.

states = ['A', 'B', {'name': 'C', 'children': [{'name': '1', 'children': ['a', 'b', 'c'], 'initial': 'a',
                                                'transitions': [['go', 'a', 'b']]},
                                               {'name': '2', 'children': ['x', 'y', 'z'], 'initial': 'z'}],
                      'transitions': [['go', '2_z', '2_x']]}]

Is this expected behavior ?

xiaohuihui1024 commented 4 years ago

The behavior I desired is: the graph can show the internal transitions defined in the substate, and in addition the go transitions above can be displayed in parallel.

aleneum commented 4 years ago

Hi @xiaohuihui1024,

thank you for the report and your test case. Transitions should be shown regardless where they originate and where they are going. I guess that edges are currently not correctly collected for the new HSM version. I will have a look and report back!

aleneum commented 4 years ago

The error you have found has been fixed. I also improved the layout of parallel states a bit. However, parallel state/transition support is still work in progress. Only the last transition can be shown at the moment. Since machine.go() triggers actually two independent transitions, only the last one (2_z -> 2_x) is shown.

from transitions.extensions.factory import HierarchicalGraphMachine as Machine
import logging
states = ['A', 'B', {'name': 'C', 'parallel': [{'name': '1', 'children': ['a', 'b', 'c'], 'initial': 'a',
                                                'transitions': [['go', 'a', 'b']]},
                                               {'name': '2', 'children': ['x', 'y', 'z'], 'initial': 'z'}],
                      'transitions': [['go', '2_z', '2_x']]}]

transitions = [['reset', 'C_1_b', 'B'], ['start', 'A', 'C']]
logging.basicConfig(level=logging.INFO)
Machine.hierarchical_machine_attributes['ranksep'] = '0.3'  # shorter edges
extra_args = dict(show_conditions=True, show_state_attributes=True, use_pygraphviz=False)
machine = Machine(states=states, transitions=transitions, initial='A', **extra_args)
machine.start()
machine.get_graph().draw('diagram_01.png', prog='dot')
machine.go()
machine.get_graph().draw('diagram_02.png', prog='dot')

diagram_01 diagram_02

I will close this issue for now but please do not hestitate to comment if you find further problems with diagrams or (py)graphviz. I will reopen the issue if necessary.