Переводим функции Julia в код на C¶
В этом примере мы продемонстрируем один прием, который позволяет генерировать код на Си из функции, созданной в Julia.
Описание задачи¶
Предположим, что у нас есть код на Julia, который мы хотим перенести на другую платформу, для которой нам неудобно компилировать код на платформе. Например, на микроконтроллер. При этом хорошо было бы сохранить возможность запускать тот же код в модельном контуре внутри Engee. Как поступить?
Воспользуемся механизмом полиморфизма. В ваш код можно подать числа, и тогда он выполнится и вычислит вам численный результат. Можно подать матрицы, тогда результат будет матричным. Подадим на вход символьные переменные и посмотрим, что получится.
Pkg.add( "Symbolics" )
Генерация кода из простой функции¶
Рассмотрим генерацию кода из очень простой функции, которая возвращает нам одно число:
simple_fcn(x1, x2) = 2 .* x1 + sin( x2 )
simple_fcn(1, 2)
Объявим, на глобальном уровне, две переменные x1
и x2
, с которыми можно делать символьные операции.
using Symbolics
@variables x_1 x_2
Если подставить символьные переменные в качестве аргументов нашей простой функции, ничего необычного мы не увидим (разве что увидим уравнение, заложенное в функцию):
simple_fcn( x_1, x_2 )
Но зато мы получим хорошо структурированный объект, из которого можно генерировать код:
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 )
Мы получили код, оптимизированный для определенного "таргета" CTarget
.
Этому коду могут потребоваться различные доработки. Например, перевод в тип float
или смена названий функций (exp
на expf
) под требования стандарта, используемого в вашей платформе. Или добавление функции main
.
Дальше этот код можно сохранить в файл simple_function.c
, скомпилировать командой gcc -o out simple_function.c -lm
.
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
Компилируем полученный код:
;gcc -o out simple_function.c -lm
И проверим, что полученный двоичный файл выдает то же значение, что и исходная функция:
;./out
Результат совпадает с тем, что мы вычислили в начале примера.
Заключение¶
Мы сгенерироавли код для очень простой функции на Julia, скомпилировали и выполнили его в Engee, могли бы также поместить его в блок C Function
для упрощения полунатурных испытания.
Стоит отметить, что не всякий код легко переводится в Си-код этим методом, часто процедуру перевода нужно будет существенно изменить, чтобы она позволяла коду принимать на вход матрицы. Но так можно сгенерировать достаточно большой подкласс алгоритмов, полезных в полунатурном сценарии моделирования.