Engee 文档
Notebook

使用状态变量创建Engee功能块

在这个例子中,我们将创建一个块 Engee Function,其参数可随时间变化取决于各种因素。 通常,基于输入数据和时间矢量计算用户块的输出数据。 但是在这个例子中,我们将展示如何使用可以存储块内部状态的变量。

实际上,我们将展示如何根据块内部实现的内部执行计数器来更改计算用户块的结果。

组织Engee功能块的参数

块参数 Engee Function 您可以通过不同的方式进行设置:

*使用常量,通过设置窗口 Parameters
*使用全局变量空间中的变量Engee(在变量窗口中可见)
*使用其代码中指定的块的内部参数(并且它们的生存期不限于下一个计算周期,而是等于模型的生存期)。

前两个选项的实现几乎相同。 如果参数的数量非零,那么您需要为每个参数设置一个名称。 Name 和价值 Value.

image_3.png

这里我们设置了用于计算统计参数的窗口大小。 max_len_init 并给它赋值 100.

我们可以给它赋值 N,设置在全局变量空间中。 应该记住模型编译器的一个限制:当从全局空间传递变量时,参数名称(Name)不应与全局变量的名称相同(Value).

image_2.png

这种临时编译器行为很快就会被修复。

Engee函数代码中的变量参数

让我们举一个实际的例子。 我们将实现一个块,该块计算落在给定大小的滑动窗口内的样本部分的统计参数。 窗口将被初始化为零,因此前N个结果将被偏移,但随后我们将看到包含最后一个滑动窗口的统计信息 N 来自观察样品的值。

Engee Function 它将接受标量值作为输入,并在其上累积统计信息。 输入将包含一个噪声生成单元,一个均匀分布的随机变量,其值范围为(-1.1)。 块的接口将如下所示:

image_2.png

这里 MA 手段 Moving Average (移动平均线),以及 MDMoving Deviation (滑动方差)。

值得评论定义自定义块行为的代码的每个部分。

代码从初始化用户块的结构开始。 标题 Block 随机选择,它可以是任何东西。 在内部,我们设置了几个参数:

  • i -循环累加器中最后一个添加项的计数器
  • max_len -滑动窗口的大小
  • X -样本累加器(矢量大小max_len)

在功能 Block() 内部参数接收特定值,包括从外部获得的值(窗口大小 max_len_init 它设置在选项卡上 Parameters).

解析Engee函数组件内部的代码

值得仔细研究将数据类型分配给块参数的问题。 如果未将定义状态变量的数据结构声明为类型的结构 mutable,那么结构中的所有"简单"类型都将保持不变(您将不得不使用向量或引用,例如 i :: Ref{Int32};). 我们希望参数能够改变。 因此,我们为我们的结构设置了一个特殊的修饰符。:

``'茱莉亚
可变结构块<:AbstractCausalComponent

i::Int32;
max_len::Int32;
X::矢量{Float64};

功能块()
    返回new(1,max_len_init,零(Float64,max_len_init))
结束

结束


然后,在代码中,我们看到每次访问块时计算的函数。

非常重要的是在访问此函数时,其内部变量不会发生变化。 方法(函子) step 它可以在模拟步骤之间多次调用,因此如果更改块的内部状态,则会更改得太频繁。

在这里我们可以访问块的外部和内部参数(c.параметр).

``'茱莉亚
函数(c::块)(t::实,x)

#局部变量仅用于可读性
X=c.X
N=c.max_len

MA=sum(X[1:N])/N;
MD=sqrt(sum((X[1:N]。-妈)。^2)/(N-1))

返回(MA,MD)

结束


最后我们看到功能 update!,其设计用于对块参数进行更改 c.

*我们替换当前值 x 与街区入口 Engee Function 在向量中 X 按索引 i;
*然后我们将指数增加1 i,而当它达到的值 max_len,将其减少回一个;
*最后,我们返回更新的参数结构。 c 以在下一次迭代中访问它。

``'茱莉亚
功能更新!(c::Block,t::Real,x)

c.X[c.i]=x
c.i=max(1,(c.i+1)%(c.max_len-1))

返回c

结束


里面的代码 update! 这在块的情况下尤其重要 Engee Function 它在代数循环中(例如,它接受自己的输出作为输入或有一个参数集 direct_feedthrough=false). 在没有循环的模型中,一切都可以在第二个代码块(函子)中完成 step).

启动模型并讨论结果

让我们使用软件控制命令运行此模型:

In [ ]:
mName = "engee_function_moving_average"
model = mName in [m.name for m in engee.get_all_models()] ? engee.open( mName ) : engee.load( "$(@__DIR__)/$(mName).engee" );
data = engee.run( mName )
Out[0]:
Dict{String, DataFrame} with 3 entries:
  "MA"  => 501×2 DataFrame…
  "src" => 501×2 DataFrame…
  "MD"  => 501×2 DataFrame

输出结果:

In [ ]:
using Plots, Formatting

p = plot( data["MD"].value, data["MA"].value, label="MA(MD)", linez=range(0.4, 1, length(data["MA"].value)), c=:blues, cbar=:none)
# Последняя точка на графике
scatter!( p, [data["MD"].value[end]], [data["MA"].value[end]], c=:cyan )
# Текстовая подпись к этой точке 
annotate!( p, data["MD"].value[end], data["MA"].value[end],
           text("MA=$(sprintf1("%.2f",data["MD"].value[end])), MD=$(sprintf1("%.2f",data["MA"].value[end]))  ", 8, :right ),
           legend=:none )

# Вывод двух графиков
plot(
    # График слева: шум
    plot( data["src"].time, data["src"].value, label="input", c=:red, legend=:none ),
    # График справа – зависимость мат.ожидания от дисперсии
    p,
    size=(800,300)
)
Out[0]:

左图显示了在用户块入口处接收到的噪声信号。 Engee Function. 右图显示了移动平均线对滑动窗口方差值的依赖性。

可以注意到,均值和方差的起点是空样本的值,但经过几十个步骤后,这些参数的估计值转移到该点 , . 到计费周期结束时,当滑动窗口的初始化停止影响统计时,图形上的点开始明显地偏移到零数学期望。 .

结论

如果您将组件中的代码添加到原始模板中 Engee Function 如果有几个标准设计,那么这个组件可以体现基于外部代码和可变参数的非常复杂的行为。 在Engee中,有许多图形和文本方式来描述模型的行为,除此之外,您还可以使用带有自定义代码的动态块。

示例中使用的块