Engee 文档
Notebook

将Julia函数转换为C代码

在这个例子中,我们将演示一种技术,它允许您从Julia中创建的函数生成C代码。

任务说明

让我们假设我们在Julia上有代码,我们想迁移到另一个平台,我们在平台上编译代码不方便。 例如,在微控制器上。 与此同时,保持在Engee内部的模型循环中运行相同代码的能力会很好。 我该怎么办?

让我们使用多态机制。 您可以将数字输入代码,然后它将为您执行并计算数字结果。 你可以提交矩阵,那么结果将是一个矩阵。 让我们输入符号变量,看看会发生什么。

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} \mathtt{x\_1} \\ \mathtt{x\_2} \\ \end{array} \right] \end{equation} $$

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

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

但另一方面,我们将得到一个结构良好的对象,我们可以从中生成代码。:

In [ ]:
c_model_code = build_function( simple_fcn( x_1, x_2 ), [x_1, x_2]; target=Symbolics.CTarget(), fname="SIMPLE_FCN", lhsname=:c, rhsnames=[:x] )
println( c_model_code )
#include <math.h>
void SIMPLE_FCN(double* c, const double* x) {
  c[0] = 2 * x[0] + sin(x[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
In [ ]:
simple_fcn(1, 2)
Out[0]:
2.909297426825682

结果与我们在示例开始时计算的结果相同。

结论

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

值得注意的是,并非每个代码都可以通过这种方法轻松地翻译成C代码,通常需要显着更改翻译过程,以便允许代码接受矩阵作为输入。 但是通过这种方式,可以生成一个相当大的算法子类,这些算法在半自然模拟场景中很有用。