Mesh Oriented datABase  (version 5.4.1)
Array-based unstructured mesh datastructure
LloydRelaxation.cpp

Perform Lloyd relaxation on a mesh and its dual
To run: mpiexec -np <np> LloydRelaxation [filename]

Briefly, Lloyd relaxation is a technique to smooth out a mesh. The centroid of each cell is computed from its vertex positions, then vertices are placed at the average of their connected cells' centroids.

In the parallel algorithm, an extra ghost layer of cells is exchanged. This allows us to compute the centroids for boundary cells on each processor where they appear; this eliminates the need for one round of data exchange (for those centroids) between processors. New vertex positions must be sent from owning processors to processors sharing those vertices. Convergence is measured as the maximum distance moved by any vertex.

In this implementation, a fixed number of iterations is performed. The final mesh is output to 'lloydfinal.h5m' in the current directory (H5M format must be used since the file is written in parallel).

/** @example LloydRelaxation.cpp \n
 * \brief Perform Lloyd relaxation on a mesh and its dual \n
 * <b>To run</b>: mpiexec -np <np> LloydRelaxation [filename]\n
 *
 * Briefly, Lloyd relaxation is a technique to smooth out a mesh.  The centroid of each cell is
 * computed from its vertex positions, then vertices are placed at the average of their connected
 * cells' centroids.
 *
 * In the parallel algorithm, an extra ghost layer of cells is exchanged.  This allows us to compute
 * the centroids for boundary cells on each processor where they appear; this eliminates the need
 * for one round of data exchange (for those centroids) between processors.  New vertex positions
 * must be sent from owning processors to processors sharing those vertices.  Convergence is
 * measured as the maximum distance moved by any vertex.
 *
 * In this implementation, a fixed number of iterations is performed.  The final mesh is output to
 * 'lloydfinal.h5m' in the current directory (H5M format must be used since the file is written in
 * parallel).
 */

#include "moab/Core.hpp"
#ifdef MOAB_HAVE_MPI
#include "moab/ParallelComm.hpp"
#include "MBParallelConventions.h"
#endif
#include "moab/Skinner.hpp"
#include "moab/CN.hpp"
#include "moab/CartVect.hpp"
#include <iostream>
#include <sstream>

using namespace moab;
using namespace std;

string test_file_name = string( MESH_DIR ) + string( "/surfrandomtris-4part.h5m" );

ErrorCode perform_lloyd_relaxation( Interface* mb, Range& verts, Range& cells, Tag fixed, int num_its, int report_its );

int main( int argc, char** argv )
{
    int num_its    = 10;
    int report_its = 1;

#ifdef MOAB_HAVE_MPI
    MPI_Init( &argc, &argv );
#endif

    // Need option handling here for input filename
    if( argc > 1 )
    {
        // User has input a mesh file
        test_file_name = argv[1];
    }

    // Get MOAB instance
    Interface* mb = new( std::nothrow ) Core;
    if( NULL == mb ) return 1;

#ifdef MOAB_HAVE_MPI
    // Get the ParallelComm instance
    ParallelComm* pcomm = new ParallelComm( mb, MPI_COMM_WORLD );
    int nprocs          = pcomm->size();
#else
    int nprocs = 1;
#endif
    string options;
    if( nprocs > 1 )  // If reading in parallel, need to tell it how
        options = "PARALLEL=READ_PART;PARTITION=PARALLEL_PARTITION;PARALLEL_RESOLVE_SHARED_ENTS;"
                  "PARALLEL_GHOSTS=2.0.1;DEBUG_IO=0;DEBUG_PIO=0";

    // Load the test file with specified options
    ErrorCode rval = mb->load_file( test_file_name.c_str(), 0, options.c_str() );MB_CHK_ERR( rval );

    // Make tag to specify fixed vertices, since it's input to the algorithm; use a default value of
    // non-fixed so we only need to set the fixed tag for skin vertices
    Tag fixed;
    int def_val = 0;
    rval        = mb->tag_get_handle( "fixed", 1, MB_TYPE_INTEGER, fixed, MB_TAG_CREAT | MB_TAG_DENSE, &def_val );MB_CHK_ERR( rval );

    // Get all vertices and faces
    Range verts, faces, skin_verts;
    rval = mb->get_entities_by_type( 0, MBVERTEX, verts );MB_CHK_ERR( rval );
    rval = mb->get_entities_by_dimension( 0, 2, faces );MB_CHK_ERR( rval );

    // Get the skin vertices of those faces and mark them as fixed; we don't want to fix the
    // vertices on a part boundary, but since we exchanged a layer of ghost faces, those vertices
    // aren't on the skin locally ok to mark non-owned skin vertices too, I won't move those anyway
    // use MOAB's skinner class to find the skin
    Skinner skinner( mb );
    rval = skinner.find_skin( 0, faces, true, skin_verts );MB_CHK_ERR( rval );  // 'true' param indicates we want vertices back, not faces

    vector< int > fix_tag( skin_verts.size(), 1 );  // Initialized to 1 to indicate fixed
    rval = mb->tag_set_data( fixed, skin_verts, &fix_tag[0] );MB_CHK_ERR( rval );

    // Now perform the Lloyd relaxation
    rval = perform_lloyd_relaxation( mb, verts, faces, fixed, num_its, report_its );MB_CHK_ERR( rval );

    // Delete fixed tag, since we created it here
    rval = mb->tag_delete( fixed );MB_CHK_ERR( rval );

    // Output file, using parallel write

#ifdef MOAB_HAVE_MPI
    options = "PARALLEL=WRITE_PART";
#endif

    rval = mb->write_file( "lloydfinal.h5m", NULL, options.c_str() );MB_CHK_ERR( rval );

    // Delete MOAB instance
    delete mb;

#ifdef MOAB_HAVE_MPI
    MPI_Finalize();
#endif

    return 0;
}

ErrorCode perform_lloyd_relaxation( Interface* mb, Range& verts, Range& faces, Tag fixed, int num_its, int report_its )
{
    ErrorCode rval;

#ifdef MOAB_HAVE_MPI
    ParallelComm* pcomm = ParallelComm::get_pcomm( mb, 0 );
    int nprocs          = pcomm->size();
#else
    int nprocs = 1;
#endif

    // Perform Lloyd relaxation:
    // 1. Setup: set vertex centroids from vertex coords; filter to owned verts; get fixed tags

    // Get all verts coords into tag; don't need to worry about filtering out fixed verts,
    // we'll just be setting to their fixed coords
    vector< double > vcentroids( 3 * verts.size() );
    rval = mb->get_coords( verts, &vcentroids[0] );MB_CHK_ERR( rval );

    Tag centroid;
    rval = mb->tag_get_handle( "centroid", 3, MB_TYPE_DOUBLE, centroid, MB_TAG_CREAT | MB_TAG_DENSE );MB_CHK_ERR( rval );
    rval = mb->tag_set_data( centroid, verts, &vcentroids[0] );MB_CHK_ERR( rval );

    // Filter verts down to owned ones and get fixed tag for them
    Range owned_verts, shared_owned_verts;
    if( nprocs > 1 )
    {
#ifdef MOAB_HAVE_MPI
        rval = pcomm->filter_pstatus( verts, PSTATUS_NOT_OWNED, PSTATUS_NOT, -1, &owned_verts );MB_CHK_ERR( rval );
#endif
    }
    else
        owned_verts = verts;
    vector< int > fix_tag( owned_verts.size() );
    rval = mb->tag_get_data( fixed, owned_verts, &fix_tag[0] );MB_CHK_ERR( rval );

    // Now fill vcentroids array with positions of just owned vertices, since those are the ones
    // we're actually computing
    vcentroids.resize( 3 * owned_verts.size() );
    rval = mb->tag_get_data( centroid, owned_verts, &vcentroids[0] );MB_CHK_ERR( rval );

#ifdef MOAB_HAVE_MPI
    // Get shared owned verts, for exchanging tags
    rval = pcomm->get_shared_entities( -1, shared_owned_verts, 0, false, true );MB_CHK_ERR( rval );
    // Workaround: if no shared owned verts, put a non-shared one in the list, to prevent exchanging
    // tags for all shared entities
    if( shared_owned_verts.empty() ) shared_owned_verts.insert( *verts.begin() );
#endif

    // Some declarations for later iterations
    vector< double > fcentroids( 3 * faces.size() );         // fcentroids for face centroids
    vector< double > ctag( 3 * CN::MAX_NODES_PER_ELEMENT );  // Temporary coordinate storage for verts bounding a face
    const EntityHandle* conn;                                // const ptr & size to face connectivity
    int nconn;
    Range::iterator fit, vit;          // For iterating over faces, verts
    int f, v;                          // For indexing into centroid vectors
    vector< EntityHandle > adj_faces;  // Used in vertex iteration

    // 2. For num_its iterations:
    for( int nit = 0; nit < num_its; nit++ )
    {
        double mxdelta = 0.0;

        // 2a. For each face: centroid = sum(vertex centroids)/num_verts_in_cell
        for( fit = faces.begin(), f = 0; fit != faces.end(); ++fit, f++ )
        {
            // Get verts for this face
            rval = mb->get_connectivity( *fit, conn, nconn );MB_CHK_ERR( rval );
            // Get centroid tags for those verts
            rval = mb->tag_get_data( centroid, conn, nconn, &ctag[0] );MB_CHK_ERR( rval );
            fcentroids[3 * f + 0] = fcentroids[3 * f + 1] = fcentroids[3 * f + 2] = 0.0;
            for( v = 0; v < nconn; v++ )
            {
                fcentroids[3 * f + 0] += ctag[3 * v + 0];
                fcentroids[3 * f + 1] += ctag[3 * v + 1];
                fcentroids[3 * f + 2] += ctag[3 * v + 2];
            }
            for( v = 0; v < 3; v++ )
                fcentroids[3 * f + v] /= nconn;
        }
        rval = mb->tag_set_data( centroid, faces, &fcentroids[0] );MB_CHK_ERR( rval );

        // 2b. For each owned vertex:
        for( vit = owned_verts.begin(), v = 0; vit != owned_verts.end(); ++vit, v++ )
        {
            // if !fixed
            if( fix_tag[v] ) continue;
            // vertex centroid = sum(cell centroids)/ncells
            adj_faces.clear();
            rval = mb->get_adjacencies( &( *vit ), 1, 2, false, adj_faces );MB_CHK_ERR( rval );
            rval = mb->tag_get_data( centroid, &adj_faces[0], adj_faces.size(), &fcentroids[0] );MB_CHK_ERR( rval );
            double vnew[] = { 0.0, 0.0, 0.0 };
            for( f = 0; f < (int)adj_faces.size(); f++ )
            {
                vnew[0] += fcentroids[3 * f + 0];
                vnew[1] += fcentroids[3 * f + 1];
                vnew[2] += fcentroids[3 * f + 2];
            }
            for( f = 0; f < 3; f++ )
                vnew[f] /= adj_faces.size();
            double delta = ( CartVect( vnew ) - CartVect( &vcentroids[3 * v] ) ).length();
            mxdelta      = std::max( delta, mxdelta );
            for( f = 0; f < 3; f++ )
                vcentroids[3 * v + f] = vnew[f];
        }

        // Set the centroid tag; having them only in vcentroids array isn't enough, as vertex
        // centroids are accessed randomly in loop over faces
        rval = mb->tag_set_data( centroid, owned_verts, &vcentroids[0] );MB_CHK_ERR( rval );

        // 2c. Exchange tags on owned verts
        if( nprocs > 1 )
        {
#ifdef MOAB_HAVE_MPI
            rval = pcomm->exchange_tags( centroid, shared_owned_verts );MB_CHK_ERR( rval );
#endif
        }

        if( !( nit % report_its ) )
        {
            // Global reduce for maximum delta, then report it
            double global_max = mxdelta;
            int myrank        = 0;
#ifdef MOAB_HAVE_MPI
            if( nprocs > 1 ) MPI_Reduce( &mxdelta, &global_max, 1, MPI_DOUBLE, MPI_MAX, 0, pcomm->comm() );
            myrank = pcomm->rank();
#endif
            if( 1 == nprocs || 0 == myrank ) cout << "Max delta = " << global_max << endl;
        }
    }

    // Write the tag back onto vertex coordinates
    rval = mb->set_coords( owned_verts, &vcentroids[0] );MB_CHK_ERR( rval );

    // Delete the centroid tag, since we don't need it anymore
    rval = mb->tag_delete( centroid );MB_CHK_ERR( rval );

    return MB_SUCCESS;
}
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines