如何存储复杂的状态机

问题描述

我有一个相当复杂的状态机,需要在Python中实现。到目前为止,我尝试过的所有操作都非常混乱,包含一百万个循环和if语句。我大约有100个节点,每个节点可以有多个传入和传出弧。

首先,我已经有了状态机的设计,这不是需要学习的东西。其次,我不想使用任何scikit-learn之类的软件包。显然,pandasnumpy很好,但是我想用Python对其进行草绘,然后将其引入C#,因此它不能太依赖于Python包。

我也尝试过使用图形,但是感觉不正确,因为存在循环,并且遍历算法取决于决策,而不是成本。我也在考虑编写一种领域特定的语言,但是在投入大量时间之前,我想确保自己已经做过一些研究。

用于存储状态机的典型数据结构是什么?它需要能够考虑周期和下沉。 DSL可以走吗?如果是这样,您有任何指针吗?

谢谢!

p.s看起来像这样。

https://www.researchgate.net/profile/Alex_Capaldi/publication/321496819/figure/fig1/AS:567955499831296@1512422551790/This-decision-tree-describes-a-birds-decision-making-process-for-each-timestep-in-the.png

来源:Figures - uploaded by Alex Capaldi from referenced research An AgentBased Modeling Approach to Determine Winter Survival Rates of American Robins and Eastern Bluebirds

解决方法

状态机的最简单表示形式是图形。

在大多数语言中,可以使用其索引表示状态且其元素是到其他状态的转换列表的数组。将人类可读的状态名称与每个索引相关联以支持调试通常很方便。过渡需要两个部分:过渡条件和目标状态号。

通过命名所需的图形构造函数和元素,我们在python中设计了一个“内部” DSL。我们可以定义一个状态机(原谅我的Python,我不是专家):

   Class Transition  // defines a single transition
      def __init__(self,condition,target):
        self.condition = condition // store a python lambda here
        self.target = target // may be a state name (optimize to a state index)

   Class State
      def __init__(self,state_name,transitions):
        self.state_name=state_name   // text name of state
        self.transitions=transitions // a "set" (array) of transitions

   Class StateMachine:
      def __init__(self,states):
        self.states = states  // a "set" (array) of named states
      def AddState(self,state):
        self.states.append(state) 
      def compile(self):
        // replaces state names with state indexes; left as reader exercise

我们可以构造一个状态机来识别典型的标识符名称(与正则表达式“ [A-Z] [A-Z0-9] *”相同):

    MyIdentifierRecognizer = StateMachine([])
    MyIdentifierRecognizer.AddState(State("ScanFirstIdentifierCharacter",[Transition(lambda x: IsLetter(x),"ScanNthIdentifierCharacter"),Transition(lambda x: not(IsLetter(x)),"NotAnIdentifier)]))
    MyIdentifierRecognizer.AddState(State("ScanNthIdentifierCharacter",[Transition(lambda x: IsLetterOrDigit(x),Transition(lambda x: not(IsLetterOrDigit(x)),"EndOfValidIdentifier)])) 
    MyIdentifierRecognizer.AddState(State("NotAnIdentifier",[])) // no further transitions posssible
    MyIdentifierRecognizer.AddState(State("EndOfValidIdentifier",[])) // no further transitions possible

运行状态机很容易。我们首先将current_state设置为状态机的初始状态:

     current_state = "ScanFirstIdentifierCharacter"

然后输入一个字符:

     character = ...

我们在状态机中找到current_state,然后处理转换以查找满足更改current_state的转换:

     for s in StateMachine.states   // find the actual state matching current_state
         if s.name == current_state
            state = s
            break
     for t in state.transitions // process all transitions (assumed mutually exclusive)
          if t.condition(character)
             current_state=t.target
             break  // adding this line means "take the first valid transition"

您可以通过在定义状态机后运行一些“编译器”来提高效率,该状态机用数组索引替换文本状态名。然后状态查询可以是直接数组索引。

如果解释该状态机的速度不够快,则可以将各个状态编译为等效的目标语言代码,然后导入该代码。如果做得正确,这实际上会产生非常快的状态机。

[注意:我完成此回答后,作者在他的问题中添加了示例流程图。我把它留给他,以这种状态机符号转换他的流程图。]

OP最初提出的与决策树有关的另一个考虑因素:过渡谓词如何存储?在我这里的版本中,它们是“不透明的” Python lambda,可以满足某些需求。 如果OP希望以非透明的方式存储复杂的转换谓词, 他将需要“ transition”字段来包含表示感兴趣的布尔条件的抽象语法树。有关如何解析(布尔)和执行布尔表达式的详细信息,请参见https://stackoverflow.com/a/2336769/120163