From 6ad6406324af8ccdc9526a4bb322d531666a9aa7 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Thu, 27 Nov 2025 11:20:53 -0500 Subject: [PATCH] feat(fortran): Added fortran bindings Building of the C API GridFire can now be used from fotran using the gridfire_mod fortran module. This exposes the same, limited, set of funcitonality that the C API does. --- README.md | 216 +++++++++++++++ meson.build | 4 + src/extern/fortran/gridfire_mod.f90 | 258 ++++++++++++++++++ src/extern/fortran/meson.build | 11 + .../include/gridfire/extern/gridfire_extern.h | 2 +- src/extern/lib/gridfire_extern.cpp | 3 +- src/extern/meson.build | 2 + tests/extern/C/gridfire_evolve.c | 11 +- tests/extern/fortran/gridfire_evolve.f90 | 89 ++++++ tests/extern/fortran/meson.build | 5 + tests/extern/meson.build | 3 +- 11 files changed, 599 insertions(+), 5 deletions(-) create mode 100644 src/extern/fortran/gridfire_mod.f90 create mode 100644 src/extern/fortran/meson.build create mode 100644 tests/extern/fortran/gridfire_evolve.f90 create mode 100644 tests/extern/fortran/meson.build diff --git a/README.md b/README.md index 7cc99311..93112da2 100644 --- a/README.md +++ b/README.md @@ -896,6 +896,220 @@ if __name__ == "__main__": ``` +# External Usage +C++ does not have a stable ABI nor does it make any strong guarantees about stl container layouts between compiler versions. +Therefore, GridFire includes a set of stable C bindings which can be used to interface with a limited subset of GridFire functionality +from other languages. + +> **Note:** These bindings are not intended to allow GridFire to be extended from other languages; rather, they are intended to allow GridFire to be used as a +> black-box library from other languages. + +> **Note:** One assumption for external usage is that the ordering of the species list will not change. That is to say that whatever order the array +> used to register the species is will be assumed to always be the order used when passing abundance arrays to and from GridFire. + +> **Note:** Because the C API does not pass the general Composition object a `mass_lost` +> output parameter has been added to the evolve calls, this tracks the total mass in species which have not been registered with the C API GridFire by the caller +## C API Overview +In general when using the C API the workflow is to + +1. create a `gf_context` pointer. This object holds the state of GridFire so that it does not need to be re-initialized for each call. +2. call initialization routines on the context to set up the engine and solver you wish to use. +3. call the `gf_evolve` function to evolve a network over some time. +4. At each state check the ret code of the function to ensure that no errors occurred. Valid ret-codes are 0 and 1. All other ret codes indicate an error. +5. Finally, call `gf_free` to free the context and all associated memory. + +### C Example + +```c++ +#include "gridfire/extern/gridfire_extern.h" +#include + +#define NUM_SPECIES 8 + +// Define a macro to check return codes +#define GF_CHECK_RET_CODE(ret, ctx, msg) \ + if (ret != 0 && ret != 1) { \ + printf("Error %s: %s\n", msg, gf_get_last_error_message(ctx)); \ + gf_free(ctx); \ + return ret; \ + } + +int main() { + void* gf_context = gf_init(); + + const char* species_names[NUM_SPECIES]; + species_names[0] = "H-1"; + species_names[1] = "He-3"; + species_names[2] = "He-4"; + species_names[3] = "C-12"; + species_names[4] = "N-14"; + species_names[5] = "O-16"; + species_names[6] = "Ne-20"; + species_names[7] = "Mg-24"; + const double abundances[NUM_SPECIES] = {0.702616602672027, 9.74791583949078e-06, 0.06895512307276903, 0.00025, 7.855418029399437e-05, 0.0006014411598306529, 8.103062886768109e-05, 2.151340851063217e-05}; + + int ret = gf_register_species(gf_context, NUM_SPECIES, species_names); + GF_CHECK_RET_CODE(ret, gf_context, "Species Registration"); + + ret = gf_construct_engine_from_policy(gf_context, "MAIN_SEQUENCE_POLICY", abundances, NUM_SPECIES); + GF_CHECK_RET_CODE(ret, gf_context, "Policy and Engine Construction"); + + ret = gf_construct_solver_from_engine(gf_context, "CVODE"); + GF_CHECK_RET_CODE(ret, gf_context, "Solver Construction"); + + // When using the C API it is assumed that the caller will ensure that the output arrays are large enough to hold the results. + double Y_out[NUM_SPECIES]; + double energy_out; + double dEps_dT; + double dEps_dRho; + double mass_lost; + + ret = gf_evolve( + gf_context, + abundances, + NUM_SPECIES, + 1.5e7, // Temperature in K + 1.5e2, // Density in g/cm^3 + 3e17, // Time step in seconds + 1e-12, // Initial time step in seconds + Y_out, + &energy_out, + &dEps_dT, + &dEps_dRho, &mass_lost + ); + + GF_CHECK_RET_CODE(ret, gf_context, "Evolution"); + + + printf("Evolved abundances:\n"); + for (size_t i = 0; i < NUM_SPECIES; i++) { + printf("Species %s: %e\n", species_names[i], Y_out[i]); + } + printf("Energy output: %e\n", energy_out); + printf("dEps/dT: %e\n", dEps_dT); + printf("dEps/dRho: %e\n", dEps_dRho); + printf("Mass lost: %e\n", mass_lost); + + gf_free(gf_context); + + return 0; +} + +``` + +## Fortran API Overview + +GridFire makes use of the stable C API and Fortran 2003's `iso_c_bindings` to provide a Fortran interface for legacy +code. The fortran interface is designed to be very similar to the C API and exposes the same functionality. + +1. `GridFire%gff_init`: Initializes a GridFire context and returns a handle to it. +2. `GridFire%register_species`: Registers species with the GridFire context. +3. `GridFire%setup_policy`: Configures the engine using a specified policy and initial abundances. +4. `GridFire%setup_solver`: Sets up the solver for the engine. +5. `GridFire%evolve`: Evolves the network over a specified time step. +6. `GridFire%get_last_error`: Retrieves the last error message from the GridFire context. +7. `GridFire%gff_free`: Frees the GridFire context and associated resources. + +> **Note:** You must instantiate a `GridFire` type object to access these methods. + +> **Note:** free and init have had the `gff_` prefix (GridFire Fortran) to avoid name clashes with common Fortran functions. + +When building GridFire a fortran module file `gridfire_mod.mod` is generated which contains all the necessary +bindings to use GridFire from Fortran. You must also link your code against the C API library `libgridfire_extern`. + +### Fortran Example + +```fortran +program main + use iso_c_binding + use gridfire_mod + implicit none + + type(GridFire) :: net + integer(c_int) :: ierr + integer :: i + + ! --- 1. Define Species and Initial Conditions --- + ! Note: String lengths must match or exceed the longest name. + ! We pad with spaces, which 'trim' handles inside the module. + character(len=5), dimension(8) :: species_names = [ & + "H-1 ", & + "He-3 ", & + "He-4 ", & + "C-12 ", & + "N-14 ", & + "O-16 ", & + "Ne-20", & + "Mg-24" & + ] + + ! Initial Mass Fractions (converted to Molar Abundances Y = X/A) + ! Standard solar-ish composition + real(c_double), dimension(8) :: Y_in = [ & + 0.702616602672027, & + 9.74791583949078e-06, & + 0.06895512307276903, & + 0.00025, & + 7.855418029399437e-05, & + 0.0006014411598306529, & + 8.103062886768109e-05, & + 2.151340851063217e-05 & + ] + + ! Output buffers + real(c_double), dimension(8) :: Y_out + real(c_double) :: energy_out, dedt, dedrho, dmass + + ! Thermodynamic Conditions (Solar Core-ish) + real(c_double) :: T = 1.5e7 ! 15 Million K + real(c_double) :: rho = 150.0e0 ! 150 g/cm^3 + real(c_double) :: dt = 3.1536e17 ! ~10 Gyr timestep + + ! --- 2. Initialize GridFire --- + print *, "Initializing GridFire..." + call net%gff_init() + + ! --- 3. Register Species --- + print *, "Registering species..." + call net%register_species(species_names) + + ! --- 4. Configure Engine & Solver --- + print *, "Setting up Main Sequence Policy..." + call net%setup_policy("MAIN_SEQUENCE_POLICY", Y_in) + + print *, "Setting up CVODE Solver..." + call net%setup_solver("CVODE") + + ! --- 5. Evolve --- + print *, "Evolving system (dt =", dt, "s)..." + call net%evolve(Y_in, T, rho, dt, Y_out, energy_out, dedt, dedrho, dmass, ierr) + + if (ierr /= 0) then + print *, "Evolution Failed with error code: ", ierr + print *, "Error Message: ", net%get_last_error() + call net%gff_free() ! Always cleanup + stop + end if + + ! --- 6. Report Results --- + print *, "" + print *, "--- Results ---" + print '(A, ES12.5, A)', "Energy Generation: ", energy_out, " erg/g/s" + print '(A, ES12.5)', "dEps/dT: ", dedt + print '(A, ES12.5)', "Mass Change: ", dmass + + print *, "" + print *, "Abundances:" + do i = 1, size(species_names) + print '(A, " : ", ES12.5, " -> ", ES12.5)', & + trim(species_names(i)), Y_in(i), Y_out(i) + end do + + ! --- 7. Cleanup --- + call net%gff_free() + +end program main +``` # Related Projects @@ -910,3 +1124,5 @@ GridFire integrates with and builds upon several key 4D-STAR libraries: utilities. - [liblogging](https://github.com/4D-STAR/liblogging): Flexible logging framework. - [libconstants](https://github.com/4D-STAR/libconstants): Physical constants +- [libplugin](https://github.com/4D-STAR/libplugin): Dynamically loadable plugin + framework. diff --git a/meson.build b/meson.build index 70ab4a64..ed1928d8 100644 --- a/meson.build +++ b/meson.build @@ -24,6 +24,10 @@ project('GridFire', ['c', 'cpp', 'fortran'], version: 'v0.7.0_rc1', default_opti add_project_arguments('-fvisibility=default', language: 'cpp') message('Found CXX compiler: ' + meson.get_compiler('cpp').get_id()) +message('Found FORTRAN compiler: ' + meson.get_compiler('fortran').get_id()) +message('C++ standard set to: ' + get_option('cpp_std')) +message('Fortran standard set to: ' + get_option('fortran_std')) + if meson.get_compiler('cpp').get_id() == 'clang' # We disable these because of CppAD diff --git a/src/extern/fortran/gridfire_mod.f90 b/src/extern/fortran/gridfire_mod.f90 new file mode 100644 index 00000000..fa02f1ce --- /dev/null +++ b/src/extern/fortran/gridfire_mod.f90 @@ -0,0 +1,258 @@ +module gridfire_mod + use iso_c_binding + implicit none + + enum, bind (C) + enumerator :: FDSSE_NON_4DSTAR_ERROR = -102 + enumerator :: FDSSE_UNKNOWN_ERROR = -101 + + enumerator :: FDSSE_SUCCESS = 1 + enumerator :: FDSSE_UNKNOWN_SYMBOL_ERROR = 100 + + enumerator :: FDSSE_SPECIES_ERROR = 101 + enumerator :: FDSSE_INVALID_COMPOSITION_ERROR = 102 + enumerator :: FDSSE_COMPOSITION_ERROR = 103 + + enumerator :: GF_NON_GRIDFIRE_ERROR = -2 + enumerator :: GF_UNKNOWN_ERROR = -1 + enumerator :: GF_SUCCESS = 0 + + enumerator :: GF_INVALID_QSE_SOLUTION_ERROR = 5 + enumerator :: GF_FAILED_TO_PARTITION_ERROR = 6 + enumerator :: GF_NETWORK_RESIZED_ERROR = 7 + enumerator :: GF_UNABLE_TO_SET_NETWORK_REACTIONS_ERROR = 8 + enumerator :: GF_BAD_COLLECTION_ERROR = 9 + enumerator :: GF_BAD_RHS_ENIGNE_ERROR = 10 + enumerator :: GF_STALE_JACOBIAN_ERROR = 11 + enumerator :: GF_UNINITIALIZED_JACOBIAN_ERROR = 12 + enumerator :: GF_UNKNONWN_JACOBIAN_ERROR = 13 + enumerator :: GF_JACOBIAN_ERROR = 14 + enumerator :: GF_ENGINE_ERROR = 15 + + enumerator :: GF_MISSING_BASE_REACTION_ERROR = 16 + enumerator :: GF_MISSING_SEED_SPECIES_ERROR = 17 + enumerator :: GF_MISSING_KEY_REACTION_ERROR = 18 + enumerator :: GF_POLICY_ERROR = 19 + + enumerator :: GF_REACTION_PARSING_ERROR = 20 + enumerator :: GF_REACTOION_ERROR = 21 + + enumerator :: GF_SINGULAR_JACOBIAN_ERROR = 22 + enumerator :: GF_ILL_CONDITIONED_JACOBIAN_ERROR = 23 + enumerator :: GF_CVODE_SOLVER_FAILURE_ERROR = 24 + enumerator :: GF_KINSOL_SOLVER_FAILURE_ERROR = 25 + enumerator :: GF_SUNDIALS_ERROR = 26 + enumerator :: GF_SOLVER_ERROR = 27 + + enumerator :: GF_HASHING_ERROR = 28 + enumerator :: GF_UTILITY_ERROR = 29 + + enumerator :: GF_DEBUG_ERRROR = 30 + + enumerator :: GF_GRIDFIRE_ERROR = 31 + end enum + + interface + ! void* gf_init() + function gf_init() bind(C, name="gf_init") + import :: c_ptr + type(c_ptr) :: gf_init + end function gf_init + + ! void gf_free(void* gf) + subroutine gf_free(gf) bind(C, name="gf_free") + import :: c_ptr + type(c_ptr), value :: gf + end subroutine gf_free + + ! char* gf_get_last_error_message(void* ptr); + function gf_get_last_error_message(ptr) result(c_msg) bind(C, name="gf_get_last_error_message") + import + type(c_ptr), value :: ptr + type(c_ptr) :: c_msg + end function + + ! int gf_register_species(void* ptr, const int num_species, const char** species_names); + function gf_register_species(ptr, num_species, species_names) result(ierr) bind(C, name="gf_register_species") + import + type(c_ptr), value :: ptr + integer(c_int), value :: num_species + type(c_ptr), dimension(*), intent(in) :: species_names ! Array of C pointers + integer(c_int) :: ierr + end function + + ! int gf_construct_engine_from_policy(void* ptr, const char* policy_name, const double *abundances, size_t num_species); + function gf_construct_engine_from_policy(ptr, policy_name, abundances, num_species) result(ierr) & + bind(C, name="gf_construct_engine_from_policy") + import + type(c_ptr), value :: ptr + character(kind=c_char), dimension(*), intent(in) :: policy_name + real(c_double), dimension(*), intent(in) :: abundances + integer(c_size_t), value :: num_species + integer(c_int) :: ierr + end function + + ! int gf_construct_solver_from_engine(void* ptr, const char* solver_name); + function gf_construct_solver_from_engine(ptr, solver_name) result(ierr) & + bind(C, name="gf_construct_solver_from_engine") + import + type(c_ptr), value :: ptr + character(kind=c_char), dimension(*), intent(in) :: solver_name + integer(c_int) :: ierr + end function + + ! int gf_evolve(...) + function gf_evolve(ptr, Y_in, num_species, T, rho, dt, Y_out, energy_out, dEps_dT, dEps_dRho, mass_lost) result(ierr) & + bind(C, name="gf_evolve") + import + type(c_ptr), value :: ptr + real(c_double), dimension(*), intent(in) :: Y_in + integer(c_size_t), value :: num_species + real(c_double), value :: T, rho, dt + real(c_double), dimension(*), intent(out) :: Y_out + real(c_double), intent(out) :: energy_out, dEps_dT, dEps_dRho, mass_lost + integer(c_int) :: ierr + end function + end interface + + type :: GridFire + type(c_ptr) :: ctx = c_null_ptr + integer(c_size_t) :: num_species = 0 + contains + procedure :: gff_init + procedure :: gff_free + procedure :: register_species + procedure :: setup_policy + procedure :: setup_solver + procedure :: evolve + procedure :: get_last_error + end type GridFire + + contains + subroutine gff_init(self) + class(GridFire), intent(out) :: self + + self%ctx = gf_init() + end subroutine gff_init + + subroutine gff_free(self) + class(GridFire), intent(inout) :: self + + if (c_associated(self%ctx)) then + call gf_free(self%ctx) + self%ctx = c_null_ptr + end if + end subroutine gff_free + + function get_last_error(self) result(msg) + class(GridFire), intent(in) :: self + character(len=:), allocatable :: msg + type(c_ptr) :: c_msg_ptr + character(kind=c_char), pointer :: char_ptr(:) + integer :: i, len_str + + c_msg_ptr = gf_get_last_error_message(self%ctx) + if (.not. c_associated(c_msg_ptr)) then + msg = "GridFire: Unknown Error (Null Pointer returned)" + return + end if + + call c_f_pointer(c_msg_ptr, char_ptr, [1024]) + len_str = 0 + do i = 1, 1024 + if (char_ptr(i) == c_null_char) exit + len_str = len_str + 1 + end do + + msg = repeat(' ', len_str+10) + msg(1:10) = "GridFire: " + do i = 1, len_str + msg(i+10:i+10) = char_ptr(i) + end do + end function get_last_error + + subroutine register_species(self, species_list) + class(GridFire), intent(inout) :: self + character(len=*), dimension(:), intent(in) :: species_list + + type(c_ptr), allocatable, dimension(:) :: c_ptrs + character(kind=c_char, len=:), allocatable, target :: temp_strs(:) + integer :: i, n, ierr + + print *, "Registering ", size(species_list), " species." + n = size(species_list) + self%num_species = int(n, c_size_t) + + allocate(c_ptrs(n)) + allocate(character(len=len(species_list(1))+1) :: temp_strs(n)) ! +1 for null terminator + + do i = 1, n + temp_strs(i) = trim(species_list(i)) // c_null_char + c_ptrs(i) = c_loc(temp_strs(i)) + end do + + print *, "Calling gf_register_species..." + ierr = gf_register_species(self%ctx, int(n, c_int), c_ptrs) + print *, "gf_register_species returned with code: ", ierr + + if (ierr /= GF_SUCCESS .AND. ierr /= FDSSE_SUCCESS) then + print *, "GridFire: ", self%get_last_error() + error stop + end if + end subroutine register_species + + subroutine setup_policy(self, policy_name, abundances) + class(GridFire), intent(in) :: self + character(len=*), intent(in) :: policy_name + real(c_double), dimension(:), intent(in) :: abundances + integer(c_int) :: ierr + + if (size(abundances) /= self%num_species) then + print *, "GridFire Error: Abundance array size mismatch." + error stop + end if + + ierr = gf_construct_engine_from_policy(self%ctx, & + trim(policy_name) // c_null_char, & + abundances, & + self%num_species) + + if (ierr /= GF_SUCCESS .AND. ierr /= FDSSE_SUCCESS) then + print *, "GridFire Policy Error: ", self%get_last_error() + error stop + end if + end subroutine setup_policy + + subroutine setup_solver(self, solver_name) + class(GridFire), intent(in) :: self + character(len=*), intent(in) :: solver_name + integer(c_int) :: ierr + + ierr = gf_construct_solver_from_engine(self%ctx, trim(solver_name) // c_null_char) + if (ierr /= GF_SUCCESS .AND. ierr /= FDSSE_SUCCESS) then + print *, "GridFire Solver Error: ", self%get_last_error() + error stop + end if + end subroutine setup_solver + + subroutine evolve(self, Y_in, T, rho, dt, Y_out, energy, dedt, dedrho, mass_lost, ierr) + class(GridFire), intent(in) :: self + real(c_double), dimension(:), intent(in) :: Y_in + real(c_double), value :: T, rho, dt + real(c_double), dimension(:), intent(out) :: Y_out + real(c_double), intent(out) :: energy, dedt, dedrho, mass_lost + integer, intent(out) :: ierr + integer(c_int) :: c_ierr + + c_ierr = gf_evolve(self%ctx, & + Y_in, self%num_species, & + T, rho, dt, & + Y_out, & + energy, dedt, dedrho, mass_lost) + + ierr = int(c_ierr) + if (ierr /= GF_SUCCESS .AND. ierr /= FDSSE_SUCCESS) then + print *, "GridFire Evolve Error: ", self%get_last_error() + end if + end subroutine evolve +end module gridfire_mod \ No newline at end of file diff --git a/src/extern/fortran/meson.build b/src/extern/fortran/meson.build new file mode 100644 index 00000000..5ff8d233 --- /dev/null +++ b/src/extern/fortran/meson.build @@ -0,0 +1,11 @@ +gridfire_fortran_lib = library('gridfire_fortran', + 'gridfire_mod.f90', + link_with: libgridfire_extern, + install: true, + install_dir: get_option('libdir') +) + +gridfire_fortran_dep = declare_dependency( + link_with: [gridfire_fortran_lib, libgridfire_extern], + include_directories: include_directories('.') +) \ No newline at end of file diff --git a/src/extern/include/gridfire/extern/gridfire_extern.h b/src/extern/include/gridfire/extern/gridfire_extern.h index 5c535fc8..7ae2edb5 100644 --- a/src/extern/include/gridfire/extern/gridfire_extern.h +++ b/src/extern/include/gridfire/extern/gridfire_extern.h @@ -62,7 +62,7 @@ extern "C" { void* gf_init(); - int gf_free(void* ctx); + void gf_free(void* ctx); int gf_register_species(void* ptr, const int num_species, const char** species_names); diff --git a/src/extern/lib/gridfire_extern.cpp b/src/extern/lib/gridfire_extern.cpp index b51a7edb..a8c7fe7d 100644 --- a/src/extern/lib/gridfire_extern.cpp +++ b/src/extern/lib/gridfire_extern.cpp @@ -8,9 +8,8 @@ extern "C" { return new GridFireContext(); } - int gf_free(void* ctx) { + void gf_free(void* ctx) { delete static_cast(ctx); - return 0; } int gf_register_species(void* ptr, const int num_species, const char** species_names) { diff --git a/src/extern/meson.build b/src/extern/meson.build index ba77e65a..49e3739a 100644 --- a/src/extern/meson.build +++ b/src/extern/meson.build @@ -22,3 +22,5 @@ gridfire_extern_dep = declare_dependency( ) install_subdir('include/gridfire', install_dir: get_option('includedir')) + +subdir('fortran') diff --git a/tests/extern/C/gridfire_evolve.c b/tests/extern/C/gridfire_evolve.c index c171e7f2..9055f601 100644 --- a/tests/extern/C/gridfire_evolve.c +++ b/tests/extern/C/gridfire_evolve.c @@ -22,7 +22,16 @@ int main() { species_names[5] = "O-16"; species_names[6] = "Ne-20"; species_names[7] = "Mg-24"; - const double abundances[NUM_SPECIES] = {0.702616602672027, 9.74791583949078e-06, 0.06895512307276903, 0.00025, 7.855418029399437e-05, 0.0006014411598306529, 8.103062886768109e-05, 2.151340851063217e-05}; + const double abundances[NUM_SPECIES] = { + 0.702616602672027, + 9.74791583949078e-06, + 0.06895512307276903, + 0.00025, + 7.855418029399437e-05, + 0.0006014411598306529, + 8.103062886768109e-05, + 2.151340851063217e-05 + }; int ret = gf_register_species(ctx, NUM_SPECIES, species_names); CHECK_RET_CODE(ret, ctx, "SPECIES"); diff --git a/tests/extern/fortran/gridfire_evolve.f90 b/tests/extern/fortran/gridfire_evolve.f90 new file mode 100644 index 00000000..592a01f9 --- /dev/null +++ b/tests/extern/fortran/gridfire_evolve.f90 @@ -0,0 +1,89 @@ +program main + use iso_c_binding + use gridfire_mod + implicit none + + type(GridFire) :: net + integer(c_int) :: ierr + integer :: i + + ! --- 1. Define Species and Initial Conditions --- + ! Note: String lengths must match or exceed the longest name. + ! We pad with spaces, which 'trim' handles inside the module. + character(len=5), dimension(8) :: species_names = [ & + "H-1 ", & + "He-3 ", & + "He-4 ", & + "C-12 ", & + "N-14 ", & + "O-16 ", & + "Ne-20", & + "Mg-24" & + ] + + ! Initial Mass Fractions (converted to Molar Abundances Y = X/A) + ! Standard solar-ish composition + real(c_double), dimension(8) :: Y_in = [ & + 0.702616602672027, & + 9.74791583949078e-06, & + 0.06895512307276903, & + 0.00025, & + 7.855418029399437e-05, & + 0.0006014411598306529, & + 8.103062886768109e-05, & + 2.151340851063217e-05 & + ] + + ! Output buffers + real(c_double), dimension(8) :: Y_out + real(c_double) :: energy_out, dedt, dedrho, dmass + + ! Thermodynamic Conditions (Solar Core-ish) + real(c_double) :: T = 1.5e7 ! 15 Million K + real(c_double) :: rho = 150.0e0 ! 150 g/cm^3 + real(c_double) :: dt = 3.0e17 ! 1 second timestep + + ! --- 2. Initialize GridFire --- + print *, "Initializing GridFire..." + call net%gff_init() + + ! --- 3. Register Species --- + print *, "Registering species..." + call net%register_species(species_names) + + ! --- 4. Configure Engine & Solver --- + print *, "Setting up Main Sequence Policy..." + call net%setup_policy("MAIN_SEQUENCE_POLICY", Y_in) + + print *, "Setting up CVODE Solver..." + call net%setup_solver("CVODE") + + ! --- 5. Evolve --- + print *, "Evolving system (dt =", dt, "s)..." + call net%evolve(Y_in, T, rho, dt, Y_out, energy_out, dedt, dedrho, dmass, ierr) + + if (ierr /= 0) then + print *, "Evolution Failed with error code: ", ierr + print *, "Error Message: ", net%get_last_error() + call net%gff_free() ! Always cleanup + stop + end if + + ! --- 6. Report Results --- + print *, "" + print *, "--- Results ---" + print '(A, ES12.5, A)', "Energy Generation: ", energy_out, " erg/g/s" + print '(A, ES12.5)', "dEps/dT: ", dedt + print '(A, ES12.5)', "Mass Change: ", dmass + + print *, "" + print *, "Abundances:" + do i = 1, size(species_names) + print '(A, " : ", ES12.5, " -> ", ES12.5)', & + trim(species_names(i)), Y_in(i), Y_out(i) + end do + + ! --- 7. Cleanup --- + call net%gff_free() + +end program main \ No newline at end of file diff --git a/tests/extern/fortran/meson.build b/tests/extern/fortran/meson.build new file mode 100644 index 00000000..60601a00 --- /dev/null +++ b/tests/extern/fortran/meson.build @@ -0,0 +1,5 @@ +executable('test_fortran_extern', 'gridfire_evolve.f90', + install: false, + fortran_args: ['-Wall', '-Wextra'], + dependencies: [gridfire_fortran_dep] +) \ No newline at end of file diff --git a/tests/extern/meson.build b/tests/extern/meson.build index ed6c93a5..4904143f 100644 --- a/tests/extern/meson.build +++ b/tests/extern/meson.build @@ -1 +1,2 @@ -subdir('C') \ No newline at end of file +subdir('C') +subdir('fortran') \ No newline at end of file