mod_link_atom.F90 Source File


This file depends on

sourcefile~~mod_link_atom.f90~~EfferentGraph sourcefile~mod_link_atom.f90 mod_link_atom.F90 sourcefile~mod_memory.f90 mod_memory.F90 sourcefile~mod_link_atom.f90->sourcefile~mod_memory.f90 sourcefile~mod_topology.f90 mod_topology.F90 sourcefile~mod_link_atom.f90->sourcefile~mod_topology.f90 sourcefile~mod_constants.f90 mod_constants.F90 sourcefile~mod_link_atom.f90->sourcefile~mod_constants.f90 sourcefile~mod_bonded.f90 mod_bonded.F90 sourcefile~mod_link_atom.f90->sourcefile~mod_bonded.f90 sourcefile~mod_nonbonded.f90 mod_nonbonded.F90 sourcefile~mod_link_atom.f90->sourcefile~mod_nonbonded.f90 sourcefile~mod_io.f90 mod_io.F90 sourcefile~mod_link_atom.f90->sourcefile~mod_io.f90 sourcefile~mod_utils.f90 mod_utils.F90 sourcefile~mod_link_atom.f90->sourcefile~mod_utils.f90 sourcefile~mod_prm.f90 mod_prm.F90 sourcefile~mod_link_atom.f90->sourcefile~mod_prm.f90 sourcefile~mod_electrostatics.f90 mod_electrostatics.F90 sourcefile~mod_link_atom.f90->sourcefile~mod_electrostatics.f90 sourcefile~mod_memory.f90->sourcefile~mod_constants.f90 sourcefile~mod_memory.f90->sourcefile~mod_io.f90 sourcefile~mod_topology.f90->sourcefile~mod_memory.f90 sourcefile~mod_topology.f90->sourcefile~mod_constants.f90 sourcefile~mod_topology.f90->sourcefile~mod_io.f90 sourcefile~mod_adjacency_mat.f90 mod_adjacency_mat.F90 sourcefile~mod_topology.f90->sourcefile~mod_adjacency_mat.f90 sourcefile~mod_bonded.f90->sourcefile~mod_memory.f90 sourcefile~mod_bonded.f90->sourcefile~mod_topology.f90 sourcefile~mod_bonded.f90->sourcefile~mod_constants.f90 sourcefile~mod_bonded.f90->sourcefile~mod_io.f90 sourcefile~mod_bonded.f90->sourcefile~mod_utils.f90 sourcefile~mod_jacobian_mat.f90 mod_jacobian_mat.F90 sourcefile~mod_bonded.f90->sourcefile~mod_jacobian_mat.f90 sourcefile~mod_nonbonded.f90->sourcefile~mod_memory.f90 sourcefile~mod_nonbonded.f90->sourcefile~mod_topology.f90 sourcefile~mod_nonbonded.f90->sourcefile~mod_constants.f90 sourcefile~mod_nonbonded.f90->sourcefile~mod_io.f90 sourcefile~mod_nonbonded.f90->sourcefile~mod_adjacency_mat.f90 sourcefile~mod_nonbonded.f90->sourcefile~mod_jacobian_mat.f90 sourcefile~mod_neighbors_list.f90 mod_neighbors_list.F90 sourcefile~mod_nonbonded.f90->sourcefile~mod_neighbors_list.f90 sourcefile~mod_profiling.f90 mod_profiling.F90 sourcefile~mod_nonbonded.f90->sourcefile~mod_profiling.f90 sourcefile~mod_io.f90->sourcefile~mod_constants.f90 sourcefile~mod_utils.f90->sourcefile~mod_memory.f90 sourcefile~mod_utils.f90->sourcefile~mod_constants.f90 sourcefile~mod_prm.f90->sourcefile~mod_memory.f90 sourcefile~mod_prm.f90->sourcefile~mod_topology.f90 sourcefile~mod_prm.f90->sourcefile~mod_constants.f90 sourcefile~mod_prm.f90->sourcefile~mod_bonded.f90 sourcefile~mod_prm.f90->sourcefile~mod_nonbonded.f90 sourcefile~mod_prm.f90->sourcefile~mod_io.f90 sourcefile~mod_prm.f90->sourcefile~mod_utils.f90 sourcefile~mod_prm.f90->sourcefile~mod_electrostatics.f90 sourcefile~mod_electrostatics.f90->sourcefile~mod_memory.f90 sourcefile~mod_electrostatics.f90->sourcefile~mod_topology.f90 sourcefile~mod_electrostatics.f90->sourcefile~mod_constants.f90 sourcefile~mod_electrostatics.f90->sourcefile~mod_io.f90 sourcefile~mod_electrostatics.f90->sourcefile~mod_adjacency_mat.f90 sourcefile~mod_fmm_interface.f90 mod_fmm_interface.F90 sourcefile~mod_electrostatics.f90->sourcefile~mod_fmm_interface.f90 sourcefile~mod_electrostatics.f90->sourcefile~mod_profiling.f90 sourcefile~mod_adjacency_mat.f90->sourcefile~mod_memory.f90 sourcefile~mod_adjacency_mat.f90->sourcefile~mod_utils.f90 sourcefile~mod_jacobian_mat.f90->sourcefile~mod_memory.f90 sourcefile~mod_jacobian_mat.f90->sourcefile~mod_constants.f90 sourcefile~mod_jacobian_mat.f90->sourcefile~mod_utils.f90 sourcefile~mod_fmm_interface.f90->sourcefile~mod_constants.f90 sourcefile~mod_fmm.f90 mod_fmm.F90 sourcefile~mod_fmm_interface.f90->sourcefile~mod_fmm.f90 sourcefile~mod_tree.f90 mod_tree.F90 sourcefile~mod_fmm_interface.f90->sourcefile~mod_tree.f90 sourcefile~mod_harmonics.f90 mod_harmonics.F90 sourcefile~mod_fmm_interface.f90->sourcefile~mod_harmonics.f90 sourcefile~mod_ribtree.f90 mod_ribtree.F90 sourcefile~mod_fmm_interface.f90->sourcefile~mod_ribtree.f90 sourcefile~mod_octatree.f90 mod_octatree.F90 sourcefile~mod_fmm_interface.f90->sourcefile~mod_octatree.f90 sourcefile~mod_fmm_utils.f90 mod_fmm_utils.F90 sourcefile~mod_fmm_interface.f90->sourcefile~mod_fmm_utils.f90 sourcefile~mod_neighbors_list.f90->sourcefile~mod_memory.f90 sourcefile~mod_neighbors_list.f90->sourcefile~mod_constants.f90 sourcefile~mod_neighbors_list.f90->sourcefile~mod_io.f90 sourcefile~mod_neighbors_list.f90->sourcefile~mod_adjacency_mat.f90 sourcefile~mod_neighbors_list.f90->sourcefile~mod_profiling.f90 sourcefile~mod_profiling.f90->sourcefile~mod_memory.f90 sourcefile~mod_profiling.f90->sourcefile~mod_constants.f90 sourcefile~mod_profiling.f90->sourcefile~mod_io.f90 sourcefile~mod_fmm.f90->sourcefile~mod_constants.f90 sourcefile~mod_fmm.f90->sourcefile~mod_tree.f90 sourcefile~mod_fmm.f90->sourcefile~mod_harmonics.f90 sourcefile~mod_fmm.f90->sourcefile~mod_fmm_utils.f90 sourcefile~mod_tree.f90->sourcefile~mod_constants.f90 sourcefile~mod_tree.f90->sourcefile~mod_adjacency_mat.f90 sourcefile~mod_tree.f90->sourcefile~mod_fmm_utils.f90 sourcefile~mod_harmonics.f90->sourcefile~mod_constants.f90 sourcefile~mod_harmonics.f90->sourcefile~mod_fmm_utils.f90 sourcefile~mod_ribtree.f90->sourcefile~mod_constants.f90 sourcefile~mod_ribtree.f90->sourcefile~mod_adjacency_mat.f90 sourcefile~mod_ribtree.f90->sourcefile~mod_profiling.f90 sourcefile~mod_ribtree.f90->sourcefile~mod_tree.f90 sourcefile~mod_ribtree.f90->sourcefile~mod_fmm_utils.f90 sourcefile~mod_octatree.f90->sourcefile~mod_constants.f90 sourcefile~mod_octatree.f90->sourcefile~mod_adjacency_mat.f90 sourcefile~mod_octatree.f90->sourcefile~mod_profiling.f90 sourcefile~mod_octatree.f90->sourcefile~mod_tree.f90 sourcefile~mod_octatree.f90->sourcefile~mod_fmm_utils.f90 sourcefile~mod_fmm_utils.f90->sourcefile~mod_constants.f90

Files dependent on this one

sourcefile~~mod_link_atom.f90~~AfferentGraph sourcefile~mod_link_atom.f90 mod_link_atom.F90 sourcefile~mod_mmpol.f90 mod_mmpol.F90 sourcefile~mod_mmpol.f90->sourcefile~mod_link_atom.f90 sourcefile~mod_qm_helper.f90 mod_qm_helper.F90 sourcefile~mod_qm_helper.f90->sourcefile~mod_link_atom.f90 sourcefile~mod_qm_helper.f90->sourcefile~mod_mmpol.f90 sourcefile~mod_interface.f90 mod_interface.F90 sourcefile~mod_interface.f90->sourcefile~mod_link_atom.f90 sourcefile~mod_interface.f90->sourcefile~mod_mmpol.f90 sourcefile~mod_interface.f90->sourcefile~mod_qm_helper.f90 sourcefile~mod_iohdf5.f90 mod_iohdf5.F90 sourcefile~mod_interface.f90->sourcefile~mod_iohdf5.f90 sourcefile~mod_inputloader.f90 mod_inputloader.F90 sourcefile~mod_interface.f90->sourcefile~mod_inputloader.f90 sourcefile~mod_geomgrad.f90 mod_geomgrad.F90 sourcefile~mod_interface.f90->sourcefile~mod_geomgrad.f90 sourcefile~mod_polarization.f90 mod_polarization.F90 sourcefile~mod_interface.f90->sourcefile~mod_polarization.f90 sourcefile~mod_iohdf5.f90->sourcefile~mod_mmpol.f90 sourcefile~mod_inputloader.f90->sourcefile~mod_mmpol.f90 sourcefile~mod_geomgrad.f90->sourcefile~mod_mmpol.f90 sourcefile~mod_geomgrad.f90->sourcefile~mod_polarization.f90 sourcefile~mod_c_interface.f90 mod_c_interface.F90 sourcefile~mod_c_interface.f90->sourcefile~mod_mmpol.f90 sourcefile~mod_c_interface.f90->sourcefile~mod_qm_helper.f90 sourcefile~mod_c_interface.f90->sourcefile~mod_interface.f90 sourcefile~mod_polarization.f90->sourcefile~mod_mmpol.f90

Contents

Source Code


Source Code

#define _MM_ 1
#define _QM_ 2
#define _LA_ 3

module mod_link_atom

    use mod_memory, only: rp, ip, lp, mallocate, mfree
    use mod_topology, only: ommp_topology_type
    use mod_constants, only: angstrom2au, default_link_atom_dist, &
                             default_link_atom_n_eel_remove
    use mod_nonbonded, only: vdw_geomgrad_inter
    use mod_bonded, only: ommp_bonded_type
    use mod_io, only: large_file_read
    use mod_utils, only: str_to_lower, str_uncomment

    implicit none
    private
    
    integer(ip), parameter :: link_atom_allocation_chunk = 20

    type ommp_link_atom_type
        type(ommp_topology_type), pointer :: mmtop
        !! Topology of MM part of the system
        type(ommp_topology_type), pointer :: qmtop
        !! Topology of QM part of the system
        type(ommp_topology_type), allocatable :: qmmmtop
        !! Linked QM/MM topology
        integer(ip), allocatable :: mm2full(:), qm2full(:)
        !! Maps from qm and mm systems to full topology indexes
        integer(ip) :: nla
        !! Number of link atoms
        integer(ip), allocatable :: links(:,:)
        !! Indexes of link-involved atoms: 
        !!    links(_MM_,:) contains the index of MM atom in mmtop
        !!    links(_QM_,:) contains the index of QM atom in qmtop
        !!    links(_LA_,:) contains the index of link atom in qmtop
        real(rp), allocatable :: la_distance(:)
        !! Distance of link atom from QM atom
        integer(ip), allocatable :: vdw_screening_pairs(:,:)
        !! Pairs of vdw interactions that should be screened
        real(rp), allocatable :: vdw_screening_f(:)
        !! Screening factors for each vdw pair
        integer(ip) :: vdw_n_screening
        !! Number of screening vdw to be used
        type(ommp_bonded_type), allocatable :: bds
    end type

    public :: ommp_link_atom_type, init_link_atom, add_link_atom
    public :: link_atom_position, init_vdw_for_link_atom, init_bonded_for_link_atom
    public :: init_eel_for_link_atom
    public :: default_link_atom_dist, default_link_atom_n_eel_remove, link_atom_update_merged_topology
    public :: link_atom_bond_geomgrad, link_atom_angle_geomgrad, link_atom_torsion_geomgrad, &
              link_atom_project_grd

    contains
        subroutine init_link_atom(la, qmtop, mmtop)
            use mod_topology, only: merge_top
            use mod_constants, only: OMMP_VERBOSE_LOW
            use mod_io, only: ommp_message

            implicit none
            
            type(ommp_link_atom_type), intent(inout) :: la
            type(ommp_topology_type), intent(in), target :: qmtop, mmtop

            call mallocate('init_linkatoms [links]', &
                           3_ip, link_atom_allocation_chunk, &
                           la%links)
            call mallocate('init_linkatoms [la_distance]', &
                           link_atom_allocation_chunk, la%la_distance)
            call mallocate('init_linkatoms [vdw_screening_f]', &
                           link_atom_allocation_chunk, la%vdw_screening_f)
            call mallocate('init_linkatoms [vdw_screening_pairs]', 2_ip, &
                           link_atom_allocation_chunk, la%vdw_screening_pairs)

            la%nla = 0
            la%vdw_n_screening = 0
            la%qmtop => qmtop
            la%mmtop => mmtop
            
            call ommp_message("Creating merged topology", OMMP_VERBOSE_LOW, 'linkatom')
            allocate(la%qmmmtop)
            call merge_top(la%mmtop, la%qmtop, la%qmmmtop, la%mm2full, la%qm2full)
            allocate(la%bds)
        end subroutine

        subroutine link_atom_update_merged_topology(la)
            !! Update merged topology in linkatom object so that its coordinates
            !! are the same of mmtop and qmtop.
            implicit none

            type(ommp_link_atom_type), intent(inout) :: la

            integer(ip) :: i

            do i=1, la%mmtop%mm_atoms
                la%qmmmtop%cmm(:,la%mm2full(i)) = la%mmtop%cmm(:,i)
            end do

            do i=1, la%qmtop%mm_atoms
                la%qmmmtop%cmm(:,la%qm2full(i)) = la%qmtop%cmm(:,i)
            end do
        end subroutine

        subroutine add_link_atom(la, imm, iqm, ila, la_dist)
            use mod_constants, only: OMMP_STR_CHAR_MAX, OMMP_VERBOSE_LOW
            use mod_io, only: ommp_message
            use mod_topology, only: create_new_bond
            
            implicit none

            type(ommp_link_atom_type), intent(inout) :: la
            integer(ip), intent(in) :: imm
            integer(ip), intent(in) :: iqm
            integer(ip), intent(in) :: ila
            real(rp), intent(in) :: la_dist
        
            integer(ip), allocatable :: tmp(:,:)
            real(rp), allocatable :: rtmp(:)
            integer(ip) :: nmax
            character(len=OMMP_STR_CHAR_MAX) :: message
            
            nmax = size(la%links, 2)
            if(la%nla + 1 > nmax) then
                ! Reallocate links to accomodate new link atoms
                call mallocate('create_link_atom [tmp]', 3_ip, nmax, tmp)
                call mallocate('create_link_atom [rtmp]', nmax, rtmp)
                tmp = la%links
                rtmp = la%la_distance
                call mfree('create_link_atom [la%links]', la%links)
                call mallocate('create_link_atom [la%links]', 3_ip, nmax+link_atom_allocation_chunk, la%links)
                call mfree('create_link_atom [la%la_distance]', la%la_distance)
                call mallocate('create_link_atom [la%la_distance]', nmax+link_atom_allocation_chunk, la%la_distance)
                la%links = tmp(:,0:nmax)
                la%la_distance = rtmp(0:nmax)
                call mfree('create_link_atom [tmp]', tmp)
                call mfree('create_link_atom [rtmp]', rtmp)
            end if
            ! 0.2. Link atom creation and positioning
            la%nla = la%nla + 1
            la%links(_MM_, la%nla) = imm
            la%links(_QM_, la%nla) = iqm
            la%links(_LA_, la%nla) = ila
            la%la_distance(la%nla) = la_dist
           
            if(la%qmmmtop%frozen(la%qm2full(iqm)) .and. la%qmmmtop%frozen(la%mm2full(imm))) then
                ! if QM atom and MM atoms are frozen, also LA is frozen
                la%qmmmtop%frozen(la%qm2full(ila)) = .true.
            end if
            call create_new_bond(la%qmmmtop, la%mm2full(imm), la%qm2full(iqm))

            write(message, "(A, I0, A, I0, A, I0, A)") &
                  "Created link atom MM [", imm, &
                  "] - (LA) [", ila, "] - QM [", iqm, "]"
            
            call ommp_message(message, OMMP_VERBOSE_LOW, 'linkatom')
        end subroutine

        subroutine init_eel_for_link_atom(la, imm, ila, eel, prmfile)
            use mod_memory, only: mallocate, mfree
            use mod_prm, only: assign_mpoles
            use mod_electrostatics, only: ommp_electrostatics_type, &
                                          electrostatics_init, &
                                          remove_null_pol
            use mod_io, only: fatal_error, ommp_message
            use mod_constants, only: eps_rp, OMMP_VERBOSE_LOW, &
                                     OMMP_STR_CHAR_MAX, &
                                     OMMP_VERBOSE_DEBUG

            implicit none

            integer(ip), intent(in) :: imm
            integer(ip), intent(in) :: ila
            type(ommp_link_atom_type), intent(in) :: la
            type(ommp_electrostatics_type), intent(inout) :: eel
            character(len=*), intent(in) :: prmfile

            real(rp) :: removed_charge, qred, old_q, lastq
            integer(ip) :: n_eel_remove = default_link_atom_n_eel_remove
            integer(ip) :: ist, i, j, idx, ii
            type(ommp_electrostatics_type) :: tmp_eel
            character(len=OMMP_STR_CHAR_MAX) :: msg
            character(len=OMMP_STR_CHAR_MAX), allocatable :: prm_buf(:)
            integer(ip), allocatable :: attocheck(:)
            
            ! Check if in the complete topology some of the atoms connected
            ! to imm or imm itself do change their parameters. This is a 
            ! problem that affects force-field in which parameters are 
            ! assigned based on connectivity (AMOEBA). In particular we
            ! check only 1,2 and 1,3 neighbours of QM atom that are the ones 
            ! that could be influenced by the new bond. Moreover we only
            ! take care of charges, as multipoles will be removed on those
            ! atoms in a while.

            call mallocate('init_eel_for_link_atom [attocheck]', &
                           eel%top%conn(1)%ri(imm+1) - eel%top%conn(1)%ri(imm)+1, &
                           attocheck)
            attocheck(1) = imm
            ii = 2
            do i=eel%top%conn(1)%ri(imm), eel%top%conn(1)%ri(imm+1)-1
                j = eel%top%conn(1)%ci(i)
                attocheck(ii) = j
                ii = ii + 1
            end do
            write(msg, "(A, I0, A)") "Assigning electrostatic parameter to merged&
                                     & topology. Ignore all the warnings on &
                                     &link atom (", la%qm2full(ila), ")." 
            call ommp_message(msg, OMMP_VERBOSE_LOW, 'linkatom')

            call electrostatics_init(tmp_eel, eel%amoeba, la%qmmmtop%mm_atoms, &
                                     la%qmmmtop)
            
            call large_file_read(prmfile, prm_buf)
            ! Remove comments from prm file
            !$omp parallel do
            do i=1, size(prm_buf)
                prm_buf(i) = str_to_lower(prm_buf(i))
                prm_buf(i) = str_uncomment(prm_buf(i), '!')
            end do
            
            call assign_mpoles(tmp_eel, prm_buf)
            deallocate(prm_buf) 

            do i=1, size(attocheck)
                j = attocheck(i)
                    if(abs(tmp_eel%q(1,la%mm2full(j)) - eel%q(1,j)) > eps_rp) then
                        write(msg, "(A, I0)") "Reassigning electrostatic parameter to&
                                            & atom ", j
                        call ommp_message(msg, OMMP_VERBOSE_LOW, 'linkatom')
                        write(msg, '("  Old parameters: q=", F6.2)') eel%q(1,j)
                        call ommp_message(msg, OMMP_VERBOSE_DEBUG, 'linkatom')
                        write(msg, '("  New parameters: q=", F6.2)') tmp_eel%q(1,la%mm2full(j))
                        call ommp_message(msg, OMMP_VERBOSE_DEBUG, 'linkatom')
                        if(eel%amoeba) then
                            eel%q0(:,j) = tmp_eel%q(:,la%mm2full(j))
                        else
                            eel%q(:,j) = tmp_eel%q(:,la%mm2full(j))
                        end if
                    end if
            end do
            call mfree('init_eel_for_link_atom [attocheck]', attocheck)

            ! Remove dipoles, multipoles, charges and polarizabilities
            !    on all the atoms that have a distance from (QM) atom less or equal to
            !    n_eel_remove. If n_eel_remove is 0, the MM electrostatic is not changed;
            !    if n_eel_remove is 1, only the connected atom is removed and so on.
            !    Removed charges is distributed to all the atoms 1 bond further away.

            if(n_eel_remove > 0) then
                if(n_eel_remove < 2) then
                    call fatal_error("Electrostatic interaction should be removed at least on 1,2 and &
                                    &1,3 neighbour of linked QM atom")
                end if

                if(eel%amoeba) then
                    old_q = sum(eel%q0(1,:))
                else
                    old_q = sum(eel%q(1,:))
                end if
                removed_charge = 0.0
                
                if(n_eel_remove > size(la%mmtop%conn)) then
                    call fatal_error("Connectivity rebuild is not implemented in link atom")
                end if
                
                ! Remove charges, multipoles and polarizabilities
                if(eel%amoeba) then
                    removed_charge = removed_charge + eel%q0(1,imm)
                    lastq = eel%q0(1,imm)
                    eel%q0(:,imm) = 0.0
                    if(eel%mm_polar(imm) > 0) &
                        eel%pol(eel%mm_polar(imm)) = 0.0
                else
                    removed_charge = removed_charge + eel%q(1,imm)
                    lastq = eel%q(1,imm)
                    eel%q(:,imm) = 0.0

                    if(eel%mm_polar(imm) > 0) &
                        eel%pol(eel%mm_polar(imm)) = 0.0
                end if
                write(msg, '("Removed charge, multipoles and polarizabilities on atom ", I0, "[q=", F6.3, "]")') imm, lastq
                call ommp_message(msg, OMMP_VERBOSE_LOW, 'linkatom')
                
                do i=1, n_eel_remove-1
                    do j=eel%top%conn(i)%ri(imm), eel%top%conn(i)%ri(imm+1)-1
                        idx = eel%top%conn(i)%ci(j)
                        if(eel%amoeba) then
                            removed_charge = removed_charge + eel%q0(1,idx)
                            lastq = eel%q0(1,idx)
                            eel%q0(:,idx) = 0.0
                            if(eel%mm_polar(idx) > 0) &
                                eel%pol(eel%mm_polar(idx)) = 0.0
                        else
                            removed_charge = removed_charge + eel%q(1,idx)
                            lastq = eel%q(1,idx)
                            eel%q(:,idx) = 0.0
                            if(eel%mm_polar(idx) > 0) &
                                eel%pol(eel%mm_polar(idx)) = 0.0
                        end if
                        write(msg, '("Removed charge, multipoles and polarizabilities on atom ", I0, "[q=", F6.3, "]")') idx, lastq
                        call ommp_message(msg, OMMP_VERBOSE_LOW, 'linkatom')
                    end do
                end do

                ! Redistribute removed charge to preserve neutrality
                qred = removed_charge / (eel%top%conn(n_eel_remove)%ri(imm+1) - eel%top%conn(i)%ri(imm))

                do j=eel%top%conn(n_eel_remove)%ri(imm), &
                    eel%top%conn(n_eel_remove)%ri(imm+1)-1
                    idx = eel%top%conn(n_eel_remove)%ci(j)
                    if(eel%amoeba) then
                        eel%q0(1,idx) = eel%q0(1,idx) + qred
                    else
                        eel%q(1,idx) = eel%q(1,idx) + qred
                    end if
                    write(msg, '("Redistributing charge (", F6.3, " A.U.) on atom ", I0)') qred, idx
                    call ommp_message(msg, OMMP_VERBOSE_LOW, 'linkatom')
                end do
            end if
            
            call remove_null_pol(eel)

            eel%M2M_done = .false.
            eel%M2Mgg_done = .false.
            eel%M2D_done = .false.
            eel%M2Dgg_done = .false.
            eel%ipd_done = .false.
            if(allocated(eel%TMat)) call mfree('update_coordinates [TMat]',eel%TMat)
            if(eel%amoeba) call rotate_multipoles(eel)
            write(msg, '("Charge of the systems passed from ", F6.3, " to ", F6.3, "A.U.")') &
                old_q, sum(eel%q(1,:))
            call ommp_message(msg, OMMP_VERBOSE_LOW, 'linkatom')

            
        end subroutine

        subroutine link_atom_position(la, idx, crd)
            !! Compute the cartesian coordinates of link atom idx at
            !! the current geometry.
            implicit none

            type(ommp_link_atom_type), intent(in) :: la
            integer(ip), intent(in) :: idx
            real(rp), intent(out) :: crd(3)

            real(rp), dimension(3) :: rmm, rqm, dmmqm

            rmm = la%mmtop%cmm(:,la%links(_MM_,idx))
            rqm = la%qmtop%cmm(:,la%links(_QM_,idx))
            dmmqm = rmm-rqm
            dmmqm = dmmqm / norm2(dmmqm)

            crd = rqm + la%la_distance(idx) * dmmqm
        end subroutine

        subroutine check_vdw_pairs(la, n)
            !! Check if n new screening pairs could be allocated
            !! in la structure. If the allocated arrays are too 
            !! small, they are reallocated on-the-fly.
            implicit none

            type(ommp_link_atom_type), intent(inout) :: la
            integer(ip), intent(in) :: n

            integer(ip), allocatable :: itmp(:,:)
            real(rp), allocatable :: rtmp(:)
            integer(ip) :: nnew, nold

            nnew = n + la%vdw_n_screening
            nold = size(la%vdw_screening_f)
            if(nnew > nold) then
                call mallocate('check_vdw_pairs [itmp]', 2_ip, nold, itmp)
                call mallocate('check_vdw_pairs [rtmp]', nold, rtmp)
                itmp = la%vdw_screening_pairs
                rtmp = la%vdw_screening_f
                call mfree('check_vdw_pairs [vdw_screening_pairs]', la%vdw_screening_pairs)
                call mfree('check_vdw_pairs [vdw_screening_f]', la%vdw_screening_f)
                
                call mallocate('check_vdw_pairs [vdw_screening_pairs]', 2_ip, nnew, la%vdw_screening_pairs)
                call mallocate('check_vdw_pairs [vdw_screening_f]', nnew, la%vdw_screening_f)

                la%vdw_screening_pairs(:,:nold) = itmp
                la%vdw_screening_f(:nold) = rtmp

                call mfree('check_vdw_pairs [itmp]', itmp)
                call mfree('check_vdw_pairs [rtmp]', rtmp)
            end if
        end subroutine

        subroutine add_screening_pair(la, iqm, imm, s)
            use mod_io, only: ommp_message
            use mod_constants, only: OMMP_STR_CHAR_MAX, OMMP_VERBOSE_DEBUG
            !! Insert a VdW screening pair in the link atom structure
            implicit none

            type(ommp_link_atom_type), intent(inout) :: la
            integer(ip), intent(in) :: iqm, imm
            real(rp), intent(in) :: s
            character(len=OMMP_STR_CHAR_MAX) :: message

            ! Just for safety, it should be done elsewhere for all the pairs
            ! ones want to insert in the structure.
            call check_vdw_pairs(la, 1)

            la%vdw_n_screening = la%vdw_n_screening + 1
            la%vdw_screening_pairs(_QM_, la%vdw_n_screening) = iqm
            la%vdw_screening_pairs(_MM_, la%vdw_n_screening) = imm
            la%vdw_screening_f(la%vdw_n_screening) = s
            write(message, "(A, I0, A, I0, A, F3.2)") &
                  "Screening VdW interactions between atoms ", imm, " (MM) &
                  &and ", iqm, "(QM) by a factor ", 1.0 + s
            call ommp_message(message, OMMP_VERBOSE_DEBUG, 'linkatom')

        end subroutine

        subroutine init_vdw_for_link_atom(la, iqm, imm, vdw_screening)
            !! Initialize the quantities needed for vdw screening due to the
            !! presence of a link atom between iqm and imm
            use mod_topology, only: check_conn_matrix
            use mod_io, only: fatal_error, ommp_message
            use mod_constants, only: eps_rp, OMMP_STR_CHAR_MAX, OMMP_VERBOSE_DEBUG


            implicit none

            type(ommp_link_atom_type), intent(inout) :: la
            integer(ip), intent(in) :: iqm, imm
            real(rp), intent(in) :: vdw_screening(:)

            integer(ip) :: i, j, ineigh_qm, ineigh_mm, idist, iscr, &
                           qmneigh(4), mmneigh(4)
            real(rp) :: screen
            character(len=OMMP_STR_CHAR_MAX) :: message

            ! Check that the connectivity matrices have been built up to the
            ! required order.
            call check_conn_matrix(la%qmtop, size(vdw_screening)-1)
            call check_conn_matrix(la%mmtop, size(vdw_screening)-1)
            
            ! Count how many interactions should be screened
            qmneigh(1) = 1
            mmneigh(1) = 1
            do i=2, size(vdw_screening)
                qmneigh(i) = la%qmtop%conn(i-1)%ri(iqm+1) - &
                             la%qmtop%conn(i-1)%ri(iqm)
                mmneigh(i) = la%mmtop%conn(i-1)%ri(imm+1) - &
                             la%mmtop%conn(i-1)%ri(imm)
            end do

            iscr = 0
            do idist=1, size(vdw_screening)
                if(abs(vdw_screening(idist) - 1.0) > eps_rp) then
                    do i=1, idist
                        iscr = iscr + qmneigh(i) * mmneigh(idist-i+1)
                    end do
                end if
            end do
            write(message, "(I0, A)") &
                  iscr, " VdW interactions will be screened due to link atom"
            call ommp_message(message, OMMP_VERBOSE_DEBUG, 'linkatom')

            ! Check the allocation of vectors inside link atom structure
            call check_vdw_pairs(la, iscr)

            ! Insert the new screened interactions inside link atom structure
            do idist=1, size(vdw_screening)
                screen = vdw_screening(idist) - 1.0
                if(abs(screen) > eps_rp) then
                    do ineigh_qm=1, idist
                        ineigh_mm = idist - ineigh_qm + 1
                        if(ineigh_qm == 1) then
                            if(ineigh_mm == 1) then
                                call add_screening_pair(la, iqm, imm, screen)
                            else
                                do i=la%mmtop%conn(ineigh_mm-1)%ri(imm), &
                                     la%mmtop%conn(ineigh_mm-1)%ri(imm+1) - 1
                                    call add_screening_pair(la, &
                                                            iqm, &
                                                            la%mmtop%conn(ineigh_mm-1)%ci(i), &
                                                            screen)
                                end do
                            end if
                        else
                            if(ineigh_mm == 1) then
                                do i=la%qmtop%conn(ineigh_qm-1)%ri(iqm), &
                                     la%qmtop%conn(ineigh_qm-1)%ri(iqm+1) -1
                                    call add_screening_pair(la, &
                                                            la%qmtop%conn(ineigh_qm-1)%ci(i), &
                                                            imm, &
                                                            screen)
                                end do
                            else
                                do i=la%mmtop%conn(ineigh_mm-1)%ri(imm), &
                                     la%mmtop%conn(ineigh_mm-1)%ri(imm+1) - 1
                                    do j=la%qmtop%conn(ineigh_qm-1)%ri(iqm), &
                                         la%qmtop%conn(ineigh_qm-1)%ri(iqm+1) -1
                                        call add_screening_pair(la, &
                                                                la%qmtop%conn(ineigh_qm-1)%ci(j), &
                                                                la%mmtop%conn(ineigh_mm-1)%ci(i), &
                                                                screen)
                                    end do
                                end do
                            end if
                        end if
                    end do
                end if
            end do

        end subroutine

        subroutine init_bonded_for_link_atom(la, prmfile)
            !! Insert in the bonded parameter required for the link atom between iqm and imm
            use mod_prm, only: assign_bond, assign_angle, assign_torsion
            use mod_bonded, only: bonded_terminate, &
                                  bond_init, angle_init, torsion_init, &
                                  bond_terminate, angle_terminate, torsion_terminate
            use mod_bonded, only: OMMP_ANG_SIMPLE, OMMP_ANG_H0, OMMP_ANG_H1, &
                                  OMMP_ANG_H2, OMMP_ANG_INPLANE, &
                                  OMMP_ANG_INPLANE_H0, OMMP_ANG_INPLANE_H1
            use mod_io, only: ommp_message, fatal_error
            use mod_constants, only: OMMP_STR_CHAR_MAX, OMMP_VERBOSE_LOW
            use mod_topology, only: check_conn_matrix

            implicit none

            type(ommp_link_atom_type), intent(inout), target :: la
            character(len=*), intent(in) :: prmfile
            
            type(ommp_bonded_type) :: tmp_bnd
            integer(ip), parameter :: maxt = 1024
            integer(ip) :: ist, i, j, nqm, nterms, iterms(maxt)
            character(len=OMMP_STR_CHAR_MAX) :: message
            character(len=OMMP_STR_CHAR_MAX), allocatable :: prm_buf(:)

            call check_conn_matrix(la%qmmmtop, 4)
            tmp_bnd%top => la%qmmmtop
            la%bds%top => la%qmmmtop

            call large_file_read(prmfile, prm_buf)
            ! Remove comments from prm file
            !$omp parallel do
            do i=1, size(prm_buf)
                prm_buf(i) = str_to_lower(prm_buf(i))
                prm_buf(i) = str_uncomment(prm_buf(i), '!')
            end do
            ! Bonded terms
            call assign_bond(tmp_bnd, prm_buf, la%qm2full, 2)
            if(tmp_bnd%use_bond) then
                nterms = 0
                do i=1, tmp_bnd%nbond
                    nqm = 0
                    if(any(la%qm2full == tmp_bnd%bondat(1,i))) nqm = nqm+1
                    if(any(la%qm2full == tmp_bnd%bondat(2,i))) nqm = nqm+1
                    if(nqm == 1) then
                        nterms = nterms + 1
                        if(nterms > maxt) then
                            call fatal_error("Maximum number of terms to be added due to link atom &
                                            &exceeded. This is probably a bug.")
                        end if
                        iterms(nterms) = i
                    end if
                end do
                
                if(la%bds%use_bond) then
                    ! Bond terms are already initializad by a previous
                    ! link atom
                    call bond_terminate(la%bds) 
                end if

                call bond_init(la%bds, nterms)

                do i=1, nterms
                    la%bds%bondat(:,i) = tmp_bnd%bondat(:,iterms(i))
                    la%bds%kbond(i) = tmp_bnd%kbond(iterms(i))
                    la%bds%l0bond(i) = tmp_bnd%l0bond(iterms(i))
                end do
                la%bds%bond_cubic = tmp_bnd%bond_cubic
                la%bds%bond_quartic = tmp_bnd%bond_quartic
                
                write(message, "(I0, A)") nterms, " bond terms added due to link atoms."
                call ommp_message(message, OMMP_VERBOSE_LOW, "linkatom")
            end if
            
            call assign_angle(tmp_bnd, prm_buf, la%qm2full, 2)
            if(tmp_bnd%use_angle) then
                nterms = 0
                do i=1, tmp_bnd%nangle
                    if(tmp_bnd%anglety(i) == OMMP_ANG_SIMPLE .or. &
                       tmp_bnd%anglety(i) == OMMP_ANG_H0 .or. &
                       tmp_bnd%anglety(i) == OMMP_ANG_H1 .or. &
                       tmp_bnd%anglety(i) == OMMP_ANG_H2) then
                        nqm = 0
                        if(any(la%qm2full == tmp_bnd%angleat(1,i))) nqm = nqm+1
                        if(any(la%qm2full == tmp_bnd%angleat(2,i))) nqm = nqm+1
                        if(any(la%qm2full == tmp_bnd%angleat(3,i))) nqm = nqm+1

                        if(nqm == 1) then
                            nterms = nterms + 1
                            if(nterms > maxt) then
                                call fatal_error("Maximum number of terms to be added due to link atom &
                                                &exceeded. This is probably a bug.")
                            end if
                            iterms(nterms) = i
                        end if
                    else if(tmp_bnd%anglety(i) == OMMP_ANG_INPLANE .or. &
                            tmp_bnd%anglety(i) == OMMP_ANG_INPLANE_H0 .or. &
                            tmp_bnd%anglety(i) == OMMP_ANG_INPLANE_H1) then
                        nqm = 0
                        if(any(la%qm2full == tmp_bnd%angleat(1,i))) nqm = nqm+1
                        if(any(la%qm2full == tmp_bnd%angleat(2,i))) nqm = nqm+1
                        if(any(la%qm2full == tmp_bnd%angleat(3,i))) nqm = nqm+1
                        if(nqm == 1 .and. .not. any(la%qm2full == tmp_bnd%angauxat(i))) then
                            nterms = nterms + 1
                            if(nterms > maxt) then
                                call fatal_error("Maximum number of terms to be added due to link atom &
                                                &exceeded. This is probably a bug.")
                            end if
                            iterms(nterms) = i
                        end if
                    end if
                end do
                
                if(la%bds%use_angle) then
                    call angle_terminate(la%bds)
                end if
                call angle_init(la%bds, nterms)

                do i=1, nterms
                    la%bds%angleat(:,i) = tmp_bnd%angleat(:,iterms(i))
                    la%bds%anglety(i) = tmp_bnd%anglety(iterms(i))
                    la%bds%angauxat(i) = tmp_bnd%angauxat(iterms(i))
                    la%bds%kangle(i) = tmp_bnd%kangle(iterms(i))
                    la%bds%eqangle(i) = tmp_bnd%eqangle(iterms(i))
                end do
                la%bds%angle_cubic = tmp_bnd%angle_cubic
                la%bds%angle_quartic = tmp_bnd%angle_quartic
                la%bds%angle_pentic = tmp_bnd%angle_pentic
                la%bds%angle_sextic = tmp_bnd%angle_sextic
                
                write(message, "(I0, A)") nterms, " angle terms added due to link atoms."
                call ommp_message(message, OMMP_VERBOSE_LOW, "linkatom")
            end if
            
            call assign_torsion(tmp_bnd, prm_buf)
            if(tmp_bnd%use_torsion) then
                nterms = 0
                do i=1, tmp_bnd%ntorsion
                    nqm = 0
                    if(any(la%qm2full == tmp_bnd%torsionat(1,i))) nqm = nqm+1
                    if(any(la%qm2full == tmp_bnd%torsionat(2,i))) nqm = nqm+1
                    if(any(la%qm2full == tmp_bnd%torsionat(3,i))) nqm = nqm+1
                    if(any(la%qm2full == tmp_bnd%torsionat(4,i))) nqm = nqm+1
                    !! Link atoms should never appear in bonded interactions!
                    do j=1, 4
                        if(any(la%qm2full(la%links(_LA_,1:la%nla)) == tmp_bnd%torsionat(j,i))) nqm = -1
                    end do

                    if(nqm == 1 .or. nqm == 2) then
                        nterms = nterms + 1
                        if(nterms > maxt) then
                            call fatal_error("Maximum number of terms to be added due to link atom &
                                            &exceeded. This is probably a bug.")
                        end if
                        iterms(nterms) = i
                    end if
                end do

                if(la%bds%use_torsion) then
                    call torsion_terminate(la%bds)
                end if
                call torsion_init(la%bds, nterms)
                
                do i=1, nterms
                    la%bds%torsionat(:,i) = tmp_bnd%torsionat(:,iterms(i))
                    la%bds%torsn(:,i) = tmp_bnd%torsn(:,iterms(i))
                    la%bds%torsamp(:,i) = tmp_bnd%torsamp(:,iterms(i))
                    la%bds%torsphase(:,i) = tmp_bnd%torsphase(:,iterms(i))
                end do
                
                write(message, "(I0, A)") nterms, " torsion terms added due to link atoms."
                call ommp_message(message, OMMP_VERBOSE_LOW, "linkatom")
            end if
            
            deallocate(prm_buf)
            call bonded_terminate(tmp_bnd)
        end subroutine

        subroutine link_atom_bond_geomgrad(la, qmg, mmg, doqm, domm) 
            use mod_bonded, only: bond_geomgrad

            implicit none

            type(ommp_link_atom_type), intent(in) :: la
            real(rp), intent(inout) :: qmg(:,:), &
                                       mmg(:,:)
            logical, intent(in) :: doqm, domm

            real(rp), allocatable :: grd(:,:)
            integer(ip) :: i

            call mallocate('link_atom_bond_geomgrad [grd]', &
                           3_ip, la%qmmmtop%mm_atoms, grd)
            grd = 0.0
            call bond_geomgrad(la%bds, grd)
            
            if(doqm) then
                do i=1, la%qmtop%mm_atoms
                    qmg(:,i) = qmg(:,i) + grd(:,la%qm2full(i))
                end do
            end if

            if(domm) then
                do i=1, la%mmtop%mm_atoms
                    mmg(:,i) = mmg(:,i) + grd(:,la%mm2full(i))
                end do
            end if

            call mfree('link_atom_bond_geomgrad [grd]', grd)
        end subroutine
        
        subroutine link_atom_angle_geomgrad(la, qmg, mmg, doqm, domm) 
            use mod_bonded, only: angle_geomgrad

            implicit none

            type(ommp_link_atom_type), intent(in) :: la
            real(rp), intent(inout) :: qmg(:,:), &
                                       mmg(:,:)
            logical, intent(in) :: doqm, domm

            real(rp), allocatable :: grd(:,:)
            integer(ip) :: i

            call mallocate('link_atom_bond_geomgrad [grd]', &
                           3_ip, la%qmmmtop%mm_atoms, grd)
            grd = 0.0
            call angle_geomgrad(la%bds, grd)
            
            if(doqm) then
                do i=1, la%qmtop%mm_atoms
                    qmg(:,i) = qmg(:,i) + grd(:,la%qm2full(i))
                end do
            end if

            if(domm) then
                do i=1, la%mmtop%mm_atoms
                    mmg(:,i) = mmg(:,i) + grd(:,la%mm2full(i))
                end do
            end if

            call mfree('link_atom_bond_geomgrad [grd]', grd)
        end subroutine

        subroutine link_atom_torsion_geomgrad(la, qmg, mmg, doqm, domm)
            use mod_bonded, only: torsion_geomgrad

            implicit none

            type(ommp_link_atom_type), intent(in) :: la
            real(rp), intent(inout) :: qmg(:,:), &
                                       mmg(:,:)
            logical, intent(in) :: doqm, domm

            real(rp), allocatable :: grd(:,:)
            integer(ip) :: i

            call mallocate('link_atom_bond_geomgrad [grd]', &
                           3_ip, la%qmmmtop%mm_atoms, grd)
            grd = 0.0
            call torsion_geomgrad(la%bds, grd)
            
            if(doqm) then
                do i=1, la%qmtop%mm_atoms
                    qmg(:,i) = qmg(:,i) + grd(:,la%qm2full(i))
                end do
            end if

            if(domm) then
                do i=1, la%mmtop%mm_atoms
                    mmg(:,i) = mmg(:,i) + grd(:,la%mm2full(i))
                end do
            end if

            call mfree('link_atom_bond_geomgrad [grd]', grd)
        end subroutine
        
        subroutine link_atom_project_grd(la, laforces, qmg, mmg)
            use mod_utils, only: versor_der

            implicit none

            type(ommp_link_atom_type), intent(in) :: la
            real(rp), intent(in) :: laforces(3, la%nla)
            real(rp), intent(inout) :: qmg(3,la%qmtop%mm_atoms), &
                                       mmg(3,la%mmtop%mm_atoms)

            integer(ip) :: i, iqm, imm, ila
            real(rp) :: delta(3), rmm(3), rqm(3), dedqm(3,3), dedmm(3,3)

            do i=1, la%nla
                iqm = la%links(_QM_,i)
                imm = la%links(_MM_,i)
                ila = la%links(_LA_,i)

                rmm = la%mmtop%cmm(:,imm)
                rqm = la%qmtop%cmm(:,iqm)
                delta = rmm-rqm

                dedmm = la%la_distance(i) * versor_der(delta)
               
                dedqm = 0.0
                dedqm(1,1) = 1.0
                dedqm(2,2) = 1.0
                dedqm(3,3) = 1.0
                dedqm = dedqm - dedmm
                
                if(.not. la%qmmmtop%frozen(la%qm2full(iqm))) &
                    qmg(:, iqm) = qmg(:, iqm) + matmul(dedqm, laforces(:,i))
                if(.not. la%qmmmtop%frozen(la%mm2full(imm))) &
                    mmg(:, imm) = mmg(:, imm) + matmul(dedmm, laforces(:,i))
                qmg(:, ila) = -laforces(:,i)
            end do
        end subroutine
end module