! Defines molecules and molecular constraints
module MW_molecule
   use MW_kinds, only: wp, PARTICLE_NAME_LEN
   implicit none
   private

   public :: MW_molecule_t

   public :: define_type
   public :: void_type
   public :: print_type
   public :: set_rattle_parameters
   public :: set_linear_parameters
   public :: print_trajectories
   public :: read_parameters
   public :: set_sites_index
   public :: set_index

   type MW_molecule_t
      integer :: n                             !> number of molecules of this type
      character(len=PARTICLE_NAME_LEN) :: name !> name of the molecule
      integer :: index = 0                     !> Index of the molecule

      integer :: num_sites = 0             !> number of sites in the molecule
      integer, allocatable :: sites(:) !> ion types index of each site
      character(len=PARTICLE_NAME_LEN), allocatable :: site_names(:) !> ion types name of each site

      ! Constraints
      integer :: num_constraints = 0 !> Number of constraints
      integer, allocatable :: constrained_sites(:,:) !> Pairs of constrained sites
      real(wp), allocatable :: constrained_dr(:)     !> Constrained distance between pairs of sites

      integer :: constraint_algorithm = 0 !> constraint algorithm id
      integer :: constraint_max_iteration = 0 !> maximum iteration used in constraint algorithm
      real(wp) :: constraint_tolerance = 0.0_wp !> tolerance on constraints

      logical :: linear = .false. !> flag if molecule is linear
      integer, allocatable :: basis_sites(:) !> indices of the two basis sites
      integer, allocatable :: basis_constrained_sites(:,:) !> Pair of constrained basis sites
      real(wp), allocatable :: basis_constrained_dr(:)      !> Constrained distance between pairs of basis sites
      integer, allocatable :: other_sites(:) !> indices of the non-basis sites
      real(wp), allocatable :: other_sites_coordinates(:,:) !> coordinates of the other sites with respect to the basis

      ! intra-molecular potentials
      integer :: num_harmonic_bonds = 0                   ! Number of harmonic bonds
      integer, allocatable :: harmonic_bonds_sites(:,:)   ! Pairs of sites linked by a harmonic bond
      real(wp), allocatable :: harmonic_bonds_strength(:) ! Strength of the harmonic bond
      real(wp), allocatable :: harmonic_bonds_length(:)   ! Length at rest of the harmonic bond

      integer :: num_harmonic_angles = 0                   ! Number of harmonic angles
      integer, allocatable :: harmonic_angles_sites(:,:)   ! Triplet of sites linked by a harmonic angle
      real(wp), allocatable :: harmonic_angles_strength(:) ! Strength of the harmonic bond
      real(wp), allocatable :: harmonic_angles_angle(:)    ! Angle at rest of the harmonic bond

      integer :: num_born_repulsions = 0                   ! Number of born repulsions
      integer, allocatable :: born_repulsions_sites(:,:)   ! Triplet of sites linked by a harmonic angle
      real(wp), allocatable :: born_repulsions_energy(:)   ! Energy of the repulsion at r=0
      real(wp), allocatable :: born_repulsions_eta(:)      ! Inverse length of the born repulsion

      integer :: num_internal_coulomb = 0
      integer, allocatable :: internal_coulomb_sites(:,:)  ! Pairs of sites interacting <ith short range Coulomb
      logical, allocatable :: internal_coulomb_pairs(:,:)  !> flag if site pairs are interacting via short range Coulomb

      integer :: num_dihedrals = 0                   ! Number of dihedrals
      integer, allocatable :: dihedrals_sites(:,:)   ! Quadruplet of sites linked by a dihedral
      real(wp), allocatable :: dihedrals_v1(:) ! Parameters of the dihedral potential
      real(wp), allocatable :: dihedrals_v2(:) ! Parameters of the dihedral potential
      real(wp), allocatable :: dihedrals_v3(:) ! Parameters of the dihedral potential
      real(wp), allocatable :: dihedrals_v4(:) ! Parameters of the dihedral potential

      integer :: num_impropers = 0                   ! Number of impropers
      integer, allocatable :: impropers_sites(:,:)   ! Quadruplet of sites linked by a improper
      real(wp), allocatable :: impropers_v1(:) ! Parameters of the improper potential
      real(wp), allocatable :: impropers_v2(:) ! Parameters of the improper potential
      real(wp), allocatable :: impropers_v3(:) ! Parameters of the improper potential
      real(wp), allocatable :: impropers_v4(:) ! Parameters of the improper potential

      ! Interacting pairs
      logical, allocatable :: interacting_pairs(:,:) !> flag if site pairs are interacting via potentials
      logical, allocatable :: halfinteracting_pairs(:,:) !> flag if site pairs are interacting via potentials

      ! Four-site model
      logical :: fourth_site
      real(wp) :: fourth_distance
      real(wp) :: alpha

   end type MW_molecule_t

   integer, parameter, public :: CONSTRAINT_ALGORITHM_NONE = 0
   integer, parameter, public :: CONSTRAINT_ALGORITHM_RATTLE = 1
   integer, parameter, public :: CONSTRAINT_ALGORITHM_LINEAR = 2

contains

   !================================================================================
   ! Define the data structure
   subroutine define_type(this, name, n, num_sites, num_constraints, num_harmonic_bonds, &
         num_harmonic_angles,num_dihedrals,num_impropers, num_born_repulsions, num_internal_coulomb, &
         fourth_site, fourth_distance)
      use MW_errors, only: MW_errors_allocate_error => allocate_error, &
            MW_errors_runtime_error => runtime_error
      implicit none
      ! Passed in/out
      type(MW_molecule_t), intent(inout) :: this

      ! Passed in
      character(len=*), intent(in) :: name       !> name of the molecule
      integer, intent(in) :: n                   !> number of molecules of this type
      integer, intent(in) :: num_sites           !> number of sites in this molecule
      integer, intent(in) :: num_constraints     !> number of constraints in this molecule
      integer, intent(in) :: num_harmonic_bonds  !> number of harmonic bonds
      integer, intent(in) :: num_harmonic_angles !> number of harmonic angles
      integer, intent(in) :: num_born_repulsions !> number of born repulsions
      integer, intent(in) :: num_internal_coulomb
      integer, intent(in) :: num_dihedrals       !> number of dihedrals
      integer, intent(in) :: num_impropers       !> number of impropers
      logical, intent(in) :: fourth_site         !> is there a fourth site?
      real(wp), intent(in) :: fourth_distance    !> distance fourth-third site
      ! Local
      integer :: i, j, ierr

      this%name = name
      this%n = n

      allocate(this%sites(num_sites), this%site_names(num_sites), stat=ierr)
      if (ierr /= 0) then
         call MW_errors_allocate_error("define_type", "molecule.f90",ierr)
      end if
      this%num_sites = num_sites
      do i = 1, num_sites
         this%sites(i) = 0
         this%site_names(i) = ""
      end do

      this%num_constraints = num_constraints
      allocate(this%constrained_sites(2, num_constraints), this%constrained_dr(num_constraints), stat=ierr)
      if (ierr /= 0) then
         call MW_errors_allocate_error("define_type", "molecule.f90", ierr)
      end if
      do i= 1, num_constraints
         this%constrained_sites(1,i) = 0
         this%constrained_sites(2,i) = 0
         this%constrained_dr(i) = 0.0_wp
      end do

      this%num_harmonic_bonds = num_harmonic_bonds
      allocate(this%harmonic_bonds_sites(2, num_harmonic_bonds), &
            this%harmonic_bonds_strength(num_harmonic_bonds), &
            this%harmonic_bonds_length(num_harmonic_bonds), stat=ierr)
      if (ierr /= 0) then
         call MW_errors_allocate_error("define_type", "molecule.f90", ierr)
      end if
      do i= 1, num_harmonic_bonds
         this%harmonic_bonds_sites(1,i) = 0
         this%harmonic_bonds_sites(2,i) = 0
         this%harmonic_bonds_strength(i) = 0.0_wp
         this%harmonic_bonds_length(i) = 0.0_wp
      end do

      this%num_harmonic_angles = num_harmonic_angles
      allocate(this%harmonic_angles_sites(3, num_harmonic_angles), &
            this%harmonic_angles_strength(num_harmonic_angles), &
            this%harmonic_angles_angle(num_harmonic_angles), stat=ierr)
      if (ierr /= 0) then
         call MW_errors_allocate_error("define_type", "molecule.f90", ierr)
      end if
      do i= 1, num_harmonic_angles
         this%harmonic_angles_sites(1,i) = 0
         this%harmonic_angles_sites(2,i) = 0
         this%harmonic_angles_sites(3,i) = 0
         this%harmonic_angles_strength(i) = 0.0_wp
         this%harmonic_angles_angle(i) = 0.0_wp
      end do

      this%num_born_repulsions = num_born_repulsions
      allocate(this%born_repulsions_sites(2, num_born_repulsions), &
            this%born_repulsions_energy(num_born_repulsions), &
            this%born_repulsions_eta(num_born_repulsions), stat=ierr)
      if (ierr /= 0) then
         call MW_errors_allocate_error("define_type", "molecule.f90", ierr)
      end if
      do i= 1, num_born_repulsions
         this%born_repulsions_sites(1,i) = 0
         this%born_repulsions_sites(2,i) = 0
         this%born_repulsions_energy(i) = 0.0_wp
         this%born_repulsions_eta(i) = 0.0_wp
      enddo

      this%num_internal_coulomb = num_internal_coulomb
      allocate(this%internal_coulomb_sites(2, num_internal_coulomb), &
         this%internal_coulomb_pairs(num_sites,num_sites), stat=ierr)
      if (ierr /= 0) then
         call MW_errors_allocate_error("define_type", "molecule.f90", ierr)
      end if
      do i= 1, num_internal_coulomb
         this%internal_coulomb_sites(1,i) = 0
         this%internal_coulomb_sites(2,i) = 0
      enddo

      do i = 1, num_sites
         do j = 1, num_sites
            this%internal_coulomb_pairs(i,j) = .false.
         end do
      end do

      this%num_dihedrals = num_dihedrals
      allocate(this%dihedrals_sites(4, num_dihedrals), &
            this%dihedrals_v1(num_dihedrals), &
            this%dihedrals_v2(num_dihedrals), &
            this%dihedrals_v3(num_dihedrals), &
            this%dihedrals_v4(num_dihedrals), stat=ierr)
      if (ierr /= 0) then
         call MW_errors_allocate_error("define_type", "molecule.f90", ierr)
      end if
      do i= 1, num_dihedrals
         this%dihedrals_sites(1,i) = 0
         this%dihedrals_sites(2,i) = 0
         this%dihedrals_sites(3,i) = 0
         this%dihedrals_sites(4,i) = 0
         this%dihedrals_v1(i) = 0.0_wp
         this%dihedrals_v2(i) = 0.0_wp
         this%dihedrals_v3(i) = 0.0_wp
         this%dihedrals_v4(i) = 0.0_wp
      end do

      this%num_impropers = num_impropers
      allocate(this%impropers_sites(4, num_impropers), &
            this%impropers_v1(num_impropers), &
            this%impropers_v2(num_impropers), &
            this%impropers_v3(num_impropers), &
            this%impropers_v4(num_impropers), stat=ierr)
      if (ierr /= 0) then
         call MW_errors_allocate_error("define_type", "molecule.f90", ierr)
      end if
      do i= 1, num_impropers
         this%impropers_sites(1,i) = 0
         this%impropers_sites(2,i) = 0
         this%impropers_sites(3,i) = 0
         this%impropers_sites(4,i) = 0
         this%impropers_v1(i) = 0.0_wp
         this%impropers_v2(i) = 0.0_wp
         this%impropers_v3(i) = 0.0_wp
         this%impropers_v4(i) = 0.0_wp
      end do

      allocate(this%interacting_pairs(num_sites,num_sites), stat=ierr)
      if (ierr /= 0) then
         call MW_errors_allocate_error("define_type", "molecule.f90",ierr)
      end if
      do i = 1, num_sites
         do j = 1, num_sites
            this%interacting_pairs(i,j) = .true.
         end do
      end do

      allocate(this%halfinteracting_pairs(num_sites,num_sites), stat=ierr)
      if (ierr /= 0) then
         call MW_errors_allocate_error("define_type", "molecule.f90",ierr)
      end if
      do i = 1, num_sites
         do j = 1, num_sites
            this%halfinteracting_pairs(i,j) = .false.
         end do
      end do

      this%fourth_site = fourth_site
      this%fourth_distance = fourth_distance
      this%alpha = 0.5
      if (fourth_site) then
         if (num_sites /= 4) then
            call MW_errors_runtime_error("define_type", "molecule.f90", "Number of sites for molecule should be 4")
         end if
         this%interacting_pairs(1, 4) = .false.
         this%interacting_pairs(4, 1) = .false.
         this%interacting_pairs(2, 4) = .false.
         this%interacting_pairs(4, 2) = .false.
         this%interacting_pairs(3, 4) = .false.
         this%interacting_pairs(4, 3) = .false.
      end if

   end subroutine define_type

   ! ==============================================================================
   ! Void the data type
   subroutine void_type(this)
      use MW_errors, only: MW_errors_deallocate_error => deallocate_error
      implicit none
      type(MW_molecule_t), intent(inout) :: this
      integer :: ierr

      this%name = ""
      this%n = 0

      if (allocated(this%sites)) then
         deallocate(this%sites, stat=ierr)
         if (ierr /= 0) then
            call MW_errors_deallocate_error("define_type", "molecule.f90",ierr)
         end if
      end if
      if (allocated(this%site_names)) then
         deallocate(this%site_names, stat=ierr)
         if (ierr /= 0) then
            call MW_errors_deallocate_error("define_type", "molecule.f90",ierr)
         end if
      end if
      this%num_sites = 0

      if (allocated(this%constrained_sites)) then
         deallocate(this%constrained_sites, this%constrained_dr, stat=ierr)
         if (ierr /= 0) then
            call MW_errors_deallocate_error("define_type", "molecule.f90", ierr)
         end if
      end if
      this%num_constraints = 0

      if (allocated(this%harmonic_bonds_sites)) then
         deallocate(this%harmonic_bonds_sites, &
               this%harmonic_bonds_strength, &
               this%harmonic_bonds_length, stat=ierr)
         if (ierr /= 0) then
            call MW_errors_deallocate_error("define_type", "molecule.f90", ierr)
         end if
      end if
      this%num_harmonic_bonds = 0

      if (allocated(this%harmonic_angles_sites)) then
         deallocate(this%harmonic_angles_sites, &
               this%harmonic_angles_strength, &
               this%harmonic_angles_angle, stat=ierr)
         if (ierr /= 0) then
            call MW_errors_deallocate_error("define_type", "molecule.f90", ierr)
         end if
      end if
      this%num_harmonic_angles = 0

      if (allocated(this%dihedrals_sites)) then
         deallocate(this%dihedrals_sites, &
               this%dihedrals_v1, &
               this%dihedrals_v2, &
               this%dihedrals_v3, &
               this%dihedrals_v4, stat=ierr)
         if (ierr /= 0) then
            call MW_errors_deallocate_error("define_type", "molecule.f90", ierr)
         end if
      end if
      this%num_dihedrals = 0

     if (allocated(this%born_repulsions_sites)) then
         deallocate(this%born_repulsions_sites, &
               this%born_repulsions_energy, &
               this%born_repulsions_eta, stat=ierr)
         if (ierr /= 0) then
            call MW_errors_deallocate_error("define_type", "molecule.f90", ierr)
         end if
      end if
      this%num_born_repulsions = 0

      if (allocated(this%impropers_sites)) then
         deallocate(this%impropers_sites, &
               this%impropers_v1, &
               this%impropers_v2, &
               this%impropers_v3, &
               this%impropers_v4, stat=ierr)
         if (ierr /= 0) then
            call MW_errors_deallocate_error("define_type", "molecule.f90", ierr)
         end if
      end if
      this%num_impropers = 0

     if (allocated(this%internal_coulomb_sites)) then
         deallocate(this%internal_coulomb_sites, &
               this%internal_coulomb_pairs, stat=ierr)
         if (ierr /= 0) then
            call MW_errors_deallocate_error("define_type", "molecule.f90", ierr)
         end if
      end if
      this%num_internal_coulomb = 0

      if (allocated(this%basis_sites)) then
         deallocate(this%basis_sites, this%basis_constrained_sites, &
               this%basis_constrained_dr, this%other_sites, &
               this%other_sites_coordinates, stat=ierr)
         if (ierr /= 0) then
            call MW_errors_deallocate_error("define_type", "this.f90", ierr)
         end if
      end if
      this%linear = .false.

      if (allocated(this%interacting_pairs)) then
         deallocate(this%interacting_pairs, stat=ierr)
         if (ierr /= 0) then
            call MW_errors_deallocate_error("define_type", "this.f90", ierr)
         end if
      end if

      if (allocated(this%halfinteracting_pairs)) then
         deallocate(this%halfinteracting_pairs, stat=ierr)
         if (ierr /= 0) then
            call MW_errors_deallocate_error("define_type", "this.f90", ierr)
         end if
      end if
   end subroutine void_type

   ! ==============================================================================
   ! Prints the data
   subroutine print_type(this, ounit, names)
      implicit none
      type(MW_molecule_t), intent(in) :: this  ! structure to be printed
      integer, intent(in)             :: ounit ! output unit
      character(len=PARTICLE_NAME_LEN), intent(in) :: names(:)

      integer :: i, ia, ib, ic, id

      write(ounit, '("|molecule| ",a8," n = ",i6)') this%name, this%n
      write(ounit, '("|molecule| number of sites           = ", i3)') this%num_sites
      write(ounit, '("|molecule| number of constraints     = ",i3)')  this%num_constraints
      write(ounit, '("|molecule| number of harmonic bonds  = ",i3)')  this%num_harmonic_bonds
      write(ounit, '("|molecule| number of harmonic angles = ",i3)')  this%num_harmonic_angles
!      write(ounit, '("|molecule| number of born repulsions = ",i3)')  this%num_born_repulsions
!      write(ounit, '("|molecule| number of internal Coulomb= ",i3)')  this%num_internal_coulomb
      write(ounit, '("|molecule| number of dihedrals       = ",i3)')  this%num_dihedrals
      write(ounit, '("|molecule| number of impropers       = ",i3)')  this%num_impropers
      write(ounit, '("|molecule| sites:")')
      do i = 1, this%num_sites
         write(ounit, '("|molecule| - ",a8)') names(this%sites(i))
      end do
      if (this%num_constraints > 0) then
         write(ounit, '("|molecule| constraints:")')
!        do i = 1, this%num_constraints
!           ia = this%constrained_sites(1,i)
!           ib = this%constrained_sites(2,i)
!           write(ounit, '("|molecule| - ",a8," ",a8," ", es12.5)') &
!                 names(this%sites(ia)), names(this%sites(ib)), this%constrained_dr(i)
!        end do
         write(ounit, '("|molecule| tolerance     = ", es12.5)') this%constraint_tolerance
         write(ounit, '("|molecule| max iteration = ", i12)') this%constraint_max_iteration

         if (this%linear) then
            write(ounit, '("|molecule| The molecule is linear.")')
            write(ounit, '("|molecule| basis sites: ",4(a8,1x))') &
                  (names(this%sites(this%basis_sites(i))), i=1,size(this%basis_sites,1))
            write(ounit, '("|molecule| basis sites constraints:")')
            do i = 1, size(this%basis_constrained_dr,1)
               ia = this%basis_constrained_sites(1,i)
               ib = this%basis_constrained_sites(2,i)
               write(ounit, '("|molecule| - ",a8," ",a8," ", es12.5)') &
                     names(this%sites(ia)), names(this%sites(ib)), this%basis_constrained_dr(i)
            end do
            write(ounit, '("|molecule| non-basis sites coordinates:")')
            do i = 1, size(this%other_sites,1)
               ia = this%other_sites(i)
               write(ounit,'("|molecule| ",a8," = ",4(es12.5,1x,a8,:," + "))') &
                     names(this%sites(ia)), &
                     (this%other_sites_coordinates(ib,i), names(this%sites(this%basis_sites(ib))), &
                     ib=1,size(this%basis_sites,1))
            end do
         end if
      end if

      if (this%num_harmonic_bonds > 0) then
         write(ounit, '("|molecule| harmonic bonds: U(b) = k*(b-b0)^2")')
         do i = 1, this%num_harmonic_bonds
            ia = this%harmonic_bonds_sites(1,i)
            ib = this%harmonic_bonds_sites(2,i)
            write(ounit, '("|molecule| - ",a8," ",a8," k = ", es12.5," b0 = ",es12.5)') &
                  names(this%sites(ia)), names(this%sites(ib)), &
                  this%harmonic_bonds_strength(i), &
                  this%harmonic_bonds_length(i)
         end do
      end if

      if (this%num_harmonic_angles > 0) then
         write(ounit, '("|molecule| harmonic angles: U(theta) = k*(theta-theta0)^2")')
         do i = 1, this%num_harmonic_angles
            ia = this%harmonic_angles_sites(1,i)
            ib = this%harmonic_angles_sites(2,i)
            ic = this%harmonic_angles_sites(3,i)
            write(ounit, '("|molecule| - ",a8," ",a8," ",a8," k = ", es12.5," theta0 = ",es12.5)') &
                  names(this%sites(ia)), names(this%sites(ib)), names(this%sites(ic)), &
                  this%harmonic_angles_strength(i), &
                  this%harmonic_angles_angle(i)
         end do
      end if

      if (this%num_dihedrals > 0) then
         write(ounit, '("|molecule| dihedrals: U(phi) = 0.5*(v1(1+cos_phi)+v2(1-cos_2phi)'//&
               '+v3(1+cos_3phi)+v4*(1-cos_4phi))")')
         do i = 1, this%num_dihedrals
            ia = this%dihedrals_sites(1,i)
            ib = this%dihedrals_sites(2,i)
            ic = this%dihedrals_sites(3,i)
            id = this%dihedrals_sites(4,i)
            write(ounit, '("|molecule| - ",a8," ",a8," ",a8," ",a8," v1 = ", es12.5,'//&
                  '" v2 = ",es12.5," v3 = ",es12.5," v4 = ",es12.5)') &
                  names(this%sites(ia)), names(this%sites(ib)), names(this%sites(ic)),&
                  names(this%sites(id)), this%dihedrals_v1(i), this%dihedrals_v2(i), &
                  this%dihedrals_v3(i), this%dihedrals_v4(i)
         end do
      end if

      if (this%num_born_repulsions > 0) then
         write(ounit, '("|molecule| born repulsions: U(b) = k*exp(-b*eta)")')
         do i = 1, this%num_born_repulsions
            ia = this%born_repulsions_sites(1,i)
            ib = this%born_repulsions_sites(2,i)
            write(ounit, '("|molecule| - ",a8," ",a8," k = ", es12.5," eta = ",es12.5)') &
                  names(this%sites(ia)), names(this%sites(ib)), &
                  this%born_repulsions_energy(i), &
                  this%born_repulsions_eta(i)
         end do
      end if

      if (this%num_internal_coulomb > 0) then
         write(ounit, '("|molecule| internal Coulomb: U(b) = qi*qj/b")')
         do i = 1, this%num_internal_coulomb
            ia = this%internal_coulomb_sites(1,i)
            ib = this%internal_coulomb_sites(2,i)
            write(ounit, '("|molecule| - ",a8," ",a8)') &
                  names(this%sites(ia)), names(this%sites(ib))
         end do
      end if

      if (this%num_impropers > 0) then
         write(ounit, '("|molecule| impropers: U(phi) = 0.5*(v1(1+cos_phi)+v2(1-cos_2phi)+'//&
               'v3(1+cos_3phi)+v4*(1-cos_4phi))")')
         do i = 1, this%num_impropers
            ia = this%impropers_sites(1,i)
            ib = this%impropers_sites(2,i)
            ic = this%impropers_sites(3,i)
            id = this%impropers_sites(4,i)
            write(ounit, '("|molecule| - ",a8," ",a8," ",a8," ",a8," v1 = ", es12.5," v2 = ",'//&
                  'es12.5," v3 = ",es12.5," v4 = ",es12.5)') &
                  names(this%sites(ia)), names(this%sites(ib)), names(this%sites(ic)), &
                  names(this%sites(id)), this%impropers_v1(i), this%impropers_v2(i), &
                  this%impropers_v3(i), this%impropers_v4(i)
         end do
      end if

      if (this%fourth_site) then
         write(ounit, '("|molecule| fourth_site atom")')
         write(ounit, '("|molecule| WARNING site order should be 1:O 2:H 3:H 4:M")')
      end if

   end subroutine print_type

   ! ================================================================================
   subroutine set_rattle_parameters(this, tolerance, max_iteration)
      use MW_kinds, only: wp
      ! Parameters
      ! ----------
      type(MW_molecule_t), intent(inout) :: this
      real(wp), intent(in) :: tolerance
      integer, intent(in) :: max_iteration

      this%constraint_algorithm = CONSTRAINT_ALGORITHM_RATTLE
      this%constraint_max_iteration = max_iteration
      this%constraint_tolerance = tolerance
   end subroutine set_rattle_parameters

   ! ========================================================================
   ! Define the parameters for a linear molecule
   subroutine set_linear_parameters(molecule, siteA, siteB)
      use MW_errors, only: MW_errors_runtime_error => runtime_error
      implicit none
      ! Passed in
      ! ---------
      type(MW_molecule_t), intent(inout) :: molecule
      integer, intent(in) :: siteA
      integer, intent(in) :: siteB

      ! Local
      ! -----
      integer :: num_basis, num_other, num_basis_pairs
      integer :: i, iA, iB
      integer :: siteC
      real(wp) :: bond_lengths(3,3)
      integer :: ierr
      if (molecule%num_sites /= 3) then
         call MW_errors_runtime_error("define_type", "linear_molecule.f90", &
               "Linear molecule must have 3 atoms exactly.")
      end if
      if (molecule%num_constraints /= 3) then
         call MW_errors_runtime_error("define_type", "linear_molecule.f90", &
               "Linear molecule must have 3 constraints exactly.")
      end if

      num_basis = 2
      num_other = 1
      num_basis_pairs = 1
      allocate(molecule%basis_sites(num_basis), &
            molecule%basis_constrained_sites(2,num_basis_pairs), &
            molecule%basis_constrained_dr(num_basis_pairs), &
            molecule%other_sites(num_other), &
            molecule%other_sites_coordinates(num_basis,num_other), stat=ierr)


      bond_lengths(:,:) = 0.0_wp
      do i = 1, molecule%num_constraints
         iA = molecule%constrained_sites(1,i)
         iB = molecule%constrained_sites(2,i)
         bond_lengths(iA,iB) = molecule%constrained_dr(i)
         bond_lengths(iB,iA) = molecule%constrained_dr(i)
      end do

      if (siteA < siteB) then
         molecule%basis_sites(1) = siteA
         molecule%basis_sites(2) = siteB
      else
         molecule%basis_sites(1) = siteB
         molecule%basis_sites(2) = siteA
      end if

      molecule%basis_constrained_sites(1,1) = siteA
      molecule%basis_constrained_sites(2,1) = siteB
      molecule%basis_constrained_dr(1) = bond_lengths(siteA, siteB)

      do siteC = 1, 3
         if ((siteC /= siteA) .and. (siteC /= siteB)) exit
      end do
      molecule%other_sites(1) = siteC

      select case (siteC)
      case (1)
         ! C-A-B
         molecule%other_sites_coordinates(1,1) = +bond_lengths(siteC,siteB) / bond_lengths(siteA, siteB)
         molecule%other_sites_coordinates(2,1) = -bond_lengths(siteC,siteA) / bond_lengths(siteA, siteB)
      case (2)
         ! A-C-B
         molecule%other_sites_coordinates(1,1) = bond_lengths(siteC,siteB) / bond_lengths(siteA, siteB)
         molecule%other_sites_coordinates(2,1) = bond_lengths(siteC,siteA) / bond_lengths(siteA, siteB)
      case (3)
         ! A-B-C
         molecule%other_sites_coordinates(1,1) = -bond_lengths(siteC,siteB) / bond_lengths(siteA, siteB)
         molecule%other_sites_coordinates(2,1) = +bond_lengths(siteC,siteA) / bond_lengths(siteA, siteB)
      end select

      molecule%linear = .true.
      molecule%constraint_algorithm = CONSTRAINT_ALGORITHM_LINEAR
   end subroutine set_linear_parameters


   ! ==============================================================================
   ! Prints the positions of the molecule sites
   subroutine print_trajectories(this, ounit, ions, offset_ions, xyz_ions, velocity_ions)
      use MW_kinds, only: wp
      use MW_ion, only: MW_ion_t
      implicit none
      type(MW_molecule_t), intent(in) :: this  ! structure to be printed
      integer, intent(in)             :: ounit ! output unit
      type(MW_ion_t), intent(in) :: ions(:)
      integer, intent(in) :: offset_ions(:)
      real(wp), intent(in) :: xyz_ions(:,:)
      real(wp), intent(in) :: velocity_ions(:,:)

      integer :: imol, isite, itype,iion

      do imol = 1, this%n
         do isite = 1, this%num_sites
            itype = this%sites(isite)
            iion = offset_ions(itype) + imol
            write(ounit,'("(",i5,")",a8,6(1x,es23.15))') imol, ions(itype)%name, &
                  xyz_ions(iion,1), xyz_ions(iion,2), xyz_ions(iion,3), &
                  velocity_ions(iion,1), velocity_ions(iion,2), velocity_ions(iion,3)
         end do
      end do
   end subroutine print_trajectories

   ! ================================================================================
   !> Set the index of the molecule
   subroutine set_index(this, index)
      implicit none
      type(MW_molecule_t), intent(inout) :: this
      integer, intent(in) :: index
      this%index = index
   end subroutine set_index

   ! ================================================================================
   !> Set the species type index for the molecule
   !!
   !! Map the molecule to the site species index
   !! Map the site species to the molecule
   !! Set molecule count from site species count
   subroutine set_sites_index(this, species)
      use MW_species, only: MW_species_t, &
            MW_species_identify => identify, &
            MW_species_set_molecule => set_molecule
      use MW_errors, only: MW_errors_runtime_error => runtime_error
      implicit none
      ! Parameters
      ! ----------
      type(MW_molecule_t), intent(inout) :: this
      type(MW_species_t),  intent(inout) :: species(:) !< array of species

      ! Local
      ! -----
      integer :: isite
      integer :: species_index
      character(256) :: errmsg

      do isite = 1, this%num_sites
         call MW_species_identify(this%site_names(isite), species, species_index)
         if (species_index > 0) then
            this%sites(isite) = species_index
            call MW_species_set_molecule(species(species_index), this%name, this%index)
            if (this%n /= species(species_index)%count) then
               write(errmsg,'("In molecule ",a8," invalid count for species,",a8,", got ",'//&
                     'i5," expected ", i5)') this%name, this%site_names(isite), &
                     species(species_index)%count, this%n
               call MW_errors_runtime_error("set_species","electrode_parameters.f90", &
                     errmsg)
            end if
         else
            write(errmsg,'("Unknown species name,",a8,", for molecule ",a8)') &
                  this%site_names(isite), this%name
            call MW_errors_runtime_error("set_species","electrode_parameters.f90", &
                  errmsg)
         end if
      end do
   end subroutine set_sites_index

   ! ================================================================================
   ! Read molecule type parameters
   !
   !   molecule_type *block*
   !     name *string*
   !     count *int*
   !     sites *string* [*string* ...]
   !
   !     constraint *string* *string* *real*
   !     [constraint *string* *string* *real*
   !         ...]
   !     [constraints_algorithm *string* *parameters depend on algorithm name*]
   !
   !     harmonic_bond *string* *string* *real* *real*
   subroutine read_parameters(this, funit, line_num)
      use MW_configuration_line, only: MW_configuration_line_t, &
            MW_configuration_line_seek_next_data => seek_next_data, &
            MW_configuration_line_get_word => get_word, &
            max_word_length
      use MW_kinds, only: wp, PARTICLE_NAME_LEN
      use MW_errors, only: MW_errors_runtime_error => runtime_error, &
            MW_errors_allocate_error => allocate_error
      implicit none
      ! Parameters
      ! ----------
      type(MW_molecule_t), intent(inout) :: this
      integer, intent(in) :: funit
      integer, intent(inout) :: line_num

      ! Local
      ! -----
      integer :: ierr
      character(256) :: errmsg
      type(MW_configuration_line_t) :: config_line
      character(max_word_length) :: keyword
      character(len=PARTICLE_NAME_LEN) :: name
      integer :: count
      integer :: num_sites, num_constraints, num_harmonic_bonds, num_harmonic_angles,&
            num_dihedrals, num_impropers, num_born_repulsions, num_internal_coulomb
      character(len=PARTICLE_NAME_LEN), allocatable :: site_names(:)
      character(len=PARTICLE_NAME_LEN) :: siteA, siteB, siteC, siteD
      integer :: name_defined, count_defined, sites_defined, constraints_defined, &
            algorithm_defined, harmonic_bonds_defined, harmonic_angles_defined, &
            dihedrals_defined, impropers_defined, born_repulsions_defined, &
            internal_coulomb_defined, fourth_site_defined
      character(max_word_length) :: algorithm
      integer :: max_iteration
      real(wp) :: tolerance
      integer :: isiteA, isiteB, isiteC,isiteD, iconstraint, ibond, iangle, isite, idihedral,&
            iimproper, iborn, iintcoul
      real(wp) :: dr, strength, length, v1, v2, v3, v4, fourth_distance
      integer :: last_line
      integer :: basis_siteA, basis_siteB
      logical :: fourth_site

      ! Void data structure
      call void_type(this)

      ! Set default values
      name = "****"
      count = 0
      num_sites = 0
      num_constraints = 0
      num_harmonic_bonds = 0
      num_harmonic_angles = 0
      num_born_repulsions = 0
      num_internal_coulomb = 0
      num_dihedrals = 0
      num_impropers = 0
      algorithm = "rattle"
      max_iteration = 100
      tolerance = 1.0e-6_wp
      fourth_site = .false.
      fourth_distance = 0.0_wp

      ! Flag each section as undefined (line number will be stored)
      name_defined = 0
      count_defined = 0
      sites_defined = 0
      constraints_defined = 0
      algorithm_defined = 0
      harmonic_bonds_defined = 0
      harmonic_angles_defined = 0
      born_repulsions_defined = 0
      internal_coulomb_defined = 0
      dihedrals_defined = 0
      impropers_defined = 0
      fourth_site_defined = 0

      call MW_configuration_line_seek_next_data(config_line, funit, line_num)
      keywordLoop: do while(config_line%num_words > 0)

         ! Look for keyword and read corresponding value
         call MW_configuration_line_get_word(config_line, 1, keyword)

         select case(keyword)

         case("name")
            if (name_defined > 0) then
               write(errmsg,'("At line ",i4,", redefinition of |name|. First definition at line ",i4,".")') &
                     line_num, name_defined
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", errmsg)
            end if
            name_defined = line_num

            ! Read name parameters
            if (config_line%num_words /= 2) then
               write(errmsg,'("Invalid format for name parameters at line ",i4)') line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if
            call MW_configuration_line_get_word(config_line, 2, name)

         case("count")
            if (count_defined > 0) then
               write(errmsg,'("At line ",i4,", redefinition of |count|. First definition at line ",i4,".")') &
                     line_num, count_defined
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", errmsg)
            end if
            count_defined = line_num

            ! Read count parameters
            if (config_line%num_words /= 2) then
               write(errmsg,'("Invalid format for count parameters at line ",i4)') line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if
            call MW_configuration_line_get_word(config_line, 2, count)

         case("sites")
            if (sites_defined > 0) then
               write(errmsg,'("At line ",i4,", redefinition of |sites|. First definition at line ",i4,".")') &
                     line_num, sites_defined
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", errmsg)
            end if
            sites_defined = line_num

            ! Read sites parameters
            num_sites = config_line%num_words - 1
            allocate(site_names(num_sites), stat=ierr)
            if (ierr /= 0) then
               call MW_errors_allocate_error("read_molecule_type_parameters", "configuration.f90", ierr)
            end if
            do isite = 1, num_sites
               call MW_configuration_line_get_word(config_line, isite+1, site_names(isite))
            end do

         case("fourth_site")
            if (fourth_site_defined > 0) then
               write(errmsg,'("At line ",i4,", redefinition of |fourth_site|. First definition at line ",i4,".")') &
                     line_num, fourth_site_defined
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", errmsg)
            end if
            fourth_site_defined = line_num

            ! Read fourth_site parameters
            if (config_line%num_words /= 2) then
               write(errmsg,'("Invalid format for fourth_site parameters at line ",i4)') line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if
            fourth_site = .true.
            call MW_configuration_line_get_word(config_line, 2, fourth_distance)

         case("constraint")
            if (constraints_defined > 0) then
               write(errmsg,'("At line ",i4,", redefinition of |constraints|. First definition at line ",i4,".")') &
                     line_num, constraints_defined
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", errmsg)
            end if
            constraints_defined = line_num

            num_constraints = 1
            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            do while (keyword == "constraint")
               num_constraints = num_constraints + 1
               call MW_configuration_line_seek_next_data(config_line, funit, line_num)
               call MW_configuration_line_get_word(config_line, 1, keyword)
            end do
            ! backspace just before 1st keyword which is not "constraint"
            backspace(funit)
            line_num = line_num - 1

         case("constraints_algorithm")
            if (algorithm_defined > 0) then
               write(errmsg,'("At line ",i4,", redefinition of |constraints_algorithm|. First definition at line ",i4,".")') &
                     line_num, algorithm_defined
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", errmsg)
            end if
            algorithm_defined = line_num

            ! Read name parameters
            if (config_line%num_words < 2) then
               write(errmsg,'("Invalid format for algorithm parameters at line ",i4)') line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if
            call MW_configuration_line_get_word(config_line, 2, algorithm)

            select case (algorithm)
            case ("rattle")
               if (config_line%num_words /= 4) then
                  write(errmsg,'("Invalid format for algotithm parameters at line ",i4)') line_num
                  call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                        errmsg)
               end if
               call MW_configuration_line_get_word(config_line, 3, tolerance)
               call MW_configuration_line_get_word(config_line, 4, max_iteration)
            case("linear")
               if (config_line%num_words /= 4) then
                  write(errmsg,'("Invalid format for algotithm parameters at line ",i4)') line_num
                  call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                        errmsg)
               end if
               call MW_configuration_line_get_word(config_line, 3, siteA)
               basis_siteA = 0
               do isite = 1, num_sites
                  if (siteA == site_names(isite)) then
                     basis_siteA = isite
                     exit
                  end if
               end do
               if (basis_siteA == 0) then
                  write(errmsg,'("Invalid site ",a," in linear basis specification for molecule parameters at line ",i4)') &
                        siteA, line_num
                  call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                        errmsg)
               end if

               call MW_configuration_line_get_word(config_line, 4, siteB)
               basis_siteB = 0
               do isite = 1, num_sites
                  if (siteB == site_names(isite)) then
                     basis_siteB = isite
                     exit
                  end if
               end do
               if (basis_siteB == 0) then
                  write(errmsg,'("Invalid site ",a," in linear basis specification for molecule parameters at line ",i4)') &
                        siteB, line_num
                  call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                        errmsg)
               end if

            case default
               write(errmsg,'("Invalid algorithm keyword at line ",i4)') line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end select

         case("harmonic_bond")
            if (harmonic_bonds_defined > 0) then
               write(errmsg,'("At line ",i4,", redefinition of |harmonic_bond|. First definition at line ",i4,".")') &
                     line_num, harmonic_bonds_defined
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", errmsg)
            end if
            harmonic_bonds_defined = line_num

            num_harmonic_bonds = 1
            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            do while (keyword == "harmonic_bond")
               num_harmonic_bonds = num_harmonic_bonds + 1
               call MW_configuration_line_seek_next_data(config_line, funit, line_num)
               call MW_configuration_line_get_word(config_line, 1, keyword)
            end do
            ! backspace just before 1st keyword which is not "constraint"
            backspace(funit)
            line_num = line_num - 1

         case("harmonic_angle")
            if (harmonic_angles_defined > 0) then
               write(errmsg,'("At line ",i4,", redefinition of |harmonic_angle|. First definition at line ",i4,".")') &
                     line_num, harmonic_angles_defined
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", errmsg)
            end if
            harmonic_angles_defined = line_num

            num_harmonic_angles = 1
            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            do while (keyword == "harmonic_angle")
               num_harmonic_angles = num_harmonic_angles + 1
               call MW_configuration_line_seek_next_data(config_line, funit, line_num)
               call MW_configuration_line_get_word(config_line, 1, keyword)
            end do
            ! backspace just before 1st keyword which is not "constraint"
            backspace(funit)
            line_num = line_num - 1

         case("born_repulsion")
            if (born_repulsions_defined > 0) then
               write(errmsg,'("At line ",i4,", redefinition of |born_repulsion|. First definition at line ",i4,".")') &
                     line_num, born_repulsions_defined
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", errmsg)
            end if
            born_repulsions_defined = line_num

            num_born_repulsions = 1
            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            do while (keyword == "born_repulsion")
               num_born_repulsions = num_born_repulsions + 1
               call MW_configuration_line_seek_next_data(config_line, funit, line_num)
               call MW_configuration_line_get_word(config_line, 1, keyword)
            end do
            ! backspace just before 1st keyword which is not "born_repulsion"
            backspace(funit)
            line_num = line_num - 1

         case("internal_coulomb")
            if (internal_coulomb_defined > 0) then
               write(errmsg,'("At line ",i4,", redefinition of |internal_coulomb|. First definition at line ",i4,".")') &
                     line_num, internal_coulomb_defined
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", errmsg)
            end if
            internal_coulomb_defined = line_num

            num_internal_coulomb = 1
            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            do while (keyword == "internal_coulomb")
               num_internal_coulomb = num_internal_coulomb + 1
               call MW_configuration_line_seek_next_data(config_line, funit, line_num)
               call MW_configuration_line_get_word(config_line, 1, keyword)
            end do
            ! backspace just before 1st keyword which is not "internal_coulomb"
            backspace(funit)
            line_num = line_num - 1

         case("dihedral")
            if (dihedrals_defined > 0) then
               write(errmsg,'("At line ",i4,", redefinition of |dihedrals|. First definition at line ",i4,".")') &
                     line_num, dihedrals_defined
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", errmsg)
            end if
            dihedrals_defined = line_num

            num_dihedrals = 1
            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            do while (keyword == "dihedral")
               num_dihedrals = num_dihedrals + 1
               call MW_configuration_line_seek_next_data(config_line, funit, line_num)
               call MW_configuration_line_get_word(config_line, 1, keyword)
            end do
            ! backspace just before 1st keyword which is not "constraint"
            backspace(funit)
            line_num = line_num - 1

         case("improper")
            if (impropers_defined > 0) then
               write(errmsg,'("At line ",i4,", redefinition of |impropers|. First definition at line ",i4,".")') &
                     line_num, impropers_defined
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", errmsg)
            end if
            impropers_defined = line_num

            num_impropers = 1
            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            do while (keyword == "improper")
               num_impropers = num_impropers + 1
               call MW_configuration_line_seek_next_data(config_line, funit, line_num)
               call MW_configuration_line_get_word(config_line, 1, keyword)
            end do
            ! backspace just before 1st keyword which is not "constraint"
            backspace(funit)
            line_num = line_num - 1

         case default
            ! Keyword not expected in molecule section assume end of box section
            ! Backspace to previous line and give hand back to parent box
            backspace(funit)
            line_num = line_num - 1
            exit keywordLoop

         end select

         call MW_configuration_line_seek_next_data(config_line, funit, line_num)
      end do keywordLoop
      last_line = line_num

      ! Define data structure
      call define_type(this, name, count, num_sites, num_constraints, &
            num_harmonic_bonds, num_harmonic_angles, num_dihedrals, num_impropers, &
            num_born_repulsions, num_internal_coulomb, fourth_site, fourth_distance)

      ! Setup sites
      do isite = 1, num_sites
         this%site_names(isite) = site_names(isite)
      end do

      if (num_constraints > 0) then

         ! Backspace to 1st constraint
         do while (line_num >= constraints_defined)
            backspace(funit, iostat=ierr)
            if (ierr /= 0) then
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", &
                     "An error while backspace back to beginning of constraints section.")
            end if
            line_num = line_num-1
         end do
      
         ! Read constraints
         do iconstraint = 1, num_constraints

            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            if (keyword /= "constraint") then
               write(errmsg,'("Expected |constraint| keyword for molecule parameters at line ",i4)') line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if
            if (config_line%num_words /= 4) then
               write(errmsg,'("Invalid constraint specification for molecule parameters at line ",i4)') &
                     line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 1st site in constraint
            call MW_configuration_line_get_word(config_line, 2, siteA)
            do isite = 1, num_sites
               if (siteA == site_names(isite)) then
                  this%constrained_sites(1,iconstraint) = isite
                  exit
               end if
            end do
            if (this%constrained_sites(1,iconstraint) == 0) then
               write(errmsg,'("Invalid site ",a," in constraint specification for molecule parameters at line ",i4)') &
                     siteA, line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 2nd site in constraint
            call MW_configuration_line_get_word(config_line, 3, siteB)
            do isite = 1, num_sites
               if (siteB == site_names(isite)) then
                  this%constrained_sites(2,iconstraint) = isite
                  exit
               end if
            end do
            if (this%constrained_sites(2,iconstraint) == 0) then
               write(errmsg,'("Unknown ion type ",a," in constraint specification for molecule parameters at line ",i4)') &
                     siteB, line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify constrained distance
            call MW_configuration_line_get_word(config_line, 4, dr)
            this%constrained_dr(iconstraint) = dr

            ! Pairs involved in a constraint do not interact via short range potentials
            isiteA = this%constrained_sites(1,iconstraint)
            isiteB = this%constrained_sites(2,iconstraint)
            this%interacting_pairs(isiteA,isiteB) = .false.
            this%interacting_pairs(isiteB,isiteA) = .false.

         end do

         ! Go back to end of molecule type definition
         do while (line_num < last_line)
            read(funit, *)
            line_num = line_num+1
         end do
      end if

      if (num_harmonic_bonds > 0) then
         ! Backspace to 1st harmonic bond definition
         do while (line_num >= harmonic_bonds_defined)
            backspace(funit, iostat=ierr)
            if (ierr /= 0) then
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", &
                     "An error while backspace back to beginning of harmonic bonds section.")
            end if
            line_num = line_num-1
         end do

         ! Read bonds
         do ibond = 1, num_harmonic_bonds

            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            if (keyword /= "harmonic_bond") then
               write(errmsg,'("Expected |harmonic_bond| keyword for molecule parameters at line ",i4)') line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if
            if (config_line%num_words /= 5) then
               write(errmsg,'("Invalid harmonic bond specification for molecule parameters at line ",i4)') &
                     line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 1st site in constraint
            call MW_configuration_line_get_word(config_line, 2, siteA)
            do isite = 1, num_sites
               if (siteA == site_names(isite)) then
                  this%harmonic_bonds_sites(1,ibond) = isite
                  exit
               end if
            end do
            if (this%harmonic_bonds_sites(1,ibond) == 0) then
               write(errmsg,'("Invalid site ",a," in harmonic bond specification for molecule parameters at line ",i4)') &
                     siteA, line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 2nd site in constraint
            call MW_configuration_line_get_word(config_line, 3, siteB)
            do isite = 1, num_sites
               if (siteB == site_names(isite)) then
                  this%harmonic_bonds_sites(2,ibond) = isite
                  exit
               end if
            end do
            if (this%harmonic_bonds_sites(2,ibond) == 0) then
               write(errmsg,'("Unknown ion type ",a," in harmonic bond specification for molecule parameters at line ",i4)') &
                     siteB, line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify constrained distance
            call MW_configuration_line_get_word(config_line, 4, strength)
            this%harmonic_bonds_strength(ibond) = strength

            call MW_configuration_line_get_word(config_line, 5, length)
            this%harmonic_bonds_length(ibond) = length

            ! Pairs involved in a constraint do not interact via short range potentials
            isiteA = this%harmonic_bonds_sites(1,ibond)
            isiteB = this%harmonic_bonds_sites(2,ibond)
            this%interacting_pairs(isiteA,isiteB) = .false.
            this%interacting_pairs(isiteB,isiteA) = .false.

         end do

         ! Go back to end of molecule type definition
         do while (line_num < last_line)
            read(funit, *)
            line_num = line_num+1
         end do
      end if

      if (num_harmonic_angles > 0) then
         ! Backspace to 1st harmonic bond definition
         do while (line_num >= harmonic_angles_defined)
            backspace(funit, iostat=ierr)
            if (ierr /= 0) then
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", &
                     "An error while backspace back to beginning of harmonic angles section.")
            end if
            line_num = line_num-1
         end do

         ! Read constraints
         do iangle = 1, num_harmonic_angles

            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            if (keyword /= "harmonic_angle") then
               write(errmsg,'("Expected |harmonic_angle| keyword for molecule parameters at line ",i4)') line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if
            if (config_line%num_words /= 6) then
               write(errmsg,'("Invalid harmonic angle specification for molecule parameters at line ",i4)') &
                     line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 1st site in constraint
            call MW_configuration_line_get_word(config_line, 2, siteA)
            do isite = 1, num_sites
               if (siteA == site_names(isite)) then
                  this%harmonic_angles_sites(1,iangle) = isite
                  exit
               end if
            end do
            if (this%harmonic_angles_sites(1,iangle) == 0) then
               write(errmsg,'("Invalid site ",a," in harmonic angle specification for molecule parameters at line ",i4)') &
                     siteA, line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 2nd site in constraint
            call MW_configuration_line_get_word(config_line, 3, siteB)
            do isite = 1, num_sites
               if (siteB == site_names(isite)) then
                  this%harmonic_angles_sites(2,iangle) = isite
                  exit
               end if
            end do
            if (this%harmonic_angles_sites(2,iangle) == 0) then
               write(errmsg,'("Unknown ion type ",a," in harmonic angle specification for molecule parameters at line ",i4)') &
                     siteB, line_num
               call MW_errors_runtime_error("read_molecules_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 3rd site in constraint
            call MW_configuration_line_get_word(config_line, 4, siteC)
            do isite = 1, num_sites
               if (siteC == site_names(isite)) then
                  this%harmonic_angles_sites(3,iangle) = isite
                  exit
               end if
            end do
            if (this%harmonic_angles_sites(3,iangle) == 0) then
               write(errmsg,'("Unknown ion type ",a," in harmonic angle specification for molecule parameters at line ",i4)') &
                     siteB, line_num
               call MW_errors_runtime_error("read_molecules_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify constrained distance
            call MW_configuration_line_get_word(config_line, 5, strength)
            this%harmonic_angles_strength(iangle) = strength

            call MW_configuration_line_get_word(config_line, 6, length)
            this%harmonic_angles_angle(iangle) = length

            ! Pairs involved in a constraint do not interact via short range potentials
            isiteA = this%harmonic_angles_sites(1,iangle)
            isiteB = this%harmonic_angles_sites(2,iangle)
            isitec = this%harmonic_angles_sites(3,iangle)
            this%interacting_pairs(isiteA,isiteB) = .false.
            this%interacting_pairs(isiteB,isiteA) = .false.
            this%interacting_pairs(isiteA,isiteC) = .false.
            this%interacting_pairs(isiteC,isiteA) = .false.
            this%interacting_pairs(isiteB,isiteC) = .false.
            this%interacting_pairs(isiteC,isiteB) = .false.

         end do

         ! Go back to end of molecule type definition
         do while (line_num < last_line)
            read(funit, *)
            line_num = line_num+1
         end do
      end if

      if (num_dihedrals > 0) then
         ! Backspace to 1st dihedral definition
         do while (line_num >= dihedrals_defined)
            backspace(funit, iostat=ierr)
            if (ierr /= 0) then
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", &
                     "An error while backspace back to beginning of dihedrals section.")
            end if
            line_num = line_num-1
         end do
         do idihedral = 1, num_dihedrals

            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            if (keyword /= "dihedral") then
               write(errmsg,'("Expected |dihedral| keyword for molecule parameters at line ",i4)') line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if
            if (config_line%num_words /= 9) then
               write(errmsg,'("Invalid dihedral specification for molecule parameters at line ",i4)') &
                     line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 1st site in constraint
            call MW_configuration_line_get_word(config_line, 2, siteA)
            do isite = 1, num_sites
               if (siteA == site_names(isite)) then
                  this%dihedrals_sites(1,idihedral) = isite
                  exit
               end if
            end do
            if (this%dihedrals_sites(1,idihedral) == 0) then
               write(errmsg,'("Invalid site ",a," in dihedral specification for molecule parameters at line ",i4)') &
                     siteA, line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 2nd site in constraint
            call MW_configuration_line_get_word(config_line, 3, siteB)
            do isite = 1, num_sites
               if (siteB == site_names(isite)) then
                  this%dihedrals_sites(2,idihedral) = isite
                  exit
               end if
            end do
            if (this%dihedrals_sites(2,idihedral) == 0) then
               write(errmsg,'("Invalid site ",a," in dihedral specification for molecule parameters at line ",i4)') &
                     siteB, line_num
               call MW_errors_runtime_error("read_molecules_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 3rd site in constraint
            call MW_configuration_line_get_word(config_line, 4, siteC)
            do isite = 1, num_sites
               if (siteC == site_names(isite)) then
                  this%dihedrals_sites(3,idihedral) = isite
                  exit
               end if
            end do
            if (this%dihedrals_sites(3,idihedral) == 0) then
               write(errmsg,'("Invalid site ",a," in dihedral specification for molecule parameters at line ",i4)') &
                     siteC, line_num
               call MW_errors_runtime_error("read_molecules_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 4th site in constraint
            call MW_configuration_line_get_word(config_line, 5, siteD)
            do isite = 1, num_sites
               if (siteD == site_names(isite)) then
                  this%dihedrals_sites(4,idihedral) = isite
                  exit
               end if
            end do
            if (this%dihedrals_sites(4,idihedral) == 0) then
               write(errmsg,'("Invalid site ",a," in dihedral specification for molecule parameters at line ",i4)') &
                     siteD, line_num
               call MW_errors_runtime_error("read_molecules_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify constrained distance
            call MW_configuration_line_get_word(config_line, 6, v1)
            this%dihedrals_v1(idihedral) = v1

            call MW_configuration_line_get_word(config_line, 7, v2)
            this%dihedrals_v2(idihedral) = v2

            call MW_configuration_line_get_word(config_line, 8, v3)
            this%dihedrals_v3(idihedral) = v3

            call MW_configuration_line_get_word(config_line, 9, v4)
            this%dihedrals_v4(idihedral) = v4

            ! Pairs involved in a constraint do not interact via short range potentials
            isiteA = this%dihedrals_sites(1,idihedral)
            isiteB = this%dihedrals_sites(2,idihedral)
            isiteC = this%dihedrals_sites(3,idihedral)
            isiteD = this%dihedrals_sites(4,idihedral)
            this%interacting_pairs(isiteA,isiteB) = .false.
            this%interacting_pairs(isiteB,isiteA) = .false.
            this%interacting_pairs(isiteA,isiteC) = .false.
            this%interacting_pairs(isiteC,isiteA) = .false.
            if(this%interacting_pairs(isiteA,isiteD))this%halfinteracting_pairs(isiteA,isiteD) = .true.
            if(this%interacting_pairs(isiteD,isiteA))this%halfinteracting_pairs(isiteD,isiteA) = .true.
            this%interacting_pairs(isiteB,isiteC) = .false.
            this%interacting_pairs(isiteC,isiteB) = .false.
            this%interacting_pairs(isiteB,isiteD) = .false.
            this%interacting_pairs(isiteD,isiteB) = .false.
            this%interacting_pairs(isiteC,isiteD) = .false.
            this%interacting_pairs(isiteD,isiteC) = .false.

         end do

         ! Go back to end of molecule type definition
         do while (line_num < last_line)
            read(funit, *)
            line_num = line_num+1
         end do
      end if

      if (num_impropers > 0) then
         ! Backspace to 1st improper definition
         do while (line_num >= impropers_defined)
            backspace(funit, iostat=ierr)
            if (ierr /= 0) then
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", &
                     "An error while backspace back to beginning of impropers section.")
            end if
            line_num = line_num-1
         end do

         ! Read constraints
         do iimproper = 1, num_impropers

            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            if (keyword /= "improper") then
               write(errmsg,'("Expected |improper| keyword for molecule parameters at line ",i4)') line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if
            if (config_line%num_words /= 9) then
               write(errmsg,'("Invalid improper specification for molecule parameters at line ",i4)') &
                     line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 1st site in constraint
            call MW_configuration_line_get_word(config_line, 2, siteA)
            do isite = 1, num_sites
               if (siteA == site_names(isite)) then
                  this%impropers_sites(1,iimproper) = isite
                  exit
               end if
            end do
            if (this%impropers_sites(1,iimproper) == 0) then
               write(errmsg,'("Invalid site ",a," in improper specification for molecule parameters at line ",i4)') &
                     siteA, line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 2nd site in constraint
            call MW_configuration_line_get_word(config_line, 3, siteB)
            do isite = 1, num_sites
               if (siteB == site_names(isite)) then
                  this%impropers_sites(2,iimproper) = isite
                  exit
               end if
            end do
            if (this%impropers_sites(2,iimproper) == 0) then
               write(errmsg,'("Invalid site ",a," in improper specification for molecule parameters at line ",i4)') &
                     siteB, line_num
               call MW_errors_runtime_error("read_molecules_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 3rd site in constraint
            call MW_configuration_line_get_word(config_line, 4, siteC)
            do isite = 1, num_sites
               if (siteC == site_names(isite)) then
                  this%impropers_sites(3,iimproper) = isite
                  exit
               end if
            end do
            if (this%impropers_sites(3,iimproper) == 0) then
               write(errmsg,'("Invalid site ",a," in improper specification for molecule parameters at line ",i4)') &
                     siteC, line_num
               call MW_errors_runtime_error("read_molecules_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 4th site in constraint
            call MW_configuration_line_get_word(config_line, 5, siteD)
            do isite = 1, num_sites
               if (siteD == site_names(isite)) then
                  this%impropers_sites(4,iimproper) = isite
                  exit
               end if
            end do
            if (this%impropers_sites(4,iimproper) == 0) then
               write(errmsg,'("Invalid site ",a," in improper specification for molecule parameters at line ",i4)') &
                     siteD, line_num
               call MW_errors_runtime_error("read_molecules_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify constrained distance
            call MW_configuration_line_get_word(config_line, 6, v1)
            this%impropers_v1(iimproper) = v1

            call MW_configuration_line_get_word(config_line, 7, v2)
            this%impropers_v2(iimproper) = v2

            call MW_configuration_line_get_word(config_line, 8, v3)
            this%impropers_v3(iimproper) = v3

            call MW_configuration_line_get_word(config_line, 9, v4)
            this%impropers_v4(iimproper) = v4

            ! Pairs involved in a constraint do not interact via short range potentials
            isiteA = this%impropers_sites(1,iimproper)
            isiteB = this%impropers_sites(2,iimproper)
            isiteC = this%impropers_sites(3,iimproper)
            isiteD = this%impropers_sites(4,iimproper)
            this%interacting_pairs(isiteA,isiteB) = .false.
            this%interacting_pairs(isiteB,isiteA) = .false.
            this%interacting_pairs(isiteA,isiteC) = .false.
            this%interacting_pairs(isiteC,isiteA) = .false.
            this%interacting_pairs(isiteA,isiteD) = .false.
            this%interacting_pairs(isiteD,isiteA) = .false.
            this%interacting_pairs(isiteB,isiteC) = .false.
            this%interacting_pairs(isiteC,isiteB) = .false.
            this%interacting_pairs(isiteB,isiteD) = .false.
            this%interacting_pairs(isiteD,isiteB) = .false.
            this%interacting_pairs(isiteC,isiteD) = .false.
            this%interacting_pairs(isiteD,isiteC) = .false.

         end do

         ! Go back to end of molecule type definition
         do while (line_num < last_line)
            read(funit, *)
            line_num = line_num+1
         end do
      end if

      if (num_born_repulsions > 0) then
         ! Backspace to 1st harmonic bond definition
         do while (line_num >= born_repulsions_defined)
            backspace(funit, iostat=ierr)
            if (ierr /= 0) then
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", &
                     "An error while backspace back to beginning of born repulsions section.")
            end if
            line_num = line_num-1
         end do

         ! Read constraints
         do iborn = 1, num_born_repulsions

            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            if (keyword /= "born_repulsion") then
               write(errmsg,'("Expected |born_repulsion| keyword for molecule parameters at line ",i4)') line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if
            if (config_line%num_words /= 5) then
               write(errmsg,'("Invalid born repulsion specification for molecule parameters at line ",i4)') &
                     line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 1st site
            call MW_configuration_line_get_word(config_line, 2, siteA)
            do isite = 1, num_sites
               if (siteA == site_names(isite)) then
                  this%born_repulsions_sites(1, iborn) = isite
                  exit
               end if
            end do

            if (this%born_repulsions_sites(1,iborn) == 0) then
               write(errmsg,'("Invalid site ",a," in born_repulsion specification for molecule parameters at line ",i4)') &
                     siteA, line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 2nd site
            call MW_configuration_line_get_word(config_line, 3, siteB)
            do isite = 1, num_sites
               if (siteB == site_names(isite)) then
                  this%born_repulsions_sites(2,iborn) = isite
                  exit
               end if
            end do
            if (this%born_repulsions_sites(2,iborn) == 0) then
               write(errmsg,'("Unknown ion type ",a," in born repulsion specification for molecule parameters at line ",i4)') &
                     siteB, line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify constrained distance
            call MW_configuration_line_get_word(config_line, 4, strength)
            this%born_repulsions_energy(iborn) = strength

            call MW_configuration_line_get_word(config_line, 5, length)
            this%born_repulsions_eta(iborn) = length

            ! Pairs involved in a constraint do not interact via short range potentials
            isiteA = this%born_repulsions_sites(1,iborn)
            isiteB = this%born_repulsions_sites(2,iborn)
            this%interacting_pairs(isiteA,isiteB) = .false.
            this%interacting_pairs(isiteB,isiteA) = .false.

         end do

         ! Go back to end of molecule type definition
         do while (line_num < last_line)
            read(funit, *)
            line_num = line_num+1
         end do
      end if

      if (num_internal_coulomb > 0) then
         ! Backspace to 1st harmonic bond definition
         do while (line_num >= internal_coulomb_defined)
            backspace(funit, iostat=ierr)
            if (ierr /= 0) then
               call MW_errors_runtime_error("read_molecule_type_parameters", "configuration.f90", &
                     "An error while backspace back to beginning of internal coulomb section.")
            end if
            line_num = line_num-1
         end do

         ! Read constraints
         do iintcoul = 1, num_internal_coulomb

            call MW_configuration_line_seek_next_data(config_line, funit, line_num)
            call MW_configuration_line_get_word(config_line, 1, keyword)
            if (keyword /= "internal_coulomb") then
               write(errmsg,'("Expected |internal_coulomb| keyword for molecule parameters at line ",i4)') line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if
            if (config_line%num_words /= 3) then
               write(errmsg,'("Invalid internal coulomb specification for molecule parameters at line ",i4)') &
                     line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if


            ! Identify 1st site
            call MW_configuration_line_get_word(config_line, 2, siteA)
            do isite = 1, num_sites
               if (siteA == site_names(isite)) then
                  this%internal_coulomb_sites(1,iintcoul) = isite
                  exit
               end if
            end do
            if (this%internal_coulomb_sites(1,iintcoul) == 0) then
               write(errmsg,'("Invalid site ",a," in internal coulomb specification for molecule parameters at line ",i4)') &
                     siteA, line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Identify 2nd site
            call MW_configuration_line_get_word(config_line, 3, siteB)
            do isite = 1, num_sites
               if (siteB == site_names(isite)) then
                  this%internal_coulomb_sites(2,iintcoul) = isite
                  exit
               end if
            end do
            if (this%internal_coulomb_sites(2,iintcoul) == 0) then
               write(errmsg,'("Unknown ion type ",a," in internal coulomb specification for molecule parameters at line ",i4)') &
                     siteB, line_num
               call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
                     errmsg)
            end if

            ! Pairs involved in a constraint do not interact via short range potentials
            isiteA = this%internal_coulomb_sites(1,iintcoul)
            isiteB = this%internal_coulomb_sites(2,iintcoul)
            this%internal_coulomb_pairs(isiteA,isiteB) = .true.
            this%internal_coulomb_pairs(isiteB,isiteA) = .true.
         end do

         ! Go back to end of molecule type definition
         do while (line_num < last_line)
            read(funit, *)
            line_num = line_num+1
         end do
      end if

      ! Define algorithm parameters
      select case (algorithm)
      case("rattle")
         call set_rattle_parameters(this, tolerance, max_iteration)
      case("linear")
         call set_linear_parameters(this, basis_siteA, basis_siteB)
      case default
         write(errmsg,'("Unknown constraint algorithm ",a," at line ",i4)') &
               algorithm, algorithm_defined
         call MW_errors_runtime_error("read_molecule_type_parameters","configuration.f90", &
               errmsg)
      end select

      if (allocated(site_names)) deallocate(site_names)

   end subroutine read_parameters

end module MW_molecule
