CFD Online Logo CFD Online URL
www.cfd-online.com
[Sponsors]
Home > Forums > General Forums > Main CFD Forum

fortran-c interoperable derived types

Register Blogs Community New Posts Updated Threads Search

Like Tree4Likes
  • 1 Post By Eifoehn4
  • 1 Post By Eifoehn4
  • 1 Post By aerosayan
  • 1 Post By aerosayan

Reply
 
LinkBack Thread Tools Search this Thread Display Modes
Old   December 20, 2020, 13:50
Default fortran-c interoperable derived types
  #1
Senior Member
 
Sayan Bhattacharjee
Join Date: Mar 2020
Posts: 495
Rep Power: 8
aerosayan is on a distinguished road
My most favorite feature in Modern Fortran is being able to store all of the arrays, matrices inside a derived type and sending all of that to a function/subroutine.


But I haven't been able to find a method by which I can store all of my matrices/arrays in a c struct, and send them to fortran. I consulted the book "Modern Fortran explained", and that says that allocateable arrays can't be inside fortran-c interoperable derived types.


Without this feature, we go back to subroutine calls that take 70 arguments, and fill up half of the screen.


In the interop-5-1 example code I wrote, I was able to store all of the array/matrix sizes inside the c struct, and send that to the Fortran subroutine as an argument. This would definitely allow us to package all of our Plain Old Data types like ints, doubles into the c struct, and reduce the number of arguments that need to be passed to the Fortran subroutines.


https://github.com/aerosayan/fortran-c-interop


In case the link goes down, here's the main.c

Code:
// AIM - Show how to pass a C struct to fortran


#include <stdio.h>
#include <stdlib.h>

struct blah
{
    int  len_array1; // We will use only one, but you could use all of them.
    int  len_array2;
    int  len_array3;
    int  len_array4;
};

void send_cstruct_to_fortran_(struct blah* s, int* array);

int main()
{
    // No. of elements in the array
    int n = 5;

    // Dynamic array
    int* array = (int*)malloc(n*sizeof(int));

    // Insert elements into dynamic array
    for(int i=0; i<n; ++i)
    {
        array[i] = i;
    }

    // Create object of struct
    struct blah s;

    // Store the first array length into struct
    // We don't care about the rest for now...
    s.len_array1 = n;


    // Send c struct to fortran, along with packaged data
    send_cstruct_to_fortran_(&s, array);

    // Scrub clean
    free(array);

    return 0;
}
In case the link goes down, here's the send.F90 code where the subroutine send_cstruct_to_fortran is present, and will be called from main.c
Code:
subroutine send_cstruct_to_fortran(cstruct, array)
    use iso_c_binding, only : c_int, c_ptr
    implicit none

    ! Fortran doesn't know how the c struct is defined, So, we use bind(c) to
    ! define a user defined type to match the c struct.
    !
    type, bind(c) :: cstruct_interface
        integer(c_int) :: n1
        integer(c_int) :: n2
        integer(c_int) :: n3
        integer(c_int) :: n4
    end type

    ! The struct that has been passed from c to fortran
    type(cstruct_interface), intent(in) :: cstruct

    ! We're using the cstruct%n1 to define the array length, thus not needing
    ! to pass the size of every array/matrix/tensor with every function call.
    !
    ! It's not perfect since we can't pass allocatable arrays inside structs.
    ! Thus, we have to pass them as subroutine/function arguments.
    !
    integer(4), intent(in) :: array(cstruct%n1)

    print *, "- FORTRAN SUBROUTINE CALLED..."
    print *, "  '- SUBROUTINE :: SEND_CSTRUCT_TO_FORTRAN(CSTRUCT)"
    print *, "  '- CSTRUCT%N1        : ", cstruct%n1
    print *, "  '- ARRAY             : ", array
    print *, "- FORTRAN SUBROUTINE FINISHED..."

end subroutine
The output was :
Code:
   - FORTRAN SUBROUTINE CALLED...
   '- SUBROUTINE :: SEND_CSTRUCT_TO_FORTRAN(CSTRUCT)
   '- CSTRUCT%N1        :            5
   '- ARRAY             :            0           1           2           3           4
 - FORTRAN SUBROUTINE FINISHED...
Using this method, I was only required to send in the array pointer and the rest of the Plain Old Data could be hidden inside the cstruct object.


This is a good step in the right direction, but we still have to pass all of the array/matrix pointers separately.

How can we send the array/matrix pointers to Fortran in a better way such that our subroutine calls won't require 70 arguments and fill up half of the screen?

Thanks
aerosayan is offline   Reply With Quote

Old   December 20, 2020, 15:51
Default
  #2
Senior Member
 
Eifoehn4's Avatar
 
-
Join Date: Jul 2012
Location: Germany
Posts: 184
Rep Power: 14
Eifoehn4 is on a distinguished road
You may write a parent C-wrapper routine with a clean struct object, which simply loops over all arrays in the C-struct. Here you can use your code to send each C-array (one after the other) to Fortran. In Fortran you also have a parent wrapper routine, which collects all Fortran arrays into a clean type object and outputs the result.

Just some thoughts.
aerosayan likes this.
__________________
Check out my side project:

A multiphysics discontinuous Galerkin framework: Youtube, Gitlab.
Eifoehn4 is offline   Reply With Quote

Old   December 20, 2020, 16:29
Default
  #3
Senior Member
 
Sayan Bhattacharjee
Join Date: Mar 2020
Posts: 495
Rep Power: 8
aerosayan is on a distinguished road
Quote:
Originally Posted by Eifoehn4 View Post
You may write a parent C-wrapper routine with a clean struct object, which simply loops over all arrays in the C-struct. Here you can use your code to send each C-array (one after the other) to Fortran. In Fortran you also have a parent wrapper routine, which collects all Fortran arrays into a clean type object and outputs the result.

Just some thoughts.

Hi Eifoehn4,
Thanks for the help.
Do you have any working example code for the technique you mentioned?
I never heard of such a technique, so I don't know how to implement it.


Thanks
aerosayan is offline   Reply With Quote

Old   December 20, 2020, 16:43
Default
  #4
Senior Member
 
Eifoehn4's Avatar
 
-
Join Date: Jul 2012
Location: Germany
Posts: 184
Rep Power: 14
Eifoehn4 is on a distinguished road
No not really. I don't think, there is an easy (intrinsic) workaround for this. However, i am quite sure it is possible to write a clean wrapper which does exactly what you want.

Regards
aerosayan likes this.
__________________
Check out my side project:

A multiphysics discontinuous Galerkin framework: Youtube, Gitlab.
Eifoehn4 is offline   Reply With Quote

Old   December 20, 2020, 16:50
Default
  #5
Senior Member
 
Sayan Bhattacharjee
Join Date: Mar 2020
Posts: 495
Rep Power: 8
aerosayan is on a distinguished road
Quote:
Originally Posted by Eifoehn4 View Post
No not really. I don't think, there is an easy (intrinsic) workaround for this. However, i am quite sure it is possible to write a clean wrapper which does exactly what you want.

Regards

If I understood you correctly, you meant to say that we could create a C function/subroutine that when called, will return the array/matrix that we want to access.


For example: If there are three matrices called flux, U, residual, we could write three unique wrapper functions called get_flux, get_U, get_residual to access those particular matrices from anywhere in our code.


Or if we don't want to make separate functions for separate matrices, we could create a single function like get_matrix(index_of_matrix), where we would access the particular matrix we want from an array in C, using the int index_of_matrix.



Is that right?


Regards
aerosayan is offline   Reply With Quote

Old   December 21, 2020, 08:39
Default
  #6
Senior Member
 
Sayan Bhattacharjee
Join Date: Mar 2020
Posts: 495
Rep Power: 8
aerosayan is on a distinguished road
Quote:
Originally Posted by Eifoehn4 View Post
You may write a parent C-wrapper routine with a clean struct object, which simply loops over all arrays in the C-struct. Here you can use your code to send each C-array (one after the other) to Fortran. In Fortran you also have a parent wrapper routine, which collects all Fortran arrays into a clean type object and outputs the result.

Just some thoughts.

Hey E,


I implemented your idea. Man, was it difficult!
Thanks for the idea. I was able to get the pointers from C to Fortran, and store them inside a derived type. I also verified that the pointers are pointing to the correct data.


PROBLEM SOLVED!!!

To show gratitude for your help, let me share one cat picture and a meme as a reward. lol


Thanks
~Sayan



Code is in example interop-6-1 : https://github.com/aerosayan/fortran-c-interop/


In case the link goes down, here are the code.


main.c
Code:
// AIM - Show how to use memory allocated in c, for fortran allocatable arrays
//       Show how to package these allocatable arrays inside derived types.

#include <stdio.h>
#include <stdlib.h>

// We're using global variables to make the demostration simpler
int* array;
int* matrix;

// Call from fortran to get the array
int* get_array_from_c(){return array;}
// Call from fortran to get the matrix
int* get_matrix_from_c(){return matrix;}

// Call fortran subroutine and send our data
//
// If you notice carefully, we don't need to send the array and matrix
// by this method. We can define functions like get_array_from_c and
// get_matrix_from_c to get whatever data we need from c.
//
// If you notice more carefully, we don't need to define a separate function
// for separate arrays/matrices/tensors. We can store the array pointers in
// a long array, and use an integer index to access them.
//
// For example : flux_array = get_array(FLUX_ARRAY_INDEX);
//
// Where, FLUX_ARRAY_INDEX is an integer index,
// and get_array will be defined as get_array(int index){return data[index];}
//
void call_fortran_(int* array, int* matrix, int n);

int main()
{
    // No. of elements in the array
    int n = 5;

    // Dynamic array
    array = (int*)malloc(n*sizeof(int));
    // Dynamic matrix
    matrix = (int*)malloc(n*n*sizeof(int));

    // Insert elements into dynamic array
    for(int i=0; i<n; ++i)
    {
        array[i] = i;
    }
    // Inser elements into dynamic matrix
    for(int i=0; i<n*n; ++i)
    {
        matrix[i] = i;
    }

    // Send array, matrix to fortran
    call_fortran_(array, matrix, n);

    // Scrub clean
    free(array);
    free(matrix);

    return 0;
}
send.F90
Code:
! We will package and encapsulate our data types inside a derived data type
! so that it can be sent to other fortran functions/subroutines along with
! the object of the derived type, and without needing to type many arguments
! for each function/subroutine.
!
module my_types
type data_container
    ! c_f_pointer expects a pointer to the arrays/matrices,
    ! so we have to define them as pointers.
    !
    integer(4), pointer, dimension(:)   :: encapsulated_arr
    integer(4), pointer, dimension(:,:) :: encapsulated_mat
end type
end module

subroutine call_fortran(arr, mat, n)
    use iso_c_binding, only : c_int, c_ptr, c_f_pointer
    use my_types
    implicit none

    ! Interface to our c function that returns int* array
    interface
    function get_array_from_c() bind(c, name="get_array_from_c")
        import :: c_ptr
        type(c_ptr) :: get_array_from_c
    end function
    end interface

    ! Interface to our c function that returns int* matrix
    interface
    function get_matrix_from_c() bind(c, name="get_matrix_from_c")
        import :: c_ptr
        type(c_ptr) :: get_matrix_from_c
    end function
    end interface

    ! No. of elements in the array and no. of rows,columns in the matrix
    integer(c_int), value :: n
    ! Defining the array and matrix
    ! We will print the values, then call c_f_pointer to get the array & matrix
    ! from c, and then print them to verify the transfer. We will then modify
    ! the array and matrix received from c_f_pointer, then see if we the
    ! original array and matrix were modified.
    !
    integer(c_int), intent(in) :: arr(n)
    integer(c_int), intent(in) :: mat(n,n)

    ! Derived data type to encapsulate our data
    type(data_container) :: dc

    print *, "- FORTRAN SUBROUTINE CALLED..."
    print *, "  '- SUBROUTINE :: CALL_FORTRAN(ARR, MAT, N)"
    print *, "  '- N        : ", n
    print *, "  '- ARR      : ", arr
    print *, "  '- MAT(:,1) : ", mat(:,1)
    print *, "  '- MAT(:,N) : ", mat(:,N)

    print *, "- ENCAPSULATING DATA INSIDE DERIVED TYPE..."
    ! Use c_f_pointer to call get_array_from_c and store it inside
    ! the pointer dc%encapsulated_arr which has is a single dimension
    ! array of length n.
    !
    call c_f_pointer(get_array_from_c(), dc%encapsulated_arr, [n])
    ! Do the same for the matrix
    call c_f_pointer(get_matrix_from_c(), dc%encapsulated_mat, [n,n])

    ! Print results to verify...
    print *, "  '- N        : ", n
    print *, "  '- EARR     : ", dc%encapsulated_arr
    print *, "  '- EMAT(:,1): ", dc%encapsulated_mat(:,1)
    print *, "  '- EMAT(:,N): ", dc%encapsulated_mat(:,N)

    ! Modifying the arrays through the derived type
    print *, "- ADDING ONE TO EARR, EMAT THROUGH THE DERIVED TYPE..."
    print *, "  '- OPERATION :: dc%earr(:)   = dc%earr+1"
    dc%encapsulated_arr(:) = dc%encapsulated_arr+1

    print *, "  '- OPERATION :: dc%emat(:,:) = dc%emat+1"
    dc%encapsulated_mat(:,:) = dc%encapsulated_mat+1

    print *, "- VERIFYING IF ORIGINAL ARR, MAT WERE MODIFIED..."
    print *, "  '- ARR      : ", arr
    print *, "  '- MAT(:,1) : ", mat(:,1)
    print *, "  '- MAT(:,N) : ", mat(:,N)

    print *, "- FORTRAN SUBROUTINE FINISHED..."
end subroutine
output.txt
Code:
 - FORTRAN SUBROUTINE CALLED...
   '- SUBROUTINE :: CALL_FORTRAN(ARR, MAT, N)
   '- N        :            5
   '- ARR      :            0           1           2           3           4
   '- MAT(:,1) :            0           1           2           3           4
   '- MAT(:,N) :           20          21          22          23          24
 - ENCAPSULATING DATA INSIDE DERIVED TYPE...
   '- N        :            5
   '- EARR     :            0           1           2           3           4
   '- EMAT(:,1):            0           1           2           3           4
   '- EMAT(:,N):           20          21          22          23          24
 - ADDING ONE TO EARR, EMAT THROUGH THE DERIVED TYPE...
   '- OPERATION :: dc%earr(:)   = dc%earr+1
   '- OPERATION :: dc%emat(:,:) = dc%emat+1
 - VERIFYING IF ORIGINAL ARR, MAT WERE MODIFIED...
   '- ARR      :            1           2           3           4           5
   '- MAT(:,1) :            1           2           3           4           5
   '- MAT(:,N) :           21          22          23          24          25
 - FORTRAN SUBROUTINE FINISHED...
Attached Images
File Type: png malloc.png (126.6 KB, 9 views)
File Type: jpg kot.jpg (34.0 KB, 5 views)
Eifoehn4 likes this.
aerosayan is offline   Reply With Quote

Old   December 21, 2020, 16:13
Default
  #7
Senior Member
 
Sayan Bhattacharjee
Join Date: Mar 2020
Posts: 495
Rep Power: 8
aerosayan is on a distinguished road
Quote:
Originally Posted by Eifoehn4 View Post
You may write a parent C-wrapper routine with a clean struct object, which simply loops over all arrays in the C-struct. Here you can use your code to send each C-array (one after the other) to Fortran. In Fortran you also have a parent wrapper routine, which collects all Fortran arrays into a clean type object and outputs the result.

Just some thoughts.

It's me again.

I found an infinitely better solution. Look at interop-5-2.

I can now send the arrays by packaging them directly inside the struct by defining them as type(c_ptr) and use them in fortran by using c_f_pointer. Now, I don't need any of that pesky function calls to get array pointers from C that I showed in interop-6-1. Now, we can pack everything inside the struct, and send that in to fortran. No need for function calls with 70 arguments.

Thanks
~Sayan


main.c

Code:
// AIM - Show how to pass a C struct to fortran.
//       Show how to pass a dynamic integer array from C to fortran using c struct.


#include <stdio.h>
#include <stdlib.h>

struct blah
{
    int  n;
    int* array;
};

void send_cstruct_to_fortran_(struct blah* s);

int main()
{
    // No. of elements in the array
    int n = 5;

    // Dynamic array
    int* array = (int*)malloc(n*sizeof(int));

    // Insert elements into dynamic array
    for(int i=0; i<n; ++i)
    {
        array[i] = i;
    }

    // Create object of struct
    struct blah s;

    // Store the first array length into struct
    s.n = n;
    s.array = array;

    // Send c struct to fortran, along with packaged data
    send_cstruct_to_fortran_(&s);

    // Scrub clean
    free(array);

    return 0;
}
send.F90
Code:
subroutine send_cstruct_to_fortran(cstruct)
    use iso_c_binding, only : c_int, c_ptr, c_f_pointer
    implicit none

    ! Fortran doesn't know how the c struct is defined, So, we use bind(c) to
    ! define a user defined type to match the c struct.
    !
    type, bind(c) :: cstruct_interface
        integer(c_int) :: n
        type(c_ptr)    :: array
    end type

    ! The struct that has been passed from c to fortran
    type(cstruct_interface), intent(in) :: cstruct

    ! We want to access cstruct%array.
    ! Thus we define array_local as a pointer to an array.
    ! We then use c_f_pointer to cast the c pointer cstruct%array to a
    ! fortran compatible pointer array_local. We can then use array_local
    !
    integer(4), pointer, dimension(:) :: array_local

    call c_f_pointer(cstruct%array, array_local, [cstruct%n])

    print *, "- FORTRAN SUBROUTINE CALLED..."
    print *, "  '- SUBROUTINE :: SEND_CSTRUCT_TO_FORTRAN(CSTRUCT)"
    print *, "  '- CSTRUCT%N    : ", cstruct%n
    print *, "  '- ARRAY_LOCAL  : ", array_local
    print *, "- FORTRAN SUBROUTINE FINISHED..."

end subroutine
output.txt
Code:
 - FORTRAN SUBROUTINE CALLED...
   '- SUBROUTINE :: SEND_CSTRUCT_TO_FORTRAN(CSTRUCT)
   '- CSTRUCT%N    :            5
   '- ARRAY_LOCAL  :            0           1           2           3           4
 - FORTRAN SUBROUTINE FINISHED...
Eifoehn4 likes this.
aerosayan is offline   Reply With Quote

Reply

Tags
fortran, fortran-c/c++


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are Off
Pingbacks are On
Refbacks are On


Similar Threads
Thread Thread Starter Forum Replies Last Post
Fortran Compiler-CFX12.1 Araz CFX 13 March 27, 2017 06:37
CFD Code Conversion from Intel Fortran to GNU Fortran pitto Main CFD Forum 4 August 4, 2016 15:51
CFX11 + Fortran compiler ? Mohan CFX 20 March 30, 2011 19:56
fortran derived type assignment Paolo Lampitella Main CFD Forum 1 September 12, 2008 05:53
Help: Reading values in derived types for VFortran Wee Main CFD Forum 0 October 15, 2006 10:39


All times are GMT -4. The time now is 13:08.