symengine / symengine.f90

Fortran wrappers of SymEngine
10 stars 6 forks source link

Using Shroud to generate the interface automatically #3

Open ivan-pi opened 4 years ago

ivan-pi commented 4 years ago

Looking at the C header file in SymEngine, one could probably do everything manually. On the long term however, an automatic tool will probably be easier.

Would using Shroud (https://github.com/LLNL/shroud) be an option?

A presentation (PowerPoint file) is also available from FortranCon: https://tcevents.chem.uzh.ch/event/12/contributions/30/attachments/29/98/Taylor-Shroud-forcon.pptx

certik commented 4 years ago

Yes, down the road we should generate it automatically to ensure the interface is consistent with any (future) possible changes to the C API.

ivan-pi commented 4 years ago

Out of curiosity I pulled together the interface routines needed to run the following program:

module small_test

use symengine_cwrapper
use iso_c_binding

implicit none

contains

  function c_char_ptr_to_fstring(c_char_ptr) result(fc)
    type(c_ptr) :: c_char_ptr
    character(len=:,kind=c_char), allocatable :: fc
    character(len=1000,kind=c_char), pointer :: f_string

    if (.not. c_associated(c_char_ptr)) then
        fc = ""
    else
        call c_f_pointer(c_char_ptr,f_string)
        fc = f_string(1:index(f_string,c_null_char))
    end if
  end function

subroutine f(i)

  integer(c_long), intent(in) :: i

  type(c_ptr) :: s = c_null_ptr ! string

  type(c_ptr) :: x = c_null_ptr
  type(c_ptr) :: y = c_null_ptr
  type(c_ptr) :: e = c_null_ptr
  type(c_ptr) :: n = c_null_ptr

  type(c_ptr) :: exception = c_null_ptr

  print *, "Symengine version: "//c_char_ptr_to_fstring(symengine_version())

  call basic_new_stack(x)
  call basic_new_stack(y)
  call basic_new_stack(e)
  call basic_new_stack(n)

  exception = symbol_set(x,"x"//c_null_char)
  exception = symbol_set(y,"y"//c_null_char)

  exception = integer_set_si(n, i);
  exception = basic_mul(e, n, x);
  exception = basic_add(e, e, y);

  s = basic_str(e)
  print *, "Result: ", c_char_ptr_to_fstring(s)
  call basic_str_free(s)
  s = c_null_ptr

  print *, c_associated(s), c_char_ptr_to_fstring(s)

  call basic_free_stack(x)
  call basic_free_stack(y)
  call basic_free_stack(e)
  call basic_free_stack(n)

end subroutine

end module

program small_test_program

  use small_test
  use iso_c_binding
  implicit none

  call f(5_c_long)

end program

(Note: the symengine_wrapper module is not included because I have some mistakes left).

After figuring out all the libraries needed to install symengine, I was able to compile the example using:

gfortran -Wall -c symengine_cwrapper.f90
gfortran -Wall -c test_symengine.f90
gfortran -o test_symengine -I/usr/local/include/symengine symengine_cwrapper.o test_symengine.o -L/usr/local/lib -lsymengine -lteuchos -lstdc++ -lmpfr -lgmp -lbfd

And finally, the result of the program:

$ ./test_symengine
 Symengine version: 0.6.0
 Result: 5*x + y
 F
certik commented 4 years ago

Thanks! The Fortran part of the API must be improved, but it works.

On Mon, Sep 28, 2020, at 7:52 PM, Ivan wrote:

Out of curiosity I pulled together the interface routines needed to run the following program:

module small_test

use symengine_cwrapper use iso_c_binding

implicit none

contains

function c_char_ptr_to_fstring(c_char_ptr) result(fc) type(c_ptr) :: c_char_ptr character(len=:,kind=c_char), allocatable :: fc character(len=1000,kind=c_char), pointer :: f_string

if (.not. c_associated(c_char_ptr)) then
    fc = ""
else
    call c_f_pointer(c_char_ptr,f_string)
    fc = f_string(1:index(f_string,c_null_char))
end if

end function

subroutine f(i)

integer(c_long), intent(in) :: i

type(c_ptr) :: s = c_null_ptr ! string

type(c_ptr) :: x = c_null_ptr type(c_ptr) :: y = c_null_ptr type(c_ptr) :: e = c_null_ptr type(c_ptr) :: n = c_null_ptr

type(c_ptr) :: exception = c_null_ptr

print *, "Symengine version: "//c_char_ptr_to_fstring(symengine_version())

call basic_new_stack(x) call basic_new_stack(y) call basic_new_stack(e) call basic_new_stack(n)

exception = symbol_set(x,"x"//c_null_char) exception = symbol_set(y,"y"//c_null_char)

exception = integer_set_si(n, i); exception = basic_mul(e, n, x); exception = basic_add(e, e, y);

s = basic_str(e) print *, "Result: ", c_char_ptr_to_fstring(s) call basic_str_free(s) s = c_null_ptr

print *, c_associated(s), c_char_ptr_to_fstring(s)

call basic_free_stack(x) call basic_free_stack(y) call basic_free_stack(e) call basic_free_stack(n)

end subroutine

end module

program small_test_program

use small_test use iso_c_binding implicit none

call f(5_c_long)

end program (Note: the symengine_wrapper module is not included because I have some mistakes left).

After figuring out all the libraries needed to install symengine, I was able to compile the example using:

gfortran -Wall -c symengine_cwrapper.f90 gfortran -Wall -c test_symengine.f90 gfortran -o test_symengine -I/usr/local/include/symengine symengine_cwrapper.o test_symengine.o -L/usr/local/lib -lsymengine -lteuchos -lstdc++ -lmpfr -lgmp -lbfd And finally, the result of the program:

$ ./test_symengine Symengine version: 0.6.0 Result: 5*x + y F — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/symengine/symengine.f90/issues/3#issuecomment-700377033, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAFAWCXC4SPA7OOFS3KBBLSIE4WRANCNFSM4R4XAFYQ.

ivan-pi commented 4 years ago

Could you point me to the files containing the C++ API in the original symengine repository?

Typically, the high-level Fortran API can be made very similar to the C++ one.

certik commented 4 years ago

Yes, the main C++ API is in this directory in the header files:

https://github.com/symengine/symengine/tree/master/symengine

For example the Mul class is here:

https://github.com/symengine/symengine/blob/9fc2716cab4a6d2d89a3f9d765a04ef1594c6bcf/symengine/mul.h

The best way to understand how to use it is from tests, e.g., here:

https://github.com/symengine/symengine/blob/9fc2716cab4a6d2d89a3f9d765a04ef1594c6bcf/symengine/tests/basic/test_arit.cpp

This is the main API used throughout SymEngine. Then there is a simpler higher level API here:

https://github.com/symengine/symengine/blob/9fc2716cab4a6d2d89a3f9d765a04ef1594c6bcf/symengine/expression.h https://github.com/symengine/symengine/blob/9fc2716cab4a6d2d89a3f9d765a04ef1594c6bcf/symengine/tests/expression/test_expression.cpp

Which is just a thin wrapper to provide easier C++ interface, with a possible small performance hit.

For Fortran, I suggest we use the C interface, as it takes care of exceptions and other things, and we build whatever is the most natural in Fortran. If we can somehow overload operators in Fortran and not leak memory, that would be great.