Fortran and Engee integration
This project addresses the issue of integrating legacy Fortran and Engee code.
Two scenarios are being considered:
- Integration into the environment of technical calculations
- Integration into the simulation environment
Let's say you need to integrate such code:
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.
Preparation for work
Before starting work, we note the following:
We will need to modify the code of the subroutine so that it can correctly bind to the name C or C++.:
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 running the following command:
run(`objdump -x vect_add.so`)
This command outputs characters in a binary file, and we see that the vect_add character exists in our library.:
00000000000010f9 g F .text 00000000000000e1 vect_add
Integration into the environment of technical calculations
For proper integration, we will use Dynamic Linker. This will allow us to upload and download our shared library as needed.
using Pkg; Pkg.add("Libdl")
using Libdl
Download our library:
lib = Libdl.dlopen("./vect_add.so");
foo = Libdl.dlsym(lib, :vect_add);
Let's create the test data and call our routine using the ccall function.
A feature of ccall's work with fortran is that all parameters of Fortran routines are passed by reference.
Julia has a special type for this. 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've finished working with the library, we need to download it.:
Libdl.dlclose(lib)
Integration into the simulation environment
To integrate the compiled library into the simulation environment, we will use the C Function block.
As with any library, a header file is required to work with library functions. As it's easy to see, we don't have one. However, gfortran allows you to generate it automatically. To do this, we will recompile our code, additionally specifying the key -fc-prototypes and by writing the output to a 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, we will configure the paths to the 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 a pointer is applied to its input, we must transmit the addresses of the input signals.
Additionally, you need to add the generated header file and library to the build 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 this code into a shared library and calling this library from a simulation environment or an interactive script. This approach allows you to use previous developments without the cost of rewriting the code in Julia.