Engee 文档
Notebook

整合 Fortran 和 Engee

该项目解决了整合传统 Fortran 和 Engee 代码的问题。

考虑了两种情况:

  • 集成到技术计算环境中
  • 集成到建模环境中

假设需要集成以下代码:

子程序 vect_add(a,b,res,len)

    整数 len
    整数 i
    
    real, intent(inout) :: res(len)
    real a(len)
    real b(len)
    
    并发(i = 1:len
        res(i) = a(i) + b(i)
    end do
    
    返回
结束子程序 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 符号存在于我们的程序库中:

0000000000000000000010f9 g F .text 0000000000000000000000e1 vect_add

集成到技术计算环境中

为了正确集成,我们将使用动态链接器。这样,我们就可以根据需要加载和卸载我们的共享库。

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 函数调用我们的子程序。

与 fortran 相比,ccall 操作的特殊性在于 fortran 子程序的所有参数都是通过引用传递的。

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 功能块。 与任何库一样,使用库函数需要头文件。您不难发现,我们没有头文件。不过,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 功能块配置头文件和库的路径:

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

程序的调用位于 C 功能块的输出代码部分。由于其输入是指针,我们必须传递输入信号的地址。

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 代码编译到共享库中,然后从建模环境或交互式脚本中调用该库,从而实现 Fortran 代码集成。这种方法可以利用以前的工作,而不需要重新编写 Julia 代码。

示例中使用的块