问题描述
我正在使用 pytransitions 并且遇到了需要有几个与其他状态无关的状态,并且使用 non deterministic state machine 进行建模非常有意义,这在数学上是等效的。
我想要类似以下的内容
from transitions import Machine
from transitions import EventData
class Matter(object):
def __init__(self):
transitions1 = [
{'trigger': 'heat','source': 'solid','dest': 'liquid'},{'trigger': 'heat','source': 'liquid','dest': 'gas'},{'trigger': 'cool','source': 'gas','dest': 'solid'}
]
transitions2 = [
{'trigger': 'turn_on','source': 'off','dest': 'on'},{'trigger': 'turn_off','source': 'on','dest': 'off'},]
self.machine = Machine(
model=self,states=[['solid','liquid','gas'],['on','off']],transitions=[transitions1,transitions2],initial=['solid','off'],send_event=True
)
def on_enter_gas(self,event: EventData):
print(f"entering gas from {event.transition.source}")
def on_enter_liquid(self,event: EventData):
print(f"entering liquid from {event.transition.source}")
def on_enter_solid(self,event: EventData):
print(f"entering solid from {event.transition.source}")
def on_enter_on(self,event: EventData):
print(f"entering on from {event.transition.source}")
def on_enter_off(self,event: EventData):
print(f"entering off from {event.transition.source}")
我可以定义一组新的状态为 states=itertools.product(states1,states2)
,然后定义所有的转换,如等价定理所示。
我想知道库中是否支持这种行为,如果支持,如何实现。
我有不止 2 组(大部分)独立的状态。真的,我有一堆开关,偶尔会有交互,但大部分都是独立的。
解决方法
... 具有多个与其他状态无关的状态,并且使用非确定性状态机进行建模很有意义
对我来说,这听起来像是您要寻找的不一定是非确定性的,而是 hierarchical/compound 状态和 concurrency/parallelism。
您可以使用具有并发功能的转换 Hierarchical State Machine 扩展:
from transitions.extensions import HierarchicalMachine
states1 = ['solid','liquid','gas']
states2 = ['on','off']
transitions1 = [
{'trigger': 'heat','source': 'solid','dest': 'liquid'},{'trigger': 'heat','source': 'liquid','dest': 'gas'},{'trigger': 'cool','source': 'gas','dest': 'solid'}
]
transitions2 = [
{'trigger': 'turn_on','source': 'off','dest': 'on'},{'trigger': 'turn_off','source': 'on','dest': 'off'},]
combined_states = [
{"name": "running","parallel":
[
dict(name="component1",states=states1,transitions=transitions1,initial=states1[0]),dict(name="component2",states=states2,transitions=transitions2,initial=states2[0])
]
}
]
m = HierarchicalMachine(states=combined_states,auto_transitions=False,initial="running")
print(m.state) # >>> ['running_component1_solid','running_component2_on']
m.turn_off()
print(m.state) # >>> ['running_component1_solid','running_component2_off']
然而,HSM 比简单的 Machines
复杂得多。该文档提到了一些考虑到命名约定和需要遵循的嵌套/初始化配置的限制。
这就是为什么我通常会尝试为我的 FSM 架构找到最简单的解决方案。现在您的嵌套相当平坦,也可以通过一组模型和 Machines
来实现。转换的“规则手册”方法使得只需一台机器及其“调度”方法即可轻松管理处于不同状态的多个模型:
from transitions import Machine
class Model:
pass
class MultiMachine(Machine):
def __init__(self,configurations):
# Initialize the machine blank,no states,no transitions and
# no initial states. Disable auto_transitions since there shouldn't
# be the possibility to transition e.g. from 'on' to 'liquid'.
# Furthermore,ignore_invalid_triggers so that events not considered
# by a model will not throw an exception.
super().__init__(model=None,states=[],transitions=[],initial=None,ignore_invalid_triggers=True)
# create a model for each configuration
for states,transitions,initial in configurations:
self.add_states(states)
self.add_transitions(transitions)
self.add_model(Model(),initial=initial)
@property
def state(self):
return [model.state for model in self.models]
m = MultiMachine([(states1,transitions1,'solid'),(states2,transitions2,'off')])
assert m.state == ['solid','off']
m.dispatch("turn_on")
assert m.state == ['solid','on']
m.dispatch("heat")
assert m.state == ['liquid','on']
来自您的评论:
如何根据另一台子机的状态在一台子机中添加条件转换?例如,加热应该只有在打开的情况下才能使固体变成气体? [...] HSM,也许在这种情况下更好。
这可以通过 HSM 解决,方法是仅在源状态 heat
上定义 on_*
事件。但是,如果您有许多这些因变量,则嵌套可能会变得非常复杂。相反,您可以将对其他机器的 is_<state>
便利函数的引用添加到所有相关转换的条件列表中。这可以在初始化后完成,以防引导出现问题:
from transitions import Machine
from transitions.core import Condition
states1 = ['solid','gas']
states2 = ['off','on']
m1 = Machine(states=states1,initial=states1[0],transitions=[{'trigger': 'heat','dest': 'solid'}])
m2 = Machine(states=states2,initial=states2[0],transitions=[{'trigger': 'turn_on','dest': 'off'}])
# get all heat transitions and add the condition that they may only be valid when m2.is_on returns True
for trans in m1.get_transitions("heat"):
trans.conditions.append(Condition(func=m2.is_on))
# if you want to add an 'unless' statement pass `target=False`
# to the condition. e.g. "heat unless m2 is off"
# trans.conditions.append(Condition(func=m2.is_off,target=False))
assert m1.is_solid()
assert m2.is_off()
assert not m1.heat()
assert m1.is_solid()
assert m2.turn_on()
assert m1.heat()
assert m1.is_liquid()