Engee功能块的高级设置
Engee功能块允许用户将他们的算法嵌入到Engee模型中,而无需在基元上重绘它。 与此同时,用户可以完全控制确定块的行为。
这样的控制方法的例子在下面给出。
输出参数过载
示例:转换为矢量
需要实现将其输入转换为矢量的块。
不需要登录操作。 唯一需要的是将输入转换为矢量。 为此,计算向量的长度并获取其元素的类型。
此任务由VectorizeIT块在vectorize_it模型中实现。
考虑这个Engee函数的代码。 我们对块函子和重新定义输出的维度和类型的方法感兴趣。
让我们从函子的定义开始:
``'茱莉亚
函数(c::块)(T::Real,缓存,in1)
如果in1isa AbstractVector
缓存。=in1
elseif in1isa抽象阵列
缓存。=vec(in1)
elseif in1是真实的
缓存[1]=in1
结束
结束
这里要注意cache变量。 如果启用该设置,则为Engee功能块声明此变量。 `Use external cache for non-scalar output`:
<img src="/helpcenter/stable/_next/static/media/example_resources/EF_adv_programming_media/image.png" alt="image.png" class="notebook-media">
只有当块只有**一个**输出时,此设置才可用。
接下来,我们将考虑输出信号的维度和类型的定义。 您可以通过选择_Override类型继承method_和_Override维度继承method_选项来单独定义它们。 但是,可以在单个函数中定义它们。 为此,请选择_use common method for types and dimensions inheritance_(启用这两种方法后,该选择将变为可用):
<img src="/helpcenter/stable/_next/static/media/example_resources/EF_adv_programming_media/image_2.png" alt="image_2.png" class="notebook-media">
后一种选择最适合这种情况。 考虑此方法的代码:
``'茱莉亚
函数propagate_types_and_dimensions(inputs_types::Vector{DataType},inputs_dimensions::Vector{<:Dimensions})::Tuple{Vector{DataType},Vector{<:Dimensions}}
in1t=inputs_types[1]
in1d=inputs_dimensions[1]
outd=(prod(in1d),)
返回([eltype(in1t)],[outd]);
结束
输入接收两个向量:输入类型及其维度。 应在输出端形成由两个向量组成的元组:
*输出信号的数据类型的向量
*输出信号维数向量
应该单独注意的是,维度作为元组传递(即,以及函数的结果 size()),并将数据类型作为元素类型。 在我们的情况下,它不会是 Vector{eltype(in1t)},而只是 eltype(in1t).
严格的块类型和缓存结果
示例:方程组的计算
Julia语言允许您动态分配内存,但这会减慢代码的速度。 因此,Engee功能块可以对仿真速度产生负面影响。 此示例演示如何最大限度地提高Engee功能块的性能。
使用单独的变量来存储块操作的结果(缓存)可以让您摆脱动态内存分配并显着加快块操作。 让我们举一个简单的例子:
a = [[i, 2i, 3i] for i in 1:1_000_000]
b = [[4i, 5i, 6i] for i in 1:1_000_000]
cache = [0, 0, 0]
function sum_without_cache(a, b)
@time for i in eachindex(a)
a[i] .+ b[i]
end
end
function sum_with_cache(cache, a, b)
@time for i in eachindex(a)
cache .= a[i] .+ b[i]
end
end
println("Without cache:")
sum_without_cache(a, b)
println("With cache:")
sum_with_cache(cache, a, b);
从示例中可以看出,使用缓存是一种非常理想的做法。 对于Engee函数,我们可以通过两种方式定义缓存:
- 通过为非标量output_指定复选框_Use external cache(对于一个输出)
- 在块定义中手动创建缓存
为了获得最大的性能,块结构中的所有字段都必须具有特定的类型和固定的大小。
例如,考虑cached_calcs模型和ComputingTypesRuntime块。
此块实现以下方程组:
该系统的实现涉及使用缓存,因为输入可以是非标量的。
要创建缓存,我们需要定义一个类型化结构。 通常的Define component structure方法不允许这样做,但我们可以启用Use common code设置,并在"common"代码中为块创建结构。 块结构定义代码将如下所示:
``'茱莉亚
struct Block{CacheType1,CacheType2}<:AbstractCausalComponent
c1::CacheType1;
c2::CacheType2;
功能块()
sz1=OUTPUT_SIGNAL_ATTRIBUTES[1]。尺寸;
sz2=OUTPUT_SIGNAL_ATTRIBUTES[2]。尺寸;
type1=OUTPUT_SIGNAL_ATTRIBUTES[1]。类型;
type2=OUTPUT_SIGNAL_ATTRIBUTES[2]。类型;
c1=isempty(d1)? 零(t1):零(t1,d1)
c2=isempty(d2)? 零(t2):零(t2,d2)
new{typeof(c1),typeof(c2)}(c1,c2);
结束
结束
应该注意的是,缓存的类型和维度是从输出信号中派生出来的。 并且输出信号的属性在_use common method for types and dimensions inheritance_中输出,使用方程组的单个计算:
``'茱莉亚
函数propagate_types_and_dimensions(inputs_types::Vector{DataType},
inputs_dimensions::Vector{<:Dimensions})
::Tuple{Vector{DataType},Vector{<:Dimensions}}
in1t=inputs_types[1]
in2t=inputs_types[2]
in1d=inputs_dimensions[1]
in2d=inputs_dimensions[2]
mockin1=零(in1t,in1d)
mockin2=零(in2t,in2d)
mockout1=罪。(mockin1)。+cos.(mockin2)
mockout2=mockin1。+mockin2
return([eltype(mockout1),eltype(mockout1)],[size(mockout1),size(mockout1)])
结束
这种技术只会导致单个内存分配。
直接馈通
示例:打破代数循环
作为一个例子,让我们考虑打破代数循环的实际问题。 建议使用延迟块打破代数循环。 但这种技术导致结果的失真。 和IC单元不破环。
因此,我们将创建一个Engee功能块,实现以下方程组并打破代数循环:
要打破代数循环,我们需要删除直接馈通属性。 这既可以从Engee功能块的设置中完成,也可以通过设置_Override直接馈通设置method_选项以编程方式完成。
示例实现在LoopBreaker块中的loopbreaker模型中可用。
块的操作原理如下:参数设置 IC,其将在模型的初始化时输出,并在仿真的初始时输出。 在仿真的后续步骤中,块的输入将立即传输到输出。 让我们来看看这个算法的实现。:
块定义为:
``'茱莉亚
struct Block{T}<:AbstractCausalComponent
InCn::T
功能块()
InCn_t=INPUT_SIGNAL_ATTRIBUTES[1]。类型;
新IC
结束
结束
它的函子就像:
``'茱莉亚
函数(c::块)(t::实,in1)
如果t<=0.0
返回C.InCn
其他
返回1
结束
结束
此外,还定义了_Override直接馈通设置method_。 这里不需要逻辑或计算,所以我们只是打开循环。:
``'茱莉亚
函数direct_feedthroughs()::矢量{Bool}
返回[错误]
结束
让我们确保模拟工作正常。:
demoroot = @__DIR__
mdl = engee.load(joinpath((demoroot),"loopbreaker.engee");force=true);
simres = engee.run(mdl)
st = collect(simres["Ref.1"])
res = collect(simres["LoopBreaker.1"])
using Plots
p = Plots.plot(st.time, st.value, label="Step")
Plots.plot!(res.time, res.value, label="LoopBreaker")
Plots.plot!(legend=:topleft)
engee.close(mdl;force=true)
display(p)
设置采样时间
示例:"减慢"输出
这样的模型和算法是可能的,其中需要提供更新块的输出的一定周期。 在Engee功能块的情况下,用户有两种方法来实现这样的要求。:
-通过块参数显式指示采样周期
-编写自己的算法来确定所需的周期
在这个例子中,考虑了第二个选项。
考虑sample_time_override模型:
在功能上,Engee函数ST_Orig和ST_Override块对应于来自缓存和强类型部分的算法。 然而,现在有一个要求,以最低的周期产生的结果。 让我们来看看采样周期的内置继承机制是如何工作的。:
可以看出,周期D1是继承的,即最高周期。 但挑战在于继承D2。 为此,我们将在Engee函数代码中包含复盖示例时间继承方法部分,并选择最慢的周期。:
``'茱莉亚
函数propagate_sample_times(inputs_sample_times::Vector{SampleTime},
fixed_solver::Bool)::SampleTime
max_period=argmax(t->t.period,inputs_sample_times)
返回max_period
结束
本方法实施后,得到如下结果:
<img src="/helpcenter/stable/_next/static/media/example_resources/EF_adv_programming_media/image_3.png" alt="image_3.png" class="notebook-media">
可以看出,期望的周期D2现在是继承的。
#### 重要!
第一个参数是SampleTime类型的命名元组的向量,定义为:
``'茱莉亚
SampleTime=NamedTuple{(:period,:offset,:mode),Tuple{Rational{Int64},Rational{Int64},Symbol}}
此元组的mode字段采用以下值::Discrete,:Continuous,:Constant,:Fim。
第二个参数是显示求解器类的标志。 如果标志为true,则选择恒定步长求解器。
结论
考虑了Engee功能块高级编程的方法. 这些方法允许您详细确定所需的块行为并实现最大性能。 本文不介绍编写块代码时的典型错误以及编写安全代码的技术。