I wrote the following Fortran wrapper around an existing Fortran library that defines some ‘global’ parameters that persist between subroutines. General idea is that libfida
defines a subroutine read_tables
that loads some atomic physics cross-sections from an HDF5 file, which is then used by other subroutines like bb_cx_rates
here.
module cx_helper
implicit none
private
public :: cx_matrix, cx_rates
contains
subroutine cx_matrix(velocity,nlevels,rates) bind(C,name="cx_matrix")!{{{
use libfida, only : SimulationInputs,bb_cx_rates,read_tables, nlevs,inputs,impurity_charge
use, intrinsic :: iso_c_binding, only: c_int, c_double
implicit none
real(c_double), intent(in) :: velocity
integer, intent(in) :: nlevels
real(c_double), dimension(nlevels,nlevels), intent(inout) :: rates !! catches the output from bb_cx_rates
integer :: n_in !! incoming neutral energy level (principle quantum number)
real(c_double), dimension(nlevels) :: denn !! density [cm^-3] of each energy state of neutrals
real(c_double), dimension(3) :: vn = [0.0,0.0,0.0] !! cm/s x,y,z components of velocity of neutral
real(c_double), parameter, dimension(3) :: vi = [0.0,0.0,0.0]!! velocity of ion
if (nlevels .gt. nlevs) then
print *,"nlevels must be less than ",nlevs
stop
endif
vn(1) = velocity
impurity_charge = 6
inputs%verbose = 1
inputs%tables_file = '/Users/lmorton/Code/FIDASIM_lib/tables/atomic_tables.h5'
inputs%calc_neutron = 0
call read_tables
do n_in=1,6
denn = 0.0
denn(n_in) = 1.0
call bb_cx_rates(denn,vn,vi,rates(n_in,:))
enddo
end subroutine cx_matrix!}}}
The Julia wrapper then looks like:
function cx_matrix(velocity::Float64)
nlevs = Int32(6) #Maximum is 6, but can be lower
σ = Array{Float64,2}(undef,nlevs,nlevs)
ccall((:cx_matrix, "./cx_helper.so"), Cvoid, (Ptr{Float64},Ref{Int32},Ptr{Float64}), Ref(velocity),Ref(nlevs),σ)
return σ
end
This works OK the first time I call it, but the second time I crash the REPL:
At line 3767 of file fidasim.f90
Fortran runtime error: Attempting to allocate already allocated variable 'cross'
The line referenced looks like this:
subroutine read_atomic_cross(fid, grp, cross)
...
allocate(cross%log_cross(cross%m_max,cross%n_max, cross%nenergy))
Basically, it looks like (much to my surprise) that the Fortran variables are persisting in memory outside the initial ccall
. I assumed that the ccall
invocation would destroy the entire Fortran runtime session prior to returning control to Julia, so that I would just need to re-invoke the read_tables
subroutine each time. It’s handy to avoid that, but now I have more issues:
- How do I destroy the Fortran variables when I’m done with them, to avoid memory leakage?
- How do I test whether the variables are still alive, so I don’t allocate twice & crash the REPL?
More generally, I don’t have a very good sense for what’s happening under the hood when Julia calls a foreign function, and I’m not sure where to find info/documentation on that.