mqc_config_parser_fragments.f90 Source File

Fragments and connectivity section parsers for MQC config files Handles: fragments, connectivity sections and their generic helpers


This file depends on

sourcefile~~mqc_config_parser_fragments.f90~~EfferentGraph sourcefile~mqc_config_parser_fragments.f90 mqc_config_parser_fragments.f90 sourcefile~mqc_config_parser.f90 mqc_config_parser.F90 sourcefile~mqc_config_parser_fragments.f90->sourcefile~mqc_config_parser.f90 sourcefile~mqc_physical_fragment.f90 mqc_physical_fragment.f90 sourcefile~mqc_config_parser_fragments.f90->sourcefile~mqc_physical_fragment.f90 sourcefile~mqc_config_parser.f90->sourcefile~mqc_physical_fragment.f90 sourcefile~mqc_calc_types.f90 mqc_calc_types.f90 sourcefile~mqc_config_parser.f90->sourcefile~mqc_calc_types.f90 sourcefile~mqc_calculation_defaults.f90 mqc_calculation_defaults.f90 sourcefile~mqc_config_parser.f90->sourcefile~mqc_calculation_defaults.f90 sourcefile~mqc_error.f90 mqc_error.f90 sourcefile~mqc_config_parser.f90->sourcefile~mqc_error.f90 sourcefile~mqc_geometry.f90 mqc_geometry.f90 sourcefile~mqc_config_parser.f90->sourcefile~mqc_geometry.f90 sourcefile~mqc_method_types.f90 mqc_method_types.f90 sourcefile~mqc_config_parser.f90->sourcefile~mqc_method_types.f90 sourcefile~mqc_cgto.f90 mqc_cgto.f90 sourcefile~mqc_physical_fragment.f90->sourcefile~mqc_cgto.f90 sourcefile~mqc_elements.f90 mqc_elements.f90 sourcefile~mqc_physical_fragment.f90->sourcefile~mqc_elements.f90 sourcefile~mqc_physical_fragment.f90->sourcefile~mqc_error.f90 sourcefile~mqc_physical_fragment.f90->sourcefile~mqc_geometry.f90 sourcefile~mqc_physical_constants.f90 mqc_physical_constants.f90 sourcefile~mqc_physical_fragment.f90->sourcefile~mqc_physical_constants.f90 sourcefile~mqc_xyz_reader.f90 mqc_xyz_reader.f90 sourcefile~mqc_physical_fragment.f90->sourcefile~mqc_xyz_reader.f90 sourcefile~mqc_xyz_reader.f90->sourcefile~mqc_error.f90 sourcefile~mqc_xyz_reader.f90->sourcefile~mqc_geometry.f90

Source Code

!! Fragments and connectivity section parsers for MQC config files
!! Handles: fragments, connectivity sections and their generic helpers
submodule(mqc_config_parser) mqc_config_parser_fragments
   implicit none

contains

   module subroutine parse_fragments_section(unit, config, error)
      integer, intent(in) :: unit
      type(mqc_config_t), intent(inout) :: config
      type(error_t), intent(out) :: error

      call parse_fragments_generic(unit, config%nfrag, config%fragments, error)

   end subroutine parse_fragments_section

   module subroutine parse_connectivity_section(unit, config, error)
      integer, intent(in) :: unit
      type(mqc_config_t), intent(inout) :: config
      type(error_t), intent(out) :: error

      call parse_connectivity_generic(unit, config%nbonds, config%nbroken, config%bonds, error)

   end subroutine parse_connectivity_section

   module subroutine parse_fragments_generic(unit, nfrag, fragments, error)
      !! Generic parser for %fragments section (works for both config and molecule)
      integer, intent(in) :: unit
      integer, intent(inout) :: nfrag
      type(input_fragment_t), allocatable, intent(inout) :: fragments(:)
      type(error_t), intent(out) :: error

      character(len=MAX_LINE_LEN) :: line, key, value
      character(len=256) :: msg
      integer :: io_stat, eq_pos, nfrag_local, ifrag
      nfrag_local = 0

      ! First pass: read nfrag
      do
         read (unit, '(A)', iostat=io_stat) line
         if (io_stat /= 0) then
            call error%set(ERROR_IO, "Unexpected end of file in %fragments section")
            return
         end if

         line = adjustl(line)
         if (len_trim(line) == 0) cycle
         if (line(1:1) == '#' .or. line(1:1) == '!') cycle

         if (trim(strip_comment(line)) == 'end') exit

         eq_pos = index(line, '=')
         if (eq_pos > 0) then
            key = adjustl(line(1:eq_pos - 1))
            value = adjustl(line(eq_pos + 1:))

            if (trim(key) == 'nfrag') then
               read (value, *, iostat=io_stat) nfrag_local
               if (io_stat /= 0) then
                  call error%set(ERROR_PARSE, "Invalid nfrag value")
                  return
               end if
               exit
            end if
         end if
      end do

      if (nfrag_local == 0) then
         ! No fragments, just skip to end
         call skip_to_end(unit, error)
         return
      end if

      nfrag = nfrag_local
      allocate (fragments(nfrag))

      ! Parse individual fragments
      ifrag = 0
      do
         read (unit, '(A)', iostat=io_stat) line
         if (io_stat /= 0) exit

         line = adjustl(line)
         if (len_trim(line) == 0) cycle
         if (line(1:1) == '#' .or. line(1:1) == '!') cycle

         if (trim(strip_comment(line)) == 'end') exit

         if (trim(line) == '%fragment') then
            ifrag = ifrag + 1
            if (ifrag > nfrag) then
               call error%set(ERROR_PARSE, "More fragments than declared nfrag")
               return
            end if
            call parse_fragment(unit, fragments(ifrag), error)
            if (error%has_error()) then
               call error%add_context("mqc_config_parser:parse_fragments_generic")
               return
            end if
         end if
      end do

      if (ifrag /= nfrag) then
         write (msg, '(A,I0,A,I0)') "Expected ", nfrag, " fragments, found ", ifrag
         call error%set(ERROR_PARSE, trim(msg))
         return
      end if

   end subroutine parse_fragments_generic

   subroutine parse_fragment(unit, fragment, error)
      integer, intent(in) :: unit
      type(input_fragment_t), intent(inout) :: fragment
      type(error_t), intent(out) :: error

      character(len=MAX_LINE_LEN) :: line, key, value
      integer :: io_stat, eq_pos
      logical :: in_indices
      in_indices = .false.

      do
         read (unit, '(A)', iostat=io_stat) line
         if (io_stat /= 0) then
            call error%set(ERROR_IO, "Unexpected end of file in %fragment")
            return
         end if

         line = adjustl(line)
         if (len_trim(line) == 0) cycle
         if (line(1:1) == '#' .or. line(1:1) == '!') cycle

         if (trim(strip_comment(line)) == 'end') then
            if (in_indices) then
               in_indices = .false.
               cycle
            else
               exit
            end if
         end if

         if (trim(line) == '%indices') then
            in_indices = .true.
            cycle
         end if

         if (in_indices) then
            ! Read indices
            call parse_indices_line(line, fragment, error)
            if (error%has_error()) then
               call error%add_context("mqc_config_parser:parse_fragment")
               return
            end if
         else
            eq_pos = index(line, '=')
            if (eq_pos > 0) then
               key = adjustl(line(1:eq_pos - 1))
               value = adjustl(line(eq_pos + 1:))

               select case (trim(key))
               case ('charge')
                  read (value, *, iostat=io_stat) fragment%charge
               case ('multiplicity')
                  read (value, *, iostat=io_stat) fragment%multiplicity
               case default
                  call error%set(ERROR_PARSE, "Unknown key in fragment properties: "//trim(key))
                  return
               end select
            end if
         end if
      end do

   end subroutine parse_fragment

   subroutine parse_indices_line(line, fragment, error)
      character(len=*), intent(in) :: line
      type(input_fragment_t), intent(inout) :: fragment
      type(error_t), intent(out) :: error

      integer :: io_stat, pos, count, idx
      character(len=MAX_LINE_LEN) :: temp_line
      integer, allocatable :: temp_indices(:), new_indices(:)
      temp_line = line

      ! Count how many integers
      count = 0
      do
         read (temp_line, *, iostat=io_stat) idx
         if (io_stat /= 0) exit
         count = count + 1
         ! Remove the read integer from temp_line
         pos = scan(temp_line, ' ')
         if (pos == 0) exit
         temp_line = adjustl(temp_line(pos:))
      end do

      if (count == 0) return

      ! Allocate temporary array
      allocate (temp_indices(count))

      ! Read the integers
      read (line, *, iostat=io_stat) temp_indices
      if (io_stat /= 0) then
         call error%set(ERROR_PARSE, "Error reading fragment indices")
         deallocate (temp_indices)
         return
      end if

      ! Append to existing indices
      if (allocated(fragment%indices)) then
         allocate (new_indices(size(fragment%indices) + count))
         new_indices(1:size(fragment%indices)) = fragment%indices
         new_indices(size(fragment%indices) + 1:) = temp_indices
         call move_alloc(new_indices, fragment%indices)
      else
         call move_alloc(temp_indices, fragment%indices)
      end if

   end subroutine parse_indices_line

   module subroutine parse_connectivity_generic(unit, nbonds, nbroken, bonds, error)
      !! Generic parser for %connectivity section (works for both config and molecule)
      use mqc_physical_fragment, only: bond_t
      integer, intent(in) :: unit
      integer, intent(inout) :: nbonds, nbroken
      type(bond_t), allocatable, intent(inout) :: bonds(:)
      type(error_t), intent(out) :: error

      character(len=MAX_LINE_LEN) :: line, key, value, status_str
      integer :: io_stat, eq_pos, nbonds_local, ibond
      integer :: atom_i, atom_j, order
      nbonds_local = 0

      ! First pass: read nbonds
      do
         read (unit, '(A)', iostat=io_stat) line
         if (io_stat /= 0) then
            call error%set(ERROR_IO, "Unexpected end of file in %connectivity section")
            return
         end if

         line = adjustl(line)
         if (len_trim(line) == 0) cycle
         if (line(1:1) == '#' .or. line(1:1) == '!') cycle

         if (trim(strip_comment(line)) == 'end') exit

         eq_pos = index(line, '=')
         if (eq_pos > 0) then
            key = adjustl(line(1:eq_pos - 1))
            value = adjustl(line(eq_pos + 1:))

            if (trim(key) == 'nbonds') then
               read (value, *, iostat=io_stat) nbonds_local
               if (io_stat /= 0) then
                  call error%set(ERROR_PARSE, "Invalid nbonds value")
                  return
               end if
               exit
            end if
         end if
      end do

      if (nbonds_local == 0) then
         ! No bonds, just skip to end
         call skip_to_end(unit, error)
         return
      end if

      nbonds = nbonds_local
      allocate (bonds(nbonds))

      ! Read bonds
      ibond = 0
      do
         read (unit, '(A)', iostat=io_stat) line
         if (io_stat /= 0) exit

         line = adjustl(line)
         if (len_trim(line) == 0) cycle
         if (line(1:1) == '#' .or. line(1:1) == '!') cycle

         ! Check for key=value pairs (like nbroken=9)
         eq_pos = index(line, '=')
         if (eq_pos > 0) then
            key = adjustl(line(1:eq_pos - 1))
            value = adjustl(line(eq_pos + 1:))
            if (trim(key) == 'nbroken') then
               read (value, *, iostat=io_stat) nbroken
            end if
            cycle
         end if

         if (trim(strip_comment(line)) == 'end') exit

         ! Parse bond line: atom_i atom_j order broken/preserved
         read (line, *, iostat=io_stat) atom_i, atom_j, order, status_str
         if (io_stat /= 0) then
            call error%set(ERROR_PARSE, "Invalid bond format in %connectivity section")
            return
         end if

         ibond = ibond + 1
         if (ibond > nbonds) then
            call error%set(ERROR_PARSE, "More bonds than declared nbonds")
            return
         end if

         bonds(ibond)%atom_i = atom_i
         bonds(ibond)%atom_j = atom_j
         bonds(ibond)%order = order
         bonds(ibond)%is_broken = (trim(status_str) == 'broken')
      end do

   end subroutine parse_connectivity_generic

end submodule mqc_config_parser_fragments