Skip to content

Commit 66028f8

Browse files
authored
Tests: abort on NaN solutions in linear solver tests (#5671)
The tests judge their answers with a max-norm, but that reduction drops NaNs: std::max(a,b) is (a < b) ? b : a, so a NaN leaves the accumulator untouched. An all-NaN solution reports a max-norm error of 0, and since ctest reads only the exit code, a failed solve could pass the suite. Check the solution with MultiFab::contains_nan after each solve and abort if it trips. ngrow=0, since ghost cells may hold signaling NaNs. ABecLaplacian_F is left alone; the Fortran interface has no binding.
1 parent e60cdc1 commit 66028f8

21 files changed

Lines changed: 241 additions & 45 deletions

File tree

Src/LinearSolvers/MLMG/AMReX_MLEBNodeFDLaplacian.cpp

Lines changed: 57 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,21 +1293,33 @@ namespace {
12931293
int const jm = lowerNeighbor(j,1);
12941294
int const jp = upperNeighbor(j,1);
12951295
Real const xc = xa(i,j,k,n);
1296+
1297+
Real hpz = Real(1.0);
1298+
Real hmz = Real(1.0);
1299+
if constexpr (UseEB) {
1300+
hpz = (this->edgecent[1](i,j,k) == Real(1.0))
1301+
? Real(1.0) : Real(1.0)+Real(2.0)*this->edgecent[1](i,j,k);
1302+
hmz = (this->edgecent[1](i,j-1,k) == Real(1.0))
1303+
? Real(1.0) : Real(1.0)-Real(2.0)*this->edgecent[1](i,j-1,k);
1304+
}
1305+
12961306
Real out;
1297-
Real scale = Real(1.0);
1307+
Real scale;
12981308

12991309
if (r == Real(0.0)) {
13001310
Real const sigp = this->radialEdgeSigma(i,j,k,r);
13011311
if constexpr (UseEB) {
13021312
if (this->levset(i+1,j,k) >= Real(0.0)) {
13031313
Real const hp = (this->edgecent[0](i,j,k) == Real(1.0))
13041314
? Real(1.0) : Real(1.0)+Real(2.0)*this->edgecent[0](i,j,k);
1305-
out = -Real(4.0)*sigp*xc/(dr*dr*hp*hp);
1306-
scale = hp;
1315+
scale = amrex::min(hp,hmz,hpz);
1316+
out = -Real(4.0)*sigp*mlebndfdlap_scaled_h2inv(scale,hp)*xc/(dr*dr);
13071317
} else {
1308-
out = Real(4.0)*sigp*(xa(ip,j,k,n)-xc)/(dr*dr);
1318+
scale = amrex::min(Real(1.0),hmz,hpz);
1319+
out = scale*Real(4.0)*sigp*(xa(ip,j,k,n)-xc)/(dr*dr);
13091320
}
13101321
} else {
1322+
scale = Real(1.0);
13111323
out = Real(4.0)*sigp*(xa(ip,j,k,n)-xc)/(dr*dr);
13121324
}
13131325
} else {
@@ -1319,52 +1331,44 @@ namespace {
13191331
hm = (this->edgecent[0](i-1,j,k) == Real(1.0))
13201332
? Real(1.0) : Real(1.0)-Real(2.0)*this->edgecent[0](i-1,j,k);
13211333
}
1334+
scale = amrex::min(hm,hp,hmz,hpz);
13221335

13231336
Real const sigp = this->radialEdgeSigma(i,j,k,r);
13241337
Real const sigm = this->radialEdgeSigma(i-1,j,k,r);
13251338
Real tmp;
13261339
if constexpr (UseEB) {
13271340
tmp = (this->levset(i+1,j,k) < Real(0.0))
1328-
? sigp*(xa(ip,j,k,n)-xc)*(r+Real(0.5)*dr)
1329-
: -sigp*xc/hp*(r+Real(0.5)*hp*dr);
1341+
? scale*sigp*(xa(ip,j,k,n)-xc)*(r+Real(0.5)*dr)
1342+
: -sigp*mlebndfdlap_scaled_hinv(scale,hp)*xc*(r+Real(0.5)*hp*dr);
13301343
tmp += (this->levset(i-1,j,k) < Real(0.0))
1331-
? sigm*(xa(im,j,k,n)-xc)*(r-Real(0.5)*dr)
1332-
: -sigm*xc/hm*(r-Real(0.5)*hm*dr);
1344+
? scale*sigm*(xa(im,j,k,n)-xc)*(r-Real(0.5)*dr)
1345+
: -sigm*mlebndfdlap_scaled_hinv(scale,hm)*xc*(r-Real(0.5)*hm*dr);
13331346
} else {
13341347
tmp = sigp*(xa(ip,j,k,n)-xc)*(r+Real(0.5)*dr)
13351348
+ sigm*(xa(im,j,k,n)-xc)*(r-Real(0.5)*dr);
13361349
}
13371350
out = tmp*Real(2.0)/((hp+hm)*r*dr*dr);
1338-
scale = amrex::min(hm,hp);
1339-
}
1340-
1341-
Real hp = Real(1.0);
1342-
Real hm = Real(1.0);
1343-
if constexpr (UseEB) {
1344-
hp = (this->edgecent[1](i,j,k) == Real(1.0))
1345-
? Real(1.0) : Real(1.0)+Real(2.0)*this->edgecent[1](i,j,k);
1346-
hm = (this->edgecent[1](i,j-1,k) == Real(1.0))
1347-
? Real(1.0) : Real(1.0)-Real(2.0)*this->edgecent[1](i,j-1,k);
13481351
}
13491352

13501353
Real const sigp = this->axialEdgeSigma(i,j,k,r);
13511354
Real const sigm = this->axialEdgeSigma(i,j-1,k,r);
13521355
Real tmp;
13531356
if constexpr (UseEB) {
13541357
tmp = (this->levset(i,j+1,k) < Real(0.0))
1355-
? sigp*(xa(i,jp,k,n)-xc) : -sigp*xc/hp;
1358+
? scale*sigp*(xa(i,jp,k,n)-xc)
1359+
: -sigp*mlebndfdlap_scaled_hinv(scale,hpz)*xc;
13561360
tmp += (this->levset(i,j-1,k) < Real(0.0))
1357-
? sigm*(xa(i,jm,k,n)-xc) : -sigm*xc/hm;
1361+
? scale*sigm*(xa(i,jm,k,n)-xc)
1362+
: -sigm*mlebndfdlap_scaled_hinv(scale,hmz)*xc;
13581363
} else {
13591364
tmp = sigp*(xa(i,jp,k,n)-xc) + sigm*(xa(i,jm,k,n)-xc);
13601365
}
1361-
out += tmp*Real(2.0)/((hp+hm)*dz*dz);
1362-
scale = amrex::min(scale,hm,hp);
1366+
out += tmp*Real(2.0)/((hpz+hmz)*dz*dz);
13631367

13641368
if (r != Real(0.0)) {
1365-
out -= alpha*xc/(r*r);
1369+
out -= scale*alpha*xc/(r*r);
13661370
}
1367-
return out*scale;
1371+
return out;
13681372
}
13691373

13701374
Real dr;
@@ -1480,54 +1484,63 @@ namespace {
14801484
}
14811485

14821486
Real const xc = xa(i,j,k,n);
1483-
int const im = lowerNeighbor(i,0);
1484-
int const ip = upperNeighbor(i,0);
14851487
Real const hpx = (edgecent[0](i,j,k) == Real(1.0))
14861488
? Real(1.0) : Real(1.0)+Real(2.0)*edgecent[0](i,j,k);
14871489
Real const hmx = (edgecent[0](i-1,j,k) == Real(1.0))
14881490
? Real(1.0) : Real(1.0)-Real(2.0)*edgecent[0](i-1,j,k);
1491+
Real const hpy = (edgecent[1](i,j,k) == Real(1.0))
1492+
? Real(1.0) : Real(1.0)+Real(2.0)*edgecent[1](i,j,k);
1493+
Real const hmy = (edgecent[1](i,j-1,k) == Real(1.0))
1494+
? Real(1.0) : Real(1.0)-Real(2.0)*edgecent[1](i,j-1,k);
1495+
#if (AMREX_SPACEDIM == 3)
1496+
Real const hpz = (edgecent[2](i,j,k) == Real(1.0))
1497+
? Real(1.0) : Real(1.0)+Real(2.0)*edgecent[2](i,j,k);
1498+
Real const hmz = (edgecent[2](i,j,k-1) == Real(1.0))
1499+
? Real(1.0) : Real(1.0)-Real(2.0)*edgecent[2](i,j,k-1);
1500+
Real const scale = amrex::min(hmx,hpx,hmy,hpy,hmz,hpz);
1501+
#else
1502+
Real const scale = amrex::min(hmx,hpx,hmy,hpy);
1503+
#endif
1504+
1505+
int const im = lowerNeighbor(i,0);
1506+
int const ip = upperNeighbor(i,0);
14891507
Real const sigxp = this->edgeSigmaX(i,j,k);
14901508
Real const sigxm = this->edgeSigmaX(i-1,j,k);
14911509
Real tmp = (levset(i+1,j,k) < Real(0.0))
1492-
? sigxp*(xa(ip,j,k,n)-xc) : -sigxp*xc/hpx;
1510+
? sigxp*scale*(xa(ip,j,k,n)-xc)
1511+
: -sigxp*mlebndfdlap_scaled_hinv(scale,hpx)*xc;
14931512
tmp += (levset(i-1,j,k) < Real(0.0))
1494-
? sigxm*(xa(im,j,k,n)-xc) : -sigxm*xc/hmx;
1513+
? sigxm*scale*(xa(im,j,k,n)-xc)
1514+
: -sigxm*mlebndfdlap_scaled_hinv(scale,hmx)*xc;
14951515
Real y = beta[0]*tmp*Real(2.0)/(hpx+hmx);
1496-
Real scale = amrex::min(hmx,hpx);
14971516

14981517
int const jm = lowerNeighbor(j,1);
14991518
int const jp = upperNeighbor(j,1);
1500-
Real const hpy = (edgecent[1](i,j,k) == Real(1.0))
1501-
? Real(1.0) : Real(1.0)+Real(2.0)*edgecent[1](i,j,k);
1502-
Real const hmy = (edgecent[1](i,j-1,k) == Real(1.0))
1503-
? Real(1.0) : Real(1.0)-Real(2.0)*edgecent[1](i,j-1,k);
15041519
Real const sigyp = this->edgeSigmaY(i,j,k);
15051520
Real const sigym = this->edgeSigmaY(i,j-1,k);
15061521
tmp = (levset(i,j+1,k) < Real(0.0))
1507-
? sigyp*(xa(i,jp,k,n)-xc) : -sigyp*xc/hpy;
1522+
? sigyp*scale*(xa(i,jp,k,n)-xc)
1523+
: -sigyp*mlebndfdlap_scaled_hinv(scale,hpy)*xc;
15081524
tmp += (levset(i,j-1,k) < Real(0.0))
1509-
? sigym*(xa(i,jm,k,n)-xc) : -sigym*xc/hmy;
1525+
? sigym*scale*(xa(i,jm,k,n)-xc)
1526+
: -sigym*mlebndfdlap_scaled_hinv(scale,hmy)*xc;
15101527
y += beta[1]*tmp*Real(2.0)/(hpy+hmy);
1511-
scale = amrex::min(scale,hmy,hpy);
15121528

15131529
#if (AMREX_SPACEDIM == 3)
15141530
int const km = lowerNeighbor(k,2);
15151531
int const kp = upperNeighbor(k,2);
1516-
Real const hpz = (edgecent[2](i,j,k) == Real(1.0))
1517-
? Real(1.0) : Real(1.0)+Real(2.0)*edgecent[2](i,j,k);
1518-
Real const hmz = (edgecent[2](i,j,k-1) == Real(1.0))
1519-
? Real(1.0) : Real(1.0)-Real(2.0)*edgecent[2](i,j,k-1);
15201532
Real const sigzp = this->edgeSigmaZ(i,j,k);
15211533
Real const sigzm = this->edgeSigmaZ(i,j,k-1);
15221534
tmp = (levset(i,j,k+1) < Real(0.0))
1523-
? sigzp*(xa(i,j,kp,n)-xc) : -sigzp*xc/hpz;
1535+
? sigzp*scale*(xa(i,j,kp,n)-xc)
1536+
: -sigzp*mlebndfdlap_scaled_hinv(scale,hpz)*xc;
15241537
tmp += (levset(i,j,k-1) < Real(0.0))
1525-
? sigzm*(xa(i,j,km,n)-xc) : -sigzm*xc/hmz;
1538+
? sigzm*scale*(xa(i,j,km,n)-xc)
1539+
: -sigzm*mlebndfdlap_scaled_hinv(scale,hmz)*xc;
15261540
y += beta[2]*tmp*Real(2.0)/(hpz+hmz);
1527-
scale = amrex::min(scale,hmz,hpz);
15281541
#endif
15291542

1530-
return y*scale;
1543+
return y;
15311544
}
15321545

15331546
Array4<Real const> levset;

Tests/LinearSolvers/ABecLap_SP/MyTest.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ MyTest::solve ()
2828
} else {
2929
amrex::Abort("Unknown prob_type");
3030
}
31+
32+
// A failed solve often returns NaNs. Check for them explicitly, because
33+
// the max-norm checks used by these tests silently drop NaNs.
34+
for (int ilev = 0; ilev < int(solution.size()); ++ilev) {
35+
if (solution[ilev].contains_nan(0, solution[ilev].nComp(), 0)) {
36+
amrex::Abort("MyTest::solve: solution contains NaN on level "
37+
+ std::to_string(ilev));
38+
}
39+
}
3140
}
3241

3342
void

Tests/LinearSolvers/ABecLaplacian_C/MyTest.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ MyTest::solve ()
2626
#ifdef AMREX_USE_HYPRE
2727
if (use_mlhypre) {
2828
solveMLHypre();
29+
// A failed solve often returns NaNs. Check for them explicitly,
30+
// because the max-norm checks used by these tests silently drop NaNs.
31+
for (int ilev = 0; ilev < int(solution.size()); ++ilev) {
32+
if (solution[ilev].contains_nan(0, solution[ilev].nComp(), 0)) {
33+
amrex::Abort("MyTest::solve: solution contains NaN on level "
34+
+ std::to_string(ilev));
35+
}
36+
}
2937
return;
3038
}
3139
#endif
@@ -45,6 +53,15 @@ MyTest::solve ()
4553
} else {
4654
amrex::Abort("Unknown prob_type");
4755
}
56+
57+
// A failed solve often returns NaNs. Check for them explicitly, because
58+
// the max-norm checks used by these tests silently drop NaNs.
59+
for (int ilev = 0; ilev < int(solution.size()); ++ilev) {
60+
if (solution[ilev].contains_nan(0, solution[ilev].nComp(), 0)) {
61+
amrex::Abort("MyTest::solve: solution contains NaN on level "
62+
+ std::to_string(ilev));
63+
}
64+
}
4865
}
4966

5067
void

Tests/LinearSolvers/CellEB/MyTest.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ MyTest::solve ()
117117
mlmg.solve(amrex::GetVecOfPtrs(phi), amrex::GetVecOfConstPtrs(rhs), tol_rel, tol_abs);
118118
}
119119

120+
// A failed solve often returns NaNs. Check for them explicitly, because
121+
// the max-norm checks used by these tests silently drop NaNs.
122+
for (int ilev = 0; ilev < int(phi.size()); ++ilev) {
123+
if (phi[ilev].contains_nan(0, phi[ilev].nComp(), 0)) {
124+
amrex::Abort("MyTest::solve: solution contains NaN on level "
125+
+ std::to_string(ilev));
126+
}
127+
}
128+
120129
if (verbose) {
121130
Vector<MultiFab> res(max_level+1);
122131
for (int ilev = 0; ilev <= max_level; ++ilev) {

Tests/LinearSolvers/CellEB2/MyTest.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,15 @@ MyTest::solve ()
164164
}
165165
}
166166

167+
// A failed solve often returns NaNs. Check for them explicitly, because
168+
// the max-norm checks used by these tests silently drop NaNs.
169+
for (int ilev = 0; ilev < int(phi.size()); ++ilev) {
170+
if (phi[ilev].contains_nan(0, phi[ilev].nComp(), 0)) {
171+
amrex::Abort("MyTest::solve: solution contains NaN on level "
172+
+ std::to_string(ilev));
173+
}
174+
}
175+
167176
if (verbose > 0) {
168177
for (int ilev = 0; ilev <= max_level; ++ilev)
169178
{

Tests/LinearSolvers/CellOverset/MyTest.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ MyTest::solve ()
7777

7878
// In region with overset mask = 0, phi has valid solution and rhs is zero.
7979
Real mlmg_err = mlmg.solve({&phi}, {&rhs}, 1.e-11, 0.0);
80+
81+
// A failed solve often returns NaNs. Check for them explicitly, because
82+
// the max-norm checks used by these tests silently drop NaNs.
83+
if (phi.contains_nan(0, phi.nComp(), 0)) {
84+
amrex::Abort("MyTest::solve: solution contains NaN");
85+
}
8086
}
8187

8288
void

Tests/LinearSolvers/CurlCurl/MyTest.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ MyTest::solve ()
108108
mlmg.solve({&solution}, {&rhs}, tol_rel, tol_abs);
109109
}
110110

111+
// A failed solve often returns NaNs. Check for them explicitly, because
112+
// the max-norm checks used by these tests silently drop NaNs. This must
113+
// happen before the loop below, which overwrites solution with the error.
114+
for (int idim = 0; idim < 3; ++idim) {
115+
if (solution[idim].contains_nan(0, solution[idim].nComp(), 0)) {
116+
amrex::Abort("MyTest::solve: solution contains NaN in direction "
117+
+ std::to_string(idim));
118+
}
119+
}
120+
111121
amrex::Print() << " Number of cells: " << n_cell << '\n';
112122
for (int idim = 0; idim < 3; ++idim) {
113123
MultiFab::Subtract(solution[idim], exact[idim], 0, 0, 1, 0);

Tests/LinearSolvers/EBABecLapOverset/MyTest.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,15 @@ MyTest::solve (bool use_overset_mask)
148148
mlmg.solve(amrex::GetVecOfPtrs(phi), amrex::GetVecOfConstPtrs(rhs), tol_rel, tol_abs);
149149
}
150150

151+
// A failed solve often returns NaNs. Check for them explicitly, because
152+
// the max-norm checks used by these tests silently drop NaNs.
153+
for (int ilev = 0; ilev < int(phi.size()); ++ilev) {
154+
if (phi[ilev].contains_nan(0, phi[ilev].nComp(), 0)) {
155+
amrex::Abort("MyTest::solve: solution contains NaN on level "
156+
+ std::to_string(ilev));
157+
}
158+
}
159+
151160
if (verbose) {
152161
Vector<MultiFab> res(max_level+1);
153162
for (int ilev = 0; ilev <= max_level; ++ilev) {

Tests/LinearSolvers/EBConvergenceTest/MyTest.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ MyTest::solve ()
7171
const Real tol_abs = 0.0;
7272
mlmg.solve(amrex::GetVecOfPtrs(phi), amrex::GetVecOfConstPtrs(rhs), tol_rel, tol_abs);
7373

74+
// A failed solve often returns NaNs. Check for them explicitly, because
75+
// the max-norm checks used by these tests silently drop NaNs.
76+
for (int ilev = 0; ilev < int(phi.size()); ++ilev) {
77+
if (phi[ilev].contains_nan(0, phi[ilev].nComp(), 0)) {
78+
amrex::Abort("MyTest::solve: solution contains NaN on level "
79+
+ std::to_string(ilev));
80+
}
81+
}
82+
7483
for (int ilev = 0; ilev <= max_level; ++ilev) {
7584
amrex::VisMF::Write(phi[0], "phi-"+std::to_string(ilev));
7685
}

Tests/LinearSolvers/EBTensor/MyTest.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ MyTest::solve ()
122122
// solution.setVal(0.0);
123123
mlmg.solve({&solution}, {&rhs}, tol_rel, tol_abs);
124124

125+
// A failed solve often returns NaNs. Check for them explicitly, because
126+
// the max-norm checks used by these tests silently drop NaNs.
127+
if (solution.contains_nan(0, solution.nComp(), 0)) {
128+
amrex::Abort("MyTest::solve: solution contains NaN");
129+
}
130+
125131
MultiFab error(grids, dmap, 1, 0, MFInfo(), *factory);
126132
for (int idim = 0; idim < AMREX_SPACEDIM; ++idim) {
127133
amrex::Print() << "\n";

0 commit comments

Comments
 (0)