Engee 文档
Notebook

面向模型设计中的元编程

Julia中元编程的一个简单示例,对于Engee中的建模很有用。

导言

元编程—这是建立的程序,生成或修改其他代码在执行过程中。 它需要自动化日常任务,减少[模板]任务的数量。 кода](https://ru.wikipedia.org/wiki/Шаблонный_код )并创建有效的[DSL](https://ru.wikipedia.org/wiki/Предметно-ориентированный_язык 域特定语言)。

许多语言都支持元编程,例如Julia,Lisp,Ruby,Python,JavaScript和Rust。 Julia最初设计时具有强大的宏级元编程和代码生成支持。 由于[homoiconicity],在Julia中掌握它相对容易](https://ru.wikipedia.org/wiki/Гомоиконичность)(代码由语言本身的数据结构表示)和清晰的语法,但有效的使用需要对编译阶段的理解。

在这个例子中,我们将研究如何在面向模型设计(MOS)的日常和日常任务中使用Julia元编程。 这种任务的一个简单例子是设计模型回调,以从Engee脚本中的技术计算结果确定模型块的参数。

以下部分推荐给初学者,以及不熟悉MOS的用户。

详细任务说明

[基于模型的设计]的例子(https://engee.com/helpcenter/stable/ru/explanation/model-based-design.html )(MOS),列于[Engee社区](https://engee.com/community/ru/catalogs/projects ),包含至少两个文件:

*[Engee脚本](https://engee.com/helpcenter/stable/ru/guide/script-editor.html )(*。ngscript)-包含示例的描述,通常还包含稍后在建模中使用的技术计算,

*[Engee模型](https://engee.com/helpcenter/stable/ru/tutorial/building-a-model.html )(*。engee)-基于来自脚本的描述和计算,对所研究的系统执行动态建模。

MOS的工作流程,在此期间使用这些文件,可能看起来如下图所示。:

alexevs - Frame 1.jpg

我们通过技术计算获得的数据用于动态建模,然后将仿真结果用于进一步分析。

但是,在项目开发过程中,例如,在修改模型,缩放模型以及以其他方式重用模型时,任务是保存和传输在初始技术计算期间获得的模型的数据。 这些可以是,例如,模拟参数的初始条件的值,如[异步电动机的参数化]的示例(https://engee.com/community/ru/catalogs/projects/model-asinkhronnogo-dvigatelia )。 下面是此示例中的模型工作所需的代码。:

In [ ]:
Uₙ = 380;
f₁ = 50;
Pₙ = 7500.0;
Uₙ = 380;
p = 2;
R₁ = 0.6593049031972141;
X₁σ = 0.4861804567433673;
R₂ = 0.32521057126385705;
X₂σ = 0.4861804567433673;
Xₘ = 24.520695357232057;
J = 0.02;
Mₙ = 49.22317827584392;

这就是它将继续在模型中使用的方式(配置模型块时):

image.png

有几种方法可以在建模之前识别这些变量。:

*每次建模前,运行一个脚本来计算并将变量添加到工作区。 在这种情况下,未修改的示例脚本应位于文件浏览器中的已知路径上。 如果它不存在,您需要从存储它的存储库中提取它或从计算机的内存中下载它。 之后,你需要运行它。 与此同时,将执行不必要的中间计算,安装Julia软件包,对结果进行建模和分析。 换句话说,Engee花费了额外的时间和计算资源来准备模拟。
*每次建模前,运行一个新的特殊脚本进行参数化。 新脚本还需要存储在固定路径中并对其进行版本控制,但它只能包括模型参数的定义,如上面的代码单元格中所示。 这种方法比前一种方法更好,但您仍然必须使用额外的实体,这并不总是方便可靠。
*正确的方法是使用模型回调,其中确定用于建模的参数的初始值。 将代码写入原始模型的回调一次就足够了,然后,当它被重用时,它们会自动添加到工作区中。

唯一剩下的例程,耗时且不总是方便的任务是将模型参数定义代码从计算脚本传输到回调而不会出错。

与此同时,如上所述,参数名称在代码中给出,它们的值在工作区或输出代码单元格中。

我们将在下面看到的代码旨在使创建用于定义模型参数的代码变得更容易。 从问题的表述中可以清楚地看出,这是一项元编程任务。

解决方案的开始

正在开发的代码应该做什么?

我们的代码获取:变量的名称。

我们的代码返回:用于定义具有给定名称的变量的代码,该变量包含在Engee变量的工作区中。

解决方案的结果将是一个宏,因为宏本质上是一个可以像处理数据一样处理代码的函数。

让我们创建以下宏:

In [ ]:
macro get_callbacks(var)
    :(println($(string(var)), " = ", $var, ";"))
end
Out[0]:
@get_callbacks (macro with 1 method)

收到的代码的几个特点:

宏 **get_callbacks**接受论点 var(这是一个表达式,而不是一个值),并通过将其转换为字符串来操作它 **(string(var))**并用引号代替 :().

宏 **get_callbacks**不计算传递给它的表达式 :var,并将其转换为新代码。 在编译期间 **@get_callbacks(x)**将替换为生成的字符串 println("var", " = ", var, ";")

让我们传递我们的宏变量名称来获取模型回调的代码。:

In [ ]:
@get_callbacks(Uₙ)
Uₙ = 380;

结果,我们得到了代码来定义我们之前传递的变量的名称。

对于我们的宏,您还可以设置变量类型的静态定义,例如:

In [ ]:
macro get_typed_callbacks(var)
    :(println($(string(var)), "::$(typeof($var)) = ", $var, ";"))
end
Out[0]:
@get_typed_callbacks (macro with 1 method)
In [ ]:
number = 33.3
@get_typed_callbacks(number)

vector = [4, 8, 15, 16, 23, 42]
@get_typed_callbacks(vector)

name = "X Æ A-12"
@get_typed_callbacks(name)
number::Float64 = 33.3;
vector::Vector{Int64} = [4, 8, 15, 16, 23, 42];
name::String = X Æ A-12;

但是,正如我们所看到的,这样的宏不会在字符串变量的定义中为字符串打印引号。 您可以按如下方式修复此问题:

In [ ]:
macro get_typed_callbacks(var)
    quote
        value = $(esc(var))
        if typeof(value) == String
            println($(string(var)), "::$(typeof(value)) = \"", value, "\";")
        else
            println($(string(var)), "::$(typeof(value)) = ", value, ";")
        end
    end
end
Out[0]:
@get_typed_callbacks (macro with 1 method)
In [ ]:
number = 33.3
@get_typed_callbacks(number)

vector = [4, 8, 15, 16, 23, 42]
@get_typed_callbacks(vector)

name = "X Æ A-12"
@get_typed_callbacks(name)
number::Float64 = 33.3;
vector::Vector{Int64} = [4, 8, 15, 16, 23, 42];
name::String = "X Æ A-12";

现在我们的宏也考虑到了字符串变量的传递。 我们宏的功能可以进一步扩展,但让我们回到解决原始问题,并将宏细化为可以在模型开发的日常工作中使用的形式。

现成的功能

要为模型回调生成代码,而不是针对一个参数,而是一次针对多个参数,您可以从生成的表达式在宏内部创建一个向量,然后从向量创建此类表达式的块。:

In [ ]:
macro get_callbacks(vars...)
    exprs = []
    for var in vars
        push!(exprs, :(println($(string(var)), " = ", $var, ";")))
    end
    return Expr(:block, exprs...)
end
Out[0]:
@get_callbacks (macro with 2 methods)

将表达式组合成块将允许您输出一次参数化多个参数的代码。

建模中的用途

让我们使用创建的宏,创建一个代码将其写入回调[示例模型](https://engee.com/community/ru/catalogs/projects/model-asinkhronnogo-dvigatelia )从任务描述:

In [ ]:
@get_callbacks(Uₙ,f₁,Pₙ,Uₙ,p,R₁,X₁σ,R₂,X₂σ,Xₘ,J,Mₙ)
Uₙ = 380;
f₁ = 50;
Pₙ = 7500.0;
Uₙ = 380;
p = 2;
R₁ = 0.6593049031972141;
X₁σ = 0.4861804567433673;
R₂ = 0.32521057126385705;
X₂σ = 0.4861804567433673;
Xₘ = 24.520695357232057;
J = 0.02;
Mₙ = 49.22317827584392;

现在,打开模型时 im_parametrization.engee 模型块的这些参数将自动确定。

让我们为将来保存这样一个有用的宏,以便在其他模型开发项目中重用此方法。

In [ ]:
cd(@__DIR__)
open("get_callbacks.jl", "w") do file
    write(file, """
macro get_callbacks(vars...)
    exprs = []
    for var in vars
        push!(exprs, :(println(\$(string(var)), " = ", \$var, ";")))
    end
    return Expr(:block, exprs...)
end
    """)
end;

结论

在示例中,我们解决了在Julia上使用元编程自动编写模型回调代码的问题。