Документация Engee
Notebook

Интеграция Fortran и Engee

В этом проекте рассматривается вопрос интеграции унаследованного кода Fortran и Engee.

Рассматриваются два сценария:

  • Интеграция в среду технических расчетов
  • Интеграция в среду моделирования

Допустим, требуется интегрировать такой код:

subroutine vect_add(a,b,res,len)

    integer len
    integer i
    
    real, intent(inout) :: res(len)
    real a(len)
    real b(len)
    
    do concurrent (i = 1:len)
        res(i) = a(i) + b(i)
    end do
    
    return
end subroutine vect_add

Данная подпрограмма складывает два вектора. Наша задача - использовать эту подпрограмму в своих расчетах.

Подготовка к работе

Прежде чем начать работу, отметим следующее: Нам потребуется модифицировать код подпрограммы, что бы она могла корректно привязаться к имени C или C++:

subroutine vect_add(a,b,res,len) bind ( C, name="vect_add" )

Что бы корректно использовать нашу подпрограмму, она должна быть скомпилирована в разделяемую библиотеку. Для этого будем использовать компилятор gfortran

In [ ]:
run(`gfortran vect_add.f90 -shared -fPIC -o vect_add.so`)
Out[0]:
Process(`gfortran vect_add.f90 -shared -fPIC -o vect_add.so`, ProcessExited(0))

Убедимся в том, что мы можем работать с скомпилированной процедурой, выполнив следующую команду:

In [ ]:
run(`objdump -x vect_add.so`)
vect_add.so:     file format elf64-x86-64
vect_add.so
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x0000000000000000

Program Header:
    LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12
         filesz 0x0000000000000420 memsz 0x0000000000000420 flags r--
    LOAD off    0x0000000000001000 vaddr 0x0000000000001000 paddr 0x0000000000001000 align 2**12
         filesz 0x00000000000001e9 memsz 0x00000000000001e9 flags r-x
    LOAD off    0x0000000000002000 vaddr 0x0000000000002000 paddr 0x0000000000002000 align 2**12
         filesz 0x00000000000000a8 memsz 0x00000000000000a8 flags r--
    LOAD off    0x0000000000002e80 vaddr 0x0000000000003e80 paddr 0x0000000000003e80 align 2**12
         filesz 0x00000000000001a0 memsz 0x00000000000001a8 flags rw-
 DYNAMIC off    0x0000000000002e90 vaddr 0x0000000000003e90 paddr 0x0000000000003e90 align 2**3
         filesz 0x0000000000000150 memsz 0x0000000000000150 flags rw-
    NOTE off    0x0000000000000238 vaddr 0x0000000000000238 paddr 0x0000000000000238 align 2**2
         filesz 0x0000000000000024 memsz 0x0000000000000024 flags r--
EH_FRAME off    0x0000000000002000 vaddr 0x0000000000002000 paddr 0x0000000000002000 align 2**2
         filesz 0x0000000000000024 memsz 0x0000000000000024 flags r--
   STACK off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4
         filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
   RELRO off    0x0000000000002e80 vaddr 0x0000000000003e80 paddr 0x0000000000003e80 align 2**0
         filesz 0x0000000000000180 memsz 0x0000000000000180 flags r--

Dynamic Section:
  INIT                 0x0000000000001000
  FINI                 0x00000000000011dc
  INIT_ARRAY           0x0000000000003e80
  INIT_ARRAYSZ         0x0000000000000008
  FINI_ARRAY           0x0000000000003e88
  FINI_ARRAYSZ         0x0000000000000008
  GNU_HASH             0x0000000000000260
  STRTAB               0x0000000000000318
  SYMTAB               0x0000000000000288
  STRSZ                0x000000000000005e
  SYMENT               0x0000000000000018
  PLTGOT               0x0000000000004000
  RELA                 0x0000000000000378
  RELASZ               0x00000000000000a8
  RELAENT              0x0000000000000018
  RELACOUNT            0x0000000000000003

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .note.gnu.build-id 00000024  0000000000000238  0000000000000238  00000238  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .gnu.hash     00000024  0000000000000260  0000000000000260  00000260  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .dynsym       00000090  0000000000000288  0000000000000288  00000288  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .dynstr       0000005e  0000000000000318  0000000000000318  00000318  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .rela.dyn     000000a8  0000000000000378  0000000000000378  00000378  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .init         0000001b  0000000000001000  0000000000001000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  6 .plt          00000010  0000000000001020  0000000000001020  00001020  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  7 .plt.got      00000008  0000000000001030  0000000000001030  00001030  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  8 .text         0000019a  0000000000001040  0000000000001040  00001040  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  9 .fini         0000000d  00000000000011dc  00000000000011dc  000011dc  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 10 .eh_frame_hdr 00000024  0000000000002000  0000000000002000  00002000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 11 .eh_frame     00000080  0000000000002028  0000000000002028  00002028  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 12 .init_array   00000008  0000000000003e80  0000000000003e80  00002e80  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 13 .fini_array   00000008  0000000000003e88  0000000000003e88  00002e88  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 14 .dynamic      00000150  0000000000003e90  0000000000003e90  00002e90  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 15 .got          00000020  0000000000003fe0  0000000000003fe0  00002fe0  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 16 .got.plt      00000018  0000000000004000  0000000000004000  00003000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 17 .data         00000008  0000000000004018  0000000000004018  00003018  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 18 .bss          00000008  0000000000004020  0000000000004020  00003020  2**0
                  ALLOC
 19 .comment      0000002b  0000000000000000  0000000000000000  00003020  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 crtstuff.c
0000000000001040 l     F .text	0000000000000000 deregister_tm_clones
0000000000001070 l     F .text	0000000000000000 register_tm_clones
00000000000010b0 l     F .text	0000000000000000 __do_global_dtors_aux
0000000000004020 l     O .bss	0000000000000001 completed.0
0000000000003e88 l     O .fini_array	0000000000000000 __do_global_dtors_aux_fini_array_entry
00000000000010f0 l     F .text	0000000000000000 frame_dummy
0000000000003e80 l     O .init_array	0000000000000000 __frame_dummy_init_array_entry
0000000000000000 l    df *ABS*	0000000000000000 vect_add.f90
0000000000000000 l    df *ABS*	0000000000000000 crtstuff.c
00000000000020a4 l     O .eh_frame	0000000000000000 __FRAME_END__
0000000000000000 l    df *ABS*	0000000000000000 
0000000000003e90 l     O .dynamic	0000000000000000 _DYNAMIC
0000000000004020 l     O .data	0000000000000000 __TMC_END__
0000000000004018 l     O .data	0000000000000000 __dso_handle
0000000000001000 l     F .init	0000000000000000 _init
0000000000002000 l       .eh_frame_hdr	0000000000000000 __GNU_EH_FRAME_HDR
00000000000011dc l     F .fini	0000000000000000 _fini
0000000000004000 l     O .got.plt	0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000000  w      *UND*	0000000000000000 __cxa_finalize
00000000000010f9 g     F .text	00000000000000e1 vect_add
0000000000000000  w      *UND*	0000000000000000 _ITM_registerTMCloneTable
0000000000000000  w      *UND*	0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000  w      *UND*	0000000000000000 __gmon_start__


Out[0]:
Process(`objdump -x vect_add.so`, ProcessExited(0))

Данная команда выводит символы в бинарном файле, и мы видим что символ vect_add существует в нашей библиотеке:

00000000000010f9 g     F .text	00000000000000e1 vect_add

Интеграция в среду технических расчетов

Для корректной интеграции мы будем использовать Dynamic Linker. Это позволит нам загружать и выгружать нашу разделяемую библиотеку по мере необходимости.

In [ ]:
using Pkg; Pkg.add("Libdl")
using Libdl
   Resolving package versions...
   Installed MPItrampoline_jll ─ v5.5.1+2
  No Changes to `~/.project/Project.toml`
  No Changes to `~/.project/Manifest.toml`

Загрузим нашу библиотеку:

In [ ]:
lib = Libdl.dlopen("./vect_add.so");
foo = Libdl.dlsym(lib, :vect_add);

Создадим тестовые данные и вызовем нашу подпрограмму с помощью функции ccall.

Особенностью работы ccall с фортраном является то, что все параметры подпрограмм фортрана передаются по ссылке.

В Julia для этого есть специальный тип Ref.

In [ ]:
Vect_in = Vector{Float32}([1.0,2.0,3.0,4.0,5.0])
Vect_out = copy(Vect_in);
ccall(foo,Nothing,(Ref{Float32},Ref{Float32},Ref{Float32},Ref{Int}), Vect_in, Vect_in, Vect_out, 5)
println("Результат работы кода Fortran : $Vect_out")
Результат работы кода Fortran : Float32[2.0, 4.0, 6.0, 8.0, 10.0]

После того как мы закончили работать с библиотекой, ее надо выгрузить:

In [ ]:
Libdl.dlclose(lib)
Out[0]:
true

Интеграция в среду моделирования

Для интеграции скомпилированной библиотеки в среду моделирования будем использовать блок C Function. Как и для любой библиотеки для работы с библиотечными функциями требуется заголовочный файл. Как несложно заметить, у нас его нет. Однако gfortran позволяет сгенерировать его автоматически. Для этого перекомпилируем наш код, дополнительно указав ключ -fc-prototypes и записав вывод в файл vect_add.h

In [ ]:
res = read(`gfortran vect_add.f90 -shared -o libvect_add.so -fPIC -fc-prototypes`,String);
write("vect_add.h",res);

Откроем модель fort_integration:

In [ ]:
demoroot = @__DIR__
demo = joinpath("$demoroot","fortran_caller.engee");
mdl = engee.load(demo)
Out[0]:
Model(
	name: fortran_caller
	id: 953bdeb1-1e6b-46a7-b5b4-f6b171e07232
)

Дополнительно настроим пути до заголовочных файлов и библиотек для блока C Function:

In [ ]:
engee.set_param!("fortran_caller/Fortran_is_Here","IncludeDirectories"=>demoroot)
engee.set_param!("fortran_caller/Fortran_is_Here","LibraryDirectories"=>demoroot)

Вызов нашей процедуры содержится в блоке C Function, в секции output code. Так как на ее вход подается указатель, то мы должны передавать адреса входных сигналов.

image.png

Дополнительно надо добавить сгенерированный заголовочный файл и библиотеку в информацию о сборке:

image_2.png

Запустим симуляцию и посмотрим результаты:

In [ ]:
engee.run(mdl);
In [ ]:
println("Входной вектор: $(simout["fortran_caller/V_in"].value[1])")
println("Результаты работа Fortran: $(simout["fortran_caller/res"].value[1])")
Входной вектор: Float32[1.0, 2.0, 3.0, 4.0, 5.0]
Результаты работа Fortran: Float32[2.0, 4.0, 6.0, 8.0, 10.0]

Выводы

Интеграция кода на Fortran выполняется первичной компиляции этого кода в разделяемую библиотеку и вызова этой библиотеки из среды моделирования или интерактивного скрипта. Такой подход позволяет использовать предыдущие наработки без затрат на переписывание кода на языке Julia

Блоки, использованные в примере