Engee 文档
Notebook

将 Julia 函数转换为 C 代码

在本例中,我们将演示一种技术,它可以让您从 Julia 创建的函数生成 C 代码。

任务描述

假设我们想将 Julia 上的代码移植到另一个平台上,而在该平台上编译代码并不方便。例如,在微控制器上。与此同时,我们也希望能在 Engee 内部的模型循环中保留运行相同代码的能力。如何继续?

让我们使用多态机制。你可以在代码中输入数字,然后它就会执行并计算出数字结果。你可以输入矩阵,然后结果将是矩阵。让我们输入符号变量,看看会得到什么。

In [ ]:
Pkg.add(["Symbolics"])
In [ ]:
Pkg.add( "Symbolics" )

从简单函数生成代码

让我们考虑从一个返回一个数字的非常简单的函数中生成代码:

In [ ]:
simple_fcn(x1, x2) = 2 .* x1 + sin( x2 )
simple_fcn(1, 2)
Out[0]:
2.909297426825682

在全局级别声明两个变量x1x2 ,我们可以用它们进行符号运算。

In [ ]:
using Symbolics
@variables x_1 x_2
Out[0]:
$$ \begin{equation} \left[ \begin{array}{c} x_{1} \\ x_{2} \\ \end{array} \right] \end{equation} $$

如果我们将符号变量替换为简单函数的参数,我们将不会看到任何异常(除了我们将看到嵌入函数中的方程):

In [ ]:
simple_fcn( x_1, x_2 )
Out[0]:
$$ \begin{equation} 2 x_{1} + \sin\left( x_{2} \right) \end{equation} $$

但我们会得到一个结构良好的对象,并从中生成代码:

In [ ]:
c_model_code = build_function( simple_fcn( x1, x2 ), [x1, x2]; target=Symbolics.CTarget(), fname="SIMPLE_FCN", lhsname=:c, rhsnames=[:x1, :x2] )
println( c_model_code )
#include <math.h>
void SIMPLE_FCN(double* c, const double* x1) {
  c[0] = 2 * x1[0] + sin(x1[1]);
}

我们得到了针对特定 "目标 "优化的代码CTarget

这些代码可能需要进行各种修改。例如,翻译成float 类型或更改函数名称(expexpf ),以满足您的平台所使用标准的要求。或者添加函数main

此外,还可以将代码保存到文件simple_function.c 中,并使用命令gcc -o out simple_function.c -lm 进行编译。

In [ ]:
c_code_standalone = """
#include <stdio.h>

$c_model_code

int main(int argc, char const *argv[]){
    double out[1];
    double in[] = {1, 2};

    SIMPLE_FCN( out, in );

    printf( "%f\\n", out[0] );
    return 0;
}""";

# Сохраняем в файл доработанный код
open("$(@__DIR__)/simple_function.c", "w") do f
    println( f, "$c_code_standalone" )
end

编译得到的代码:

In [ ]:
;gcc -o out simple_function.c -lm

检查生成的二进制文件是否产生了与原始函数相同的值:

In [ ]:
;./out
2.909297

结果与我们在示例开始时计算的结果一致。

结论

我们在 Julia 中生成了一个非常简单函数的代码,并在 Engee 中编译和执行,我们也可以把它放在C Function 块中,以简化半自然测试。

值得注意的是,并不是所有代码都能通过这种方法轻松翻译成 C 代码,通常需要对翻译程序进行重大修改,以使代码能够接受矩阵作为输入。但这种方法可以生成相当大的算法子类,在半自然建模场景中非常有用。