!> @file config_schema.f90 !> @brief Runtime-queryable schema and typed accessors for `config_t`. !! !! This module exposes the solver-owned metadata needed by adapters: !! parameter names, groups, value kinds, bounds, and choice lists. It also !! provides typed getters/setters so higher-level integrations do not need to !! know the layout of `config_t`. module config_schema use precision, only: wp use config, only: config_t use option_registry, only: flux_scheme_names, recon_scheme_names, time_scheme_names, & limiter_names, problem_type_names, boundary_condition_names, & char_proj_mode_names, nrbc_mode_names, hybrid_sensor_names implicit none private !> Schema kind tag for integer-valued parameters. integer, parameter, public :: cfg_kind_int = 1 !> Schema kind tag for scalar real-valued parameters. integer, parameter, public :: cfg_kind_real = 2 !> Schema kind tag for logical `.true.` / `.false.` parameters. integer, parameter, public :: cfg_kind_logical = 3 !> Schema kind tag for string parameters constrained to a published choice list. integer, parameter, public :: cfg_kind_choice = 4 !> Schema kind tag for free-form string parameters such as file paths. integer, parameter, public :: cfg_kind_string = 5 !> Schema kind tag for fixed-length real vectors of length 3. integer, parameter, public :: cfg_kind_real3 = 6 integer, parameter :: choice_none = 0 integer, parameter :: choice_flux = 1 integer, parameter :: choice_recon = 2 integer, parameter :: choice_time = 3 integer, parameter :: choice_limiter = 4 integer, parameter :: choice_problem = 5 integer, parameter :: choice_bc = 6 integer, parameter :: choice_char_proj = 7 integer, parameter :: choice_nrbc_mode = 8 integer, parameter :: choice_hybrid_sensor = 9 integer, parameter :: n_schema_entries = 58 !> Metadata published for one runtime-queryable configuration key. !! !! This structure is the authoritative schema record surfaced through the !! Fortran session layer, the C ABI, and the Python binding. The table order !! is stable and intentionally exposed through 1-based schema indices. type, public :: config_schema_entry_t character(len=32) :: key = '' !< Canonical lowercase parameter name. character(len=32) :: group = '' !< Namelist group such as `grid` or `output`. integer :: value_kind = 0 !< One of the `cfg_kind_*` tags above. character(len=256) :: help = '' !< Short user-facing help text. logical :: has_min = .false. !< True when `min_value` is meaningful. real(wp) :: min_value = 0.0_wp !< Inclusive lower bound when present. logical :: has_max = .false. !< True when `max_value` is meaningful. real(wp) :: max_value = 0.0_wp !< Inclusive upper bound when present. integer :: choice_set = choice_none !< Internal choice-list selector for enum-like strings. end type config_schema_entry_t type(config_schema_entry_t), save :: schema_entries(n_schema_entries) !< Lazily initialised schema table. logical, save :: schema_ready = .false. !< Guards one-time table population. public :: config_schema_count, get_config_schema_entry, find_config_schema_entry public :: config_schema_choice_count, get_config_schema_choice public :: config_get_integer, config_get_real, config_get_logical public :: config_get_string, config_get_real3 public :: config_set_integer, config_set_real, config_set_logical public :: config_set_string, config_set_real3 public :: config_default_integer, config_default_real, config_default_logical public :: config_default_string, config_default_real3 contains !> Return the number of published schema entries. integer function config_schema_count() result(count) call ensure_schema() count = n_schema_entries end function config_schema_count !> Find a schema entry by key and return its 1-based index. !! !! Returns `0` when the key is not part of the published schema. integer function find_config_schema_entry(key) result(index) character(len=*), intent(in) :: key integer :: i character(len=32) :: normalized call ensure_schema() normalized = normalize_key(key) index = 0 do i = 1, n_schema_entries if (trim(schema_entries(i) % key) == trim(normalized)) then index = i return end if end do end function find_config_schema_entry !> Copy one schema entry by its 1-based index. !! !! Invalid indices leave `entry` reset to defaults and return `found = .false.`. subroutine get_config_schema_entry(index, entry, found) integer, intent(in) :: index type(config_schema_entry_t), intent(out) :: entry logical, intent(out) :: found call ensure_schema() if (index < 1 .or. index > n_schema_entries) then found = .false. entry = config_schema_entry_t() return end if found = .true. entry = schema_entries(index) end subroutine get_config_schema_entry !> Return the number of allowed choices for a choice-valued schema entry. !! !! Non-choice entries, or out-of-range indices, report `0`. integer function config_schema_choice_count(index) result(count) integer, intent(in) :: index type(config_schema_entry_t) :: entry logical :: found call get_config_schema_entry(index, entry, found) if (.not. found) then count = 0 return end if select case (entry % choice_set) case (choice_flux) count = size(flux_scheme_names) case (choice_recon) count = size(recon_scheme_names) case (choice_time) count = size(time_scheme_names) case (choice_limiter) count = size(limiter_names) case (choice_problem) count = size(problem_type_names) case (choice_bc) count = size(boundary_condition_names) case (choice_char_proj) count = size(char_proj_mode_names) case (choice_nrbc_mode) count = size(nrbc_mode_names) case (choice_hybrid_sensor) count = size(hybrid_sensor_names) case default count = 0 end select end function config_schema_choice_count !> Copy one allowed string token from a schema entry's choice list. !! !! `choice_index` is 1-based to match the ABI-facing schema table order. subroutine get_config_schema_choice(index, choice_index, value, found) integer, intent(in) :: index integer, intent(in) :: choice_index character(len=*), intent(out) :: value logical, intent(out) :: found type(config_schema_entry_t) :: entry logical :: entry_found call get_config_schema_entry(index, entry, entry_found) value = '' if (.not. entry_found) then found = .false. return end if select case (entry % choice_set) case (choice_flux) call copy_choice(flux_scheme_names, choice_index, value, found) case (choice_recon) call copy_choice(recon_scheme_names, choice_index, value, found) case (choice_time) call copy_choice(time_scheme_names, choice_index, value, found) case (choice_limiter) call copy_choice(limiter_names, choice_index, value, found) case (choice_problem) call copy_choice(problem_type_names, choice_index, value, found) case (choice_bc) call copy_choice(boundary_condition_names, choice_index, value, found) case (choice_char_proj) call copy_choice(char_proj_mode_names, choice_index, value, found) case (choice_nrbc_mode) call copy_choice(nrbc_mode_names, choice_index, value, found) case (choice_hybrid_sensor) call copy_choice(hybrid_sensor_names, choice_index, value, found) case default found = .false. end select end subroutine get_config_schema_choice !> Read the compiled-in default integer value for a schema key. subroutine config_default_integer(key, value, is_ok, message) character(len=*), intent(in) :: key integer, intent(out) :: value logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message type(config_t) :: defaults call config_get_integer(defaults, key, value, is_ok, message) end subroutine config_default_integer !> Read the compiled-in default scalar real value for a schema key. subroutine config_default_real(key, value, is_ok, message) character(len=*), intent(in) :: key real(wp), intent(out) :: value logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message type(config_t) :: defaults call config_get_real(defaults, key, value, is_ok, message) end subroutine config_default_real !> Read the compiled-in default logical value for a schema key. subroutine config_default_logical(key, value, is_ok, message) character(len=*), intent(in) :: key logical, intent(out) :: value logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message type(config_t) :: defaults call config_get_logical(defaults, key, value, is_ok, message) end subroutine config_default_logical !> Read the compiled-in default string or choice token for a schema key. subroutine config_default_string(key, value, is_ok, message) character(len=*), intent(in) :: key character(len=*), intent(out) :: value logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message type(config_t) :: defaults call config_get_string(defaults, key, value, is_ok, message) end subroutine config_default_string !> Read the compiled-in default length-3 real vector for a schema key. subroutine config_default_real3(key, value, is_ok, message) character(len=*), intent(in) :: key real(wp), intent(out) :: value(3) logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message type(config_t) :: defaults call config_get_real3(defaults, key, value, is_ok, message) end subroutine config_default_real3 !> Read one integer-valued field from `cfg` by schema key. subroutine config_get_integer(cfg, key, value, is_ok, message) type(config_t), intent(in) :: cfg character(len=*), intent(in) :: key integer, intent(out) :: value logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message character(len=32) :: normalized value = 0 call set_status(.true., '', is_ok, message) normalized = normalize_key(key) select case (trim(normalized)) case ('n_cell') value = cfg % n_cell case ('print_freq') value = cfg % print_freq case ('verbosity') value = cfg % verbosity case ('snapshot_freq') value = cfg % snapshot_freq case ('checkpoint_freq') value = cfg % checkpoint_freq case default call set_status(.false., 'config_schema: "'//trim(normalized)//'" is not an integer parameter', is_ok, message) end select end subroutine config_get_integer !> Read one scalar real-valued field from `cfg` by schema key. subroutine config_get_real(cfg, key, value, is_ok, message) type(config_t), intent(in) :: cfg character(len=*), intent(in) :: key real(wp), intent(out) :: value logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message character(len=32) :: normalized value = 0.0_wp call set_status(.true., '', is_ok, message) normalized = normalize_key(key) select case (trim(normalized)) case ('x_left') value = cfg % x_left case ('x_right') value = cfg % x_right case ('dt') value = cfg % dt case ('time_start') value = cfg % time_start case ('time_stop') value = cfg % time_stop case ('cfl') value = cfg % cfl case ('gam') value = cfg % gam case ('hybrid_sensor_threshold') value = cfg % hybrid_sensor_threshold case ('p_ref_left') value = cfg % p_ref_left case ('p_ref_right') value = cfg % p_ref_right case ('sigma_nrbc') value = cfg % sigma_nrbc case ('u_ref_left') value = cfg % u_ref_left case ('u_ref_right') value = cfg % u_ref_right case ('rho_ref_left') value = cfg % rho_ref_left case ('rho_ref_right') value = cfg % rho_ref_right case ('sigma_nrbc_entropy') value = cfg % sigma_nrbc_entropy case ('p_stag_left') value = cfg % p_stag_left case ('rho_stag_left') value = cfg % rho_stag_left case ('p_stag_right') value = cfg % p_stag_right case ('rho_stag_right') value = cfg % rho_stag_right case ('p_back_left') value = cfg % p_back_left case ('p_back_right') value = cfg % p_back_right case ('rho_left') value = cfg % rho_left case ('u_left') value = cfg % u_left case ('p_left') value = cfg % p_left case ('rho_right') value = cfg % rho_right case ('u_right') value = cfg % u_right case ('p_right') value = cfg % p_right case ('x_diaphragm') value = cfg % x_diaphragm case default call set_status(.false., 'config_schema: "'//trim(normalized)//'" is not a real scalar parameter', is_ok, message) end select end subroutine config_get_real !> Read one logical field from `cfg` by schema key. subroutine config_get_logical(cfg, key, value, is_ok, message) type(config_t), intent(in) :: cfg character(len=*), intent(in) :: key logical, intent(out) :: value logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message character(len=32) :: normalized value = .false. call set_status(.true., '', is_ok, message) normalized = normalize_key(key) select case (trim(normalized)) case ('lapack_solver') value = cfg % lapack_solver case ('use_positivity_limiter') value = cfg % use_positivity_limiter case ('use_hybrid_recon') value = cfg % use_hybrid_recon case ('ic_interp') value = cfg % ic_interp case ('do_timing') value = cfg % do_timing case default call set_status(.false., 'config_schema: "'//trim(normalized)//'" is not a logical parameter', is_ok, message) end select end subroutine config_get_logical !> Read one string or choice token from `cfg` by schema key. subroutine config_get_string(cfg, key, value, is_ok, message) type(config_t), intent(in) :: cfg character(len=*), intent(in) :: key character(len=*), intent(out) :: value logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message character(len=32) :: normalized value = '' call set_status(.true., '', is_ok, message) normalized = normalize_key(key) select case (trim(normalized)) case ('flux_scheme') value = trim(cfg % flux_scheme) case ('recon_scheme') value = trim(cfg % recon_scheme) case ('time_scheme') value = trim(cfg % time_scheme) case ('char_proj') value = trim(cfg % char_proj) case ('limiter') value = trim(cfg % limiter) case ('hybrid_sensor') value = trim(cfg % hybrid_sensor) case ('problem_type') value = trim(cfg % problem_type) case ('ic_file') value = trim(cfg % ic_file) case ('ic_udf_src') value = trim(cfg % ic_udf_src) case ('bc_left') value = trim(cfg % bc_left) case ('bc_right') value = trim(cfg % bc_right) case ('nrbc_mode') value = trim(cfg % nrbc_mode) case ('output_file') value = trim(cfg % output_file) case ('log_file') value = trim(cfg % log_file) case ('snapshot_file') value = trim(cfg % snapshot_file) case ('checkpoint_file') value = trim(cfg % checkpoint_file) case ('restart_file') value = trim(cfg % restart_file) case default call set_status(.false., 'config_schema: "'//trim(normalized)//'" is not a string/choice parameter', is_ok, message) end select end subroutine config_get_string !> Read one fixed-length real-3 field from `cfg` by schema key. subroutine config_get_real3(cfg, key, value, is_ok, message) type(config_t), intent(in) :: cfg character(len=*), intent(in) :: key real(wp), intent(out) :: value(3) logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message character(len=32) :: normalized value = 0.0_wp call set_status(.true., '', is_ok, message) normalized = normalize_key(key) select case (trim(normalized)) case ('neumann_grad_left') value = cfg % neumann_grad_left case ('neumann_grad_right') value = cfg % neumann_grad_right case default call set_status(.false., 'config_schema: "'//trim(normalized)//'" is not a 3-vector parameter', is_ok, message) end select end subroutine config_get_real3 !> Write one integer-valued field in `cfg` by schema key. !! !! This is a typed field assignment helper; cross-field validation still !! happens separately through `validate_config`. subroutine config_set_integer(cfg, key, value, is_ok, message) type(config_t), intent(inout) :: cfg character(len=*), intent(in) :: key integer, intent(in) :: value logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message character(len=32) :: normalized call set_status(.true., '', is_ok, message) normalized = normalize_key(key) select case (trim(normalized)) case ('n_cell') cfg % n_cell = value case ('print_freq') cfg % print_freq = value case ('verbosity') cfg % verbosity = value case ('snapshot_freq') cfg % snapshot_freq = value case ('checkpoint_freq') cfg % checkpoint_freq = value case default call set_status(.false., 'config_schema: "'//trim(normalized)//'" is not an integer parameter', is_ok, message) end select end subroutine config_set_integer !> Write one scalar real-valued field in `cfg` by schema key. subroutine config_set_real(cfg, key, value, is_ok, message) type(config_t), intent(inout) :: cfg character(len=*), intent(in) :: key real(wp), intent(in) :: value logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message character(len=32) :: normalized call set_status(.true., '', is_ok, message) normalized = normalize_key(key) select case (trim(normalized)) case ('x_left') cfg % x_left = value case ('x_right') cfg % x_right = value case ('dt') cfg % dt = value case ('time_start') cfg % time_start = value case ('time_stop') cfg % time_stop = value case ('cfl') cfg % cfl = value case ('gam') cfg % gam = value case ('hybrid_sensor_threshold') cfg % hybrid_sensor_threshold = value case ('p_ref_left') cfg % p_ref_left = value case ('p_ref_right') cfg % p_ref_right = value case ('sigma_nrbc') cfg % sigma_nrbc = value case ('u_ref_left') cfg % u_ref_left = value case ('u_ref_right') cfg % u_ref_right = value case ('rho_ref_left') cfg % rho_ref_left = value case ('rho_ref_right') cfg % rho_ref_right = value case ('sigma_nrbc_entropy') cfg % sigma_nrbc_entropy = value case ('p_stag_left') cfg % p_stag_left = value case ('rho_stag_left') cfg % rho_stag_left = value case ('p_stag_right') cfg % p_stag_right = value case ('rho_stag_right') cfg % rho_stag_right = value case ('p_back_left') cfg % p_back_left = value case ('p_back_right') cfg % p_back_right = value case ('rho_left') cfg % rho_left = value case ('u_left') cfg % u_left = value case ('p_left') cfg % p_left = value case ('rho_right') cfg % rho_right = value case ('u_right') cfg % u_right = value case ('p_right') cfg % p_right = value case ('x_diaphragm') cfg % x_diaphragm = value case default call set_status(.false., 'config_schema: "'//trim(normalized)//'" is not a real scalar parameter', is_ok, message) end select end subroutine config_set_real !> Write one logical field in `cfg` by schema key. subroutine config_set_logical(cfg, key, value, is_ok, message) type(config_t), intent(inout) :: cfg character(len=*), intent(in) :: key logical, intent(in) :: value logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message character(len=32) :: normalized call set_status(.true., '', is_ok, message) normalized = normalize_key(key) select case (trim(normalized)) case ('lapack_solver') cfg % lapack_solver = value case ('use_positivity_limiter') cfg % use_positivity_limiter = value case ('use_hybrid_recon') cfg % use_hybrid_recon = value case ('ic_interp') cfg % ic_interp = value case ('do_timing') cfg % do_timing = value case default call set_status(.false., 'config_schema: "'//trim(normalized)//'" is not a logical parameter', is_ok, message) end select end subroutine config_set_logical !> Write one string or choice token in `cfg` by schema key. subroutine config_set_string(cfg, key, value, is_ok, message) type(config_t), intent(inout) :: cfg character(len=*), intent(in) :: key character(len=*), intent(in) :: value logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message character(len=32) :: normalized call set_status(.true., '', is_ok, message) normalized = normalize_key(key) select case (trim(normalized)) case ('flux_scheme') cfg % flux_scheme = value case ('recon_scheme') cfg % recon_scheme = value case ('time_scheme') cfg % time_scheme = value case ('char_proj') cfg % char_proj = value case ('limiter') cfg % limiter = value case ('hybrid_sensor') cfg % hybrid_sensor = value case ('problem_type') cfg % problem_type = value case ('ic_file') cfg % ic_file = value case ('ic_udf_src') cfg % ic_udf_src = value case ('bc_left') cfg % bc_left = value case ('bc_right') cfg % bc_right = value case ('nrbc_mode') cfg % nrbc_mode = value case ('output_file') cfg % output_file = value case ('log_file') cfg % log_file = value case ('snapshot_file') cfg % snapshot_file = value case ('checkpoint_file') cfg % checkpoint_file = value case ('restart_file') cfg % restart_file = value case default call set_status(.false., 'config_schema: "'//trim(normalized)//'" is not a string/choice parameter', is_ok, message) end select end subroutine config_set_string !> Write one fixed-length real-3 field in `cfg` by schema key. subroutine config_set_real3(cfg, key, value, is_ok, message) type(config_t), intent(inout) :: cfg character(len=*), intent(in) :: key real(wp), intent(in) :: value(3) logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message character(len=32) :: normalized call set_status(.true., '', is_ok, message) normalized = normalize_key(key) select case (trim(normalized)) case ('neumann_grad_left') cfg % neumann_grad_left = value case ('neumann_grad_right') cfg % neumann_grad_right = value case default call set_status(.false., 'config_schema: "'//trim(normalized)//'" is not a 3-vector parameter', is_ok, message) end select end subroutine config_set_real3 !> Populate the schema table on first use. !! !! The insertion order becomes the externally visible schema index, so new !! entries should be appended intentionally to avoid reshuffling adapters. subroutine ensure_schema() if (schema_ready) return ! Keep groups clustered for discoverability in generated docs and UI forms. call set_entry(1, 'n_cell', 'grid', cfg_kind_int, 'Number of grid cells', & has_min=.true., min_value=1.0_wp, has_max=.true., max_value=100000.0_wp) call set_entry(2, 'x_left', 'grid', cfg_kind_real, 'Left boundary coordinate') call set_entry(3, 'x_right', 'grid', cfg_kind_real, 'Right boundary coordinate') call set_entry(4, 'dt', 'time_ctrl', cfg_kind_real, 'Fixed time step', has_min=.true., min_value=0.0_wp) call set_entry(5, 'time_start', 'time_ctrl', cfg_kind_real, 'Simulation start time') call set_entry(6, 'time_stop', 'time_ctrl', cfg_kind_real, 'Simulation stop time') call set_entry(7, 'cfl', 'time_ctrl', cfg_kind_real, 'CFL number', has_min=.true., min_value=0.0_wp) call set_entry(8, 'lapack_solver', 'time_ctrl', cfg_kind_logical, 'Use LAPACK banded solver for backward Euler') call set_entry(9, 'gam', 'physics', cfg_kind_real, 'Ratio of specific heats', has_min=.true., min_value=1.001_wp) call set_entry(10, 'flux_scheme', 'schemes', cfg_kind_choice, 'Numerical flux scheme', choice_set=choice_flux) call set_entry(11, 'recon_scheme', 'schemes', cfg_kind_choice, 'Spatial reconstruction scheme', choice_set=choice_recon) call set_entry(12, 'time_scheme', 'schemes', cfg_kind_choice, 'Time integration scheme', choice_set=choice_time) call set_entry(13, 'char_proj', 'schemes', cfg_kind_choice, 'Characteristic projection mode', choice_set=choice_char_proj) call set_entry(14, 'limiter', 'schemes', cfg_kind_choice, 'MUSCL limiter', choice_set=choice_limiter) call set_entry(15, 'use_positivity_limiter', 'schemes', cfg_kind_logical, 'Enable positivity limiter') call set_entry(16, 'use_hybrid_recon', 'schemes', cfg_kind_logical, 'Enable hybrid reconstruction') call set_entry(17, 'hybrid_sensor', 'schemes', cfg_kind_choice, 'Hybrid shock sensor', choice_set=choice_hybrid_sensor) call set_entry(18, 'hybrid_sensor_threshold', 'schemes', cfg_kind_real, 'Hybrid sensor threshold') call set_entry(19, 'problem_type', 'initial_condition', cfg_kind_choice, 'Initial condition preset', choice_set=choice_problem) call set_entry(20, 'ic_file', 'initial_condition', cfg_kind_string, 'Path to IC data file') call set_entry(21, 'ic_interp', 'initial_condition', cfg_kind_logical, 'Interpolate IC file onto solver grid') call set_entry(22, 'ic_udf_src', 'initial_condition', cfg_kind_string, 'Path to IC UDF source') call set_entry(23, 'bc_left', 'initial_condition', cfg_kind_choice, 'Left boundary condition', choice_set=choice_bc) call set_entry(24, 'bc_right', 'initial_condition', cfg_kind_choice, 'Right boundary condition', choice_set=choice_bc) call set_entry(25, 'p_ref_left', 'initial_condition', cfg_kind_real, 'Left NRBC reference pressure') call set_entry(26, 'p_ref_right', 'initial_condition', cfg_kind_real, 'Right NRBC reference pressure') call set_entry(27, 'sigma_nrbc', 'initial_condition', cfg_kind_real, 'NRBC relaxation factor', & has_min=.true., min_value=0.0_wp, has_max=.true., max_value=1.0_wp) call set_entry(28, 'nrbc_mode', 'initial_condition', cfg_kind_choice, 'NRBC algorithm mode', choice_set=choice_nrbc_mode) call set_entry(29, 'u_ref_left', 'initial_condition', cfg_kind_real, 'Left characteristic NRBC reference velocity') call set_entry(30, 'u_ref_right', 'initial_condition', cfg_kind_real, 'Right characteristic NRBC reference velocity') call set_entry(31, 'rho_ref_left', 'initial_condition', cfg_kind_real, 'Left characteristic NRBC reference density') call set_entry(32, 'rho_ref_right', 'initial_condition', cfg_kind_real, 'Right characteristic NRBC reference density') call set_entry(33, 'sigma_nrbc_entropy', 'initial_condition', cfg_kind_real, 'Entropy-wave relaxation factor', & has_min=.true., min_value=0.0_wp, has_max=.true., max_value=1.0_wp) call set_entry(34, 'p_stag_left', 'initial_condition', cfg_kind_real, 'Left subsonic inlet stagnation pressure', & has_min=.true., min_value=0.0_wp) call set_entry(35, 'rho_stag_left', 'initial_condition', cfg_kind_real, 'Left subsonic inlet stagnation density', & has_min=.true., min_value=0.0_wp) call set_entry(36, 'p_stag_right', 'initial_condition', cfg_kind_real, 'Right subsonic inlet stagnation pressure', & has_min=.true., min_value=0.0_wp) call set_entry(37, 'rho_stag_right', 'initial_condition', cfg_kind_real, 'Right subsonic inlet stagnation density', & has_min=.true., min_value=0.0_wp) call set_entry(38, 'p_back_left', 'initial_condition', cfg_kind_real, 'Left subsonic outlet back pressure', & has_min=.true., min_value=0.0_wp) call set_entry(39, 'p_back_right', 'initial_condition', cfg_kind_real, 'Right subsonic outlet back pressure', & has_min=.true., min_value=0.0_wp) call set_entry(40, 'neumann_grad_left', 'initial_condition', cfg_kind_real3, & 'Left Neumann-gradient conserved-variable vector') call set_entry(41, 'neumann_grad_right', 'initial_condition', cfg_kind_real3, & 'Right Neumann-gradient conserved-variable vector') call set_entry(42, 'rho_left', 'initial_condition', cfg_kind_real, 'Left density', has_min=.true., min_value=0.0_wp) call set_entry(43, 'u_left', 'initial_condition', cfg_kind_real, 'Left velocity') call set_entry(44, 'p_left', 'initial_condition', cfg_kind_real, 'Left pressure', has_min=.true., min_value=0.0_wp) call set_entry(45, 'rho_right', 'initial_condition', cfg_kind_real, 'Right density', has_min=.true., min_value=0.0_wp) call set_entry(46, 'u_right', 'initial_condition', cfg_kind_real, 'Right velocity') call set_entry(47, 'p_right', 'initial_condition', cfg_kind_real, 'Right pressure', has_min=.true., min_value=0.0_wp) call set_entry(48, 'x_diaphragm', 'initial_condition', cfg_kind_real, 'Riemann problem diaphragm location') call set_entry(49, 'output_file', 'output', cfg_kind_string, 'Final result file path') call set_entry(50, 'print_freq', 'output', cfg_kind_int, 'Residual print interval', has_min=.true., min_value=1.0_wp) call set_entry(51, 'do_timing', 'output', cfg_kind_logical, 'Enable detailed timing summary output') call set_entry(52, 'verbosity', 'output', cfg_kind_int, 'Logger verbosity', has_min=.true., min_value=0.0_wp, & has_max=.true., max_value=4.0_wp) call set_entry(53, 'log_file', 'output', cfg_kind_string, 'Log file path') call set_entry(54, 'snapshot_freq', 'output', cfg_kind_int, 'Live snapshot interval', has_min=.true., min_value=0.0_wp) call set_entry(55, 'snapshot_file', 'output', cfg_kind_string, 'Live snapshot file path') call set_entry(56, 'checkpoint_freq', 'checkpoint', cfg_kind_int, 'Checkpoint interval', has_min=.true., min_value=0.0_wp) call set_entry(57, 'checkpoint_file', 'checkpoint', cfg_kind_string, 'Checkpoint base filename') call set_entry(58, 'restart_file', 'checkpoint', cfg_kind_string, 'Checkpoint file to resume from') schema_ready = .true. end subroutine ensure_schema !> Fill one slot in the lazily initialised schema table. subroutine set_entry(index, key, group, value_kind, help, has_min, min_value, has_max, max_value, choice_set) integer, intent(in) :: index character(len=*), intent(in) :: key, group, help integer, intent(in) :: value_kind logical, intent(in), optional :: has_min, has_max real(wp), intent(in), optional :: min_value, max_value integer, intent(in), optional :: choice_set schema_entries(index) = config_schema_entry_t() schema_entries(index) % key = normalize_key(key) schema_entries(index) % group = group schema_entries(index) % value_kind = value_kind schema_entries(index) % help = help if (present(has_min)) schema_entries(index) % has_min = has_min if (present(min_value)) schema_entries(index) % min_value = min_value if (present(has_max)) schema_entries(index) % has_max = has_max if (present(max_value)) schema_entries(index) % max_value = max_value if (present(choice_set)) schema_entries(index) % choice_set = choice_set end subroutine set_entry !> Normalise a user-provided parameter key to lowercase trimmed form. pure function normalize_key(key) result(normalized) character(len=*), intent(in) :: key character(len=32) :: normalized integer :: i, code normalized = '' normalized = adjustl(trim(key)) do i = 1, len_trim(normalized) code = iachar(normalized(i:i)) if (code >= iachar('A') .and. code <= iachar('Z')) normalized(i:i) = achar(code + 32) end do end function normalize_key !> Copy one 1-based choice-list item into `value`. subroutine copy_choice(values, index, value, found) character(len=*), intent(in) :: values(:) integer, intent(in) :: index character(len=*), intent(out) :: value logical, intent(out) :: found value = '' if (index < 1 .or. index > size(values)) then found = .false. return end if found = .true. value = trim(values(index)) end subroutine copy_choice !> Fan out a success flag and optional message to caller-provided outputs. subroutine set_status(ok, err, is_ok, message) logical, intent(in) :: ok character(len=*), intent(in) :: err logical, intent(out), optional :: is_ok character(len=*), intent(out), optional :: message if (present(is_ok)) is_ok = ok if (present(message)) message = trim(err) end subroutine set_status end module config_schema