Pregunta

I have a fortran linked list that is roughly like

type :: node
    type(node), pointer :: next => null()
    integer :: value
end type node

I would ideally like to interact with this using Cpython. I have used fortran subroutines with python a lot using the f2py program to create the shared object. However, f2py can not be used with derived types.

My question is simply whether it possible at all to access something like a linked list in Fortran with cpython. I presume I need to follow the fortran to c to cpython route. However, I've read that for a fortran derived type to interoperable with c "Each component must have interoperable type and type parameters, must not be a pointer, and must not be allocatable." Likewise, the post c-fortran interoperability - derived types with pointers seems to confirm this.

I was wondering if anyone knows if it is definitely not possible to access a linked list in fortran from cpython. If it is possible, even if indirectly or in a round-about way, I would be grateful to hear more.

thank you, Mark

¿Fue útil?

Solución

As has Bálint Aradi already mentioned in the comments, the node is not interoperable with C in it's current form. For that you need to change fortran pointers into C pointers, but that makes it extremely painful to use inside fortran itself. The most elegant solution I could come up with was putting the C interoperable type inside a fortran type and holding separate versions of C and fortran pointers.

The implementation is show below, where I've also defined convenience functions to be used inside fortran to allocate, deallocate and initialize the nodes.

module node_mod

    use, intrinsic :: iso_c_binding
    implicit none

    ! the C interoperable type
    type, bind(c) :: cnode
        type(c_ptr) :: self = c_null_ptr
        type(c_ptr) :: next = c_null_ptr
        integer(c_int) :: value
    end type cnode

    ! the type used for work in fortran
    type :: fnode
        type(cnode) :: c
        type(fnode), pointer :: next => null()
    end type fnode

contains

recursive function allocate_nodes(n, v) result(node)

    integer, intent(in) :: n
    integer, optional, intent(in) :: v
    type(fnode), pointer :: node
    integer :: val

    allocate(node)
    if (present(v)) then
        val = v
    else
        val = 1
    end if
    node%c%value = val
    if (n > 1) then
        node%next => allocate_nodes(n-1, val+1)
    end if

end function allocate_nodes

recursive subroutine deallocate_nodes(node)

    type(fnode), pointer, intent(inout) :: node
    if (associated(node%next)) then
        call deallocate_nodes(node%next)
    end if
    deallocate(node)

end subroutine deallocate_nodes

end module node_mod

As you can see, there's an extra "%c" needed to access the "value" element, which is a bit of a nuisance. To use the previously defined routines inside python to retrieve the linked list, C interoperable wrappers must be defined and C pointers must be linked.

module node_mod_cinter

    use, intrinsic :: iso_c_binding
    use, non_intrinsic :: node_mod

    implicit none

contains

recursive subroutine set_cptr(node)

    type(fnode), pointer, intent(in) :: node

    node%c%self = c_loc(node)
    if (associated(node%next)) then
        node%c%next = c_loc(node%next%c)
        call set_cptr(node%next)
    end if

end subroutine set_cptr

function allocate_nodes_citer(n) bind(c, name="allocate_nodes") result(cptr)

    integer(c_int), value, intent(in) :: n
    type(c_ptr) :: cptr
    type(fnode), pointer :: node

    node => allocate_nodes(n)
    call set_cptr(node)
    cptr = c_loc(node%c)

end function allocate_nodes_citer

subroutine deallocate_nodes_citer(cptr) bind(c, name="deallocate_nodes")

    type(c_ptr), value, intent(in) :: cptr
    type(cnode), pointer :: subnode
    type(fnode), pointer :: node

    call c_f_pointer(cptr, subnode)
    call c_f_pointer(subnode%self, node)
    call deallocate_nodes(node)

end subroutine deallocate_nodes_citer

end module node_mod_cinter

The "*_nodes_citer" routines simply deal with the different pointer types and the set_cptr subroutine links the C pointers inside the C interoperable type according to the fortran pointers. I've added the node%c%self element so that the fortran pointer could be recovered and used for proper deallocation, but if you're not too much concerned about that, then its not strictly needed.

This code needs to be compiled as a shared library to be used by other programs. I used the following command with gfortran on my linux box.

gfortran -fPIC -shared -o libnode.so node.f90

Finally, the python code to allocate a list of 10 nodes, print out the node%c%value of each of them and then deallocate everything again. Additionally, the memory locations of fortran and C nodes are also shown.

#!/usr/bin/env python
import ctypes
from ctypes import POINTER, c_int, c_void_p
class Node(ctypes.Structure):
    pass
Node._fields_ = (
        ("self", c_void_p),
        ("next", POINTER(Node)),
        ("value", c_int),
        )

def define_function(res, args, paramflags, name, lib):

    prot = ctypes.CFUNCTYPE(res, *args)
    return prot((name, lib), paramflags)

def main():

    import os.path

    libpath = os.path.abspath("libnode.so")
    lib = ctypes.cdll.LoadLibrary(libpath)

    allocate_nodes = define_function(
            res=POINTER(Node),
            args=(
                c_int,
                ),
            paramflags=(
                (1, "n"),
                ),
            name="allocate_nodes",
            lib=lib,
            )

    deallocate_nodes = define_function(
            res=None,
            args=(
                POINTER(Node),
                ),
            paramflags=(
                (1, "cptr"),
                ),
            name="deallocate_nodes",
            lib=lib,
            )

    node_ptr = allocate_nodes(10)

    n = node_ptr[0]
    print "value", "f_ptr", "c_ptr"
    while True:
        print n.value, n.self, ctypes.addressof(n)
        if n.next:
            n = n.next[0]
        else:
            break

    deallocate_nodes(node_ptr)

if __name__ == "__main__":
    main()

Executing this gives me the following output:

value f_ptr c_ptr
1 15356144 15356144
2 15220144 15220144
3 15320384 15320384
4 14700384 14700384
5 15661152 15661152
6 15661200 15661200
7 15661248 15661248
8 14886672 14886672
9 14886720 14886720
10 14886768 14886768

It's interesting to note that both node types start at the same memory locations, so node%c%self was not really needed, but this is only because I was careful with the type definitions and this really should not be counted on.

And there you have it. Even without having to deal with linked lists it's quite a hassle, but ctypes are vastly more powerful and robust that f2py. Hope some good comes out of this.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top