!> Named composition parser for user-facing species input. !! !! Supports strings such as "CH4:1.0,O2:0.233,N2:0.767" and maps the names !! into the solver/Cantera species ordering. This avoids fragile assumptions !! about numeric species indices when a mechanism such as GRI30 dynamically !! defines the transported species list. module mod_species_composition use mod_precision, only : rk, zero, one, tiny_safe, name_len, fatal_error, lowercase use mod_species_registry, only : species_index_of implicit none private public :: parse_named_composition, composition_string_is_set contains !> Returns true if the composition input string is populated and non-empty. !! !! @param text String representation of initial composition to test. logical function composition_string_is_set(text) character(len=*), intent(in) :: text !< The composition input text. composition_string_is_set = len_trim(adjustl(text)) > 0 end function composition_string_is_set !> Systematically parses a composition string and maps values to species array indexes. !! !! Splits key-value tokens (e.g., "H2:2.0,O2:1.0") separated by commas, matches !! name keys with the registered species, and populates the mass fraction array. !! Optionally normalizes the output array to sum to unity. !! !! @param text The composition input string to parse. !! @param species_names Array of active species names. !! @param nspecies Number of active species. !! @param values Output array populated with parsed fractions. !! @param owner Name of the calling subsystem for error traceback. !! @param normalize Optional flag to normalize values to sum to 1.0 (default true). !! @param require_sum_positive Optional flag to mandate a non-zero sum (default true). subroutine parse_named_composition(text, species_names, nspecies, values, owner, normalize, require_sum_positive) character(len=*), intent(in) :: text !< Composition string to parse. character(len=*), intent(in) :: species_names(:) !< Array of active species names. integer, intent(in) :: nspecies !< Number of species. real(rk), intent(out) :: values(:) !< Target array to populate. character(len=*), intent(in) :: owner !< Traceback owner name. logical, intent(in), optional :: normalize !< Whether to normalize results. logical, intent(in), optional :: require_sum_positive !< Whether sum must be strictly positive. character(len=:), allocatable :: work !< Scalable workspace copy of the input string. character(len=:), allocatable :: token !< Single name-value pair segment (e.g. "CH4:1.0"). character(len=:), allocatable :: name !< Parsed species name token. character(len=:), allocatable :: value_text !< Parsed numeric value text. integer :: pos !< Position index of comma separator. integer :: colon !< Position index of colon separator. integer :: idx !< Matched index of species in the registry. real(rk) :: val !< Parsed real representation of key value. real(rk) :: sum_y !< Cumulative sum of fractions. logical :: do_normalize !< Internal resolved normalization flag. logical :: need_positive !< Internal resolved positive requirement flag. if (size(values) < nspecies) call fatal_error(trim(owner), 'composition output vector is too small') values = zero if (nspecies <= 0) then if (len_trim(text) > 0) call fatal_error(trim(owner), 'composition was specified but no species are available') return end if do_normalize = .true. if (present(normalize)) do_normalize = normalize need_positive = .true. if (present(require_sum_positive)) need_positive = require_sum_positive work = trim(adjustl(text)) do while (len_trim(work) > 0) pos = index(work, ',') if (pos > 0) then token = trim(adjustl(work(:pos-1))) work = trim(adjustl(work(pos+1:))) else token = trim(adjustl(work)) work = '' end if if (len_trim(token) == 0) cycle colon = index(token, ':') if (colon <= 1 .or. colon >= len_trim(token)) then call fatal_error(trim(owner), 'bad composition token "'//trim(token)//'"; expected NAME:VALUE') end if name = trim(adjustl(token(:colon-1))) value_text = trim(adjustl(token(colon+1:))) read(value_text, *, err=100) val if (val < zero) call fatal_error(trim(owner), 'negative mass fraction for species '//trim(name)) idx = species_index_of(species_names, nspecies, name) if (idx <= 0) then call fatal_error(trim(owner), 'composition species "'//trim(name)//'" is not in the active species list') end if values(idx) = values(idx) + val cycle 100 call fatal_error(trim(owner), 'could not parse composition value in token "'//trim(token)//'"') end do sum_y = sum(values(1:nspecies)) if (sum_y <= tiny_safe) then if (need_positive) call fatal_error(trim(owner), 'composition mass-fraction sum is zero') else if (do_normalize) then values(1:nspecies) = values(1:nspecies) / sum_y end if end subroutine parse_named_composition end module mod_species_composition