rotate_multipoles.F90 Source File


This file depends on

sourcefile~~rotate_multipoles.f90~~EfferentGraph sourcefile~rotate_multipoles.f90 rotate_multipoles.F90 sourcefile~mod_electrostatics.f90 mod_electrostatics.F90 sourcefile~rotate_multipoles.f90->sourcefile~mod_electrostatics.f90 sourcefile~mod_io.f90 mod_io.F90 sourcefile~rotate_multipoles.f90->sourcefile~mod_io.f90 sourcefile~mod_memory.f90 mod_memory.F90 sourcefile~rotate_multipoles.f90->sourcefile~mod_memory.f90 sourcefile~mod_constants.f90 mod_constants.F90 sourcefile~rotate_multipoles.f90->sourcefile~mod_constants.f90 sourcefile~mod_electrostatics.f90->sourcefile~mod_io.f90 sourcefile~mod_electrostatics.f90->sourcefile~mod_memory.f90 sourcefile~mod_electrostatics.f90->sourcefile~mod_constants.f90 sourcefile~mod_adjacency_mat.f90 mod_adjacency_mat.F90 sourcefile~mod_electrostatics.f90->sourcefile~mod_adjacency_mat.f90 sourcefile~mod_topology.f90 mod_topology.F90 sourcefile~mod_electrostatics.f90->sourcefile~mod_topology.f90 sourcefile~mod_fmm_interface.f90 mod_fmm_interface.F90 sourcefile~mod_electrostatics.f90->sourcefile~mod_fmm_interface.f90 sourcefile~mod_profiling.f90 mod_profiling.F90 sourcefile~mod_electrostatics.f90->sourcefile~mod_profiling.f90 sourcefile~mod_io.f90->sourcefile~mod_constants.f90 sourcefile~mod_memory.f90->sourcefile~mod_io.f90 sourcefile~mod_memory.f90->sourcefile~mod_constants.f90 sourcefile~mod_adjacency_mat.f90->sourcefile~mod_memory.f90 sourcefile~mod_utils.f90 mod_utils.F90 sourcefile~mod_adjacency_mat.f90->sourcefile~mod_utils.f90 sourcefile~mod_topology.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_adjacency_mat.f90 sourcefile~mod_fmm_interface.f90->sourcefile~mod_constants.f90 sourcefile~mod_tree.f90 mod_tree.F90 sourcefile~mod_fmm_interface.f90->sourcefile~mod_tree.f90 sourcefile~mod_fmm.f90 mod_fmm.F90 sourcefile~mod_fmm_interface.f90->sourcefile~mod_fmm.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_harmonics.f90 mod_harmonics.F90 sourcefile~mod_fmm_interface.f90->sourcefile~mod_harmonics.f90 sourcefile~mod_fmm_utils.f90 mod_fmm_utils.F90 sourcefile~mod_fmm_interface.f90->sourcefile~mod_fmm_utils.f90 sourcefile~mod_profiling.f90->sourcefile~mod_io.f90 sourcefile~mod_profiling.f90->sourcefile~mod_memory.f90 sourcefile~mod_profiling.f90->sourcefile~mod_constants.f90 sourcefile~mod_utils.f90->sourcefile~mod_memory.f90 sourcefile~mod_utils.f90->sourcefile~mod_constants.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_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_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_harmonics.f90->sourcefile~mod_constants.f90 sourcefile~mod_harmonics.f90->sourcefile~mod_fmm_utils.f90 sourcefile~mod_fmm_utils.f90->sourcefile~mod_constants.f90

Contents

Source Code


Source Code

#include "f_cart_components.h"

subroutine rotation_geomgrad(eel, E, Egrd, grad)
    
    use mod_memory, only: ip, rp
    use mod_electrostatics, only: ommp_electrostatics_type
    
    implicit none
  
    type(ommp_electrostatics_type), intent(in) :: eel
    real(rp), intent(in) :: E(3, eel%top%mm_atoms), Egrd(6, eel%top%mm_atoms)
    real(rp), dimension(3, eel%top%mm_atoms), intent(inout) :: grad

    integer(ip) :: j, jx, jy, jz, k, l, m, n
    real(rp), dimension(3) :: dip
    real(rp), dimension(3,3) :: r, rt, qua, rqua, ddip
    real(rp), dimension(3,3,3) :: dri, driz, drix, driy, dqua, dtmp
    logical :: frozen_j, frozen_jx, frozen_jy, frozen_jz

    ! TODO prepare_fixedelec

    ! loop over the mm sites and build the derivatives of the rotation
    ! matrices with respect to the positions of all the relevant atoms.
    do j = 1, eel%top%mm_atoms
        jz = eel%iz(j)
        if(jz == 0) jz = j
        jx = eel%ix(j)
        if(jx == 0) jx = j
        jy = eel%iy(j)
        if(jy == 0) jy = j
        
        if(eel%top%use_frozen) then
            if(eel%top%frozen(j) .and. &
               eel%top%frozen(jx) .and. &
               eel%top%frozen(jy) .and. &
               eel%top%frozen(jz)) cycle
        end if

       frozen_j = .false.
       frozen_jx = .false.
       frozen_jy = .false.
       frozen_jz = .false.
       if(eel%top%use_frozen) then
           frozen_j = eel%top%frozen(j)
           frozen_jx = eel%top%frozen(jx)
           frozen_jy = eel%top%frozen(jy)
           frozen_jz = eel%top%frozen(jz)
       end if
        
        call rotation_matrix(.true., & 
                             eel%top%cmm(:,j), eel%top%cmm(:,jx), &
                             eel%top%cmm(:,jy), eel%top%cmm(:,jz), &
                             eel%mol_frame(j), &
                             r, dri, driz, drix, driy)
        
        rt = transpose(r)
        
        ! get the multipoles. we also need the rotated quadrupoles:
        dip(_x_) = eel%q0(1+_x_,j)
        dip(_y_) = eel%q0(1+_y_,j)
        dip(_z_) = eel%q0(1+_z_,j)

        qua(_x_,_x_)  = eel%q0(4+_xx_,j)
        qua(_x_,_y_)  = eel%q0(4+_xy_,j)
        qua(_x_,_z_)  = eel%q0(4+_xz_,j)
        qua(_y_,_x_)  = eel%q0(4+_yx_,j)
        qua(_y_,_y_)  = eel%q0(4+_yy_,j)
        qua(_y_,_z_)  = eel%q0(4+_yz_,j)
        qua(_z_,_x_)  = eel%q0(4+_zx_,j)
        qua(_z_,_y_)  = eel%q0(4+_zy_,j)
        qua(_z_,_z_)  = eel%q0(4+_zz_,j)
        
        rqua(_x_,_x_)  = eel%q(4+_xx_,j)
        rqua(_x_,_y_)  = eel%q(4+_xy_,j)
        rqua(_x_,_z_)  = eel%q(4+_xz_,j)
        rqua(_y_,_x_)  = eel%q(4+_yx_,j)
        rqua(_y_,_y_)  = eel%q(4+_yy_,j)
        rqua(_y_,_z_)  = eel%q(4+_yz_,j)
        rqua(_z_,_x_)  = eel%q(4+_zx_,j)
        rqua(_z_,_y_)  = eel%q(4+_zy_,j)
        rqua(_z_,_z_)  = eel%q(4+_zz_,j)
        
        ! contributions to the forces on the j-th atoms:

        ddip = 0.0_rp
        dqua = 0.0_rp
        dtmp = 0.0_rp
        ! compute the differentiated multipoles:
        do k = 1, 3
            do l = 1, 3
                ddip(:,k) = ddip(:,k) + dri(:,k,l)*dip(l)
            end do
        end do
        
        do k = 1, 3
            do l = 1, 3
                do m = 1, 3
                    do n = 1, 3
                        !dtmp(:,l,m)*rt(m,k)
                        dqua(:,k,l) = dqua(:,k,l) + (dri(:,k,m)*qua(m,n) - &
                                      rqua(k,m)*dri(:,m,n))*rt(n,l)  
                    end do
                end do
            end do
        end do

        if(.not. frozen_j) then
            ! increment the forces for the dipoles...
            grad(:,j) = grad(:,j) - ddip(:,_x_)*E(_x_,j) &
                                - ddip(:,_y_)*E(_y_,j) & 
                                - ddip(:,_z_)*E(_z_,j)
            ! ... and for the quadrupoles:
            grad(:,j) = grad(:,j) + dqua(:,_x_,_x_)*Egrd(_xx_,j) &
                                + dqua(:,_y_,_y_)*Egrd(_yy_,j) &
                                + dqua(:,_z_,_z_)*Egrd(_zz_,j) &
                                + 2*(dqua(:,_x_,_y_)*Egrd(_xy_,j) &
                                +    dqua(:,_x_,_z_)*Egrd(_xz_,j) &
                                +    dqua(:,_y_,_z_)*Egrd(_yz_,j))
        end if
     
        ! do jx
        ddip = 0.0_rp
        dqua = 0.0_rp
        dtmp = 0.0_rp
        do k = 1, 3
            do l = 1, 3
                ddip(:,k) = ddip(:,k) + drix(:,k,l)*dip(l)
            end do
        end do
        
        do k = 1, 3
            do l = 1, 3
                do m = 1, 3
                    do n = 1, 3
                        !dtmp(:,l,m)*rt(m,k)
                        dqua(:,k,l) = dqua(:,k,l) + (drix(:,k,m)*qua(m,n) -&
                                      rqua(k,m)*drix(:,m,n))*rt(n,l)
                    end do
                end do
            end do
        end do

        if(.not. frozen_jx) then
            ! increment the forces for the dipoles...
            grad(:,jx) = grad(:,jx) - ddip(:,_x_)*E(_x_,j) &
                                    - ddip(:,_y_)*E(_y_,j) & 
                                    - ddip(:,_z_)*E(_z_,j)
            ! ... and for the quadrupoles:
            grad(:,jx) = grad(:,jx) + dqua(:,_x_,_x_)*Egrd(_xx_,j) &
                                    + dqua(:,_y_,_y_)*Egrd(_yy_,j) &
                                    + dqua(:,_z_,_z_)*Egrd(_zz_,j) &
                                    + 2*(dqua(:,_x_,_y_)*Egrd(_xy_,j) &
                                    +    dqua(:,_x_,_z_)*Egrd(_xz_,j) &
                                    +    dqua(:,_y_,_z_)*Egrd(_yz_,j))
        end if
                
        ! do jy
        ddip = 0.0_rp
        dqua = 0.0_rp
        dtmp = 0.0_rp
        do k = 1, 3
            do l = 1, 3
                ddip(:,k) = ddip(:,k) + driy(:,k,l)*dip(l)
            end do
        end do
        
        do k = 1, 3
            do l = 1, 3
                do m = 1, 3
                    do n = 1, 3
                        ! dtmp(:,l,m)*rt(m,k)
                        dqua(:,k,l) = dqua(:,k,l) + (driy(:,k,m)*qua(m,n) -&
                                      rqua(k,m)*driy(:,m,n))*rt(n,l)
                    end do
                end do
            end do
        end do
       
        if(.not. frozen_jy) then
            ! increment the forces for the dipoles...
            grad(:,jy) = grad(:,jy) - ddip(:,_x_)*E(_x_,j) &
                                    - ddip(:,_y_)*E(_y_,j) & 
                                    - ddip(:,_z_)*E(_z_,j)
            ! ... and for the quadrupoles:
            grad(:,jy) = grad(:,jy) + dqua(:,_x_,_x_)*Egrd(_xx_,j) &
                                    + dqua(:,_y_,_y_)*Egrd(_yy_,j) &
                                    + dqua(:,_z_,_z_)*Egrd(_zz_,j) &
                                    + 2*(dqua(:,_x_,_y_)*Egrd(_xy_,j) &
                                    +    dqua(:,_x_,_z_)*Egrd(_xz_,j) &
                                    +    dqua(:,_y_,_z_)*Egrd(_yz_,j))
        end if
         
        ! Do jz
        ddip = 0.0_rp
        dqua = 0.0_rp
        dtmp = 0.0_rp
        do k = 1, 3
            do l = 1, 3
                ddip(:,k) = ddip(:,k) + driz(:,k,l)*dip(l)
            end do
        end do
        
        do k = 1, 3
            do l = 1, 3
                do m = 1, 3
                    do n = 1, 3
                        ! tmp(:,l,m)*rt(m,k)
                        dqua(:,k,l) = dqua(:,k,l) + (driz(:,k,m)*qua(m,n) -&
                                      rqua(k,m)*driz(:,m,n))*rt(n,l)
                    end do
                end do
            end do
        end do
        
        if(.not. frozen_jz) then
            ! increment the forces for the dipoles...
            grad(:,jz) = grad(:,jz) - ddip(:,_x_)*E(_x_,j) &
                                    - ddip(:,_y_)*E(_y_,j) & 
                                    - ddip(:,_z_)*E(_z_,j)
            ! ... and for the quadrupoles:
            grad(:,jz) = grad(:,jz) + dqua(:,_x_,_x_)*Egrd(_xx_,j) &
                                    + dqua(:,_y_,_y_)*Egrd(_yy_,j) &
                                    + dqua(:,_z_,_z_)*Egrd(_zz_,j) &
                                    + 2*(dqua(:,_x_,_y_)*Egrd(_xy_,j) &
                                    +    dqua(:,_x_,_z_)*Egrd(_xz_,j) &
                                    +    dqua(:,_y_,_z_)*Egrd(_yz_,j))
        end if

    end do 
end subroutine rotation_geomgrad

subroutine rotate_multipoles(eel)
    !! this routine rotates the atomic multipoles from the molecular frame
    !! where they are defined as force field parameters to the lab frame.
    !! if required, it also computes the contribution to the forces that
    !! stems from the derivatives of the rotation matrices, sometimes 
    !! referred to as "torques".
    !! for the latter task, it uses the field and field gradient from 
    !! at the multipoles, which is passed in def. 
    !! for consistency reasons, def is dimensioned (ld_cder,mm_atoms). 
    
    use mod_memory, only: ip, rp
    use mod_electrostatics, only: ommp_electrostatics_type
    
    implicit none
  
    type(ommp_electrostatics_type), intent(inout) :: eel

    integer(ip) :: j, jx, jy, jz 
    real(rp), dimension(3,3) :: r, rt, qua, rqua, tmp
    real(rp), dimension(3,3,3) :: dri, driz, drix, driy

    ! loop over the mm sites and build the rotation matrices.
    do j = 1, eel%top%mm_atoms
        jz = eel%iz(j)
        if(jz == 0) jz = j
        jx = eel%ix(j)
        if(jx == 0) jx = j
        jy = eel%iy(j)
        if(jy == 0) jy = j
        
        call rotation_matrix(.false., & 
                             eel%top%cmm(:,j), eel%top%cmm(:,jx), &
                             eel%top%cmm(:,jy), eel%top%cmm(:,jz), &
                             eel%mol_frame(j), &
                             r, dri, driz, drix, driy)
        
        rt = transpose(r)
        ! copy the monopole:
        eel%q(1,j) = eel%q0(1,j)

        ! rotate the dipole 
        eel%q(2:4,j) = matmul(r,eel%q0(2:4,j))

        ! exctract, rotate and put back the quadrupole:
        qua(_x_,_x_)  = eel%q0(4+_xx_,j)
        qua(_x_,_y_)  = eel%q0(4+_xy_,j)
        qua(_x_,_z_)  = eel%q0(4+_xz_,j)
        qua(_y_,_x_)  = eel%q0(4+_yx_,j)
        qua(_y_,_y_)  = eel%q0(4+_yy_,j)
        qua(_y_,_z_)  = eel%q0(4+_yz_,j)
        qua(_z_,_x_)  = eel%q0(4+_zx_,j)
        qua(_z_,_y_)  = eel%q0(4+_zy_,j)
        qua(_z_,_z_)  = eel%q0(4+_zz_,j)
        
        tmp  = matmul(r,qua)
        rqua = matmul(tmp,rt)
        eel%q(4+_xx_,j)  = rqua(_x_,_x_)
        eel%q(4+_yy_,j)  = rqua(_y_,_y_)
        eel%q(4+_zz_,j)  = rqua(_z_,_z_)
        eel%q(4+_xy_,j)  = rqua(_x_,_y_)
        eel%q(4+_xz_,j)  = rqua(_x_,_z_)
        eel%q(4+_yz_,j)  = rqua(_y_,_z_)
    end do
end subroutine rotate_multipoles


subroutine rotation_matrix(doder,c,cx,cy,cz,mol_frame,r,dri,driz,drix,driy)
    use mod_io, only: fatal_error
    use mod_memory, only: ip, rp
    use mod_constants, only: eps_rp
    
    implicit none
    !! given an atom j and the reference atoms jx, jy, and jz, this routine
    !! computes the rotation matrix needed to rotate the multipoles on the
    !! i-th atom from the molecular frame to the lab frame.
    !! if required, it also return the derivative of the rotation matrices
    !! with respect to the coordinates of all the atoms involved in its
    !! definition. 
    !!
    !! this routine is completely general and can be easily augmented with 
    !! new rotation conventions. 
    !!
    !! given the atoms used to define the molecolar frame, identified by the
    !! indices jx, jy, jz, this routine builds the vectors 
    !!
    !!   xi   = cmm(:,jz) - cmm(:,i) 
    !!   eta  = cmm(:,jx) - cmm(:,i) 
    !!   zeta = cmm(:,jy) - cmm(:,i) 
    !!
    !! it then decodes the rotatoin conventions by introducing two vectors, 
    !! u and v, that span the xz plane. 
    !! this is the only convention-dependent part: everything else works
    !! automatically in a general way.
    !!
    !! for the definition of u and v, the unit vectors that identify the
    !! orthogonal molecular systems are built as follows:
    !!
    !! ez = u/|u|
    !! ex = gram-schmidt (v,ez)
    !! ey = ez x ex
    !!
    !! output:
    !! =======
    !!
    !! r(i,j) is the rotation matrix, whose columns are (ex,ey,ez)
    !!
    !! dri(i,j,k) contains the derivative of the i-th component of e_k 
    !! with respect to ri_j
  
    logical, intent(in)    :: doder
    real(rp), intent(in), dimension(3) :: c, cx, cy, cz
    integer(ip), intent(in) :: mol_frame

    real(rp), dimension(3,3), intent(out) :: r
    real(rp), dimension(3,3,3), intent(inout) :: dri, driz, drix, driy

    integer(ip) :: k
    real(rp) :: dot
    real(rp) :: xi(3), eta(3), zeta(3), xi_norm, eta_norm, zeta_norm
    real(rp) :: u(3), v(3), u_norm, ex(3), ey(3), ez(3), ex_nrm, ex_sq,      &
                u_sq, u_cube, vez
    real(rp), dimension(3,3) :: ez_u, ex_v, ex_ez, ey_ez, ey_ex, u_ri, u_riz, &
                                u_rix, u_riy, v_ri, v_rix, v_riy, ez_ri, &
                                ez_riz, ez_rix, ez_riy, ex_ri, ex_riz, &
                                ex_rix, ex_riy, ey_ri, ey_riz, ey_rix, ey_riy
    
    real(rp), parameter :: zofac = 0.866_rp
    
    r = 0.0_rp
    
    ! get the xi, eta and zeta vectors and their norms:
    xi = cz - c
    xi_norm  = sqrt(dot_product(xi,xi))

    eta = cx - c
    eta_norm = sqrt(dot_product(eta,eta))

    zeta = cy-c
    zeta_norm = sqrt(dot_product(zeta,zeta))
!
! build the u and v vectors, that span the xz plane according to the specific
! convention:
!
  u = 0.0_rp
  v = 0.0_rp
  if (mol_frame.eq.0) then
!
!   do nothing.
!
    r(1,1) = 1.0_rp
    r(2,2) = 1.0_rp
    r(3,3) = 1.0_rp
    if (doder) then
      dri  = 0.0_rp
      driz = 0.0_rp
      drix = 0.0_rp
      driy = 0.0_rp
    end if
    return
!
  else if (mol_frame.eq.1) then
!
!   z-then-x convention:
!
    u = xi
    v = eta
!
  else if (mol_frame.eq.2) then
!
!   bisector convention:
!
    u = eta_norm*xi + xi_norm*eta
    v = eta
!
  else if (mol_frame.eq.3) then
!
!   z-only convention:
!
    u = xi
    dot = u(3)/xi_norm
    if (dot.le.zofac .and. abs(u(2)) > eps_rp) then
      v(1) = 1.0_rp
    else
      v(2) = 1.0_rp
    end if
!
  else if (mol_frame.eq.4) then
!
!   z-bisector convention:
!
    u = xi
    v = zeta_norm*eta + eta_norm*zeta
!
  else if (mol_frame.eq.5) then
!
!   3-fold convention:
!
    u = eta_norm*zeta_norm*xi + xi_norm*zeta_norm*eta + xi_norm*eta_norm*zeta
    v = eta
!
  else
!
!   convention not yet implemented.
!
    !!TODO call fatal_error('the required rotation convention is not implemented.')
  end if
!
! build the unit vectors:
!
  if(norm2(u) < eps_rp .or. norm2(v) < eps_rp) then
      call fatal_error("Ill defined rotation axis (either u or v vectors have &
                       &0 norm.")
  end if

  u_sq   = dot_product(u,u)
  u_norm = sqrt(u_sq)
  ez     = u/u_norm
!
  vez    = dot_product(v,ez)
  ex     = v - vez*ez
  ex_sq  = dot_product(ex,ex)
  ex_nrm = sqrt(ex_sq)
  ex     = ex/ex_nrm
!
  ey(1)  = ez(2)*ex(3) - ez(3)*ex(2)
  ey(2)  = ez(3)*ex(1) - ez(1)*ex(3)
  ey(3)  = ez(1)*ex(2) - ez(2)*ex(1)
!
  r(:,1) = ex
  r(:,2) = ey
  r(:,3) = ez
!
! return if derivatives are not requested:
!
  if (.not. doder) return
!
! derivatives code
! ================
!
! the derivatives of the rotation matrices can be computed using chain-rule
! differentiation. 
! please, see JCTC 2014, 10, 1638−1651 for details.
!
! clean interemediates:
!
  ez_u  = 0.0_rp
  ex_v  = 0.0_rp
  ex_ez = 0.0_rp
  ey_ez = 0.0_rp
  ey_ex = 0.0_rp
  u_ri  = 0.0_rp
  u_riz = 0.0_rp
  u_rix = 0.0_rp
  u_riy = 0.0_rp
  v_ri  = 0.0_rp
  v_rix = 0.0_rp
  v_riy = 0.0_rp
!
! start by assembling the intermediates that do not depend on the sepcific convention:
!
  u_cube = u_sq*u_norm
  do k = 1, 3
    ez_u(k,k)  = 1.0_rp/u_norm
    ex_v(k,k)  = 1.0_rp/ex_nrm
    ex_ez(k,k) = - vez/ex_nrm
  end do
  do k = 1, 3
    ez_u(:,k)  = ez_u(:,k)  - u(:)*u(k)/u_cube
    ex_v(:,k)  = ex_v(:,k)  - ez(:)*ez(k)/ex_nrm - ex(:)*ex(k)/ex_nrm
    ex_ez(:,k) = ex_ez(:,k) + ex(:)*vez*v(k)/ex_sq - ez(:)*v(k)/ex_nrm
  end do
!
  ey_ez(1,1) =   0.0_rp
  ey_ez(1,2) =   ex(3)
  ey_ez(1,3) = - ex(2)
  ey_ez(2,1) = - ex(3)
  ey_ez(2,2) =   0.0_rp
  ey_ez(2,3) =   ex(1)
  ey_ez(3,1) =   ex(2)
  ey_ez(3,2) = - ex(1)
  ey_ez(3,3) =   0.0_rp
!
  ey_ex(1,1) =   0.0_rp
  ey_ex(1,2) = - ez(3)
  ey_ex(1,3) =   ez(2)
  ey_ex(2,1) =   ez(3)
  ey_ex(2,2) =   0.0_rp
  ey_ex(2,3) = - ez(1)
  ey_ex(3,1) = - ez(2)
  ey_ex(3,2) =   ez(1)
  ey_ex(3,3) =   0.0_rp
!
! now compute the convention-specific terms:
!
  if (mol_frame.eq.1) then
!
!   z-then-x:
!
    do k = 1, 3
      u_ri(k,k)  = - 1.0_rp
      u_riz(k,k) =   1.0_rp
      v_ri(k,k)  = - 1.0_rp
      v_rix(k,k) =   1.0_rp
    end do
!
  else if (mol_frame.eq.2) then
!
!   bisector:
!
    do k = 1, 3
      u_ri(k,k)  = - xi_norm - eta_norm
      u_riz(k,k) =   eta_norm
      u_rix(k,k) =   xi_norm
      v_ri(k,k)  = - 1.0_rp
      v_rix(k,k) =   1.0_rp
    end do
!
    do k = 1, 3
      u_ri(:,k)  = u_ri(:,k)  - eta(:)*xi(k)/xi_norm - xi(:)*eta(k)/eta_norm
      u_riz(:,k) = u_riz(:,k) + eta(:)*xi(k)/xi_norm 
      u_rix(:,k) = u_rix(:,k) + xi(:)*eta(k)/eta_norm
    end do
!
  else if (mol_frame.eq.3) then
!
!   z-only:
!
    do k = 1, 3
      u_ri(k,k)  = - 1.0_rp
      u_riz(k,k) =   1.0_rp
    end do
!
  else if (mol_frame.eq.4) then
!
!   z-bisector:
!
    do k = 1, 3
      u_ri(k,k)  = - 1.0_rp
      u_riz(k,k) =   1.0_rp
      v_ri(k,k)  = - eta_norm - zeta_norm
      v_rix(k,k) =   zeta_norm
      u_riy(k,k) =   eta_norm
    end do
!
    do k = 1, 3
      v_ri(:,k)  = v_ri(:,k)  - eta(:)*zeta(k)/zeta_norm - zeta(:)*eta(k)/eta_norm
      v_rix(:,k) = v_rix(:,k) + zeta(:)*eta(k)/eta_norm
      v_riy(:,k) = v_riy(:,k) + eta(:)*zeta(k)/zeta_norm
    end do
!
  else if (mol_frame.eq.5) then
!
!   3-fold:
!
    do k = 1, 3
      u_ri(k,k)  = - xi_norm*eta_norm - xi_norm*zeta_norm - eta_norm*zeta_norm
      u_riz(k,k) =   eta_norm*zeta_norm
      u_rix(k,k) =   xi_norm*zeta_norm
      u_riy(k,k) =   xi_norm*eta_norm
      v_ri(k,k)  = - 1.0_rp
      v_rix(k,k) =   1.0_rp
    end do
!
    do k = 1, 3
      u_ri(:,k)  = u_ri(:,k)  - (eta*zeta_norm + zeta*eta_norm)*xi(k)/xi_norm &
                              - (xi*zeta_norm  + zeta*xi_norm )*eta(k)/eta_norm &
                              - (xi*eta_norm   + eta*xi_norm  )*zeta(k)/zeta_norm
      u_riz(:,k) = u_riz(:,k) + (eta*zeta_norm + zeta*eta_norm)*xi(k)/xi_norm 
      u_rix(:,k) = u_rix(:,k) + (xi*zeta_norm  + zeta*xi_norm )*eta(k)/eta_norm 
      u_riy(:,k) = u_riy(:,k) + (xi*eta_norm   + eta*xi_norm  )*zeta(k)/zeta_norm
    end do
!
  end if
!
! proceed assembling the derivatives of the unit vectors:
!
! ez:
!
  ez_ri  = matmul(ez_u,u_ri)
  ez_riz = matmul(ez_u,u_riz)
  ez_rix = matmul(ez_u,u_rix)
  ez_riy = matmul(ez_u,u_riy)
!
! ex:
!
  ex_ri  = matmul(ex_v,v_ri)  + matmul(ex_ez,ez_ri)
  ex_riz =                      matmul(ex_ez,ez_riz)
  ex_rix = matmul(ex_v,v_rix) + matmul(ex_ez,ez_rix)
  ex_riy = matmul(ex_v,v_riy) + matmul(ex_ez,ez_riy)
!
! ey:
!
  ey_ri  = matmul(ey_ex,ex_ri)  + matmul(ey_ez,ez_ri)
  ey_riz = matmul(ey_ex,ex_riz) + matmul(ey_ez,ez_riz)
  ey_rix = matmul(ey_ex,ex_rix) + matmul(ey_ez,ez_rix)
  ey_riy = matmul(ey_ex,ex_riy) + matmul(ey_ez,ez_riy)
!
! finally, assemble the derivatives of the rotation matrices:
!
  dri(:,:,1)  = transpose(ex_ri)
  dri(:,:,2)  = transpose(ey_ri)
  dri(:,:,3)  = transpose(ez_ri)
  driz(:,:,1) = transpose(ex_riz)
  driz(:,:,2) = transpose(ey_riz)
  driz(:,:,3) = transpose(ez_riz)
  drix(:,:,1) = transpose(ex_rix)
  drix(:,:,2) = transpose(ey_rix)
  drix(:,:,3) = transpose(ez_rix)
  driy(:,:,1) = transpose(ex_riy)
  driy(:,:,2) = transpose(ey_riy)
  driy(:,:,3) = transpose(ez_riy)
end subroutine rotation_matrix