基于有限自动机的电池工作模式仿真
库"有限自动机"是一个工具,旨在方便系统状态的图形表示和描述它们之间的转换逻辑时,满足某些条件。 基于有限状态机的方法提高了复杂控制逻辑算法的可见性、可靠性和可伸缩性。
在这个例子中,我们将逐步了解有限自动机库的基本概念,并模拟简单电池的工作模式。
第1步。 状态机
让我们将[图表]块添加到画布(https://engee.com/helpcenter/stable/cn/state-machines/chart.html ),让我们进入它并创建两个状态:
*电池可以连接到外部电源并充电(条件 Charge).
*否则,它被排出(状态 Discharge).
.png)
我们刚刚开始开发最简单的状态机。 状态机旨在模拟可以处于预定义状态或操作模式之一的系统。
让我们添加状态之间的转换,并且还要注意这样一个事实: Charge 默认转换已启用。 它在最后用一个点表示,并表示在模拟的第一步将处于活动状态。
.png)
另外两个过渡仍然是无条件的。 这意味着,如果我们运行一个模拟,从一个状态到另一个状态的转换将在每个步骤执行。 也就是说,他们会反过来变得活跃。
让我们写下转换条件-逻辑表达式,如果为true,则转换将被视为有效。 它们应用方括号表示。:
*如果电池正在充电和变量 isCharging 取值 false,向状态的过渡正在进行中 Discharge.
*反之亦然,如果电池电量不足和变量 isCharging 取值 true,向状态的过渡正在进行中 Charge.
.png)
顺便说一下,除了条件之外,我们还可以指定在转换期间应该执行的操作。 它用大括号编写,最常用于分配变量。
让我们更详细地考虑执行状态机的算法。:
- 在仿真的第一步中,状态机从默认转换开始执行。
- 对图表块的后续调用导致从最后一个活动状态执行。
- 之后,开始检查源自该状态的转换的条件。 如果条件有效(即取值"True"),则执行操作并进行转换。
- 否则,正在测试其他转换。 如果它们丢失,或者没有一个转换有效,当前状态将保持活动状态。
现在让我们在状态中编写代码。:
*让电池在充电过程中不产生电力,但在放电时,它会产生3.5瓦的负载。 我们只有在进入状态时才会分配这些值,所以我们会在它们前面写一个关键字。 entry.
*此外,我们需要根据活动状态更改电池电量。 假设每个模拟步骤充电率为4%,放电率为3%。
*电荷必须改变,前提是状态在模拟的当前步骤保持活动状态,因此我们将在表达式之前写入关键字。 during.
.png)
还有一个关键字 exit. 退出状态时执行后的代码。
我们已经在代码中解决了三个变量,让我们在图表块设置中声明它们。 在这里,您可以添加输入和输出信号,局部变量和事件以及参数。 此外,您还可以指定它们的维度、复杂性和数据类型。
图表块中使用的变量通常称为符号。:
*输入是来自模拟环境信号的数据。
*输出-数据传输到模拟环境的信号。
*局部变量-在块内部用于中间计算的数据。
*参数-在AnyMath工作区中定义的数据。
创建入口 isCharging 还有两个出口 power 和 charge. 将初始充电值设置为50%。
.png)
让我们退出块,发现它有新的端口。 我们将在其入口处应用暴露时间为5的步骤。 我会立即将此信号转换为逻辑值。
在开始模拟之前,我们将记录信号,以便稍后可视化它们。
.png)
将路径保存到变量中的示例 dir:
dir = @__DIR__;
让我们运行模拟:
model = engee.load(joinpath(dir, "step_1.engee"))
result = engee.run(model)
engee.close(model; force = true)
让我们构建图表:
plot(result["power"].time, result["power"].value, label = "输出功率")
title!("电池输出功率")
xlabel!("时间,[s]")
plot(result["charge"].time, result["charge"].value, label = "收费")
title!("电池充电")
xlabel!("时间,[s]")
信号 power 符合我们的要求-放电时输出功率为3.5W,充电时为0W。
但收费水平显然有问题。 起初它变成负值,然后它超过了100%。
我们来解决这个问题。
第二步。 国家的层次结构
首先,让我们在内部创建 Charge 子状态(我们称之为 FastCharge)并向其传送确定电荷变化的代码。 这个状态应该在父状态被激活时被激活,所以我将默认转换连接到它。:
.png)
当充电水平达到80%时,让慢速模式激活,充电在100%停止。 要做到这一点,再添加两个子状态(让我们称它们为 SlowCharge 和 Full)并将其与过渡连接起来。
状态内部 SlowCharge 让我们写下电荷变化的表达式,并 Full 我们将把它留空,因为它不应该发生任何事情。
.png)
通过类比,我们将向放电模式添加子状态。:
*我们将电荷减少的表达转移到状态 Powered.
*如果电荷小于或等于3%,则应激活状态 Empty,其中电池输出功率为0。
.png)
你和我刚刚向状态机添加了层次结构。 它允许您简化具有自然层次结构的系统建模,结合重复的条件和操作,并促进模型的缩放和重用。
当父状态被激活时,执行应该切换到其子状态之一。
子状态可以通过以下方式激活:
-
默认设置。
-
跨越父状态边界的过渡(通常称为超过渡)。
也就是说,转换既可以在状态层次结构的一个级别内发生,也可以在不同级别之间发生。
顺便说一下,状态的层次结构可以是无限的嵌套。
让我们用周期为5秒,宽度为50的矩形脉冲替换输入信号%:
.png)
让我们运行模拟:
model = engee.load(joinpath(dir, "step_2.engee"))
result_2 = engee.run(model)
engee.close(model; force = true)
并确保充电水平现在可以接受。
plot(result_2["charge"].time, result_2["charge"].value, label = "收费")
title!("电池充电")
xlabel!("时间,[s]")
此外,电池的输出功率变为零,前提是它被放电。:
plot(result_2["power"].time, result_2["power"].value, label = "输出功率")
title!("电池输出功率")
xlabel!("时间,[s]")
当电池放电时,它的输出功率会有一个固定值(3.5瓦)。 让我们确保它不会超过此值,但它取决于连接设备的实际消耗。 与此同时,让我们来看看图表块内的另一种建模方法。
第三步。 过渡图
正如我前面所说,状态机是一种可视化和方便的方式来表示具有不同操作模式的系统。 例如,电视,自动变速器或电池。
算法和过程通常基于过渡图建模。 将五个节点添加到画布并连接它们。
.png)
从初始节点到最终节点有两种方法。:
-
或者沿着一条有四个十字路口的长路走。
-
或者一个短的有两个。
如果多个转换从一个节点退出(如从初始节点退出),则箭头的开头会显示数字。 这些是指示将检查转换有效性的顺序的优先级。
由于我们还没有设置任何条件,因此将始终执行第一个过渡。 这意味着我们将始终遵循更长的路径,并且数字2的过渡永远不会变得活跃。
让我们将条件写在方括号中,并将操作写在大括号中。:
*如果所需功率大于最大值,则输出功率等于最大值。
*否则输出功率等于所需功率。
*并让电荷减少输出功率值,而不考虑所选路径。
.png)
这个过渡图是一个构造 if-else. 例如,可以基于节点组装更复杂的控制结构, switch-case 和前后条件或计数器的循环。
与状态机不同,转换图在每次调用图表块时从默认转换运行到结束节点。
也就是说,过渡图用于设计决策逻辑,而不考虑状态。 在仿真的每一步,根据设定的条件,从初始节点到最终节点选择一条路线。
转换图和状态机不仅可以独立使用,还可以组合使用。 让我们从状态中删除代码 Discharge 和 Powered 我们将把转换图嵌入到状态机中。
.png)
您可能已经注意到我将状态边界连接到初始节点。 这就是所谓的内部转换,它是关键字的图形等价物。 during.
如果我将默认过渡连接到开始节点,它将对应于关键字 entry.
在块设置中添加一个局部变量。 maxPower 具有3.5w的初始值和输入信号 requiredPower. 初始充电值 charge 将其从50%更改为100%。
.png)
我们将通过幅度和偏移量为2.5且相位等于-pi/2的正弦曲线来设置所需的功率。
由于我们对电池放电感兴趣,我们将用值的常数替换矩形脉冲 false:
.png)
如果我们现在运行模拟,图表块将在每个步骤被调用,电池将很快耗尽。 让我们改变它的采样周期,例如,将其增加到十分之二秒。
.png)
我们将记录所需的功率,并在开始模拟后,我们将确保输出功率现在不超过3.5瓦,并取决于连接设备的实际消耗。
model = engee.load(joinpath(dir, "step_3.engee"))
result_3 = engee.run(model)
engee.close(model; force = true)
plot(result_3["power"].time, result_3["power"].value, label = "输出功率")
plot!(result_3["requiredPower"].time, result_3["requiredPower"].value, label = "所需功率")
title!("电池电源")
xlabel!("时间,[s]")
plot(result_3["charge"].time, result_3["charge"].value)
title!("电池充电")
xlabel!("时间,[s]")
现在让我们在我们的模型中添加另一个一次性备用电池,这将在电池耗尽的情况下工作。
第四步。 平行状态
要做到这一点,我会创建另一个状态,我会命名它 Main 我会把电池的主要功能放在里面。
.png)
在另一个有名字的州 Backup 添加备用电池操作模式:
*如果电池已充电,备用电池应处于待机模式。
*否则,它将被排出。
.png)
条件 Discharge 这将与我们之前所做的非常相似。 让我们为其添加两个子状态 Powered 和 Empty (类似于主电池)。
让我们写下改变备用电池的电荷的条件和切换到状态的条件 Empty:
.png)
以前,当我们运行模拟时,每个步骤只确定一个活动状态。 为了使我们的模型正常工作,备用电池工作模式取决于电池的当前状态是必要的。 也就是说,在模拟的每一步,不是一个状态应该是活动的,而是两个。 要在AnyMath中实现这样的逻辑,您可以启用并行状态分解或并行状态,因为它们通常被称为。
右键单击画布并更改分解类型。:
.png)
您可能注意到,各州的边界已变为虚线,右上角出现了一个数字,指示执行顺序。:
.png)
现在,在模拟的每个步骤中,将首先确定电池的活动状态,然后确定备用电池的活动状态。
请注意,尽管名称如此,并行状态不是同时执行的。 术语"并行"意味着同一层次结构级别的几个状态将在模拟中的同一时间处于活动状态。
转换不能连接到并行状态。 当父状态根据执行顺序激活时,所有并行状态都被激活。
让我们连接状态转换 Wait 和 Discharge 并写下他们的条件。
有一个协调并行状态的特殊功能。 in. 它返回值 true,如果传递给它的状态是活动的。
也就是说,我们可以写:
-
"如果电池电量不足,备用电池将进入
Discharge"; -
"如果电池正在充电,备用电池进入待机模式。"
.png)
让我们添加到各州 Wait 和 Discharge 可用于确定备用电池处于哪种模式的辅助变量。:
.png)
在块设置中添加输出信号 bCharge 初始值为100%, isMain,并将它们写下来:
.png)
让我们用矩形脉冲发生器替换常数,并通过图表块返回求解器步骤的继承。:
.png)
让我们运行模拟并确保备用电池开始工作,前提是电池电量不足。
model = engee.load(joinpath(dir, "step_4.engee"))
result_4 = engee.run(model)
engee.close(model; force = true)
plot(result_4["charge"].time, result_4["charge"].value, label = "主电池")
title!("电池充电")
xlabel!("时间,[s]")
plot(result_4["bCharge"].time, result_4["bCharge"].value, label = "备用电池")
title!("电池充电")
xlabel!("时间,[s]")
plot(result_4["isMain"].time, result_4["isMain"].value, label = "工作的标志")
title!("指示主电池的操作")
xlabel!("时间,[s]")
状态的并行分解允许:
*在单个图表块中组合相互依赖的状态机,并实现逻辑组件的明确分离。
*根据执行顺序提供直观的状态依赖关系。
结论
在这个例子中,我们检查了有限自动机库的基本概念-我们学习了如何以过渡图的形式对算法和过程进行建模,以及开发状态机,其行为取决于当前活动状 此外,我们还研究了分层状态和平行分解。
该库扩展了有限自动机的经典数学概念,并允许您以直观的方式设计真正复杂的控制逻辑。