|
[Sponsors] |
December 20, 2020, 13:50 |
fortran-c interoperable derived types
|
#1 |
Senior Member
Sayan Bhattacharjee
Join Date: Mar 2020
Posts: 495
Rep Power: 8 |
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; } 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 Code:
- FORTRAN SUBROUTINE CALLED... '- SUBROUTINE :: SEND_CSTRUCT_TO_FORTRAN(CSTRUCT) '- CSTRUCT%N1 : 5 '- ARRAY : 0 1 2 3 4 - FORTRAN SUBROUTINE FINISHED... 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 |
|
December 20, 2020, 15:51 |
|
#2 |
Senior Member
-
Join Date: Jul 2012
Location: Germany
Posts: 184
Rep Power: 14 |
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. |
|
December 20, 2020, 16:29 |
|
#3 | |
Senior Member
Sayan Bhattacharjee
Join Date: Mar 2020
Posts: 495
Rep Power: 8 |
Quote:
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 |
||
December 20, 2020, 16:43 |
|
#4 |
Senior Member
-
Join Date: Jul 2012
Location: Germany
Posts: 184
Rep Power: 14 |
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 |
|
December 20, 2020, 16:50 |
|
#5 | |
Senior Member
Sayan Bhattacharjee
Join Date: Mar 2020
Posts: 495
Rep Power: 8 |
Quote:
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 |
||
December 21, 2020, 08:39 |
|
#6 | |
Senior Member
Sayan Bhattacharjee
Join Date: Mar 2020
Posts: 495
Rep Power: 8 |
Quote:
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; } 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 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... |
||
December 21, 2020, 16:13 |
|
#7 | |
Senior Member
Sayan Bhattacharjee
Join Date: Mar 2020
Posts: 495
Rep Power: 8 |
Quote:
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; } 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 Code:
- FORTRAN SUBROUTINE CALLED... '- SUBROUTINE :: SEND_CSTRUCT_TO_FORTRAN(CSTRUCT) '- CSTRUCT%N : 5 '- ARRAY_LOCAL : 0 1 2 3 4 - FORTRAN SUBROUTINE FINISHED... |
||
Tags |
fortran, fortran-c/c++ |
|
|
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 |