MeshPartitioning.f90 Source File


Source Code

#include "Includes.h"
module MeshPartitioning
   use SMConstants
   use MeshTypes
   use HexMeshClass
   use PartitionedMeshClass
   use FileReadingUtilities            , only: RemovePath, getFileName

   private
   public   PerformMeshPartitioning

   contains
      subroutine PerformMeshPartitioning(mesh, no_of_domains, partitions, useWeights)
         implicit none
         type(HexMesh), intent(in)  :: mesh
         integer,       intent(in)  :: no_of_domains
         type(PartitionedMesh_t)    :: partitions(no_of_domains)
         logical,       intent(in)  :: useWeights
!
!        ---------------
!        Local variables
!        ---------------
!
         integer               :: fID, domain
         integer               :: elementsDomain(mesh % no_of_elements)
!
!        Initialize partitions
!        ---------------------
         do domain = 1, no_of_domains
            partitions(domain) = PartitionedMesh_t(domain)
         end do
!
!        Get each domain elements and nodes
!        ----------------------------------
         call GetElementsDomain(mesh, no_of_domains, elementsDomain, partitions, useWeights)
!
!        Get the partition boundary faces
!        --------------------------------
         call GetPartitionBoundaryFaces(mesh, no_of_domains, elementsDomain, partitions)

!
!        Export partitions file
!        ----------------------
         call WritePartitionsFile(mesh, elementsDomain)
      end subroutine PerformMeshPartitioning

      subroutine GetElementsDomain(mesh, no_of_domains, elementsDomain, partitions, useWeights)
         use IntegerDataLinkedList
         use MPI_Process_Info
         implicit none
         type(HexMesh), intent(in)              :: mesh
         integer,       intent(in)              :: no_of_domains
         integer,       intent(out)             :: elementsDomain(mesh % no_of_elements)
         type(PartitionedMesh_t), intent(inout) :: partitions(no_of_domains)      
         logical,       intent(in)  :: useWeights
!
!        ---------------
!        Local variables
!        ---------------
!
         integer, allocatable :: nodesDomain(:)


         allocate(nodesDomain(size(mesh % nodes)))
!
!        **********************************************
!        Set which elements belong to each domain using
!        **********************************************
!
         select case (MPI_Partitioning)
!     
!           Space-filling curve partitioning
!           --------------------------------
            case (SFC_PARTITIONING)
               call GetSFCElementsPartition(mesh, no_of_domains, mesh % no_of_elements, elementsDomain, useWeights=useWeights)
!     
!           METIS partitioning
!           ------------------
            case (METIS_PARTITIONING)
               call GetMETISElementsPartition(mesh, no_of_domains, elementsDomain, nodesDomain, useWeights)
         end select
!
!        ****************************************
!        Get which nodes belong to each partition
!        ****************************************
!
         call GetNodesPartition(mesh, no_of_domains, elementsDomain, partitions)   

         deallocate(nodesDomain)   
         
      end subroutine GetElementsDomain

!
!////////////////////////////////////////////////////////////////////////
!
     subroutine GetNodesPartition(mesh, no_of_domains, elementsDomain, partitions)
        use Utilities, only: Qsort
        implicit none
        type(HexMesh), intent(in)              :: mesh
        integer,       intent(in)              :: no_of_domains
        integer,       intent(in)              :: elementsDomain(mesh % no_of_elements)
        type(PartitionedMesh_t), intent(inout) :: partitions(no_of_domains)   
!
!       ---------------
!       Local Variables
!       ---------------
!
        integer              :: nvertex
        integer              :: i
        integer              :: j
        integer              :: k
        integer              :: ipoint
        integer              :: jpoint
        integer              :: idomain
        integer              :: npoints
        integer              :: ielem
        logical              :: isnewpoint
        logical              :: meshIsHOPR
        integer, allocatable :: points(:)
        integer, allocatable :: HOPRpoints(:)

        nvertex = 8
        
        meshIsHOPR = allocated (mesh % HOPRnodeIDs)
        
        do idomain=1,no_of_domains
!
!       Get the number of elements for the partition
!       --------------------------------------------
        partitions(idomain)%no_of_elements = count(elementsDomain == idomain)
        allocate(partitions(idomain)%elementIDs(partitions(idomain)%no_of_elements))
!
!       This will store the partition nodes (allocated as 8 * no_of_elements)
!       ---------------------------------------------------------------------
        allocate(points(nvertex*partitions(idomain)%no_of_elements))
        points = 0
        if (meshIsHOPR) then
            allocate(HOPRpoints(nvertex*partitions(idomain)%no_of_elements))
            HOPRpoints = 0
        end if
!
!       ****************************************
!       Gather each partition nodes and elements      
!       ****************************************
!
        k = 0
        npoints = 0
        do ielem=1,mesh % no_of_elements
           if (elementsDomain(ielem) == idomain) then
!
!             Append a new element
!             --------------------
              k = k + 1
              partitions(idomain)%elementIDs(k) = ielem
!
!             Append its nodes
!             ----------------           
              do j=1,nvertex
!
!                Get the node ID
!                ---------------
                 jpoint = mesh % elements(ielem) % nodeIDs(j)
!
!                Check if it is already stored
!                -----------------------------
                 isnewpoint = .true.
                 do i=1,npoints
                    ipoint = points(i)
                    if (jpoint == ipoint) then
                       isnewpoint = .false.
                       exit
                    end if
                 end do
!
!                Store the node
!                --------------      
                 if (isnewpoint) then
                    npoints = npoints + 1
                    points(npoints) = jpoint
                    if (meshIsHOPR) HOPRpoints(npoints) = mesh % HOPRnodeIDs(jpoint)
                 end if                  
              end do
            end if      
         end do
!
!        Put the nodeIDs into the partitions structure
!        ---------------------------------------------      
         allocate(partitions(idomain)%nodeIDs(npoints))
         partitions(idomain)%nodeIDs(:) = points(1:npoints)
         
         if (meshIsHOPR) then
            allocate(partitions(idomain)%HOPRnodeIDs(npoints))
            partitions(idomain)%HOPRnodeIDs(:) = HOPRpoints(1:npoints)
         end if
!
!        Sort the nodeIDs to read the mesh file accordingly (only needed for SpecMesh)
!        --------------------------------------------------
         if (.not. meshIsHOPR) call Qsort(partitions(idomain)%nodeIDs)

         partitions(idomain)%no_of_nodes = npoints
!
!        ****
!        Free
!        ****
!
         deallocate(points)
         safedeallocate(HOPRpoints)
      end do

     end subroutine GetNodesPartition
!
!////////////////////////////////////////////////////////////////////////
!
      subroutine GetPartitionBoundaryFaces(mesh, no_of_domains, elementsDomain, partitions)
 
         implicit none
         type(HexMesh), intent(in)  :: mesh
         integer,       intent(in)  :: no_of_domains
         integer,       intent(in)  :: elementsDomain(mesh % no_of_elements)
         type(PartitionedMesh_t)    :: partitions(no_of_domains)
!
!        ---------------
!        Local variables
!        ---------------
!
         integer  :: fID, eL, eR, dL, dR, domain
         integer  :: bfaceID(no_of_domains)
!
!        *******************************************
!        Get the number of boundary faces per domain 
!        *******************************************
!
         do fID = 1, size(mesh % faces)
            associate(f => mesh % faces(fID))
!
!           Cycle non-interior faces
!           ------------------------
            if ( f % faceType .ne. HMESH_INTERIOR ) cycle
!
!           Create references to left and right elements
!           --------------------------------------------
            associate( eL => mesh % elements(f % elementIDs(1)), &
                       eR => mesh % elements(f % elementIDs(2)))
!
!           Get each elements domain
!           ------------------------
            dL = elementsDomain(eL % eID)
            dR = elementsDomain(eR % eID)
!
!           Cycle if both elements belong to the same domain
!           ------------------------------------------------
            if ( dL .eq. dR ) cycle 
!
!           Otherwise, the face is a domain boundary face for domains dL and dR
!           -------------------------------------------------------------------
            partitions(dL) % no_of_mpifaces = partitions(dL) % no_of_mpifaces + 1
            partitions(dR) % no_of_mpifaces = partitions(dR) % no_of_mpifaces + 1

            end associate
            end associate
         end do
!
!        **************************************
!        Allocate boundary faces-related memory
!        **************************************
!
         do domain = 1, no_of_domains
            associate(nFaces => partitions(domain) % no_of_mpifaces)
            allocate(partitions(domain) % mpiface_elements(nFaces))
            allocate(partitions(domain) % element_mpifaceSide(nFaces))
            allocate(partitions(domain) % element_mpifaceSideOther(nFaces))
            allocate(partitions(domain) % mpiface_rotation(nFaces))
            allocate(partitions(domain) % mpiface_elementSide(nFaces))
            allocate(partitions(domain) % mpiface_sharedDomain(nFaces))
            end associate
         end do
!
!        ***************************
!        Get each boundary face data
!        ***************************
!
         bfaceID = 0
         do fID = 1, size(mesh % faces)
            associate(f => mesh % faces(fID))
!
!           Cycle non-interior faces
!           ------------------------
            if ( f % faceType .ne. HMESH_INTERIOR ) cycle
!
!           Create references to left and right elements
!           --------------------------------------------
            associate( eL => mesh % elements(f % elementIDs(1)), &
                       eR => mesh % elements(f % elementIDs(2)))
!
!           Get each elements domain
!           ------------------------
            dL = elementsDomain(eL % eID)
            dR = elementsDomain(eR % eID)
!
!           Cycle if both elements belong to the same domain
!           ------------------------------------------------
            if ( dL .eq. dR ) cycle 
!
!           Otherwise, the face is a domain boundary face for domains dL and dR
!           -------------------------------------------------------------------
            bfaceID(dL) = bfaceID(dL) + 1 
            bfaceID(dR) = bfaceID(dR) + 1 
!
!           Get the elements
!           ----------------
            partitions(dL) % mpiface_elements(bfaceID(dL)) = eL % eID
            partitions(dR) % mpiface_elements(bfaceID(dR)) = eR % eID
!
!           Get the face sides in the elements (current partition)
!           ------------------------------------------------------
            partitions(dL) % element_mpifaceSide(bfaceID(dL)) = f % elementSide(1)
            partitions(dR) % element_mpifaceSide(bfaceID(dR)) = f % elementSide(2)
!
!           Get the face sides in the elements (neighbor partition)
!           ------------------------------------------------------
            partitions(dL) % element_mpifaceSideOther(bfaceID(dL)) = f % elementSide(2)
            partitions(dR) % element_mpifaceSideOther(bfaceID(dR)) = f % elementSide(1)
!
!           Get the face rotation
!           ---------------------
            partitions(dL) % mpiface_rotation(bfaceID(dL)) = f % rotation 
            partitions(dR) % mpiface_rotation(bfaceID(dR)) = f % rotation 
!
!           Get the element face side
!           -------------------------
            partitions(dL) %  mpiface_elementSide(bfaceID(dL)) = 1
            partitions(dR) %  mpiface_elementSide(bfaceID(dR)) = 2
!
!           Get the shared domain
!           ---------------------
            partitions(dL) % mpiface_sharedDomain(bfaceID(dL)) = dR
            partitions(dR) % mpiface_sharedDomain(bfaceID(dR)) = dL

            end associate
            end associate
         end do

      end subroutine GetPartitionBoundaryFaces
!
!///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
!
!     --------------------------------
!     Space-filling curve partitioning
!     --------------------------------
      subroutine GetSFCElementsPartition(mesh, no_of_domains, nelem, elementsDomain, useWeights)
         implicit none
         !-arguments--------------------------------------------------
         type(HexMesh), intent(in)        :: mesh
         integer, intent(in)    :: no_of_domains
         integer, intent(in)    :: nelem
         integer, intent(inout) :: elementsDomain(nelem)
         logical, intent(in)    :: useWeights
         !-local-variables--------------------------------------------
         integer :: elems_per_domain(no_of_domains)
         integer :: biggerdomains
         integer :: first, last, domain
         integer :: ielem, ndof, max_dof, dof_in_domain
         integer :: dof_per_domain(no_of_domains), start_index(no_of_domains+1)
         logical                :: neddWeights
         integer, allocatable, target   :: weights(:)
         !------------------------------------------------------------

         if (useWeights) then
             allocate(weights(nelem))
             do ielem=1,nelem
                 weights(ielem) = product(mesh % elements(ielem) % Nxyz + 1)
             end do
             if (maxval(weights) .eq. minval(weights)) then
                 neddWeights = .false.
                 deallocate(weights)
             else
                 neddWeights = .true.
                 ndof = sum(weights)
             endif
         end if 
         
         elems_per_domain = nelem / no_of_domains
         biggerdomains = mod(nelem,no_of_domains)
         elems_per_domain(1:biggerdomains) = elems_per_domain(1:biggerdomains) + 1
         
         first = 1
         do domain = 1, no_of_domains
            last = first + elems_per_domain(domain) - 1
            elementsDomain(first:last) = domain
            first = last + 1
         end do

         if (neddWeights) then

             max_dof = ndof / no_of_domains
             start_index = 1
             do domain = 1, no_of_domains
                 start_index(domain+1) = start_index(domain) + elems_per_domain(domain)
             end do

             do domain = 1, no_of_domains-1
                 if (start_index(domain) .ge. start_index(domain+1)) start_index(domain+1) = start_index(domain) + 1
                 dof_in_domain = sum(weights(start_index(domain):start_index(domain+1)))
                 do ielem=1,nelem
                     if (dof_in_domain .lt. max_dof) then
                         start_index(domain+1) = start_index(domain+1) + 1
                         dof_in_domain = sum(weights(start_index(domain):start_index(domain+1)))
                         if (abs(dof_in_domain-max_dof) .le. abs(dof_in_domain-max_dof+weights(start_index(domain+1)+1))) exit
                     else
                         start_index(domain+1) = start_index(domain+1) - 1
                         dof_in_domain = sum(weights(start_index(domain):start_index(domain+1)))
                         if (abs(dof_in_domain-max_dof) .le. abs(dof_in_domain-max_dof-weights(start_index(domain+1)-1))) exit
                     end if
                 end do
             end do

             dof_per_domain = 0
             do domain = 1, no_of_domains
                 dof_per_domain(domain) = sum(weights(start_index(domain):start_index(domain+1)-1))
             end do

             do domain = 1, no_of_domains
                elementsDomain(start_index(domain):start_index(domain+1)-1) = domain
             end do

         end if
         
      end subroutine GetSFCElementsPartition
!
!///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
!
!     ---------------------------------
!     Write the partitions' information
!     ---------------------------------
      subroutine WritePartitionsFile(mesh,elementsDomain)
         implicit none
         !-arguments--------------------------------------------------
         type(HexMesh), intent(in)  :: mesh
         integer      , intent(in)  :: elementsDomain(mesh % no_of_elements)
         !-local-variables--------------------------------------------
         character(LINE_LENGTH)     :: pmeshName
         integer                    :: fID, eID
         !------------------------------------------------------------
         
         pmeshName = "./MESH/" // trim(removePath(getFileName(mesh % meshFileName))) // ".pmesh"
         
         open(newunit = fID, file=trim(pmeshName),action='write')
         
         write(fID,*) mesh % no_of_elements
         do eID = 1, mesh % no_of_elements
            write(fID,*) elementsDomain(eID)
         end do
            
         close(fID)
         
      end subroutine WritePartitionsFile
         
end module MeshPartitioning