Briefly, Laplacian 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 'laplacianfinal.h5m' in the current directory (H5M format must be used since the file is written in parallel).
Usage: mpiexec -n 2 valgrind ./LaplacianSmoother -f input/surfrandomtris-64part.h5m -r 2 -p 2 -n 25
#include <iostream>
#include <sstream>
#ifdef MOAB_HAVE_MPI
#endif
#include "moab/VerdictWrapper.hpp"
#include "matrix.h"
using namespace std;
#define WRITE_DEBUG_FILES
#ifdef MESH_DIR
#else
#endif
#define RC MB_CHK_ERR( rval )
#define dbgprint( MSG ) \
do \
{ \
if( !global_rank ) std::cerr << ( MSG ) << std::endl; \
} while( false )
Range& cells,
Range& verts,
bool use_hc = false,
bool use_acc = false,
int acc_method = 1,
int num_its = 10,
double rel_eps = 1e-5,
double alpha = 0.0,
double beta = 0.5,
int report_its = 1 );
std::vector< double >& verts_o,
std::vector< double >& verts_n,
double alpha,
double beta );
std::vector< double >& verts_o,
std::vector< double >& verts_n,
bool use_updated = true );
int main(
int argc,
char** argv )
{
int num_its = 10;
int num_ref = 0;
int num_dim = 2;
int report_its = 1;
int num_degree = 2;
bool use_hc = false;
bool use_acc = false;
int acc_method = 1;
double alpha = 0.5, beta = 0.0;
double rel_eps = 1e-5;
const int nghostrings = 1;
std::stringstream sstr;
int global_rank;
MPI_Init( &argc, &argv );
MPI_Comm_rank( MPI_COMM_WORLD, &global_rank );
opts.
addOpt<
int >( std::string(
"niter,n" ),
std::string( "Number of Laplacian smoothing iterations (default=10)" ), &num_its );
opts.
addOpt<
double >( std::string(
"eps,e" ),
std::string( "Tolerance for the Laplacian smoothing error (default=1e-5)" ), &rel_eps );
opts.
addOpt<
double >( std::string(
"alpha" ),
std::string( "Tolerance for the Laplacian smoothing error (default=0.0)" ), &alpha );
opts.
addOpt<
double >( std::string(
"beta" ),
std::string( "Tolerance for the Laplacian smoothing error (default=0.5)" ), &beta );
opts.
addOpt<
int >( std::string(
"dim,d" ), std::string(
"Topological dimension of the mesh (default=2)" ),
&num_dim );
opts.
addOpt< std::string >( std::string(
"file,f" ),
std::string( "Input mesh file to smoothen (default=surfrandomtris-4part.h5m)" ),
std::string( "nrefine,r" ),
std::string( "Number of uniform refinements to perform and apply smoothing cycles (default=1)" ), &num_ref );
opts.
addOpt<
int >( std::string(
"ndegree,p" ), std::string(
"Degree of uniform refinement (default=2)" ),
&num_degree );
opts.
addOpt<
void >( std::string(
"humphrey,c" ),
std::string( "Use Humphrey’s Classes algorithm to reduce shrinkage of "
"Laplacian smoother (default=false)" ),
&use_hc );
opts.
addOpt<
void >( std::string(
"aitken,d" ),
std::string( "Use Aitken \\delta^2 acceleration to improve convergence of "
"Lapalace smoothing algorithm (default=false)" ),
&use_acc );
opts.
addOpt<
int >( std::string(
"acc,a" ),
std::string( "Type of vector Aitken process to use for acceleration (default=1)" ),
&acc_method );
if( NULL ==
mb )
return 1;
int global_size = 1;
ParallelComm* pcomm =
new ParallelComm(
mb, MPI_COMM_WORLD );
global_size = pcomm->size();
string roptions, woptions;
if( global_size > 1 )
{
sstr.str( "" );
sstr << "PARALLEL=READ_PART;PARTITION=PARALLEL_PARTITION;PARALLEL_RESOLVE_SHARED_ENTS;"
"PARALLEL_GHOSTS="
<< num_dim << ".0." << nghostrings << ";DEBUG_IO=0;DEBUG_PIO=0";
roptions = sstr.str();
woptions = "PARALLEL=WRITE_PART";
}
currset = fileset;
std::vector< EntityHandle > hsets( num_ref + 1, fileset );
if( num_ref )
{
#ifdef MOAB_HAVE_MPI
NestedRefine* uref =
new NestedRefine(
mb, pcomm, currset );
#else
NestedRefine* uref =
new NestedRefine(
mb, 0, currset );
#endif
std::vector< int > num_degrees( num_ref, num_degree );
rval = uref->generate_mesh_hierarchy( num_ref, &num_degrees[0], hsets );
rval = uref->exchange_ghosts( hsets, nghostrings );
delete uref;
}
for( int iref = 0; iref <= num_ref; ++iref )
{
currset = hsets[iref];
int def_val = 0;
Range verts, cells, skin_verts;
rval =
mb->get_entities_by_type( currset,
MBVERTEX, verts );
rval =
mb->get_entities_by_dimension( currset, num_dim, cells );
dbgprint(
"Found " << verts.size() <<
" vertices and " << cells.size() <<
" elements" );
rval = skinner.find_skin( currset, cells, true, skin_verts );
std::vector< int > fix_tag( skin_verts.size(), 1 );
rval =
mb->tag_set_data( fixed, skin_verts, &fix_tag[0] );
if( global_size > 1 )
{
#ifdef MOAB_HAVE_MPI
rval = pcomm->exchange_tags( fixed, skin_verts );
#endif
}
rel_eps, alpha, beta, report_its );
sstr.str( "" );
sstr << "LaplacianSmoother_" << iref << ".h5m";
rval =
mb->write_file( sstr.str().c_str(),
"H5M", woptions.c_str(), &currset, 1 );
rval =
mb->tag_delete( fixed );
}
delete pcomm;
MPI_Finalize();
return 0;
}
Range& cells,
Range& verts,
bool use_hc,
bool use_acc,
int acc_method,
int num_its,
double rel_eps,
double alpha,
double beta,
int report_its )
{
int global_rank = 0, global_size = 1;
int nacc = 2;
std::vector< double > verts_acc1, verts_acc2, verts_acc3;
double rat_theta = rel_eps, rat_alpha = rel_eps, rat_alphaprev = rel_eps;
#ifdef MOAB_HAVE_MPI
const char* woptions = "PARALLEL=WRITE_PART";
#else
const char* woptions = "";
#endif
std::vector< int > fix_tag( verts.size() );
rval =
mb->tag_get_data( fixed, verts, &fix_tag[0] );
#ifdef MOAB_HAVE_MPI
global_rank = pcomm->rank();
global_size = pcomm->size();
#endif
dbgprint(
"-- Starting smoothing cycle --" );
std::vector< double > verts_o, verts_n;
verts_o.resize( 3 * verts.size(), 0.0 );
verts_n.resize( 3 * verts.size(), 0.0 );
void* vdata = &verts_n[0];
rval =
mb->get_coords( verts, &verts_o[0] );
const int nbytes = sizeof( double ) * verts_o.size();
double def_val[3] = { 0.0, 0.0, 0.0 };
if( use_acc )
{
verts_acc1.resize( verts_o.size(), 0.0 );
verts_acc2.resize( verts_o.size(), 0.0 );
verts_acc3.resize( verts_o.size(), 0.0 );
memcpy( &verts_acc1[0], &verts_o[0], nbytes );
memcpy( &verts_acc2[0], &verts_o[0], nbytes );
memcpy( &verts_acc3[0], &verts_o[0], nbytes );
}
Range owned_verts, shared_owned_verts;
if( global_size > 1 )
{
#ifdef MOAB_HAVE_MPI
#endif
}
else
owned_verts = verts;
#ifdef MOAB_HAVE_MPI
rval = pcomm->get_shared_entities( -1, shared_owned_verts, 0,
false,
true );
MB_CHK_ERR( rval );
if( shared_owned_verts.empty() ) shared_owned_verts.insert( *verts.begin() );
#endif
#ifdef WRITE_DEBUG_FILES
{
std::stringstream sstr;
sstr << "LaplacianSmootherIterate_0.h5m";
rval =
mb->write_file( sstr.str().c_str(), NULL, woptions );
}
#endif
bool check_metrics = true;
vw.set_size( 1. );
double mxdelta = 0.0, global_max = 0.0;
for( int nit = 0; nit < num_its; nit++ )
{
mxdelta = 0.0;
if( use_hc )
{
rval =
hcFilter(
mb, pcomm, verts,
dim, fixed, verts_o, verts_n, alpha, beta );
}
else
{
}
if( check_metrics )
{
bool smooth_success = true;
std::vector< double > coords;
double jac = 0.0;
int num_nodes = 0;
for( unsigned ic = 0; ic < cells.size(); ++ic )
{
EntityType etype =
mb->type_from_handle( ecell );
Range everts;
rval =
mb->get_connectivity( &ecell, 1, everts, num_nodes );
coords.resize( num_nodes * 3, 0.0 );
for( int iv = 0; iv < num_nodes; ++iv )
{
const int offset =
mb->id_from_handle( everts[iv] ) * 3;
for( int ii = 0; ii < 3; ++ii )
coords[iv * 3 + ii] = verts_n[offset + ii];
}
rval = vw.quality_measure( ecell,
MB_SHAPE, jac, num_nodes, etype, &coords[0] );
if( jac < 1e-8 )
{
dbgprint(
"Inverted element " << ic <<
" with jacobian = " << jac <<
"." );
smooth_success = false;
break;
}
}
if( !smooth_success )
{
verts_o.swap( verts_n );
dbgprint(
"Found element inversions during smoothing. Stopping iterations." );
break;
}
}
if( use_acc )
{
rat_alphaprev = rat_alpha;
for( unsigned i = 0; i < verts_n.size(); ++i )
{
rat_alpha =
std::max( rat_alpha,
std::abs( ( verts_acc3[i] - verts_acc2[i] ) * ( verts_acc2[i] - verts_acc1[i] ) ) /
( ( verts_acc2[i] - verts_acc1[i] ) * ( verts_acc2[i] - verts_acc1[i] ) ) );
}
rat_theta = std::abs( rat_alpha / rat_alphaprev - 1.0 );
if( nit > 3 && ( nit % nacc ) && rat_theta < 1.0 )
{
if( acc_method == 1 )
{
double vnorm = 0.0, den, acc_alpha = 0.0, acc_gamma = 0.0;
for( unsigned i = 0; i < verts_n.size(); ++i )
{
den = ( verts_acc3[i] - 2.0 * verts_acc2[i] + verts_acc1[i] );
vnorm += den * den;
acc_alpha += ( verts_acc3[i] - verts_acc2[i] ) * ( verts_acc3[i] - verts_acc2[i] );
acc_gamma += ( verts_acc2[i] - verts_acc1[i] ) * ( verts_acc2[i] - verts_acc1[i] );
}
for( unsigned i = 0; i < verts_n.size(); ++i )
{
verts_n[i] = verts_acc2[i] + ( acc_gamma * ( verts_acc3[i] - verts_acc2[i] ) -
acc_alpha * ( verts_acc2[i] - verts_acc1[i] ) ) /
vnorm;
}
}
else if( acc_method == 2 )
{
double vnorm = 0.0, num = 0.0, den = 0.0, mu = 0.0;
for( unsigned i = 0; i < verts_n.size(); ++i )
{
num +=
( verts_acc3[i] - verts_acc2[i] ) * ( verts_acc3[i] - 2.0 * verts_acc2[i] + verts_acc1[i] );
den = ( verts_acc3[i] - 2.0 * verts_acc2[i] + verts_acc1[i] );
vnorm += den * den;
}
mu = num / vnorm;
for( unsigned i = 0; i < verts_n.size(); ++i )
{
verts_n[i] = verts_acc3[i] + mu * ( verts_acc2[i] - verts_acc3[i] );
}
}
else if( acc_method == 3 )
{
double num = 0.0, den = 0.0, mu = 0.0;
for( unsigned i = 0; i < verts_n.size(); ++i )
{
num += ( verts_acc3[i] - verts_acc2[i] ) * ( verts_acc2[i] - verts_acc1[i] );
den +=
( verts_acc2[i] - verts_acc1[i] ) * ( verts_acc3[i] - 2.0 * verts_acc2[i] + verts_acc1[i] );
}
mu = num / den;
for( unsigned i = 0; i < verts_n.size(); ++i )
{
verts_n[i] = verts_acc3[i] - mu * ( verts_acc3[i] - verts_acc2[i] );
}
}
else if( acc_method == 4 )
{
double num = 0.0, den = 0.0, lambda = 0.0;
for( unsigned i = 0; i < verts_n.size(); ++i )
{
num += ( verts_acc3[i] - verts_acc2[i] ) * ( verts_acc3[i] - verts_acc2[i] );
den += ( verts_acc2[i] - verts_acc1[i] ) * ( verts_acc2[i] - verts_acc1[i] );
}
lambda = std::sqrt( num / den );
for( unsigned i = 0; i < verts_n.size(); ++i )
{
verts_n[i] = verts_acc3[i] - lambda / ( lambda - 1.0 ) * ( verts_acc3[i] - verts_acc2[i] );
}
}
else if( acc_method == 5 )
{
Matrix U( verts_n.size(), 2 );
Vector res( verts_n.size() );
for( unsigned ir = 0; ir < verts_n.size(); ir++ )
{
U( ir, 0 ) = verts_acc2[ir] - verts_acc1[ir];
U( ir, 1 ) = verts_acc3[ir] - verts_acc2[ir];
res[ir] = -( verts_n[ir] - verts_acc3[ir] );
}
Vector acc = solve( U, res );
double accsum = acc[0] + acc[1] + 1.0;
for( unsigned ir = 0; ir < verts_n.size(); ir++ )
{
verts_n[ir] = ( verts_acc1[ir] * acc[0] + verts_acc2[ir] * acc[1] + verts_acc3[ir] ) / accsum;
}
memcpy( &verts_acc1[0], &verts_acc2[0], nbytes );
memcpy( &verts_acc2[0], &verts_acc3[0], nbytes );
memcpy( &verts_acc3[0], &verts_n[0], nbytes );
}
else
{
int offset = 0;
for( Range::const_iterator vit = verts.begin(); vit != verts.end(); ++vit, offset += 3 )
{
if( fix_tag[offset / 3] ) continue;
CartVect num1 = ( CartVect( &verts_acc3[offset] ) - CartVect( &verts_acc2[offset] ) );
CartVect num2 = ( CartVect( &verts_acc3[offset] ) - 2.0 * CartVect( &verts_acc2[offset] ) +
CartVect( &verts_acc1[offset] ) );
num1.scale( num2 );
const double mu = num1.length_squared() / num2.length_squared();
verts_n[offset + 0] =
verts_acc3[offset + 0] + mu * ( verts_acc2[offset + 0] - verts_acc3[offset + 0] );
verts_n[offset + 1] =
verts_acc3[offset + 1] + mu * ( verts_acc2[offset + 1] - verts_acc3[offset + 1] );
verts_n[offset + 2] =
verts_acc3[offset + 2] + mu * ( verts_acc2[offset + 2] - verts_acc3[offset + 2] );
}
}
}
memcpy( &verts_acc1[0], &verts_acc2[0], nbytes );
memcpy( &verts_acc2[0], &verts_acc3[0], nbytes );
memcpy( &verts_acc3[0], &verts_n[0], nbytes );
}
for( unsigned v = 0; v < verts.size(); ++v )
{
double delta = ( CartVect( &verts_n[3 * v] ) - CartVect( &verts_o[3 * v] ) ).
length();
mxdelta = std::max( delta, mxdelta );
rval =
mb->tag_set_data( errt, &vh, 1, &mxdelta );
}
global_max = mxdelta;
#ifdef MOAB_HAVE_MPI
if( global_size > 1 ) MPI_Allreduce( &mxdelta, &global_max, 1, MPI_DOUBLE, MPI_MAX, pcomm->comm() );
#endif
if( !( nit % report_its ) )
{
dbgprint(
"\tIterate " << nit <<
": Global Max delta = " << global_max <<
"." );
}
#ifdef WRITE_DEBUG_FILES
{
rval =
mb->set_coords( verts, &verts_n[0] );
std::stringstream sstr;
sstr << "LaplacianSmootherIterate_" << nit + 1 << ".h5m";
rval =
mb->write_file( sstr.str().c_str(), NULL, woptions );
}
#endif
#ifdef MOAB_HAVE_MPI
if( global_size > 1 )
{
rval =
mb->tag_set_data( vpost, owned_verts, vdata );
rval = pcomm->exchange_tags( vpost, shared_owned_verts );
}
#endif
if( global_max < rel_eps )
break;
else
{
std::copy( verts_n.begin(), verts_n.end(), verts_o.begin() );
}
}
rval =
mb->set_coords( verts, &verts_n[0] );
dbgprint(
"-- Final iterate error = " << global_max <<
".\n" );
}
std::vector< double >& verts_o,
std::vector< double >& verts_n,
bool use_updated )
{
std::vector< int > fix_tag( verts.
size() );
double* data;
memcpy( &verts_n[0], &verts_o[0], sizeof( double ) * verts_o.size() );
if( use_updated )
data = &verts_n[0];
else
data = &verts_o[0];
Range owned_verts;
{
#ifdef MOAB_HAVE_MPI
#endif
}
else
owned_verts = verts;
rval =
mb->tag_get_data( fixed, owned_verts, &fix_tag[0] );
int vindex = 0;
for( Range::const_iterator vit = owned_verts.begin(); vit != owned_verts.end(); ++vit, vindex++ )
{
if( fix_tag[vindex] ) continue;
const int index = verts.
index( *vit ) * 3;
rval =
mb->get_adjacencies( &( *vit ), 1,
dim,
false, adjelems );
rval =
mb->get_connectivity( adjelems, adjverts );
const int nadjs = adjverts.
size();
if( nadjs )
{
double delta[3] = { 0.0, 0.0, 0.0 };
for( int j = 0; j < nadjs; ++j )
{
const int joffset = verts.
index( adjverts[j] ) * 3;
delta[0] += data[joffset + 0];
delta[1] += data[joffset + 1];
delta[2] += data[joffset + 2];
}
verts_n[index + 0] = delta[0] / nadjs;
verts_n[index + 1] = delta[1] / nadjs;
verts_n[index + 2] = delta[2] / nadjs;
}
}
}
std::vector< double >& verts_o,
std::vector< double >& verts_n,
double alpha,
double beta )
{
std::vector< double > verts_hc( verts_o.size() );
std::vector< int > fix_tag( verts.
size() );
for( unsigned index = 0; index < verts_o.size(); ++index )
{
verts_hc[index] = verts_n[index] - ( alpha * verts_n[index] + ( 1.0 - alpha ) * verts_o[index] );
}
Range owned_verts;
#ifdef MOAB_HAVE_MPI
{
}
else
#endif
owned_verts = verts;
rval =
mb->tag_get_data( fixed, owned_verts, &fix_tag[0] );
int vindex = 0;
for( Range::const_iterator vit = owned_verts.begin(); vit != owned_verts.end(); ++vit, vindex++ )
{
if( fix_tag[vindex] ) continue;
const int index = verts.
index( *vit ) * 3;
rval =
mb->get_adjacencies( &( *vit ), 1,
dim,
false, adjelems );
rval =
mb->get_connectivity( adjelems, adjverts );
const int nadjs = adjverts.
size();
double delta[3] = { 0.0, 0.0, 0.0 };
for( int j = 0; j < nadjs; ++j )
{
const int joffset = verts.
index( adjverts[j] ) * 3;
delta[0] += verts_hc[joffset + 0];
delta[1] += verts_hc[joffset + 1];
delta[2] += verts_hc[joffset + 2];
}
verts_n[index + 0] -= beta * verts_hc[index + 0] + ( ( 1.0 - beta ) / nadjs ) * delta[0];
verts_n[index + 1] -= beta * verts_hc[index + 1] + ( ( 1.0 - beta ) / nadjs ) * delta[1];
verts_n[index + 2] -= beta * verts_hc[index + 2] + ( ( 1.0 - beta ) / nadjs ) * delta[2];
}
}