Integrating Fortran and Engee¶
This project addresses the issue of integrating legacy Fortran and Engee code.
Two scenarios are considered:
- Integration into a technical calculation environment
- Integration into a modelling environment
Suppose the following code needs to be integrated:
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
This subroutine adds two vectors. Our task is to use this subroutine in our calculations.
Preparing for work¶
Before starting the work, let us note the following: We will need to modify the code of the subroutine so that it can correctly bind to the C or C++ name:
subroutine vect_add(a,b,res,len) bind ( C, name="vect_add" )
In order to use our subroutine correctly, it must be compiled into a shared library. To do this, we will use the gfortran compiler
run(`gfortran vect_add.f90 -shared -fPIC -o vect_add.so`)
Let's make sure that we can work with the compiled procedure by executing the following command:
run(`objdump -x vect_add.so`)
This command outputs the symbols in the binary, and we can see that the vect_add symbol exists in our library:
0000000000000000000010f9 g F .text 00000000000000000000000000e1 vect_add
Integration into the technical calculation environment¶
For proper integration, we will be using Dynamic Linker. This will allow us to load and unload our shared library as needed.
using Pkg; Pkg.add("Libdl")
using Libdl
Let's load our library:
lib = Libdl.dlopen("./vect_add.so");
foo = Libdl.dlsym(lib, :vect_add);
Create test data and call our subroutine using the ccall function.
The peculiarity of ccall operation with fortran is that all parameters of fortran subroutines are passed by reference.
Julia has a special type for this purpose Ref
.
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")
After we have finished working with the library, it should be unloaded:
Libdl.dlclose(lib)
Integration into the modelling environment¶
To integrate the compiled library into the modelling environment we will use the C Function block.
As for any library, a header file is required to work with library functions. As you can easily notice, we don't have one. However, gfortran allows us to generate it automatically. To do this, recompile our code by additionally specifying the -fc-prototypes
key and writing the output to the file vect_add.h
res = read(`gfortran vect_add.f90 -shared -o libvect_add.so -fPIC -fc-prototypes`,String);
write("vect_add.h",res);
Open the model fort_integration
:
demoroot = @__DIR__
demo = joinpath("$demoroot","fortran_caller.engee");
mdl = engee.load(demo)
Additionally configure paths to header files and libraries for the C Function block:
engee.set_param!("fortran_caller/Fortran_is_Here","IncludeDirectories"=>demoroot)
engee.set_param!("fortran_caller/Fortran_is_Here","LibraryDirectories"=>demoroot)
The call to our procedure is contained in the C Function block, in the output code section. Since its input is a pointer, we must pass the addresses of the input signals.
Additionally, we must add the generated header file and library to the assembly information:
Let's run the simulation and see the results:
engee.run(mdl);
println("Входной вектор: $(simout["fortran_caller/V_in"].value[1])")
println("Результаты работа Fortran: $(simout["fortran_caller/res"].value[1])")
Conclusions¶
Fortran code integration is performed by first compiling that code into a shared library and calling that library from a modelling environment or interactive script. This approach allows previous work to be utilised without the cost of rewriting code in Julia