Mesh Optimisation
1. Work around the Mesh
Previously, we use the method createGMSH
( and so GMSH ) to build the mesh associated to our surface reconstrution problem. As shown ahead, it take a lot of time ( \(\simeq 70 s\)). Two possibilties will be explored here :
-
First, a field, named update, of the
createGMSH
method can be used to determine how the mesh will be built. -
After this, we want to generate manually our mesh, without
createGMSH
.
We will also add the condition on the heigth mean, for the moment equals to 0, in order to compare directly our results to the real surface data.
1.1. Mesh Enhancement
1.1.1. Update field
The update field on the createGMSH
method allow us to determine what we want the mesh built and what we want it let behind. In our case, the only useful information is the adjacency between nodes and elements, in order to parallelize in the future the code. Things, like informations on elements face, aren’t needed here and can be shelve to gain time.
This field is used like this
_uh = createGMSHMesh( _mesh=new Mesh<Hypercube<2>>,
_structured=1,
_h=doption(_name="pixelsize"),
_desc=domain(_name="polymere",
_xmax=(x.cols()-1)*doption(_name="pixelsize"),
_ymax=(x.rows()-1)*doption(_name="pixelsize")),
_update=size_type(MESH_UPDATE_ELEMENTS_ADJACENCY|MESH_NO_UPDATE_MEASURES));
After recompilation and execution, we finally obtain the mesh built time
[Mesh built] Time : 41.2557s
We have gain \(\simeq 30 s\) with this mesh beside the initial one.
1.1.2. Manually mesh built
An another way to gain time on mesh built is to manually build it, without using GMSH. We can so direclty build the wanted informations whitout have to read it from a gmsh file. To manage this, we inspire us from methods used to read nodes and elements from GMSH to use these informations into Feel++.
It used like this in the main code :
auto mesh = boost::make_shared<MeshStructured>( x.rows()-1, x.cols()-1,
doption(_name="pixelsize"),Environment::worldComm() );
mesh->components().reset();
mesh->components().set( size_type(MESH_NO_UPDATE_MEASURES) );
mesh->updateForUse();
Nodes
The main part of the nodes are define like this
int start = (partId == 0) ? 0 : cx[partId]+1;
int stop = (partId == (procSize-1)) ? M_ny : cx[partId+1];
for( int j = start ; j < stop; ++j )
{
std::cout << "j = " << j << std::endl;
ghosts.clear();
if (j == cx[partId] && j != 0)
ghosts.push_back(partId-1);
else if (j == cx[partId+1]-1 && j != M_ny-1)
ghosts.push_back(partId+1);
for( int i = 0; i < M_nx; ++i )
{
// point
int ptid = (M_ny)*i+j;
//std::cout << "ptId = " << ptid << std::endl;
coords[0]=M_pixelsize*j;
coords[1]=M_pixelsize*i;
point_type pt( ptid,
coords,
i == 0 || i == M_nx-1
|| j%((M_ny-1)/procSize) == 0 );
//// Is on boundary ?
pt.setMarker(1);
//// Actual rank ID
pt.setProcessId( partId );
//// NO GHOST POINTS I SAID
pt.setProcessIdInPartition( partId );
pt.setNeighborPartitionIds( ghosts );
this->addPoint( pt );
}
}
Nodes are composed of several elements we will describe here :
-
An id, named ptid
-
A pair of coordinates, coords, which refer to its space position
-
A ProcessId equals to the id of the processor the node belongs
-
A ProcessIdInPartition equals to the id of the processor we currently work on
We choose here to define nodes present at internal boundaries, i.e at interfaces between processors, apart, in case of specific actions we could realise on them.
For left interfaces
// First column
if(partId > 0)
{
int j = cx[partId];
std::cout << "j = " << j << std::endl;
ghosts.clear();
ghosts.push_back(partId-1);
for( int i = 0; i < M_nx; ++i )
{
// point
int ptid = (M_ny)*i+j;
//std::cout << "ptId = " << ptid << std::endl;
coords[0]=M_pixelsize*j;
coords[1]=M_pixelsize*i;
point_type pt( ptid,
coords,
i == 0 || i == M_nx-1
|| j%((M_ny-1)/procSize) == 0 );
// Is on boundary ?
pt.setMarker(1);
// Actual rank ID
pt.setProcessId( -1 );
pt.setProcessIdInPartition( partId );
pt.setNeighborPartitionIds( ghosts );
this->addPoint( pt );
}
}
and for the right ones
// Last columnif(partId < (procSize-1))
{
int j = cx[partId+1];
//std::cout << "j = " << j << std::endl;
ghosts.clear();
ghosts.push_back(partId+1);
for( int i = 0; i < M_nx; ++i )
{
// point
int ptid = (M_ny)*i+j;
//std::cout << "ptId = " << ptid << std::endl;
coords[0]=M_pixelsize*j;
coords[1]=M_pixelsize*i;
point_type pt( ptid,
coords,
i == 0 || i == M_nx-1
|| j%((M_ny-1)/procSize) == 0 );
// Is on boundary ?
pt.setMarker(1);
// Actual rank ID
pt.setProcessId( partId );
pt.setProcessIdInPartition( partId );
pt.setNeighborPartitionIds( ghosts );
this->addPoint( pt );
}
}
One of the main difference is that we set NeighborPartitionIds as we know we are at the interface with an another processor and we know its id.
Elements
The elements are then defined
/*
* Generates elements whithout ghosts
*/
tic();
for( int j = cx[partId]; j < cx[partId+1]; ++j )
{
ghosts.clear();
if (j == cx[partId] && j != 0)
ghosts.push_back(partId-1);
else if (j == cx[partId+1]-1 && j != M_ny-2)
{
ghosts.push_back(partId+1);
}
for( int i = 0; i < M_nx-1; ++i )
{
element_type e;
int eid = (M_ny-1)*i+j; // GMSH Id
e.setId( eid ); // eid == gmsh id
e.setProcessIdInPartition( partId );
e.setProcessId( partId );
e.setMarker(1);
e.setNeighborPartitionIds( ghosts );
// points definition
int ptid[4] = { (M_ny)*(i+1)+j, // 0
(M_ny)*(i+1)+j+1, // 1
(M_ny)*i+j+1, // 2
(M_ny)*i+j // 3
};
for( int k = 0; k < 4; ++k )
{
e.setPoint( k, this->point( ptid[k] ) );
}
e.setOnBoundary( i==0 || i==(M_nx-2)
|| j%((M_ny-2)/procSize)==0 );
// true -> id = global elt id
// false -> id = local elt id
this->addElement( e, true );
// e.id() is modified to Feel++ Id
__idGmshToFeel.insert( std::make_pair(eid,e.id()));
// e.id() == Feel++ Id
}
}
First at all we define the possible processors id that can be neighborhood to the ones we want to build. We can obtain this information with the value of j, our horizontal unknown.
After that we build the several part of an element :
-
An id, named eid
-
A ProcessId equals to the id of the processor the node belongs
-
A ProcessIdInPartition equals to the id of the processor we currently work on
-
A set of points, ptid, which refer to nodes of our element. In this case, we work with square ( hypercube in 2D ) so we have four points.
We also fill a map, __idGmshToFeel, that will make the connection between GMSH Id and Feel Id.
Timing Result
The mesh built time obtain after this is equal to
[Mesh built] Time : 11.2078s
This time is decomposed as
Timer Count Total(s) Max(s) Min(s) Mean(s) StdDev(s)
Mesh built 1 1.12e+01 1.12e+01 1.12e+01 1.12e+01 0.00e+00
Mesh::updateForUse update geomap cache 1 7.00e-02 7.00e-02 7.00e-02 7.00e-02 0.00e+00
Mesh::updateForUse update setMesh in elements and faces 1 2.85e-01 2.85e-01 2.85e-01 2.85e-01 0.00e+00
MeshStructured Elements 1 6.60e+00 6.60e+00 6.60e+00 6.60e+00 0.00e+00
MeshStructured Nodes 1 3.52e+00 3.52e+00 3.52e+00 3.52e+00 0.00e+00
We gain \(\simeq 30 s\) from using only the update field and \(\simeq 60 s\) from the original resolution.