Skip to content

Commit 62b50f2

Browse files
AngelyrNightly Botcwsmithbobpawjoshia5
authored
Coarsen and Snapping (#514)
Changes to coarsen - improvement to independent set - fixed bug that made it impossible to collapse edge in both directions - fixed bug that made it slower to collapse in one direction in parallel - added multi-step coarsen Changes to snapping - fixed bug with snapping coarsening the mesh too mush - fixed bug where not all regions were included in first problem plane - should snap vertices in most cases - added quality settings for split collapse More testing - added test for common errors in coarsen, refinement and snapping Most of the these changes to snapping and coarsen came from Li's thesis with some minor exceptions: - In the coarsen procedure in Li's thesis it describes getting the shortest edge next to a vertex and collapsing it. We found we had better results if we try collapsing the shortest edge and then grab the next shortest until one succeeds. - Another difference is that Li's thesis mentions collapses, but it has very little detail in how those collapses are selected, so our logic for collapses is taken from our old mesh adapt library. - Li's thesis also describes using FaceSwap in snapping and vertex repositioning in snapping and coarsen, but we haven't created either of those operators yet. --------- Signed-off-by: Aiden Woodruff <[email protected]> Co-authored-by: Nightly Bot <[email protected]> Co-authored-by: Cameron Smith <[email protected]> Co-authored-by: Aiden Woodruff <[email protected]> Co-authored-by: Aditya Joshi <[email protected]>
1 parent fe00c06 commit 62b50f2

31 files changed

+1463
-439
lines changed

gmi_cap/gmi_cap.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,3 +541,7 @@ GDBI* gmi_export_cap(gmi_model* m)
541541
cap_model* cm = (cap_model*)m;
542542
return cm->geomInterface;
543543
}
544+
545+
int gmi_cap_test(struct gmi_model* model) {
546+
return model->ops == &ops ? 1 : 0;
547+
}

gmi_cap/gmi_cap.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ struct gmi_ent;
5757
* \note This call is required before calling any other gmi_cap functions.
5858
*/
5959
void gmi_cap_start(void);
60+
6061
/**
6162
* \brief Finalize gmi_cap library.
6263
*
@@ -94,6 +95,15 @@ struct gmi_model* gmi_cap_load(const char* creFileName);
9495
*/
9596
void gmi_cap_write(struct gmi_model* model, const char* creFileName);
9697

98+
/**
99+
* \brief Test if model is a Capstone gmi_model.
100+
*
101+
* \param model An abstract gmi_model which may have an underlying Capstone
102+
* database.
103+
* \return 1 if model is a Capstone gmi_model and 0 otherwise.
104+
*/
105+
int gmi_cap_test(struct gmi_model* model);
106+
97107
#ifdef __cplusplus
98108

99109
/**

ma/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ target_link_libraries(ma
8585
pcu
8686
)
8787

88+
if (ENABLE_CAPSTONE)
89+
target_link_libraries(ma PRIVATE apf_cap)
90+
endif()
91+
8892
scorec_export_library(ma)
8993

9094
bob_end_subdir()

ma/ma.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ void adaptVerbose(Input* in, bool verbose)
100100
int count = 0;
101101
double lMax = ma::getMaximumEdgeLength(a->mesh, a->sizeField);
102102
print(a->mesh->getPCU(), "Maximum (metric) edge length in the mesh is %f", lMax);
103-
while (lMax > 1.5) {
103+
while (lMax > MAXLENGTH) {
104104
print(a->mesh->getPCU(), "%dth additional refine-snap call", count);
105105
refine(a);
106106
snap(a);

ma/maCoarsen.cc

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,81 @@
77
of the SCOREC Non-Commercial License this program is distributed under.
88
99
*******************************************************************************/
10+
/*
11+
This file contains two coarsening alogrithms.
12+
1. coarsenMultiple: will repeatedly create independent sets and collapse the mesh
13+
until there are no more short edges left. This code is currently only used for testing
14+
since more work needs to be done to achieve better performance and have the code work in
15+
parallel, at which point the other coarsen algorithm can be deleted.
16+
2. coarsen: The first will create one independent set and then collapse all of the edges
17+
in that independent set.
18+
*/
1019
#include "maCoarsen.h"
1120
#include "maAdapt.h"
1221
#include "maCollapse.h"
1322
#include "maMatchedCollapse.h"
1423
#include "maOperator.h"
24+
#include "maDBG.h"
1525
#include <pcu_util.h>
26+
#include "apfShape.h"
27+
#include <vector>
28+
#include <list>
29+
#include <algorithm>
1630

1731
namespace ma {
1832

33+
namespace {
34+
35+
//Measures edge length and stores the result so it doesn't have to be calculated again
36+
double getLength(Adapt* a, Tag* lengthTag, Entity* edge)
37+
{
38+
double length = 0;
39+
if (a->mesh->hasTag(edge, lengthTag))
40+
a->mesh->getDoubleTag(edge, lengthTag, &length);
41+
else {
42+
length = a->sizeField->measure(edge);
43+
a->mesh->setDoubleTag(edge, lengthTag, &length);
44+
}
45+
return length;
46+
}
47+
48+
//Make sure that a collapse will not create an edge longer than the max
49+
bool collapseSizeCheck(Adapt* a, Entity* vertex, Entity* edge, apf::Up& adjacent)
50+
{
51+
Entity* vCollapse = getEdgeVertOppositeVert(a->mesh, edge, vertex);
52+
for (int i=0; i<adjacent.n; i++) {
53+
Entity* newEdgeVerts[2]{vertex, getEdgeVertOppositeVert(a->mesh, adjacent.e[i], vCollapse)};
54+
Entity* newEdge = a->mesh->createEntity(apf::Mesh::EDGE, 0, newEdgeVerts);
55+
double length = a->sizeField->measure(newEdge);
56+
destroyElement(a, newEdge);
57+
if (length > MAXLENGTH) return false;
58+
}
59+
return true;
60+
}
61+
62+
bool tryCollapseEdge(Adapt* a, Entity* edge, Entity* keep, Collapse& collapse, apf::Up& adjacent)
63+
{
64+
PCU_ALWAYS_ASSERT(a->mesh->getType(edge) == apf::Mesh::EDGE);
65+
bool alreadyFlagged = true;
66+
if (keep) alreadyFlagged = getFlag(a, keep, DONT_COLLAPSE);
67+
if (!alreadyFlagged) setFlag(a, keep, DONT_COLLAPSE);
68+
69+
double quality = a->input->shouldForceAdaptation ? a->input->validQuality
70+
: a->input->goodQuality;
71+
72+
bool result = false;
73+
if (collapse.setEdge(edge) &&
74+
collapse.checkClass() &&
75+
collapse.checkTopo() &&
76+
collapseSizeCheck(a, keep, edge, adjacent) &&
77+
collapse.tryBothDirections(quality)) {
78+
result = true;
79+
}
80+
if (!alreadyFlagged) clearFlag(a, keep, DONT_COLLAPSE);
81+
return result;
82+
}
83+
}
84+
1985
class CollapseChecker : public apf::CavityOp
2086
{
2187
public:
@@ -253,4 +319,153 @@ bool coarsen(Adapt* a)
253319
return true;
254320
}
255321

322+
/*
323+
To be used after collapsing a vertex to flag adjacent vertices as NEED_NOT_COLLAPSE. This will create a
324+
set of collapses where no two adjacent are vertices collapsed. Will also clear adjacent vertices for
325+
collapse in next independent set since they might succeed after a collapse.
326+
*/
327+
void flagIndependentSet(Adapt* a, apf::Up& adjacent, size_t& checked)
328+
{
329+
for (int adj=0; adj < adjacent.n; adj++) {
330+
Entity* vertices[2];
331+
a->mesh->getDownward(adjacent.e[adj],0, vertices);
332+
for (int v = 0; v < 2; v++) {
333+
setFlag(a, vertices[v], NEED_NOT_COLLAPSE);
334+
if (getFlag(a, vertices[v], CHECKED)){
335+
clearFlag(a, vertices[v], CHECKED); //needs to be checked again in next independent set
336+
checked--;
337+
}
338+
}
339+
}
340+
}
341+
342+
struct EdgeLength
343+
{
344+
Entity* edge;
345+
double length;
346+
bool operator<(const EdgeLength& other) const {
347+
return length < other.length;
348+
}
349+
};
350+
351+
/*
352+
Given an iterator pointing to a vertex we will collapse the shortest adjacent edge and try the next
353+
shorted until one succeeds and then it will expand independent set. In Li's thesis it only attempts
354+
to collapse the shortest edge, but this gave us better results.
355+
*/
356+
bool collapseShortest(Adapt* a, Collapse& collapse, std::list<Entity*>& shortEdgeVerts, std::list<Entity*>::iterator& itr, size_t& checked, apf::Up& adjacent, Tag* lengthTag)
357+
{
358+
Entity* vertex = *itr;
359+
std::vector<EdgeLength> sorted;
360+
for (int i=0; i < adjacent.n; i++) {
361+
double length = getLength(a, lengthTag, adjacent.e[i]);
362+
EdgeLength measured{adjacent.e[i], length};
363+
if (measured.length > MINLENGTH) continue;
364+
sorted.push_back(measured);
365+
}
366+
if (sorted.size() == 0) { //performance optimization, will rarely result in a missed edge
367+
itr = shortEdgeVerts.erase(itr);
368+
return false;
369+
}
370+
std::sort(sorted.begin(), sorted.end());
371+
for (size_t i=0; i < sorted.size(); i++) {
372+
Entity* keepVertex = getEdgeVertOppositeVert(a->mesh, sorted[i].edge, vertex);
373+
if (!tryCollapseEdge(a, sorted[i].edge, keepVertex, collapse, adjacent)) continue;
374+
flagIndependentSet(a, adjacent, checked);
375+
itr = shortEdgeVerts.erase(itr);
376+
collapse.destroyOldElements();
377+
return true;
378+
}
379+
setFlag(a, vertex, CHECKED);
380+
checked++;
381+
return false;
382+
}
383+
384+
/*
385+
Iterates through shortEdgeVerts until it finds a vertex that is adjacent to an
386+
independent set. We want our collapses to touch the independent set in order to
387+
reduce adjacent collapses, since collapsing adjacent vertices will result in a
388+
lower quality mesh.
389+
*/
390+
bool getAdjIndependentSet(Adapt* a, std::list<Entity*>& shortEdgeVerts, std::list<Entity*>::iterator& itr, bool& independentSetStarted, apf::Up& adjacent)
391+
{
392+
size_t numItr=0;
393+
do {
394+
numItr++;
395+
if (itr == shortEdgeVerts.end()) itr = shortEdgeVerts.begin();
396+
Entity* vertex = *itr;
397+
if (getFlag(a, vertex, CHECKED)) {itr++; continue;} //Already tried to collapse
398+
a->mesh->getUp(vertex, adjacent);
399+
if (!independentSetStarted) return true;
400+
if (getFlag(a, vertex, NEED_NOT_COLLAPSE)) {itr++; continue;} //Too close to last collapse
401+
for (int i=0; i < adjacent.n; i++)
402+
{
403+
Entity* opposite = getEdgeVertOppositeVert(a->mesh, adjacent.e[i], vertex);
404+
if (getFlag(a, opposite, NEED_NOT_COLLAPSE)) return true; //Touching independent set
405+
}
406+
itr++;
407+
} while (numItr < shortEdgeVerts.size());
408+
for (auto v : shortEdgeVerts) clearFlag(a, v, NEED_NOT_COLLAPSE);
409+
independentSetStarted = false;
410+
return false;
411+
}
412+
413+
//returns a list of vertices that bound a short edge and flags them
414+
std::list<Entity*> getShortEdgeVerts(Adapt* a, Tag* lengthTag)
415+
{
416+
std::list<Entity*> shortEdgeVerts;
417+
Iterator* it = a->mesh->begin(1);
418+
Entity* edge;
419+
while ((edge = a->mesh->iterate(it)))
420+
{
421+
double length = getLength(a, lengthTag, edge);
422+
if (length > MINLENGTH) continue;
423+
Entity* vertices[2];
424+
a->mesh->getDownward(edge,0,vertices);
425+
for (int i = 0; i < 2; i++) {
426+
if (getFlag(a, vertices[i], CHECKED)) continue;
427+
setFlag(a, vertices[i], CHECKED);
428+
shortEdgeVerts.push_back(vertices[i]);
429+
}
430+
}
431+
for (auto v : shortEdgeVerts) clearFlag(a, v, CHECKED);
432+
a->mesh->end(it);
433+
return shortEdgeVerts;
434+
}
435+
436+
/*
437+
Follows the alogritm in Li's thesis in order to coarsen all short edges
438+
in a mesh while maintaining a decent quality mesh.
439+
*/
440+
bool coarsenMultiple(Adapt* a)
441+
{
442+
if (!a->input->shouldCoarsen)
443+
return false;
444+
double t0 = pcu::Time();
445+
Tag* lengthTag = a->mesh->createDoubleTag("edge_length", 1);
446+
std::list<Entity*> shortEdgeVerts = getShortEdgeVerts(a, lengthTag);
447+
448+
Collapse collapse;
449+
collapse.Init(a);
450+
int success = 0;
451+
size_t checked = 0;
452+
bool independentSetStarted = false;
453+
std::list<Entity*>::iterator itr = shortEdgeVerts.begin();
454+
while (checked < shortEdgeVerts.size())
455+
{
456+
apf::Up adjacent;
457+
if (!getAdjIndependentSet(a, shortEdgeVerts, itr, independentSetStarted, adjacent)) continue;
458+
if (collapseShortest(a, collapse, shortEdgeVerts, itr, checked, adjacent, lengthTag)) {
459+
independentSetStarted=true;
460+
success++;
461+
}
462+
}
463+
ma::clearFlagFromDimension(a, NEED_NOT_COLLAPSE | CHECKED, 0);
464+
a->mesh->destroyTag(lengthTag);
465+
double t1 = pcu::Time();
466+
print(a->mesh->getPCU(), "coarsened %d edges in %f seconds", success, t1-t0);
467+
return true;
468+
}
469+
470+
256471
}

ma/maCoarsen.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace ma {
1414

1515
class Adapt;
1616

17+
bool coarsenMultiple(Adapt* a);
1718
bool coarsen(Adapt* a);
1819
bool coarsenLayer(Adapt* a);
1920

ma/maCollapse.cc

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,34 @@ bool Collapse::tryThisDirectionNoCancel(double qualityToBeat)
5555
return true;
5656
}
5757

58+
/*Sometimes the snapping procedure will attempt to collapse a edge that was just
59+
refined. This function will prevent that when the growth of the edge is above a
60+
thresh hold where it might not reach the target edge length*/
61+
bool Collapse::edgesGoodSize() {
62+
PCU_ALWAYS_ASSERT(elementsToKeep.size());
63+
PCU_ALWAYS_ASSERT(elementsToKeep.size() == newElements.size());
64+
size_t i=0;
65+
double maxSize=0;
66+
double ratioAtMaxSize=0;
67+
APF_ITERATE(EntitySet,elementsToKeep,it) {
68+
Entity* edgesBefore[6];
69+
adapt->mesh->getDownward(*it,1,edgesBefore);
70+
Entity* edgesAfter[6];
71+
adapt->mesh->getDownward(newElements[i++],1,edgesAfter);
72+
for (int j=0; j<6; j++) {
73+
double sizeBefore = adapt->sizeField->measure(edgesBefore[j]);
74+
double sizeAfter = adapt->sizeField->measure(edgesAfter[j]);
75+
double ratio = sizeAfter/sizeBefore;
76+
if (sizeAfter > maxSize) {
77+
maxSize = sizeAfter;
78+
ratioAtMaxSize = ratio;
79+
}
80+
}
81+
}
82+
if (maxSize > MAXLENGTH && ratioAtMaxSize > MAXLENGTHRATIO) return false;
83+
return true;
84+
}
85+
5886
bool Collapse::tryThisDirection(double qualityToBeat)
5987
{
6088
if (!tryThisDirectionNoCancel(qualityToBeat)) {
@@ -74,17 +102,20 @@ bool Collapse::tryBothDirections(double qualityToBeat)
74102
qualityToBeat = std::min(adapt->input->goodQuality,
75103
std::max(getOldQuality(),adapt->input->validQuality));
76104

77-
if (tryThisDirection(qualityToBeat))
105+
if (tryThisDirectionNoCancel(qualityToBeat))
78106
return true;
79-
if ( ! getFlag(adapt,vertToKeep,COLLAPSE))
80-
return false;
81-
std::swap(vertToKeep,vertToCollapse);
82-
computeElementSets();
83-
if (!adapt->input->shouldForceAdaptation)
84-
qualityToBeat = std::min(adapt->input->goodQuality,
85-
std::max(getOldQuality(),adapt->input->validQuality));
107+
else destroyNewElements();
108+
109+
if (getFlag(adapt,vertToKeep,COLLAPSE)) {
110+
std::swap(vertToKeep,vertToCollapse);
111+
computeElementSets();
112+
if (tryThisDirectionNoCancel(qualityToBeat))
113+
return true;
114+
else destroyNewElements();
115+
}
86116

87-
return tryThisDirection(qualityToBeat);
117+
unmark();
118+
return false;
88119
}
89120

90121
bool Collapse::setEdge(Entity* e)
@@ -127,8 +158,10 @@ bool checkEdgeCollapseEdgeRings(Adapt* a, Entity* edge)
127158
Mesh* m = a->mesh;
128159
Entity* v[2];
129160
m->getDownward(edge,0,v);
130-
PCU_ALWAYS_ASSERT( ! m->isShared(v[0]));
131-
PCU_ALWAYS_ASSERT( ! m->isShared(v[1]));
161+
if (!getFlag(a, v[0], DONT_COLLAPSE)) //Allow collapse in one direction
162+
PCU_ALWAYS_ASSERT( ! m->isShared(v[0]));
163+
if (!getFlag(a, v[1], DONT_COLLAPSE)) //Allow collapse in one direction
164+
PCU_ALWAYS_ASSERT( ! m->isShared(v[1]));
132165
apf::Up ve[2];
133166
m->getUp(v[0],ve[0]);
134167
m->getUp(v[1],ve[1]);

ma/maCollapse.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class Collapse
4141
bool tryThisDirectionNoCancel(double qualityToBeat);
4242
bool tryBothDirections(double qualityToBeat);
4343
void getOldElements(EntityArray& oldElements);
44+
bool edgesGoodSize();
4445
double getOldQuality();
4546
Adapt* adapt;
4647
Entity* edge;

0 commit comments

Comments
 (0)