!> @file solver_c_api.f90 !> @brief C ABI wrapper over the polling-friendly solver session API. module solver_c_api use iso_c_binding, only: c_ptr, c_loc, c_f_pointer, c_null_ptr, c_associated, & c_int, c_double, c_char, c_null_char use precision, only: wp use config_schema, only: config_schema_entry_t, cfg_kind_int, cfg_kind_real, & cfg_kind_logical, cfg_kind_choice, cfg_kind_string, & cfg_kind_real3, config_schema_count, get_config_schema_entry, & config_schema_choice_count, get_config_schema_choice, & config_default_integer, config_default_real, config_default_logical, & config_default_string, config_default_real3 use solver_session, only: solver_session_t, solver_progress_t, solver_status_ok, & solver_status_invalid_argument, solver_status_invalid_state, & solver_status_config_error, solver_status_io_error, & solver_status_busy, solver_session_create, solver_session_destroy, & solver_session_load_namelist, solver_session_initialize, & solver_session_advance, solver_session_run_to_end, & solver_session_get_progress, solver_session_get_point_count, & solver_session_copy_solution, solver_session_write_result, & solver_session_write_checkpoint, solver_session_last_error, & solver_session_get_integer, solver_session_get_real, & solver_session_get_logical, solver_session_get_string, & solver_session_get_real3, solver_session_set_integer, & solver_session_set_real, solver_session_set_logical, & solver_session_set_string, solver_session_set_real3 implicit none private integer, parameter :: handle_token = 1 !< Sentinel used to reject stale/foreign handles. !> C-interoperable progress record returned by `cfd_solver_get_progress`. type, bind(c) :: cfd_solver_progress_c_t integer(c_int) :: iteration = 0 !< Completed solver iterations. integer(c_int) :: n_point = 0 !< Grid-point count (`n_cell + 1`). integer(c_int) :: is_initialized = 0 !< `1` after runtime initialisation, otherwise `0`. integer(c_int) :: is_finished = 0 !< `1` once `time_stop` has been reached. real(c_double) :: sim_time = 0.0_c_double !< Current simulation time. [s] real(c_double) :: time_stop = 0.0_c_double !< Configured stop time. [s] real(c_double) :: dt = 0.0_c_double !< Current or configured time step. [s] real(c_double) :: residual = 0.0_c_double !< Latest residual scalar from the runtime. end type cfd_solver_progress_c_t !> Opaque token wrapper used to validate ABI handles from foreign code. type, bind(c) :: cfd_solver_handle_t integer(c_int) :: token = 0 !< Sentinel copied into the exported opaque handle. end type cfd_solver_handle_t ! V1 supports a single active session per host process even though the API is ! already shaped around opaque handles to avoid a future ABI break. type(solver_session_t), pointer, save :: active_session => null() type(cfd_solver_handle_t), pointer, save :: active_handle => null() character(len=512), save :: api_last_error = '' !< Process-global error fallback before session resolution. public :: cfd_solver_progress_c_t public :: cfd_solver_create, cfd_solver_destroy, cfd_solver_load_namelist public :: cfd_solver_initialize, cfd_solver_advance, cfd_solver_run_to_end public :: cfd_solver_get_progress, cfd_solver_get_point_count public :: cfd_solver_copy_solution, cfd_solver_write_result public :: cfd_solver_write_checkpoint, cfd_solver_get_last_error public :: cfd_solver_get_int, cfd_solver_get_real, cfd_solver_get_bool public :: cfd_solver_get_string, cfd_solver_get_real3 public :: cfd_solver_set_int, cfd_solver_set_real, cfd_solver_set_bool public :: cfd_solver_set_string, cfd_solver_set_real3 public :: cfd_solver_schema_count, cfd_solver_schema_key, cfd_solver_schema_group public :: cfd_solver_schema_help, cfd_solver_schema_kind, cfd_solver_schema_bounds public :: cfd_solver_schema_choice_count, cfd_solver_schema_choice public :: cfd_solver_schema_default_int, cfd_solver_schema_default_real public :: cfd_solver_schema_default_bool, cfd_solver_schema_default_string public :: cfd_solver_schema_default_real3 contains !> Allocate the sole supported v1 session and return its opaque handle. integer(c_int) function cfd_solver_create(handle) bind(c, name='cfd_solver_create') result(status) type(c_ptr), intent(out) :: handle integer :: alloc_stat handle = c_null_ptr ! Keep the externally visible handle model stable now, even though the ! implementation still enforces a single active session in this first ABI. if (associated(active_session)) then api_last_error = 'cfd_solver: only one active session is supported in v1' status = solver_status_busy return end if allocate (active_session, stat=alloc_stat) if (alloc_stat /= 0) then api_last_error = 'cfd_solver: failed to allocate session' status = solver_status_io_error return end if call solver_session_create(active_session) allocate (active_handle, stat=alloc_stat) if (alloc_stat /= 0) then deallocate (active_session) nullify (active_session) api_last_error = 'cfd_solver: failed to allocate handle' status = solver_status_io_error return end if active_handle % token = handle_token handle = c_loc(active_handle) api_last_error = '' status = solver_status_ok end function cfd_solver_create !> Destroy the active session referenced by `handle`. integer(c_int) function cfd_solver_destroy(handle) bind(c, name='cfd_solver_destroy') result(status) type(c_ptr), value :: handle type(solver_session_t), pointer :: session status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_destroy(session) deallocate (active_session) deallocate (active_handle) nullify (active_session) nullify (active_handle) api_last_error = '' status = solver_status_ok end function cfd_solver_destroy !> Load a namelist file into the session configuration. integer(c_int) function cfd_solver_load_namelist(handle, filename) bind(c, name='cfd_solver_load_namelist') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: filename(*) type(solver_session_t), pointer :: session character(len=512) :: path, err status = resolve_session(handle, session) if (status /= solver_status_ok) return path = c_string_to_fortran(filename) call solver_session_load_namelist(session, trim(path), status, err) api_last_error = trim(err) end function cfd_solver_load_namelist !> Validate config and allocate the runtime owned by `handle`. integer(c_int) function cfd_solver_initialize(handle) bind(c, name='cfd_solver_initialize') result(status) type(c_ptr), value :: handle type(solver_session_t), pointer :: session character(len=512) :: err status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_initialize(session, status, err) api_last_error = trim(err) end function cfd_solver_initialize !> Advance the solver by at most `max_steps` iterations. !! !! This is the canonical polling-first live-update path for GUI adapters. integer(c_int) function cfd_solver_advance(handle, max_steps, steps_taken, finished) & bind(c, name='cfd_solver_advance') result(status) type(c_ptr), value :: handle integer(c_int), value :: max_steps integer(c_int), intent(out) :: steps_taken integer(c_int), intent(out) :: finished type(solver_session_t), pointer :: session integer :: steps_done logical :: is_finished character(len=512) :: err steps_taken = 0_c_int finished = 0_c_int status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_advance(session, int(max_steps), steps_done, is_finished, status, err) steps_taken = int(steps_done, c_int) if (is_finished) finished = 1_c_int api_last_error = trim(err) end function cfd_solver_advance !> Run the session to completion without intermediate polling. integer(c_int) function cfd_solver_run_to_end(handle) bind(c, name='cfd_solver_run_to_end') result(status) type(c_ptr), value :: handle type(solver_session_t), pointer :: session character(len=512) :: err status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_run_to_end(session, status, err) api_last_error = trim(err) end function cfd_solver_run_to_end !> Copy out the current progress snapshot for a session. integer(c_int) function cfd_solver_get_progress(handle, progress) bind(c, name='cfd_solver_get_progress') result(status) type(c_ptr), value :: handle type(cfd_solver_progress_c_t), intent(out) :: progress type(solver_session_t), pointer :: session type(solver_progress_t) :: progress_f character(len=512) :: err progress = cfd_solver_progress_c_t() status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_get_progress(session, progress_f, status, err) if (status == solver_status_ok) then progress % iteration = int(progress_f % iteration, c_int) progress % n_point = int(progress_f % n_point, c_int) progress % is_initialized = merge(1_c_int, 0_c_int, progress_f % is_initialized) progress % is_finished = merge(1_c_int, 0_c_int, progress_f % is_finished) progress % sim_time = real(progress_f % sim_time, c_double) progress % time_stop = real(progress_f % time_stop, c_double) progress % dt = real(progress_f % dt, c_double) progress % residual = real(progress_f % residual, c_double) end if api_last_error = trim(err) end function cfd_solver_get_progress !> Return the grid-point count required by solution-copy buffers. integer(c_int) function cfd_solver_get_point_count(handle, n_point) bind(c, name='cfd_solver_get_point_count') result(status) type(c_ptr), value :: handle integer(c_int), intent(out) :: n_point type(solver_session_t), pointer :: session integer :: n_point_f character(len=512) :: err n_point = 0_c_int status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_get_point_count(session, n_point_f, status, err) if (status == solver_status_ok) n_point = int(n_point_f, c_int) api_last_error = trim(err) end function cfd_solver_get_point_count !> Copy the primitive solution into caller-owned C buffers. !! !! `n_point` must match the exact grid-point count reported by !! `cfd_solver_get_point_count`. integer(c_int) function cfd_solver_copy_solution(handle, x, rho, u, p, n_point) & bind(c, name='cfd_solver_copy_solution') result(status) type(c_ptr), value :: handle real(c_double), intent(out) :: x(*), rho(*), u(*), p(*) integer(c_int), value :: n_point type(solver_session_t), pointer :: session real(wp), allocatable :: x_f(:), rho_f(:), u_f(:), p_f(:) integer :: i, alloc_stat character(len=512) :: err status = resolve_session(handle, session) if (status /= solver_status_ok) return if (n_point < 0_c_int) then api_last_error = 'cfd_solver: n_point must be >= 0' status = solver_status_invalid_argument return end if allocate (x_f(int(n_point)), rho_f(int(n_point)), u_f(int(n_point)), p_f(int(n_point)), stat=alloc_stat) if (alloc_stat /= 0) then api_last_error = 'cfd_solver: failed to allocate solution copy buffers' status = solver_status_io_error return end if ! Bridge the assumed-size C buffers through explicit Fortran temporaries so ! the session layer can keep a simple bounds-checked assumed-shape API. call solver_session_copy_solution(session, x_f, rho_f, u_f, p_f, status, err) if (status == solver_status_ok) then do i = 1, int(n_point) x(i) = real(x_f(i), c_double) rho(i) = real(rho_f(i), c_double) u(i) = real(u_f(i), c_double) p(i) = real(p_f(i), c_double) end do end if api_last_error = trim(err) end function cfd_solver_copy_solution !> Write the current solution in the standard text result format. integer(c_int) function cfd_solver_write_result(handle, filename) bind(c, name='cfd_solver_write_result') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: filename(*) type(solver_session_t), pointer :: session character(len=512) :: path, err status = resolve_session(handle, session) if (status /= solver_status_ok) return path = c_string_to_fortran(filename) call solver_session_write_result(session, trim(path), status, err) api_last_error = trim(err) end function cfd_solver_write_result !> Force an immediate checkpoint write for the active runtime. integer(c_int) function cfd_solver_write_checkpoint(handle, base) bind(c, name='cfd_solver_write_checkpoint') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: base(*) type(solver_session_t), pointer :: session character(len=512) :: base_path, err status = resolve_session(handle, session) if (status /= solver_status_ok) return base_path = c_string_to_fortran(base) call solver_session_write_checkpoint(session, trim(base_path), status, err) api_last_error = trim(err) end function cfd_solver_write_checkpoint !> Copy the last surfaced error string into a C buffer. !! !! When `handle` cannot be resolved, the function falls back to the process-wide !! API error string so callers can still retrieve create/resolve failures. integer(c_int) function cfd_solver_get_last_error(handle, buffer, buffer_len) & bind(c, name='cfd_solver_get_last_error') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(out) :: buffer(*) integer(c_int), value :: buffer_len type(solver_session_t), pointer :: session character(len=512) :: err if (buffer_len <= 0_c_int) then status = solver_status_invalid_argument return end if ! When the handle is NULL we cannot and should not call resolve_session(), ! because resolve_session() would overwrite api_last_error with a ! "null session handle" message, hiding any earlier error from, for example, ! a failed cfd_solver_create() call. Return the preserved api_last_error ! directly instead. if (.not. c_associated(handle)) then call fortran_to_c_buffer(trim(api_last_error), buffer, int(buffer_len)) status = solver_status_ok return end if if (resolve_session(handle, session) == solver_status_ok) then call solver_session_last_error(session, err) else err = trim(api_last_error) end if call fortran_to_c_buffer(trim(err), buffer, int(buffer_len)) status = solver_status_ok end function cfd_solver_get_last_error !> Read an integer configuration field by canonical schema key. integer(c_int) function cfd_solver_get_int(handle, key, value) bind(c, name='cfd_solver_get_int') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: key(*) integer(c_int), intent(out) :: value type(solver_session_t), pointer :: session integer :: value_f character(len=512) :: err value = 0_c_int status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_get_integer(session, trim(c_string_to_fortran(key)), value_f, status, err) if (status == solver_status_ok) value = int(value_f, c_int) api_last_error = trim(err) end function cfd_solver_get_int !> Read a scalar real configuration field by canonical schema key. integer(c_int) function cfd_solver_get_real(handle, key, value) bind(c, name='cfd_solver_get_real') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: key(*) real(c_double), intent(out) :: value type(solver_session_t), pointer :: session real(wp) :: value_f character(len=512) :: err value = 0.0_c_double status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_get_real(session, trim(c_string_to_fortran(key)), value_f, status, err) if (status == solver_status_ok) value = real(value_f, c_double) api_last_error = trim(err) end function cfd_solver_get_real !> Read a logical configuration field by canonical schema key. integer(c_int) function cfd_solver_get_bool(handle, key, value) bind(c, name='cfd_solver_get_bool') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: key(*) integer(c_int), intent(out) :: value type(solver_session_t), pointer :: session logical :: value_f character(len=512) :: err value = 0_c_int status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_get_logical(session, trim(c_string_to_fortran(key)), value_f, status, err) if (status == solver_status_ok) value = merge(1_c_int, 0_c_int, value_f) api_last_error = trim(err) end function cfd_solver_get_bool !> Read a string or choice-token configuration field by canonical schema key. integer(c_int) function cfd_solver_get_string(handle, key, buffer, buffer_len) & bind(c, name='cfd_solver_get_string') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: key(*) character(kind=c_char), intent(out) :: buffer(*) integer(c_int), value :: buffer_len type(solver_session_t), pointer :: session character(len=512) :: value_f, err status = resolve_session(handle, session) if (status /= solver_status_ok) return if (buffer_len <= 0_c_int) then api_last_error = 'cfd_solver: buffer_len must be > 0' status = solver_status_invalid_argument return end if call solver_session_get_string(session, trim(c_string_to_fortran(key)), value_f, status, err) if (status == solver_status_ok) call fortran_to_c_buffer(trim(value_f), buffer, int(buffer_len)) api_last_error = trim(err) end function cfd_solver_get_string !> Read a length-3 real configuration field by canonical schema key. integer(c_int) function cfd_solver_get_real3(handle, key, values, n_values) bind(c, name='cfd_solver_get_real3') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: key(*) real(c_double), intent(out) :: values(*) integer(c_int), value :: n_values type(solver_session_t), pointer :: session real(wp) :: value_f(3) integer :: i character(len=512) :: err status = resolve_session(handle, session) if (status /= solver_status_ok) return if (n_values < 3_c_int) then api_last_error = 'cfd_solver: n_values must be at least 3 for real3 parameters' status = solver_status_invalid_argument return end if call solver_session_get_real3(session, trim(c_string_to_fortran(key)), value_f, status, err) if (status == solver_status_ok) then do i = 1, 3 values(i) = real(value_f(i), c_double) end do end if api_last_error = trim(err) end function cfd_solver_get_real3 !> Set an integer configuration field by canonical schema key. integer(c_int) function cfd_solver_set_int(handle, key, value) bind(c, name='cfd_solver_set_int') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: key(*) integer(c_int), value :: value type(solver_session_t), pointer :: session character(len=512) :: err status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_set_integer(session, trim(c_string_to_fortran(key)), int(value), status, err) api_last_error = trim(err) end function cfd_solver_set_int !> Set a scalar real configuration field by canonical schema key. integer(c_int) function cfd_solver_set_real(handle, key, value) bind(c, name='cfd_solver_set_real') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: key(*) real(c_double), value :: value type(solver_session_t), pointer :: session character(len=512) :: err status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_set_real(session, trim(c_string_to_fortran(key)), real(value, wp), status, err) api_last_error = trim(err) end function cfd_solver_set_real !> Set a logical configuration field by canonical schema key. integer(c_int) function cfd_solver_set_bool(handle, key, value) bind(c, name='cfd_solver_set_bool') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: key(*) integer(c_int), value :: value type(solver_session_t), pointer :: session character(len=512) :: err status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_set_logical(session, trim(c_string_to_fortran(key)), value /= 0_c_int, status, err) api_last_error = trim(err) end function cfd_solver_set_bool !> Set a string or choice-token configuration field by canonical schema key. integer(c_int) function cfd_solver_set_string(handle, key, value) bind(c, name='cfd_solver_set_string') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: key(*) character(kind=c_char), intent(in) :: value(*) type(solver_session_t), pointer :: session character(len=512) :: err status = resolve_session(handle, session) if (status /= solver_status_ok) return call solver_session_set_string(session, trim(c_string_to_fortran(key)), trim(c_string_to_fortran(value)), status, err) api_last_error = trim(err) end function cfd_solver_set_string !> Set a length-3 real configuration field by canonical schema key. integer(c_int) function cfd_solver_set_real3(handle, key, values, n_values) bind(c, name='cfd_solver_set_real3') result(status) type(c_ptr), value :: handle character(kind=c_char), intent(in) :: key(*) real(c_double), intent(in) :: values(*) integer(c_int), value :: n_values type(solver_session_t), pointer :: session real(wp) :: values_f(3) integer :: i character(len=512) :: err status = resolve_session(handle, session) if (status /= solver_status_ok) return if (n_values < 3_c_int) then api_last_error = 'cfd_solver: n_values must be at least 3 for real3 parameters' status = solver_status_invalid_argument return end if do i = 1, 3 values_f(i) = real(values(i), wp) end do call solver_session_set_real3(session, trim(c_string_to_fortran(key)), values_f, status, err) api_last_error = trim(err) end function cfd_solver_set_real3 !> Return the number of schema entries exported through the ABI. !! !! Schema indices are 1-based to match the native Fortran table ordering. integer(c_int) function cfd_solver_schema_count() bind(c, name='cfd_solver_schema_count') result(count) count = int(config_schema_count(), c_int) end function cfd_solver_schema_count !> Copy the canonical key for schema entry `index`. integer(c_int) function cfd_solver_schema_key(index, buffer, buffer_len) bind(c, name='cfd_solver_schema_key') result(status) integer(c_int), value :: index character(kind=c_char), intent(out) :: buffer(*) integer(c_int), value :: buffer_len type(config_schema_entry_t) :: entry logical :: found status = schema_entry_text(index, buffer, buffer_len, found, entry, 'key') end function cfd_solver_schema_key !> Copy the namelist group name for schema entry `index`. integer(c_int) function cfd_solver_schema_group(index, buffer, buffer_len) bind(c, name='cfd_solver_schema_group') result(status) integer(c_int), value :: index character(kind=c_char), intent(out) :: buffer(*) integer(c_int), value :: buffer_len type(config_schema_entry_t) :: entry logical :: found status = schema_entry_text(index, buffer, buffer_len, found, entry, 'group') end function cfd_solver_schema_group !> Copy the short help text for schema entry `index`. integer(c_int) function cfd_solver_schema_help(index, buffer, buffer_len) bind(c, name='cfd_solver_schema_help') result(status) integer(c_int), value :: index character(kind=c_char), intent(out) :: buffer(*) integer(c_int), value :: buffer_len type(config_schema_entry_t) :: entry logical :: found status = schema_entry_text(index, buffer, buffer_len, found, entry, 'help') end function cfd_solver_schema_help !> Return the `cfg_kind_*` tag for schema entry `index`. integer(c_int) function cfd_solver_schema_kind(index, value_kind) bind(c, name='cfd_solver_schema_kind') result(status) integer(c_int), value :: index integer(c_int), intent(out) :: value_kind type(config_schema_entry_t) :: entry logical :: found value_kind = 0_c_int call get_config_schema_entry(int(index), entry, found) if (.not. found) then api_last_error = 'cfd_solver: schema index out of range' status = solver_status_invalid_argument return end if value_kind = int(entry % value_kind, c_int) api_last_error = '' status = solver_status_ok end function cfd_solver_schema_kind !> Return optional numeric bounds for schema entry `index`. integer(c_int) function cfd_solver_schema_bounds(index, has_min, min_value, has_max, & max_value) bind(c, name='cfd_solver_schema_bounds') result(status) integer(c_int), value :: index integer(c_int), intent(out) :: has_min real(c_double), intent(out) :: min_value integer(c_int), intent(out) :: has_max real(c_double), intent(out) :: max_value type(config_schema_entry_t) :: entry logical :: found has_min = 0_c_int has_max = 0_c_int min_value = 0.0_c_double max_value = 0.0_c_double call get_config_schema_entry(int(index), entry, found) if (.not. found) then api_last_error = 'cfd_solver: schema index out of range' status = solver_status_invalid_argument return end if has_min = merge(1_c_int, 0_c_int, entry % has_min) has_max = merge(1_c_int, 0_c_int, entry % has_max) min_value = real(entry % min_value, c_double) max_value = real(entry % max_value, c_double) api_last_error = '' status = solver_status_ok end function cfd_solver_schema_bounds !> Return the number of allowed string choices for schema entry `index`. integer(c_int) function cfd_solver_schema_choice_count(index, count) bind(c, name='cfd_solver_schema_choice_count') result(status) integer(c_int), value :: index integer(c_int), intent(out) :: count type(config_schema_entry_t) :: entry logical :: found count = 0_c_int call get_config_schema_entry(int(index), entry, found) if (.not. found) then api_last_error = 'cfd_solver: schema index out of range' status = solver_status_invalid_argument return end if count = int(config_schema_choice_count(int(index)), c_int) api_last_error = '' status = solver_status_ok end function cfd_solver_schema_choice_count !> Copy one allowed string token for a choice-valued schema entry. !! !! Both `index` and `choice_index` are 1-based. integer(c_int) function cfd_solver_schema_choice(index, choice_index, buffer, buffer_len) & bind(c, name='cfd_solver_schema_choice') result(status) integer(c_int), value :: index integer(c_int), value :: choice_index character(kind=c_char), intent(out) :: buffer(*) integer(c_int), value :: buffer_len character(len=64) :: choice_value logical :: found if (buffer_len <= 0_c_int) then api_last_error = 'cfd_solver: buffer_len must be > 0' status = solver_status_invalid_argument return end if call get_config_schema_choice(int(index), int(choice_index), choice_value, found) if (.not. found) then api_last_error = 'cfd_solver: schema choice index out of range' status = solver_status_invalid_argument return end if call fortran_to_c_buffer(trim(choice_value), buffer, int(buffer_len)) api_last_error = '' status = solver_status_ok end function cfd_solver_schema_choice !> Copy the compiled-in default integer value for schema entry `index`. integer(c_int) function cfd_solver_schema_default_int(index, value) bind(c, name='cfd_solver_schema_default_int') result(status) integer(c_int), value :: index integer(c_int), intent(out) :: value character(len=32) :: key integer :: value_f logical :: found, ok type(config_schema_entry_t) :: entry character(len=256) :: err value = 0_c_int call get_config_schema_entry(int(index), entry, found) if (.not. found) then api_last_error = 'cfd_solver: schema index out of range' status = solver_status_invalid_argument return end if key = trim(entry % key) call config_default_integer(key, value_f, ok, err) if (.not. ok) then api_last_error = trim(err) status = solver_status_invalid_argument return end if value = int(value_f, c_int) api_last_error = '' status = solver_status_ok end function cfd_solver_schema_default_int !> Copy the compiled-in default scalar real value for schema entry `index`. integer(c_int) function cfd_solver_schema_default_real(index, value) bind(c, name='cfd_solver_schema_default_real') result(status) integer(c_int), value :: index real(c_double), intent(out) :: value character(len=32) :: key real(wp) :: value_f logical :: found, ok type(config_schema_entry_t) :: entry character(len=256) :: err value = 0.0_c_double call get_config_schema_entry(int(index), entry, found) if (.not. found) then api_last_error = 'cfd_solver: schema index out of range' status = solver_status_invalid_argument return end if key = trim(entry % key) call config_default_real(key, value_f, ok, err) if (.not. ok) then api_last_error = trim(err) status = solver_status_invalid_argument return end if value = real(value_f, c_double) api_last_error = '' status = solver_status_ok end function cfd_solver_schema_default_real !> Copy the compiled-in default logical value for schema entry `index`. integer(c_int) function cfd_solver_schema_default_bool(index, value) bind(c, name='cfd_solver_schema_default_bool') result(status) integer(c_int), value :: index integer(c_int), intent(out) :: value character(len=32) :: key logical :: value_f, found, ok type(config_schema_entry_t) :: entry character(len=256) :: err value = 0_c_int call get_config_schema_entry(int(index), entry, found) if (.not. found) then api_last_error = 'cfd_solver: schema index out of range' status = solver_status_invalid_argument return end if key = trim(entry % key) call config_default_logical(key, value_f, ok, err) if (.not. ok) then api_last_error = trim(err) status = solver_status_invalid_argument return end if value = merge(1_c_int, 0_c_int, value_f) api_last_error = '' status = solver_status_ok end function cfd_solver_schema_default_bool !> Copy the compiled-in default string or choice token for schema entry `index`. integer(c_int) function cfd_solver_schema_default_string(index, buffer, buffer_len) & bind(c, name='cfd_solver_schema_default_string') result(status) integer(c_int), value :: index character(kind=c_char), intent(out) :: buffer(*) integer(c_int), value :: buffer_len character(len=32) :: key character(len=512) :: value_f logical :: found, ok type(config_schema_entry_t) :: entry character(len=256) :: err if (buffer_len <= 0_c_int) then api_last_error = 'cfd_solver: buffer_len must be > 0' status = solver_status_invalid_argument return end if call get_config_schema_entry(int(index), entry, found) if (.not. found) then api_last_error = 'cfd_solver: schema index out of range' status = solver_status_invalid_argument return end if key = trim(entry % key) call config_default_string(key, value_f, ok, err) if (.not. ok) then api_last_error = trim(err) status = solver_status_invalid_argument return end if call fortran_to_c_buffer(trim(value_f), buffer, int(buffer_len)) api_last_error = '' status = solver_status_ok end function cfd_solver_schema_default_string !> Copy the compiled-in default length-3 real vector for schema entry `index`. integer(c_int) function cfd_solver_schema_default_real3(index, values, n_values) & bind(c, name='cfd_solver_schema_default_real3') result(status) integer(c_int), value :: index real(c_double), intent(out) :: values(*) integer(c_int), value :: n_values character(len=32) :: key real(wp) :: value_f(3) integer :: i logical :: found, ok type(config_schema_entry_t) :: entry character(len=256) :: err if (n_values < 3_c_int) then api_last_error = 'cfd_solver: n_values must be at least 3 for real3 defaults' status = solver_status_invalid_argument return end if call get_config_schema_entry(int(index), entry, found) if (.not. found) then api_last_error = 'cfd_solver: schema index out of range' status = solver_status_invalid_argument return end if key = trim(entry % key) call config_default_real3(key, value_f, ok, err) if (.not. ok) then api_last_error = trim(err) status = solver_status_invalid_argument return end if do i = 1, 3 values(i) = real(value_f(i), c_double) end do api_last_error = '' status = solver_status_ok end function cfd_solver_schema_default_real3 !> Resolve an opaque C handle to the active Fortran session pointer. integer(c_int) function resolve_session(handle, session) result(status) type(c_ptr), value :: handle type(solver_session_t), pointer :: session type(cfd_solver_handle_t), pointer :: handle_ptr nullify (session) if (.not. c_associated(handle)) then api_last_error = 'cfd_solver: null session handle' status = solver_status_invalid_argument return end if if (.not. associated(active_session) .or. .not. associated(active_handle)) then api_last_error = 'cfd_solver: no active session' status = solver_status_invalid_state return end if call c_f_pointer(handle, handle_ptr) if (.not. associated(handle_ptr)) then api_last_error = 'cfd_solver: invalid session handle' status = solver_status_invalid_argument return end if if (handle_ptr % token /= handle_token) then api_last_error = 'cfd_solver: invalid session token' status = solver_status_invalid_argument return end if if (.not. associated(handle_ptr, active_handle)) then api_last_error = 'cfd_solver: stale session handle' status = solver_status_invalid_argument return end if session => active_session api_last_error = '' status = solver_status_ok end function resolve_session !> Shared helper for copying one text field from a schema entry into a C buffer. integer(c_int) function schema_entry_text(index, buffer, buffer_len, found, entry, field_name) result(status) integer(c_int), value :: index character(kind=c_char), intent(out) :: buffer(*) integer(c_int), value :: buffer_len logical, intent(out) :: found type(config_schema_entry_t), intent(out) :: entry character(len=*), intent(in) :: field_name found = .false. entry = config_schema_entry_t() if (buffer_len <= 0_c_int) then api_last_error = 'cfd_solver: buffer_len must be > 0' status = solver_status_invalid_argument return end if call get_config_schema_entry(int(index), entry, found) if (.not. found) then api_last_error = 'cfd_solver: schema index out of range' status = solver_status_invalid_argument return end if select case (trim(field_name)) case ('key') call fortran_to_c_buffer(trim(entry % key), buffer, int(buffer_len)) case ('group') call fortran_to_c_buffer(trim(entry % group), buffer, int(buffer_len)) case ('help') call fortran_to_c_buffer(trim(entry % help), buffer, int(buffer_len)) end select api_last_error = '' status = solver_status_ok end function schema_entry_text !> Convert a NUL-terminated C string into a fixed-length Fortran buffer. function c_string_to_fortran(cstr) result(text) character(kind=c_char), intent(in) :: cstr(*) character(len=512) :: text integer :: i text = '' do i = 1, len(text) if (cstr(i) == c_null_char) exit text(i:i) = achar(iachar(cstr(i))) end do end function c_string_to_fortran !> Copy trimmed Fortran text into a NUL-terminated C buffer. subroutine fortran_to_c_buffer(text, buffer, buffer_len) character(len=*), intent(in) :: text character(kind=c_char), intent(out) :: buffer(*) integer, intent(in) :: buffer_len integer :: i, ncopy if (buffer_len <= 0) return ncopy = min(len_trim(text), buffer_len - 1) do i = 1, ncopy buffer(i) = text(i:i) end do if (ncopy + 1 <= buffer_len) buffer(ncopy + 1) = c_null_char do i = ncopy + 2, buffer_len buffer(i) = c_null_char end do end subroutine fortran_to_c_buffer end module solver_c_api