I'm building a state-machine based on transitions but probably a generic state-machine question. I'm struggling to build a simple decision tree.
The simplified state-machine should look like
[state:new] --- generateXYZ ---> [state:generation_successful]
---> [state:generation_failed]
I understand that I could create two transitions and guard them but that doesn't feel like the ideal solution - maybe you correct me on that one.
Ideally I'd like to run some code and it's either returning true (=successful) or false(=failed).
Looking forward to any advice, maybe I'm looking at this from the wrong angle. Thanks!
Welcome to Stack Overflow!
build a simple decision tree.
I assume you are not interested in a generating
state. If generation could take a while, adding such a state might be useful to adjust the model/machine behaviour in case it is 'busy'.
I understand that I could create two transitions and guard them
I'd guess eventually two different destinations have to result in two transitions. However, with transitions
you can make use of conditions
in transition definitions and also wrap your transition creation for more convenience. I use GraphMachine
to illustrate the result:
from transitions.extensions.diagrams import GraphMachine as Machine
import random
class Model:
def __init__(self):
self.machine = Machine(self, states=['A', 'B', 'C', 'D', 'AF', 'BF', 'F'], initial='A', show_conditions=True)
# decision trees are easier to read top to bottom
# configure graphviz accordingly
self.machine.machine_attributes['rankdir'] = 'TB'
self._generated = False
def add_decision(self, trigger, source, execute, check, on_success, on_fail):
# this transition will be evaluated first, when execute returns true it will be used
self.machine.add_transition(trigger, source, on_success, prepare=execute, conditions=check)
# if this transition is evaluated, we know that a) execute has been called and b) it returned False
# thus, we could omit 'unless'
self.machine.add_transition(trigger, source, on_fail, unless=check)
def generate(self):
self._generated = bool(random.getrandbits(1)) # returns True of False randomly
def generated(self):
return self._generated
model = Model()
model.add_decision('execute', 'A', execute='generate', check='generated', on_success='B', on_fail='AF')
model.add_decision('execute', 'B', execute='generate', check='generated', on_success='C', on_fail='BF')
model.add_decision('execute', 'AF', execute='generate', check='generated', on_success='C', on_fail='F')
model.add_decision('execute', 'C', execute='generate', check='generated', on_success='D', on_fail='F')
# call execute twice
model.execute()
model.execute()
# let's see where we ended up
model.get_graph().draw('graph.png', prog='dot')
This will result in something like this:
In my case, generation was successful during the first run and failed in the second run. You can condense this code by a) not using GraphMachine
, b) making generate
return False
or True
and pass it directly to conditions:
# ... Model.add_decision
self.machine.add_transition(trigger, source, on_success, conditions=execute)
self.machine.add_transition(trigger, source, on_fail)
# ...
def generate(self):
return bool(random.getrandbits(1)) # returns True of False randomly
# ...
model.add_decision('execute', 'A', execute='generate', on_success='B', on_fail='AF')
Code and graph comprehensibility might suffer though.