Compare commits
199
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a319b653a | ||
|
|
c53a016d06 | ||
|
|
ea2b42c49d | ||
|
|
c7e066a0d3 | ||
|
|
0a5730eac3 | ||
|
|
477c475c1a | ||
|
|
34fdcddb8d | ||
|
|
91fa9936ef | ||
|
|
d95e6e0adc | ||
|
|
82d3b8b92e | ||
|
|
9c90e3830a | ||
|
|
a8e83d2f0d | ||
|
|
288657ebbd | ||
|
|
5a26bd936c | ||
|
|
05034b8917 | ||
|
|
32aea6ed2d | ||
|
|
9407050e6a | ||
|
|
0515209ffd | ||
|
|
3446b46420 | ||
|
|
f8f18f8722 | ||
|
|
e7eabeb5e2 | ||
|
|
57b5d23e4b | ||
|
|
4f3c73b3eb | ||
|
|
8be13975b0 | ||
|
|
7fce328f12 | ||
|
|
56b1a1715a | ||
|
|
3514c0f0d4 | ||
|
|
5791aa4629 | ||
|
|
18cff41dac | ||
|
|
b5366e3ad3 | ||
|
|
fb9449c47d | ||
|
|
1555bfe3a5 | ||
|
|
a3b12b6f97 | ||
|
|
c82d9bd8a0 | ||
|
|
1e6ee60790 | ||
|
|
b7ff8749a3 | ||
|
|
a72d85fa6c | ||
|
|
0fd9dcceb3 | ||
|
|
b66d56ee9b | ||
|
|
9852e93449 | ||
|
|
27db3a4121 | ||
|
|
a35ef68c2a | ||
|
|
2fca844393 | ||
|
|
698183a8db | ||
|
|
fa61508248 | ||
|
|
d2baadad26 | ||
|
|
0aa68f431f | ||
|
|
d2288ef6fd | ||
|
|
6f882ed87e | ||
|
|
813a7de323 | ||
|
|
cc4f5625e2 | ||
|
|
f4baef8b5d | ||
|
|
0e4b0f7bda | ||
|
|
7317d4d139 | ||
|
|
e9e684599f | ||
|
|
6c994fea99 | ||
|
|
3b05995fd1 | ||
|
|
5e99ffc9a3 | ||
|
|
bcbb24dce0 | ||
|
|
797113ff71 | ||
|
|
41af83b10c | ||
|
|
4da2ea6f08 | ||
|
|
9eb0f5c0c3 | ||
|
|
4a1a5dfa55 | ||
|
|
59edd7255c | ||
|
|
6d01e152de | ||
|
|
0e22b182a6 | ||
|
|
49eb2715ba | ||
|
|
5d3be16590 | ||
|
|
899a2fc7cb | ||
|
|
bd0fa51539 | ||
|
|
b151c909f3 | ||
|
|
ce7f94ec1f | ||
|
|
ee6b9fdc2f | ||
|
|
c418868c83 | ||
|
|
7a0344a6bb | ||
|
|
51812480bd | ||
|
|
61cc19ca22 | ||
|
|
5e48080f3d | ||
|
|
af73851cb2 | ||
|
|
338e4288ca | ||
|
|
dca4cd510a | ||
|
|
2b23f35ec7 | ||
|
|
ea569c5806 | ||
|
|
e2efc259b6 | ||
|
|
b1468c14fd | ||
|
|
d3281a8a86 | ||
|
|
a7d59d35e0 | ||
|
|
70680d6187 | ||
|
|
948462da6e | ||
|
|
fddefa7838 | ||
|
|
62a01ee670 | ||
|
|
e23768f5f7 | ||
|
|
53d4f78fdb | ||
|
|
eeb71eee37 | ||
|
|
3630a8f8a1 | ||
|
|
048904b731 | ||
|
|
2496b33699 | ||
|
|
1ff8b6811d | ||
|
|
cf269700a8 | ||
|
|
cb0c205bd1 | ||
|
|
2ed6fdc85a | ||
|
|
15b35a01ba | ||
|
|
ff746a8af6 | ||
|
|
41ea219782 | ||
|
|
b54ee3537f | ||
|
|
d46b421417 | ||
|
|
1d7029c5d6 | ||
|
|
5596d38532 | ||
|
|
389580efef | ||
|
|
2029636109 | ||
|
|
1ca38f826c | ||
|
|
4745e062f2 | ||
|
|
a631ab7e77 | ||
|
|
30f3e42232 | ||
|
|
4d92694fc5 | ||
|
|
a4214d22f4 | ||
|
|
ce1576411c | ||
|
|
1c4f617fd5 | ||
|
|
b234475774 | ||
|
|
5da1d7ddf3 | ||
|
|
cff3c6cb6f | ||
|
|
7ef863b731 | ||
|
|
09b31f0526 | ||
|
|
5044d9cd45 | ||
|
|
2edddb700e | ||
|
|
1d79ab00ba | ||
|
|
85e08b67a0 | ||
|
|
c513bb1276 | ||
|
|
7559524573 | ||
|
|
7ac34aab22 | ||
|
|
17edacb630 | ||
|
|
703ba47151 | ||
|
|
57caab145a | ||
|
|
b294248b06 | ||
|
|
83ca977d71 | ||
|
|
6739574668 | ||
|
|
9f153f9fcb | ||
|
|
00d35316b9 | ||
|
|
84a3fe000b | ||
|
|
7354e1ce6c | ||
|
|
d5089996cc | ||
|
|
9787ad0d3b | ||
|
|
26fb49960e | ||
|
|
5cb2c82d59 | ||
|
|
9e7f8ce838 | ||
|
|
41e4360d2e | ||
|
|
263dd0f019 | ||
|
|
433f9a4c51 | ||
|
|
08856158ce | ||
|
|
b21bf9a2d9 | ||
|
|
8d705f4230 | ||
|
|
0a6c72c52d | ||
|
|
8cefee8799 | ||
|
|
e9b631116f | ||
|
|
6468880b2a | ||
|
|
89a0e08f29 | ||
|
|
7471a22505 | ||
|
|
779ce337f4 | ||
|
|
54b24da610 | ||
|
|
1a332939a0 | ||
|
|
bbd26e1835 | ||
|
|
e8842c506a | ||
|
|
214313291b | ||
|
|
cfbaf5a6bf | ||
|
|
2e2b8faba5 | ||
|
|
bf9ce2c6a4 | ||
|
|
9f858378ca | ||
|
|
c2b948a036 | ||
|
|
0771f904c0 | ||
|
|
3b49d70f35 | ||
|
|
7c077e656d | ||
|
|
b9f146b46f | ||
|
|
07438814ce | ||
|
|
328c914481 | ||
|
|
1766007d8a | ||
|
|
6311548ac6 | ||
|
|
9d84c17e0b | ||
|
|
23c1fc6452 | ||
|
|
3117d0e8ab | ||
|
|
a9b720b79d | ||
|
|
3fe878efaa | ||
|
|
4c0def024c | ||
|
|
bf7c26ebf9 | ||
|
|
73014e34c5 | ||
|
|
6507e421e2 | ||
|
|
7edb2ed7c4 | ||
|
|
5d84aa5eb5 | ||
|
|
09988c9da3 | ||
|
|
74e4ad3e2c | ||
|
|
cf9fcd8dde | ||
|
|
fa8617ada3 | ||
|
|
f7e5db2cea | ||
|
|
60c11776b6 | ||
|
|
6238f8ca76 | ||
|
|
6a256db9aa | ||
|
|
e0fb9658ca | ||
|
|
a1089efac3 | ||
|
|
83f7f769dc |
@@ -232,6 +232,7 @@ miniapps/meshing/fit-node-position
|
||||
miniapps/meshing/trimmer
|
||||
miniapps/meshing/reflector
|
||||
miniapps/meshing/ref321
|
||||
miniapps/meshing/mesh-bounding-boxes
|
||||
miniapps/meshing/mesh-optimizer
|
||||
miniapps/meshing/pmesh-optimizer
|
||||
miniapps/meshing/pmesh-fitting
|
||||
@@ -262,6 +263,8 @@ miniapps/meshing/mesh.*
|
||||
miniapps/meshing/order.*
|
||||
miniapps/meshing/sol.*
|
||||
miniapps/meshing/refined.mesh
|
||||
miniapps/meshing/bounding-box*
|
||||
miniapps/meshing/jacobian-determinant*
|
||||
|
||||
miniapps/mtop/parheat
|
||||
miniapps/mtop/ParHeat*
|
||||
@@ -336,6 +339,7 @@ miniapps/shifted/lsf_integral
|
||||
miniapps/tools/display-basis
|
||||
miniapps/tools/load-dc
|
||||
miniapps/tools/convert-dc
|
||||
miniapps/tools/gridfunction-bounds
|
||||
miniapps/tools/lor-transfer
|
||||
miniapps/tools/plor-transfer
|
||||
miniapps/tools/get-values
|
||||
@@ -402,6 +406,9 @@ miniapps/spde/ParaView
|
||||
|
||||
miniapps/tribol/contact-patch-test
|
||||
|
||||
miniapps/diag-smoothers/abs-l1-jacobi
|
||||
miniapps/diag-smoothers/mg-abs-l1-jacobi
|
||||
|
||||
# Unit test binary and outputs
|
||||
tests/unit/output_meshes
|
||||
tests/unit/unit_tests
|
||||
|
||||
@@ -40,10 +40,20 @@ GPU computing
|
||||
conditions. A new function Vector::SetSubVectorHost has been added in cases
|
||||
where host execution is always needed (e.g. when the DOFs array is small).
|
||||
|
||||
New and updated examples and miniapps
|
||||
-------------------------------------
|
||||
- Added miniapps to demonstrate an implementation of the absolute-value
|
||||
L(1)-Jacobi preconditioners in partially assembled operators. This includes
|
||||
Multigrid wrapper to demonstrate the effectiveness of these Jacobi-type
|
||||
operators as smoothers.
|
||||
These miniapps can be found in `miniapps/diag-smoothers`.
|
||||
|
||||
API changes:
|
||||
-----------
|
||||
- mfem::internal::tensor and mfem::internal::dual have been moved to
|
||||
mfem::future::tensor and mfem::future::dual.
|
||||
- API addition: in class `Operator`, added virtual functions: `AbsMult`, and
|
||||
`AbsMultTranspose`; in class `Vector`, added `Abs` and `Pow`.
|
||||
|
||||
|
||||
Version 4.8, released on Apr 9, 2025
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@ vertices
|
||||
|
||||
nodes
|
||||
FiniteElementSpace
|
||||
FiniteElementCollection: Quadratic
|
||||
FiniteElementCollection: H1_3D_P2
|
||||
VDim: 3
|
||||
Ordering: 0
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ vertices
|
||||
|
||||
nodes
|
||||
FiniteElementSpace
|
||||
FiniteElementCollection: Quadratic
|
||||
FiniteElementCollection: H1_3D_P2
|
||||
VDim: 3
|
||||
Ordering: 0
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ vertices
|
||||
|
||||
nodes
|
||||
FiniteElementSpace
|
||||
FiniteElementCollection: Quadratic
|
||||
FiniteElementCollection: H1_2D_P2
|
||||
VDim: 2
|
||||
Ordering: 0
|
||||
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ vertices
|
||||
|
||||
nodes
|
||||
FiniteElementSpace
|
||||
FiniteElementCollection: Quadratic
|
||||
FiniteElementCollection: H1_2D_P2
|
||||
VDim: 2
|
||||
Ordering: 0
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ set(SRCS
|
||||
transfer.cpp
|
||||
hyperbolic.cpp
|
||||
integrator.cpp
|
||||
bounds.cpp
|
||||
)
|
||||
|
||||
set(HDRS
|
||||
@@ -272,6 +273,7 @@ set(HDRS
|
||||
transfer.hpp
|
||||
hyperbolic.hpp
|
||||
integrator.hpp
|
||||
bounds.hpp
|
||||
)
|
||||
|
||||
if (MFEM_USE_SIDRE)
|
||||
|
||||
+208
-170
@@ -78,7 +78,7 @@ void MFBilinearFormExtension::AssembleDiagonal(Vector &y) const
|
||||
dynamic_cast<const ElementRestriction*>(elem_restrict);
|
||||
if (H1elem_restrict)
|
||||
{
|
||||
H1elem_restrict->MultTransposeUnsigned(localY, y);
|
||||
H1elem_restrict->AbsMultTranspose(localY, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -456,7 +456,7 @@ void PABilinearFormExtension::AssembleDiagonal(Vector &y) const
|
||||
dynamic_cast<const ElementRestriction*>(elem_restrict);
|
||||
if (H1elem_restrict)
|
||||
{
|
||||
H1elem_restrict->MultTransposeUnsigned(localY, y);
|
||||
H1elem_restrict->AbsMultTranspose(localY, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -491,7 +491,7 @@ void PABilinearFormExtension::AssembleDiagonal(Vector &y) const
|
||||
assemble_diagonal_with_markers(*bdr_integs[i], bdr_markers[i],
|
||||
bdr_attributes, bdr_face_Y);
|
||||
}
|
||||
bdr_face_restrict_lex->AddMultTransposeUnsigned(bdr_face_Y, y);
|
||||
bdr_face_restrict_lex->AddAbsMultTranspose(bdr_face_Y, y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,7 +526,8 @@ void PABilinearFormExtension::FormLinearSystem(const Array<int> &ess_tdof_list,
|
||||
A.Reset(oper); // A will own oper
|
||||
}
|
||||
|
||||
void PABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
void PABilinearFormExtension::MultInternal(const Vector &x, Vector &y,
|
||||
const bool useAbs) const
|
||||
{
|
||||
Array<BilinearFormIntegrator*> &integrators = *a->GetDBFI();
|
||||
|
||||
@@ -558,11 +559,13 @@ void PABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
{
|
||||
if (integrators[i]->Patchwise())
|
||||
{
|
||||
MFEM_ASSERT(!useAbs, "AbsMult not implemented with NURBS!")
|
||||
integrators[i]->AddMultNURBSPA(x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
integrators[i]->AddMultPA(x, y);
|
||||
if (useAbs) { integrators[i]->AddAbsMultPA(x, y); }
|
||||
else { integrators[i]->AddMultPA(x, y); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -571,14 +574,30 @@ void PABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
if (iSz)
|
||||
{
|
||||
Array<Array<int>*> &elem_markers = *a->GetDBFI_Marker();
|
||||
elem_restrict->Mult(x, localX);
|
||||
auto H1elem_restrict =
|
||||
dynamic_cast<const ElementRestriction*>(elem_restrict);
|
||||
if (H1elem_restrict && useAbs)
|
||||
{
|
||||
H1elem_restrict->AbsMult(x, localX);
|
||||
}
|
||||
else
|
||||
{
|
||||
elem_restrict->Mult(x, localX);
|
||||
}
|
||||
localY = 0.0;
|
||||
for (int i = 0; i < iSz; ++i)
|
||||
{
|
||||
AddMultWithMarkers(*integrators[i], localX, elem_markers[i],
|
||||
elem_attributes, false, localY);
|
||||
elem_attributes, false, localY, useAbs);
|
||||
}
|
||||
if (H1elem_restrict && useAbs)
|
||||
{
|
||||
H1elem_restrict->AbsMultTranspose(localY, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
elem_restrict->MultTranspose(localY, y);
|
||||
}
|
||||
elem_restrict->MultTranspose(localY, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -590,6 +609,7 @@ void PABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
const int iFISz = intFaceIntegrators.Size();
|
||||
if (int_face_restrict_lex && iFISz>0)
|
||||
{
|
||||
MFEM_ASSERT(!useAbs, "AbsMult not implemented for face integrators!")
|
||||
// When assembling interior face integrators for DG spaces, we need to
|
||||
// exchange the face-neighbor information. This happens inside member
|
||||
// functions of the 'int_face_restrict_lex'. To avoid repeated calls to
|
||||
@@ -651,6 +671,7 @@ void PABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
const bool has_bdr_integs = (n_bdr_face_integs > 0 || n_bdr_integs > 0);
|
||||
if (bdr_face_restrict_lex && has_bdr_integs)
|
||||
{
|
||||
MFEM_ASSERT(!useAbs, "AbsMult not implemented for bdr integrators!")
|
||||
Array<Array<int>*> &bdr_markers = *a->GetBBFI_Marker();
|
||||
Array<Array<int>*> &bdr_face_markers = *a->GetBFBFI_Marker();
|
||||
bdr_face_restrict_lex->Mult(x, bdr_face_X);
|
||||
@@ -828,22 +849,39 @@ void PABilinearFormExtension::AddMultWithMarkers(
|
||||
const Array<int> *markers,
|
||||
const Array<int> &attributes,
|
||||
const bool transpose,
|
||||
Vector &y) const
|
||||
Vector &y,
|
||||
const bool useAbs) const
|
||||
{
|
||||
if (markers)
|
||||
{
|
||||
tmp_evec.SetSize(y.Size());
|
||||
tmp_evec = 0.0;
|
||||
if (transpose) { integ.AddMultTransposePA(x, tmp_evec); }
|
||||
else { integ.AddMultPA(x, tmp_evec); }
|
||||
if (useAbs)
|
||||
{
|
||||
if (transpose) { integ.AddAbsMultTransposePA(x, tmp_evec); }
|
||||
else { integ.AddAbsMultPA(x, tmp_evec); }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (transpose) { integ.AddMultTransposePA(x, tmp_evec); }
|
||||
else { integ.AddMultPA(x, tmp_evec); }
|
||||
}
|
||||
const int ne = attributes.Size();
|
||||
const int nd = x.Size() / ne;
|
||||
AddWithMarkers_(ne, nd, tmp_evec, *markers, attributes, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (transpose) { integ.AddMultTransposePA(x, y); }
|
||||
else { integ.AddMultPA(x, y); }
|
||||
if (useAbs)
|
||||
{
|
||||
if (transpose) { integ.AddAbsMultTransposePA(x, y); }
|
||||
else { integ.AddAbsMultPA(x, y); }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (transpose) { integ.AddMultTransposePA(x, y); }
|
||||
else { integ.AddMultPA(x, y); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1010,8 +1048,13 @@ void EABilinearFormExtension::Assemble()
|
||||
}
|
||||
}
|
||||
|
||||
void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
void EABilinearFormExtension::MultInternal(const Vector &x, Vector &y,
|
||||
const bool useTranspose,
|
||||
const bool useAbs) const
|
||||
{
|
||||
auto elemRest = dynamic_cast<const ElementRestriction*>(elem_restrict);
|
||||
MFEM_ASSERT(useAbs?(elemRest!=nullptr):true,
|
||||
"elem_restrict is not ElementRestriction*!")
|
||||
// Apply the Element Restriction
|
||||
const bool useRestrict = !DeviceCanUseCeed() && elem_restrict;
|
||||
if (!useRestrict)
|
||||
@@ -1019,6 +1062,11 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
y.UseDevice(true); // typically this is a large vector, so store on device
|
||||
y = 0.0;
|
||||
}
|
||||
else if (useAbs)
|
||||
{
|
||||
elemRest->AbsMult(x, localX);
|
||||
localY = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
elem_restrict->Mult(x, localX);
|
||||
@@ -1026,25 +1074,55 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
}
|
||||
// Apply the Element Matrices
|
||||
{
|
||||
Vector abs_ea_data;
|
||||
if (useAbs)
|
||||
{
|
||||
abs_ea_data = ea_data;
|
||||
abs_ea_data.Abs();
|
||||
}
|
||||
const int NDOFS = elemDofs;
|
||||
auto X = Reshape(useRestrict?localX.Read():x.Read(), NDOFS, ne);
|
||||
auto Y = Reshape(useRestrict?localY.ReadWrite():y.ReadWrite(), NDOFS, ne);
|
||||
auto A = Reshape(ea_data.Read(), NDOFS, NDOFS, ne);
|
||||
mfem::forall(ne*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
auto A = Reshape(useAbs?abs_ea_data.Read():ea_data.Read(), NDOFS, NDOFS, ne);
|
||||
if (!useTranspose)
|
||||
{
|
||||
const int e = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
mfem::forall(ne*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
{
|
||||
res += A(i, j, e)*X(i, e);
|
||||
}
|
||||
Y(j, e) += res;
|
||||
});
|
||||
const int e = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A(i, j, e)*X(i, e);
|
||||
}
|
||||
Y(j, e) += res;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
mfem::forall(ne*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
{
|
||||
const int e = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A(j, i, e)*X(i, e);
|
||||
}
|
||||
Y(j, e) += res;
|
||||
});
|
||||
}
|
||||
// Apply the Element Restriction transposed
|
||||
if (useRestrict)
|
||||
{
|
||||
elem_restrict->MultTranspose(localY, y);
|
||||
if (useAbs)
|
||||
{
|
||||
elemRest->AbsMultTranspose(localY, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
elem_restrict->MultTranspose(localY, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1053,6 +1131,7 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
const int iFISz = intFaceIntegrators.Size();
|
||||
if (int_face_restrict_lex && iFISz>0)
|
||||
{
|
||||
MFEM_VERIFY(!useAbs, "AbsMult not implemented with Face integrators!")
|
||||
// Apply the Interior Face Restriction
|
||||
int_face_restrict_lex->Mult(x, int_face_X);
|
||||
if (int_face_X.Size()>0)
|
||||
@@ -1064,7 +1143,65 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
auto Y = Reshape(int_face_Y.ReadWrite(), NDOFS, 2, nf_int);
|
||||
if (!factorize_face_terms)
|
||||
{
|
||||
auto A_int = Reshape(ea_data_int.Read(), NDOFS, NDOFS, 2, nf_int);
|
||||
Vector abs_ea_data_int(ea_data_int.Size());
|
||||
if (useAbs)
|
||||
{
|
||||
abs_ea_data_int = ea_data_int;
|
||||
abs_ea_data_int.Abs();
|
||||
}
|
||||
auto A_int = Reshape(useAbs?abs_ea_data_int.Read():ea_data_int.Read(),
|
||||
NDOFS, NDOFS, 2, nf_int);
|
||||
if (!useTranspose)
|
||||
{
|
||||
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
{
|
||||
const int f = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_int(i, j, 0, f)*X(i, 0, f);
|
||||
}
|
||||
Y(j, 0, f) += res;
|
||||
res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_int(i, j, 1, f)*X(i, 1, f);
|
||||
}
|
||||
Y(j, 1, f) += res;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
{
|
||||
const int f = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_int(j, i, 0, f)*X(i, 0, f);
|
||||
}
|
||||
Y(j, 0, f) += res;
|
||||
res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_int(j, i, 1, f)*X(i, 1, f);
|
||||
}
|
||||
Y(j, 1, f) += res;
|
||||
});
|
||||
}
|
||||
}
|
||||
Vector abs_ea_data_ext(ea_data_ext.Size());
|
||||
if (useAbs)
|
||||
{
|
||||
abs_ea_data_ext = ea_data_ext;
|
||||
abs_ea_data_ext.Abs();
|
||||
}
|
||||
auto A_ext = Reshape(useAbs?abs_ea_data_ext.Read():ea_data_ext.Read(),
|
||||
NDOFS, NDOFS, 2, nf_int);
|
||||
if (!useTranspose)
|
||||
{
|
||||
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
{
|
||||
const int f = glob_j/NDOFS;
|
||||
@@ -1072,35 +1209,37 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_int(i, j, 0, f)*X(i, 0, f);
|
||||
res += A_ext(i, j, 0, f)*X(i, 0, f);
|
||||
}
|
||||
Y(j, 0, f) += res;
|
||||
Y(j, 1, f) += res;
|
||||
res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_int(i, j, 1, f)*X(i, 1, f);
|
||||
res += A_ext(i, j, 1, f)*X(i, 1, f);
|
||||
}
|
||||
Y(j, 1, f) += res;
|
||||
Y(j, 0, f) += res;
|
||||
});
|
||||
}
|
||||
auto A_ext = Reshape(ea_data_ext.Read(), NDOFS, NDOFS, 2, nf_int);
|
||||
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
else
|
||||
{
|
||||
const int f = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
{
|
||||
res += A_ext(i, j, 0, f)*X(i, 0, f);
|
||||
}
|
||||
Y(j, 1, f) += res;
|
||||
res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_ext(i, j, 1, f)*X(i, 1, f);
|
||||
}
|
||||
Y(j, 0, f) += res;
|
||||
});
|
||||
const int f = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_ext(j, i, 1, f)*X(i, 0, f);
|
||||
}
|
||||
Y(j, 1, f) += res;
|
||||
res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_ext(j, i, 0, f)*X(i, 1, f);
|
||||
}
|
||||
Y(j, 0, f) += res;
|
||||
});
|
||||
}
|
||||
// Apply the Interior Face Restriction transposed
|
||||
int_face_restrict_lex->AddMultTransposeInPlace(int_face_Y, y);
|
||||
}
|
||||
@@ -1109,7 +1248,9 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
// Treatment of boundary faces
|
||||
if (!factorize_face_terms && bdr_face_restrict_lex && ea_data_bdr.Size() > 0)
|
||||
{
|
||||
MFEM_ASSERT(!useAbs, "AbsMult not implemented with Face integrators!")
|
||||
// Apply the Boundary Face Restriction
|
||||
// TODO: AbsMult if needed
|
||||
bdr_face_restrict_lex->Mult(x, bdr_face_X);
|
||||
bdr_face_Y = 0.0;
|
||||
// Apply the boundary face matrices
|
||||
@@ -1117,141 +1258,38 @@ void EABilinearFormExtension::Mult(const Vector &x, Vector &y) const
|
||||
auto X = Reshape(bdr_face_X.Read(), NDOFS, nf_bdr);
|
||||
auto Y = Reshape(bdr_face_Y.ReadWrite(), NDOFS, nf_bdr);
|
||||
auto A = Reshape(ea_data_bdr.Read(), NDOFS, NDOFS, nf_bdr);
|
||||
mfem::forall(nf_bdr*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
if (!useTranspose)
|
||||
{
|
||||
const int f = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A(i, j, f)*X(i, f);
|
||||
}
|
||||
Y(j, f) += res;
|
||||
});
|
||||
// Apply the Boundary Face Restriction transposed
|
||||
bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y);
|
||||
}
|
||||
}
|
||||
|
||||
void EABilinearFormExtension::MultTranspose(const Vector &x, Vector &y) const
|
||||
{
|
||||
// Apply the Element Restriction
|
||||
const bool useRestrict = !DeviceCanUseCeed() && elem_restrict;
|
||||
if (!useRestrict)
|
||||
{
|
||||
y.UseDevice(true); // typically this is a large vector, so store on device
|
||||
y = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
elem_restrict->Mult(x, localX);
|
||||
localY = 0.0;
|
||||
}
|
||||
// Apply the Element Matrices transposed
|
||||
{
|
||||
const int NDOFS = elemDofs;
|
||||
auto X = Reshape(useRestrict?localX.Read():x.Read(), NDOFS, ne);
|
||||
auto Y = Reshape(useRestrict?localY.ReadWrite():y.ReadWrite(), NDOFS, ne);
|
||||
auto A = Reshape(ea_data.Read(), NDOFS, NDOFS, ne);
|
||||
mfem::forall(ne*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
{
|
||||
const int e = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A(j, i, e)*X(i, e);
|
||||
}
|
||||
Y(j, e) += res;
|
||||
});
|
||||
// Apply the Element Restriction transposed
|
||||
if (useRestrict)
|
||||
{
|
||||
elem_restrict->MultTranspose(localY, y);
|
||||
}
|
||||
}
|
||||
|
||||
// Treatment of interior faces
|
||||
Array<BilinearFormIntegrator*> &intFaceIntegrators = *a->GetFBFI();
|
||||
const int iFISz = intFaceIntegrators.Size();
|
||||
if (int_face_restrict_lex && iFISz>0)
|
||||
{
|
||||
// Apply the Interior Face Restriction
|
||||
int_face_restrict_lex->Mult(x, int_face_X);
|
||||
if (int_face_X.Size()>0)
|
||||
{
|
||||
int_face_Y = 0.0;
|
||||
// Apply the interior face matrices transposed
|
||||
const int NDOFS = faceDofs;
|
||||
auto X = Reshape(int_face_X.Read(), NDOFS, 2, nf_int);
|
||||
auto Y = Reshape(int_face_Y.ReadWrite(), NDOFS, 2, nf_int);
|
||||
if (!factorize_face_terms)
|
||||
{
|
||||
auto A_int = Reshape(ea_data_int.Read(), NDOFS, NDOFS, 2, nf_int);
|
||||
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
{
|
||||
const int f = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_int(j, i, 0, f)*X(i, 0, f);
|
||||
}
|
||||
Y(j, 0, f) += res;
|
||||
res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_int(j, i, 1, f)*X(i, 1, f);
|
||||
}
|
||||
Y(j, 1, f) += res;
|
||||
});
|
||||
}
|
||||
auto A_ext = Reshape(ea_data_ext.Read(), NDOFS, NDOFS, 2, nf_int);
|
||||
mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
// TODO: useAbs
|
||||
mfem::forall(nf_bdr*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
{
|
||||
const int f = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_ext(j, i, 1, f)*X(i, 0, f);
|
||||
res += A(i, j, f)*X(i, f);
|
||||
}
|
||||
Y(j, 1, f) += res;
|
||||
res = 0.0;
|
||||
Y(j, f) += res;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: useAbs
|
||||
mfem::forall(nf_bdr*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
{
|
||||
const int f = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A_ext(j, i, 0, f)*X(i, 1, f);
|
||||
res += A(j, i, f)*X(i, f);
|
||||
}
|
||||
Y(j, 0, f) += res;
|
||||
Y(j, f) += res;
|
||||
});
|
||||
// Apply the Interior Face Restriction transposed
|
||||
int_face_restrict_lex->AddMultTransposeInPlace(int_face_Y, y);
|
||||
}
|
||||
}
|
||||
|
||||
// Treatment of boundary faces
|
||||
if (!factorize_face_terms && bdr_face_restrict_lex && ea_data_bdr.Size() > 0)
|
||||
{
|
||||
// Apply the Boundary Face Restriction
|
||||
bdr_face_restrict_lex->Mult(x, bdr_face_X);
|
||||
bdr_face_Y = 0.0;
|
||||
// Apply the boundary face matrices transposed
|
||||
const int NDOFS = faceDofs;
|
||||
auto X = Reshape(bdr_face_X.Read(), NDOFS, nf_bdr);
|
||||
auto Y = Reshape(bdr_face_Y.ReadWrite(), NDOFS, nf_bdr);
|
||||
auto A = Reshape(ea_data_bdr.Read(), NDOFS, NDOFS, nf_bdr);
|
||||
mfem::forall(nf_bdr*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j)
|
||||
{
|
||||
const int f = glob_j/NDOFS;
|
||||
const int j = glob_j%NDOFS;
|
||||
real_t res = 0.0;
|
||||
for (int i = 0; i < NDOFS; i++)
|
||||
{
|
||||
res += A(j, i, f)*X(i, f);
|
||||
}
|
||||
Y(j, f) += res;
|
||||
});
|
||||
// Apply the Boundary Face Restriction transposed
|
||||
// TODO: AbsMultTranspose if needed
|
||||
bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y);
|
||||
}
|
||||
}
|
||||
@@ -1911,7 +1949,7 @@ void PAMixedBilinearFormExtension::AssembleDiagonal_ADAt(const Vector &D,
|
||||
dynamic_cast<const ElementRestriction*>(elem_restrict_trial);
|
||||
if (H1elem_restrict_trial)
|
||||
{
|
||||
H1elem_restrict_trial->MultUnsigned(D, localTrial);
|
||||
H1elem_restrict_trial->AbsMult(D, localTrial);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1937,7 +1975,7 @@ void PAMixedBilinearFormExtension::AssembleDiagonal_ADAt(const Vector &D,
|
||||
dynamic_cast<const ElementRestriction*>(elem_restrict_test);
|
||||
if (H1elem_restrict_test)
|
||||
{
|
||||
H1elem_restrict_test->MultTransposeUnsigned(localTest, diag);
|
||||
H1elem_restrict_test->AbsMultTranspose(localTest, diag);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1993,7 +2031,7 @@ void PADiscreteLinearOperatorExtension::Assemble()
|
||||
dynamic_cast<const ElementRestriction*>(elem_restrict_test);
|
||||
if (elem_restrict)
|
||||
{
|
||||
elem_restrict->MultTransposeUnsigned(ones, test_multiplicity);
|
||||
elem_restrict->AbsMultTranspose(ones, test_multiplicity);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -91,12 +91,17 @@ public:
|
||||
Vector &x, Vector &b,
|
||||
OperatorHandle &A, Vector &X, Vector &B,
|
||||
int copy_interior = 0) override;
|
||||
void Mult(const Vector &x, Vector &y) const override;
|
||||
void Mult(const Vector &x, Vector &y) const override
|
||||
{ MultInternal(x,y); }
|
||||
void AbsMult(const Vector &x, Vector &y) const override
|
||||
{ MultInternal(x,y, true); }
|
||||
void MultTranspose(const Vector &x, Vector &y) const override;
|
||||
void Update() override;
|
||||
|
||||
protected:
|
||||
void SetupRestrictionOperators(const L2FaceValues m);
|
||||
void MultInternal(const Vector &x, Vector &y,
|
||||
const bool useAbs = false) const;
|
||||
|
||||
/// @brief Accumulate the action (or transpose) of the integrator on @a x
|
||||
/// into @a y, taking into account the (possibly null) @a markers array.
|
||||
@@ -110,12 +115,14 @@ protected:
|
||||
/// @param attributes Array of element or boundary element attributes.
|
||||
/// @param transpose Compute the action or transpose of the integrator .
|
||||
/// @param y Output E-vector
|
||||
/// @param useAbs Apply absolute-value operator
|
||||
void AddMultWithMarkers(const BilinearFormIntegrator &integ,
|
||||
const Vector &x,
|
||||
const Array<int> *markers,
|
||||
const Array<int> &attributes,
|
||||
const bool transpose,
|
||||
Vector &y) const;
|
||||
Vector &y,
|
||||
const bool useAbs = false) const;
|
||||
|
||||
/// @brief Performs the same function as AddMultWithMarkers, but takes as
|
||||
/// input and output face normal derivatives.
|
||||
@@ -152,8 +159,15 @@ public:
|
||||
EABilinearFormExtension(BilinearForm *form);
|
||||
|
||||
void Assemble() override;
|
||||
void Mult(const Vector &x, Vector &y) const override;
|
||||
void MultTranspose(const Vector &x, Vector &y) const override;
|
||||
|
||||
void Mult(const Vector &x, Vector &y) const override
|
||||
{ MultInternal(x, y, false); }
|
||||
void AbsMult(const Vector &x, Vector &y) const override
|
||||
{ MultInternal(x, y, false, true); }
|
||||
void MultTranspose(const Vector &x, Vector &y) const override
|
||||
{ MultInternal(x, y, true); }
|
||||
void AbsMultTranspose(const Vector &x, Vector &y) const override
|
||||
{ MultInternal(x, y, true, true); }
|
||||
|
||||
/// @brief Populates @a element_matrices with the element matrices.
|
||||
///
|
||||
@@ -165,6 +179,10 @@ public:
|
||||
void GetElementMatrices(DenseTensor &element_matrices,
|
||||
ElementDofOrdering ordering,
|
||||
bool add_bdr);
|
||||
|
||||
// This method needs to be public due to 'nvcc' restriction.
|
||||
void MultInternal(const Vector &x, Vector &y, const bool useTranspose,
|
||||
const bool useAbs = false) const;
|
||||
};
|
||||
|
||||
/// Data and methods for fully-assembled bilinear forms
|
||||
|
||||
@@ -121,6 +121,12 @@ void BilinearFormIntegrator::AddMultPA(const Vector &, Vector &) const
|
||||
" is not implemented for this class.");
|
||||
}
|
||||
|
||||
void BilinearFormIntegrator::AddAbsMultPA(const Vector &, Vector &) const
|
||||
{
|
||||
MFEM_ABORT("BilinearFormIntegrator:AddAbsMultPA:(...)\n"
|
||||
" is not implemented for this class.");
|
||||
}
|
||||
|
||||
void BilinearFormIntegrator::AddMultNURBSPA(const Vector &, Vector &) const
|
||||
{
|
||||
MFEM_ABORT("BilinearFormIntegrator::AddMultNURBSPA(...)\n"
|
||||
@@ -133,6 +139,13 @@ void BilinearFormIntegrator::AddMultTransposePA(const Vector &, Vector &) const
|
||||
" is not implemented for this class.");
|
||||
}
|
||||
|
||||
void BilinearFormIntegrator::AddAbsMultTransposePA(const Vector &,
|
||||
Vector &) const
|
||||
{
|
||||
MFEM_ABORT("BilinearFormIntegrator::AddAbsMultTransposePA(...)\n"
|
||||
" is not implemented for this class.");
|
||||
}
|
||||
|
||||
void BilinearFormIntegrator::AssembleMF(const FiniteElementSpace &fes)
|
||||
{
|
||||
MFEM_ABORT("BilinearFormIntegrator::AssembleMF(...)\n"
|
||||
@@ -418,6 +431,14 @@ void SumIntegrator::AddMultPA(const Vector& x, Vector& y) const
|
||||
}
|
||||
}
|
||||
|
||||
void SumIntegrator::AddAbsMultPA(const Vector& x, Vector& y) const
|
||||
{
|
||||
for (int i = 0; i < integrators.Size(); i++)
|
||||
{
|
||||
integrators[i]->AddAbsMultPA(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void SumIntegrator::AddMultTransposePA(const Vector &x, Vector &y) const
|
||||
{
|
||||
for (int i = 0; i < integrators.Size(); i++)
|
||||
@@ -426,6 +447,14 @@ void SumIntegrator::AddMultTransposePA(const Vector &x, Vector &y) const
|
||||
}
|
||||
}
|
||||
|
||||
void SumIntegrator::AddAbsMultTransposePA(const Vector &x, Vector &y) const
|
||||
{
|
||||
for (int i = 0; i < integrators.Size(); i++)
|
||||
{
|
||||
integrators[i]->AddAbsMultTransposePA(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void SumIntegrator::AssembleMF(const FiniteElementSpace &fes)
|
||||
{
|
||||
for (int i = 0; i < integrators.Size(); i++)
|
||||
|
||||
@@ -78,6 +78,8 @@ public:
|
||||
called. */
|
||||
void AddMultPA(const Vector &x, Vector &y) const override;
|
||||
|
||||
virtual void AddAbsMultPA(const Vector &x, Vector &y) const;
|
||||
|
||||
/// Method for partially assembled action on NURBS patches.
|
||||
virtual void AddMultNURBSPA(const Vector&x, Vector&y) const;
|
||||
|
||||
@@ -90,6 +92,8 @@ public:
|
||||
called. */
|
||||
virtual void AddMultTransposePA(const Vector &x, Vector &y) const;
|
||||
|
||||
virtual void AddAbsMultTransposePA(const Vector &x, Vector &y) const;
|
||||
|
||||
/// Method defining element assembly.
|
||||
/** The result of the element assembly is added to the @a emat Vector if
|
||||
@a add is true. Otherwise, if @a add is false, we set @a emat. */
|
||||
@@ -496,8 +500,12 @@ public:
|
||||
|
||||
void AddMultTransposePA(const Vector &x, Vector &y) const override;
|
||||
|
||||
void AddAbsMultTransposePA(const Vector &x, Vector &y) const override;
|
||||
|
||||
void AddMultPA(const Vector& x, Vector& y) const override;
|
||||
|
||||
void AddAbsMultPA(const Vector& x, Vector& y) const override;
|
||||
|
||||
void AssembleMF(const FiniteElementSpace &fes) override;
|
||||
|
||||
void AddMultMF(const Vector &x, Vector &y) const override;
|
||||
@@ -2320,8 +2328,12 @@ public:
|
||||
|
||||
void AddMultPA(const Vector&, Vector&) const override;
|
||||
|
||||
void AddAbsMultPA(const Vector&, Vector&) const override;
|
||||
|
||||
void AddMultTransposePA(const Vector&, Vector&) const override;
|
||||
|
||||
void AddAbsMultTransposePA(const Vector&, Vector&) const override;
|
||||
|
||||
void AddMultNURBSPA(const Vector&, Vector&) const override;
|
||||
|
||||
void AddMultPatchPA(const int patch, const Vector &x, Vector &y) const;
|
||||
@@ -2419,8 +2431,12 @@ public:
|
||||
|
||||
void AddMultPA(const Vector&, Vector&) const override;
|
||||
|
||||
void AddAbsMultPA(const Vector&, Vector&) const override;
|
||||
|
||||
void AddMultTransposePA(const Vector&, Vector&) const override;
|
||||
|
||||
void AddAbsMultTransposePA(const Vector&, Vector&) const override;
|
||||
|
||||
static const IntegrationRule &GetRule(const FiniteElement &trial_fe,
|
||||
const FiniteElement &test_fe,
|
||||
const ElementTransformation &Trans);
|
||||
@@ -2816,6 +2832,7 @@ public:
|
||||
using BilinearFormIntegrator::AssemblePA;
|
||||
void AssemblePA(const FiniteElementSpace &fes) override;
|
||||
void AddMultPA(const Vector &x, Vector &y) const override;
|
||||
void AddAbsMultPA(const Vector &x, Vector &y) const override;
|
||||
void AssembleDiagonalPA(Vector& diag) override;
|
||||
|
||||
const Coefficient *GetCoefficient() const { return Q; }
|
||||
@@ -2933,6 +2950,7 @@ public:
|
||||
void AssemblePA(const FiniteElementSpace &trial_fes,
|
||||
const FiniteElementSpace &test_fes) override;
|
||||
void AddMultPA(const Vector &x, Vector &y) const override;
|
||||
void AddAbsMultPA(const Vector &x, Vector &y) const override;
|
||||
void AddMultTransposePA(const Vector &x, Vector &y) const override;
|
||||
void AssembleDiagonalPA(Vector& diag) override;
|
||||
void AssembleEA(const FiniteElementSpace &fes, Vector &emat,
|
||||
|
||||
+715
@@ -0,0 +1,715 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
// Implementation of bounds
|
||||
|
||||
#include "bounds.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
using namespace std;
|
||||
|
||||
void PLBound::Setup(const int nb_i, const int ncp_i,
|
||||
const int b_type_i, const int cp_type_i,
|
||||
const real_t tol_i)
|
||||
{
|
||||
MFEM_VERIFY(b_type_i >= 0 && b_type_i <= 2, "Bases not supported. "
|
||||
"Please read class description to see supported types.");
|
||||
MFEM_VERIFY(cp_type_i == 0 || cp_type_i == 1,
|
||||
"Control point type not supported. Please read class "
|
||||
"description to see supported types.");
|
||||
nb = nb_i;
|
||||
ncp = ncp_i;
|
||||
b_type = b_type_i;
|
||||
cp_type = cp_type_i;
|
||||
tol = tol_i;
|
||||
lbound.SetSize(nb, ncp);
|
||||
ubound.SetSize(nb, ncp);
|
||||
nodes.SetSize(nb);
|
||||
weights.SetSize(nb);
|
||||
control_points.SetSize(ncp);
|
||||
|
||||
auto scalenodes = [](const Vector &in, const real_t a, const real_t b) -> Vector
|
||||
{
|
||||
Vector outVec(in.Size());
|
||||
real_t maxv = in.Max();
|
||||
real_t minv = in.Min();
|
||||
for (int i = 0; i < in.Size(); i++)
|
||||
{
|
||||
outVec(i) = a + (b-a)*(in(i)-minv)/(maxv-minv);
|
||||
}
|
||||
return outVec;
|
||||
};
|
||||
MFEM_VERIFY(ncp >= 2,"At least 2 control points are required.");
|
||||
|
||||
if (cp_type == 0) // GL + End Point
|
||||
{
|
||||
control_points(0) = 0.0;
|
||||
control_points(ncp-1) = 1.0;
|
||||
if (ncp > 2)
|
||||
{
|
||||
const real_t *x = poly1d.GetPoints(ncp-3, 0);
|
||||
MFEM_VERIFY(x, "Error in getting points.");
|
||||
for (int i = 0; i < ncp-2; i++)
|
||||
{
|
||||
control_points(i+1) = x[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cp_type == 1) // Chebyshev
|
||||
{
|
||||
auto GetChebyshevNodes = [](int n) -> Vector
|
||||
{
|
||||
Vector cheb(n);
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
cheb(i) = -cos(M_PI * (static_cast<real_t>(i) / (n - 1)));
|
||||
}
|
||||
return cheb;
|
||||
};
|
||||
control_points = GetChebyshevNodes(ncp);
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ABORT("Unsupported interval points. Use [0,1].\n");
|
||||
}
|
||||
control_points = scalenodes(control_points, 0.0, 1.0); // rescale to [0,1]
|
||||
|
||||
Poly_1D::Basis &basis1d(poly1d.GetBasis(nb-1, b_type));
|
||||
|
||||
// Initialize bounds
|
||||
lbound = 0.0;
|
||||
ubound = 0.0;
|
||||
|
||||
Vector bmv(nb), bpv(nb), bv(nb); // basis values
|
||||
Vector bdmv(nb), bdpv(nb), bdv(nb); // basis derivative values
|
||||
Vector vals(3);
|
||||
|
||||
// See Section 3.1.1 of https://arxiv.org/pdf/2501.12349 for explanation of
|
||||
// procedure below.
|
||||
for (int j = 0; j < ncp; j++)
|
||||
{
|
||||
real_t x = control_points(j);
|
||||
real_t xm = x;
|
||||
if (j != 0)
|
||||
{
|
||||
xm = 0.5*(control_points(j-1)+control_points(j));
|
||||
}
|
||||
real_t xp = x;
|
||||
if (j != ncp-1)
|
||||
{
|
||||
xp = 0.5*(control_points(j)+control_points(j+1));
|
||||
}
|
||||
basis1d.Eval(xm, bmv, bdmv);
|
||||
basis1d.Eval(xp, bpv, bdpv);
|
||||
basis1d.Eval(x, bv);
|
||||
real_t dm = x-xm;
|
||||
real_t dp = x-xp;
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
if (j == 0)
|
||||
{
|
||||
lbound(i, j) = bv(i);
|
||||
ubound(i, j) = bv(i);
|
||||
}
|
||||
else if (j == ncp-1)
|
||||
{
|
||||
lbound(i, j) = bv(i);
|
||||
ubound(i, j) = bv(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
vals(0) = bv(i);
|
||||
vals(1) = bmv(i) + dm*bdmv(i);
|
||||
vals(2) = bpv(i) + dp*bdpv(i);
|
||||
lbound(i, j) = vals.Min()-tol; // tolerance for good measure
|
||||
ubound(i, j) = vals.Max()+tol; // tolerance for good measure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IntegrationRule irule(nb);
|
||||
if (b_type == 0)
|
||||
{
|
||||
QuadratureFunctions1D::GaussLegendre(nb, &irule);
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
weights(i) = irule.IntPoint(i).weight;
|
||||
nodes(i) = irule.IntPoint(i).x;
|
||||
}
|
||||
}
|
||||
else if (b_type == 1)
|
||||
{
|
||||
QuadratureFunctions1D::GaussLobatto(nb, &irule);
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
weights(i) = irule.IntPoint(i).weight;
|
||||
nodes(i) = irule.IntPoint(i).x;
|
||||
}
|
||||
}
|
||||
else if (b_type == 2)
|
||||
{
|
||||
QuadratureFunctions1D::ClosedUniform(nb, &irule);
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
weights(i) = irule.IntPoint(i).weight;
|
||||
nodes(i) = irule.IntPoint(i).x;
|
||||
}
|
||||
}
|
||||
|
||||
if (b_type == 2)
|
||||
{
|
||||
nodes_int.SetSize(nb);
|
||||
weights_int.SetSize(nb);
|
||||
IntegrationRule irule_int(nb);
|
||||
{
|
||||
QuadratureFunctions1D::GaussLobatto(nb, &irule_int);
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
weights_int(i) = irule_int.IntPoint(i).weight;
|
||||
nodes_int(i) = irule_int.IntPoint(i).x;
|
||||
}
|
||||
}
|
||||
|
||||
SetupBernsteinBasisMat(basisMatNodes, nodes);
|
||||
// Setup memory for lu factors
|
||||
basisMatLU = basisMatNodes;
|
||||
lu_ip.SetSize(nb);
|
||||
// Compute lu factors
|
||||
LUFactors lu(basisMatLU.GetData(), lu_ip.GetData());
|
||||
bool factor = lu.Factor(nb);
|
||||
MFEM_VERIFY(factor,"Failure in LU factorization in PLBound.");
|
||||
|
||||
// Setup the Bernstein basis matrix for the GLL integration points. This
|
||||
// is used to compute linear fit.
|
||||
SetupBernsteinBasisMat(basisMatInt, nodes_int);
|
||||
}
|
||||
else
|
||||
{
|
||||
nodes_int.SetDataAndSize(nodes.GetData(), nb);
|
||||
weights_int.SetDataAndSize(weights.GetData(), nb);
|
||||
}
|
||||
}
|
||||
|
||||
PLBound::PLBound(FiniteElementSpace *fes, int ncp_i, int cp_type_i)
|
||||
{
|
||||
MFEM_VERIFY(!fes->IsVariableOrder(),
|
||||
"Variable order meshes not yet supported.");
|
||||
const char *name = fes->FEColl()->Name();
|
||||
string cname = name;
|
||||
|
||||
cp_type = cp_type_i;
|
||||
b_type = BasisType::Invalid;
|
||||
nb = fes->GetMaxElementOrder()+1;
|
||||
tol = 0.0;
|
||||
|
||||
int minncp = 2;
|
||||
if (nb > 12)
|
||||
{
|
||||
minncp = 2*nb;
|
||||
}
|
||||
else if (!strncmp(name, "H1_", 3) && strncmp(name, "H1_Trace_", 9))
|
||||
{
|
||||
// H1 GLL
|
||||
b_type = BasisType::GaussLobatto;
|
||||
minncp = min_ncp_gll_x[cp_type][nb-2];
|
||||
}
|
||||
else if (!strncmp(name, "H1Pos_", 6) && strncmp(name, "H1Pos_Trace_", 12))
|
||||
{
|
||||
// H1 Positive
|
||||
b_type = BasisType::Positive;
|
||||
minncp = min_ncp_pos_x[cp_type][nb-2];
|
||||
}
|
||||
else if (!strncmp(name, "L2_", 3) && strncmp(name, "L2_T", 4))
|
||||
{
|
||||
// L2 Gauss-Legendre
|
||||
b_type = BasisType::GaussLegendre;
|
||||
minncp = min_ncp_gl_x[cp_type][nb-2];
|
||||
}
|
||||
else if (!strncmp(name, "L2_T1", 5))
|
||||
{
|
||||
// L2 GLL
|
||||
b_type = BasisType::GaussLobatto;
|
||||
minncp = min_ncp_gll_x[cp_type][nb-2];
|
||||
}
|
||||
else if (!strncmp(name, "L2_T2", 5))
|
||||
{
|
||||
// L2 Positive
|
||||
b_type = BasisType::Positive;
|
||||
minncp = min_ncp_pos_x[cp_type][nb-2];
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ABORT("Only H1 GLL/Positive & L2 GL/GLL/Positive bases supported.");
|
||||
}
|
||||
|
||||
ncp = std::max(minncp, ncp_i);
|
||||
|
||||
Setup(nb, ncp, b_type, cp_type, tol);
|
||||
}
|
||||
|
||||
void PLBound::Get1DBounds(Vector &coeff, Vector &intmin, Vector &intmax) const
|
||||
{
|
||||
real_t x,w;
|
||||
intmin.SetSize(ncp);
|
||||
intmax.SetSize(ncp);
|
||||
intmin = 0.0;
|
||||
intmax = 0.0;
|
||||
Vector coeffm(nb);
|
||||
coeffm = 0.0;
|
||||
|
||||
real_t a0 = 0.0;
|
||||
real_t a1 = 0.0;
|
||||
|
||||
Vector nodal_vals, nodal_integ_vals;
|
||||
if (b_type == 2) // compute values at equispaced nodes and GLL nodes
|
||||
{
|
||||
nodal_vals.SetSize(nb);
|
||||
nodal_integ_vals.SetSize(nb);
|
||||
Vector shape(nb);
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
basisMatNodes.GetRow(i, shape);
|
||||
nodal_vals(i) = shape*coeff;
|
||||
basisMatInt.GetRow(i, shape);
|
||||
nodal_integ_vals(i) = shape*coeff;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nodal_vals.SetDataAndSize(coeff.GetData(), nb);
|
||||
nodal_integ_vals.SetDataAndSize(coeff.GetData(), nb);
|
||||
}
|
||||
|
||||
// compute L2 projection for linear bases: a0 + a1*x
|
||||
if (proj)
|
||||
{
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
x = 2.0*nodes_int(i)-1;
|
||||
w = 2.0*weights_int(i);
|
||||
a0 += 0.5*nodal_integ_vals(i)*w;
|
||||
a1 += 1.5*nodal_integ_vals(i)*w*x;
|
||||
}
|
||||
|
||||
// offset the linear fit from nodal values
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
x = 2.0*nodes(i)-1;
|
||||
coeffm(i) = nodal_vals(i) - a0 - a1*x;
|
||||
}
|
||||
|
||||
// compute coefficients for Bernstein
|
||||
if (b_type == 2)
|
||||
{
|
||||
LUFactors lu(basisMatLU.GetData(), lu_ip.GetData());
|
||||
lu.Solve(nb, 1, coeffm.GetData());
|
||||
}
|
||||
|
||||
// initialize the bounds to be the linear fit
|
||||
for (int j = 0; j < ncp; j++)
|
||||
{
|
||||
x = 2.0*control_points(j)-1;
|
||||
intmin(j) = a0 + a1*x;
|
||||
intmax(j) = intmin(j);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
coeffm.SetDataAndSize(coeff.GetData(), nb);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
real_t c = coeffm(i);
|
||||
for (int j = 0; j < ncp; j++)
|
||||
{
|
||||
intmin(j) += min(lbound(i,j)*c, ubound(i,j)*c);
|
||||
intmax(j) += max(lbound(i,j)*c, ubound(i,j)*c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PLBound::Get2DBounds(Vector &coeff, Vector &intmin, Vector &intmax) const
|
||||
{
|
||||
intmin.SetSize(ncp*ncp);
|
||||
intmax.SetSize(ncp*ncp);
|
||||
intmin = 0.0;
|
||||
intmax = 0.0;
|
||||
Vector intminT(ncp*nb);
|
||||
Vector intmaxT(ncp*nb);
|
||||
// Get bounds for each row of the solution
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
Vector solcoeff(coeff.GetData()+i*nb, nb);
|
||||
Vector intminrow(intminT.GetData()+i*ncp, ncp);
|
||||
Vector intmaxrow(intmaxT.GetData()+i*ncp, ncp);
|
||||
Get1DBounds(solcoeff, intminrow, intmaxrow);
|
||||
}
|
||||
Vector intminT2 = intminT;
|
||||
|
||||
// Compute a0 and a1 for each column of nodes
|
||||
Vector a0V(ncp), a1V(ncp);
|
||||
a0V = 0.0;
|
||||
a1V = 0.0;
|
||||
real_t x,w,t;
|
||||
if (proj)
|
||||
{
|
||||
if (b_type == 2)
|
||||
{
|
||||
// Note: DenseMatrix uses column-major ordering so we will need to
|
||||
// transpose the matrix.
|
||||
DenseMatrix intminTM(intminT.GetData(), ncp, nb),
|
||||
intmaxTM(intmaxT.GetData(), ncp, nb),
|
||||
intmeanTM(ncp, nb);
|
||||
DenseMatrix minvalsM(nb, ncp), maxvalsM(nb, ncp), meanintvalsM(nb, ncp);
|
||||
MultABt(basisMatNodes, intminTM, minvalsM);
|
||||
MultABt(basisMatNodes, intmaxTM, maxvalsM);
|
||||
intmeanTM = intminTM;
|
||||
intmeanTM += intmaxTM;
|
||||
intmeanTM *= 0.5;
|
||||
MultABt(basisMatInt, intmeanTM, meanintvalsM);
|
||||
|
||||
// Compute the linear fit along each column and then offset it from
|
||||
// the bounds on the coefficient.
|
||||
// Note: Since Bernstein bases are positive, we can use the lower
|
||||
// bounds to compute the lower bounding polynomial and subtract the
|
||||
// linear fit before finding the Bernstein coefficients corresponding
|
||||
// to the perturbation. Same for upper bounds. If the bases were not
|
||||
// always positive, it is not yet clear if the perturbation
|
||||
// coefficients will be this straightforward to compute.
|
||||
for (int j = 0; j < ncp; j++) // row of interval points
|
||||
{
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
x = 2.0*nodes_int(i)-1; // x-coordinate
|
||||
w = 2.0*weights_int(i); // weight
|
||||
t = meanintvalsM(i,j);
|
||||
a0V(j) += 0.5*t*w;
|
||||
a1V(j) += 1.5*t*w*x;
|
||||
}
|
||||
// Offset linear fit
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
x = 2.0*nodes(i)-1; // x-coordinate
|
||||
minvalsM(i,j) -= a0V(j) + a1V(j)*x;
|
||||
maxvalsM(i,j) -= a0V(j) + a1V(j)*x;
|
||||
}
|
||||
// Compute Bernstein coefficients
|
||||
LUFactors lu(basisMatLU.GetData(), lu_ip.GetData());
|
||||
lu.Solve(nb, 1, minvalsM.GetColumn(j));
|
||||
lu.Solve(nb, 1, maxvalsM.GetColumn(j));
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
intminT(i*ncp+j) = minvalsM(i,j);
|
||||
intmaxT(i*ncp+j) = maxvalsM(i,j);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 0; j < nb; j++) // row of nodes
|
||||
{
|
||||
x = 2.0*nodes(j)-1; // x-coordinate
|
||||
w = 2.0*weights(j); // weight
|
||||
for (int i = 0; i < ncp; i++) // column of interval points
|
||||
{
|
||||
t = 0.5*(intminT(j*ncp+i)+intmaxT(j*ncp+i));
|
||||
a0V(i) += 0.5*t*w;
|
||||
a1V(i) += 1.5*t*w*x;
|
||||
}
|
||||
}
|
||||
// offset the linear fit from nodal values
|
||||
for (int j = 0; j < nb; j++) // row of nodes
|
||||
{
|
||||
x = 2.0*nodes(j)-1; // x-coordinate
|
||||
for (int i = 0; i < ncp; i++) // column of interval points
|
||||
{
|
||||
t = a0V(i) + a1V(i)*x;
|
||||
intminT(j*ncp+i) -= t;
|
||||
intmaxT(j*ncp+i) -= t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize bounds using a0 and a1 values
|
||||
for (int j = 0; j < ncp; j++) // row j
|
||||
{
|
||||
x = 2.0*control_points(j)-1;
|
||||
for (int i = 0; i < ncp; i++) // column i
|
||||
{
|
||||
intmin(j*ncp+i) = a0V(i) + a1V(i)*x;
|
||||
intmax(j*ncp+i) = intmin(j*ncp+i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute bounds
|
||||
int id1 = 0, id2 = 0;
|
||||
Vector vals(4);
|
||||
for (int j = 0; j < nb; j++)
|
||||
{
|
||||
for (int i = 0; i < ncp; i++) // ith column
|
||||
{
|
||||
real_t w0 = intminT(id1++);
|
||||
real_t w1 = intmaxT(id2++);
|
||||
for (int k = 0; k < ncp; k++) // kth row
|
||||
{
|
||||
vals(0) = w0*lbound(j,k);
|
||||
vals(1) = w0*ubound(j,k);
|
||||
vals(2) = w1*lbound(j,k);
|
||||
vals(3) = w1*ubound(j,k);
|
||||
intmin(k*ncp+i) += vals.Min();
|
||||
intmax(k*ncp+i) += vals.Max();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PLBound::Get3DBounds(Vector &coeff, Vector &intmin, Vector &intmax) const
|
||||
{
|
||||
int nb2 = nb*nb,
|
||||
ncp2 = ncp*ncp,
|
||||
ncp3 = ncp*ncp*ncp;
|
||||
|
||||
intmin.SetSize(ncp3);
|
||||
intmax.SetSize(ncp3);
|
||||
intmin = 0.0;
|
||||
intmax = 0.0;
|
||||
Vector intminT(ncp2*nb);
|
||||
Vector intmaxT(ncp2*nb);
|
||||
|
||||
// Get bounds for each slice of the solution
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
Vector solcoeff(coeff.GetData()+i*nb2, nb2);
|
||||
Vector intminrow(intminT.GetData()+i*ncp2, ncp2);
|
||||
Vector intmaxrow(intmaxT.GetData()+i*ncp2, ncp2);
|
||||
Get2DBounds(solcoeff, intminrow, intmaxrow);
|
||||
}
|
||||
DenseMatrix intminTM(intminT.GetData(), ncp2, nb),
|
||||
intmaxTM(intmaxT.GetData(), ncp2, nb);
|
||||
|
||||
// Compute a0 and a1 for each tower of nodes
|
||||
Vector a0V(ncp2), a1V(ncp2);
|
||||
a0V = 0.0;
|
||||
a1V = 0.0;
|
||||
real_t x,w,t;
|
||||
if (proj)
|
||||
{
|
||||
if (b_type == 2) // Bernstein bases
|
||||
{
|
||||
// Compute the mean coefficients along each tower.
|
||||
for (int j = 0; j < ncp2; j++) // slice of interval points
|
||||
{
|
||||
Vector meanBounds(nb), minBounds(nb), maxBounds(nb);
|
||||
intminTM.GetRow(j, minBounds);
|
||||
intmaxTM.GetRow(j, maxBounds);
|
||||
for (int i = 0; i < nb; i++) // column of nodes
|
||||
{
|
||||
meanBounds(i) = 0.5*(minBounds(i)+maxBounds(i));
|
||||
}
|
||||
Vector meanNodalIntVals(nb);
|
||||
Vector minNodalVals(nb);
|
||||
Vector maxNodalVals(nb);
|
||||
Vector row(nb);
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
basisMatNodes.GetRow(i, row);
|
||||
minNodalVals(i) = row*minBounds;
|
||||
maxNodalVals(i) = row*maxBounds;
|
||||
basisMatInt.GetRow(i, row);
|
||||
meanNodalIntVals(i) = row*meanBounds;
|
||||
}
|
||||
// linear fit along each tower
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
x = 2.0*nodes_int(i)-1; // x-coordinate
|
||||
w = 2.0*weights_int(i); // weight
|
||||
a0V(j) += 0.5*meanNodalIntVals(i)*w;
|
||||
a1V(j) += 1.5*meanNodalIntVals(i)*w*x;
|
||||
}
|
||||
// offset the linear fit from bounding coefficients
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
x = 2.0*nodes(i)-1; // x-coordinate
|
||||
minBounds(i) -= a0V(j) + a1V(j)*x;
|
||||
maxBounds(i) -= a0V(j) + a1V(j)*x;
|
||||
}
|
||||
// Compute Bernstein coefficients
|
||||
LUFactors lu(basisMatLU.GetData(), lu_ip.GetData());
|
||||
lu.Solve(nb, 1, minBounds.GetData());
|
||||
lu.Solve(nb, 1, maxBounds.GetData());
|
||||
for (int i = 0; i < nb; i++)
|
||||
{
|
||||
intminT(i*ncp2+j) = minBounds(i);
|
||||
intmaxT(i*ncp2+j) = maxBounds(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// nodal bases
|
||||
for (int j = 0; j < nb; j++) // tower of nodes
|
||||
{
|
||||
x = 2.0*nodes(j)-1; // x-coordinate
|
||||
w = 2.0*weights(j); // weight
|
||||
for (int i = 0; i < ncp2; i++) // slice of interval points
|
||||
{
|
||||
t = 0.5*(intminT(j*ncp2+i)+intmaxT(j*ncp2+i));
|
||||
a0V(i) += 0.5*t*w;
|
||||
a1V(i) += 1.5*t*w*x;
|
||||
}
|
||||
}
|
||||
// offset the linear fit from nodal values
|
||||
for (int j = 0; j < nb; j++) // row of nodes
|
||||
{
|
||||
x = 2.0*nodes(j)-1; // x-coordinate
|
||||
for (int i = 0; i < ncp2; i++) // column of interval points
|
||||
{
|
||||
t = a0V(i) + a1V(i)*x;
|
||||
intminT(j*ncp2+i) -= t;
|
||||
intmaxT(j*ncp2+i) -= t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize bounds using a0 and a1 values
|
||||
for (int j = 0; j < ncp; j++) // slice j
|
||||
{
|
||||
x = 2.0*control_points(j)-1;
|
||||
for (int i = 0; i < ncp2; i++) // tower i
|
||||
{
|
||||
intmin(j*ncp2+i) = a0V(i) + a1V(i)*x;
|
||||
intmax(j*ncp2+i) = a0V(i) + a1V(i)*x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute bounds
|
||||
int id1 = 0, id2 = 0;
|
||||
Vector vals(4);
|
||||
for (int j = 0; j < nb; j++)
|
||||
{
|
||||
for (int i = 0; i < ncp2; i++) // ith tower
|
||||
{
|
||||
real_t w0 = intminT(id1++);
|
||||
real_t w1 = intmaxT(id2++);
|
||||
for (int k = 0; k < ncp; k++) // kth slice
|
||||
{
|
||||
vals(0) = w0*lbound(j,k);
|
||||
vals(1) = w0*ubound(j,k);
|
||||
vals(2) = w1*lbound(j,k);
|
||||
vals(3) = w1*ubound(j,k);
|
||||
intmin(k*ncp2+i) += vals.Min();
|
||||
intmax(k*ncp2+i) += vals.Max();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PLBound::GetNDBounds(int rdim, Vector &coeff,
|
||||
Vector &intmin, Vector &intmax) const
|
||||
{
|
||||
if (rdim == 1)
|
||||
{
|
||||
Get1DBounds(coeff, intmin, intmax);
|
||||
}
|
||||
else if (rdim == 2)
|
||||
{
|
||||
Get2DBounds(coeff, intmin, intmax);
|
||||
}
|
||||
else if (rdim == 3)
|
||||
{
|
||||
Get3DBounds(coeff, intmin, intmax);
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ABORT("Currently not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
void PLBound::SetupBernsteinBasisMat(DenseMatrix &basisMat,
|
||||
Vector &nodesBern) const
|
||||
{
|
||||
const int nbern = nodesBern.Size();
|
||||
L2_SegmentElement el(nbern-1, 2); // we use L2 to leverage lexicographic order
|
||||
Array<int> ordering = el.GetLexicographicOrdering();
|
||||
basisMat.SetSize(nbern, nbern);
|
||||
Vector shape(nbern);
|
||||
IntegrationPoint ip;
|
||||
for (int i = 0; i < nbern; i++)
|
||||
{
|
||||
ip.x = nodesBern(i);
|
||||
el.CalcShape(ip, shape);
|
||||
basisMat.SetRow(i, shape);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int PLBound::min_ncp_gl_x[2][11];
|
||||
constexpr int PLBound::min_ncp_gll_x[2][11];
|
||||
constexpr int PLBound::min_ncp_pos_x[2][11];
|
||||
|
||||
int PLBound::GetMinimumPointsForGivenBases(int nb_i, int b_type_i,
|
||||
int cp_type_i) const
|
||||
{
|
||||
MFEM_VERIFY(b_type_i >= 0 && b_type_i <= 2, "Invalid node type. Specify 0 "
|
||||
"for GL, 1 for GLL, and 2 for positive " "bases.");
|
||||
MFEM_VERIFY(cp_type_i == 0 || cp_type_i == 1, "Invalid control point type. "
|
||||
"Specify 0 for GL+end points, 1 for Chebyshev.");
|
||||
if (nb_i > 12)
|
||||
{
|
||||
MFEM_ABORT("GetMinimumPointsForGivenBases can only be used for maximum "
|
||||
"order = 11, i.e. nb=12. 2*nb points should be sufficient to "
|
||||
"bound the bases up to nb = 30.");
|
||||
}
|
||||
else if (b_type_i == 0)
|
||||
{
|
||||
return min_ncp_gl_x[cp_type_i][nb_i-2];
|
||||
}
|
||||
else if (b_type_i == 1)
|
||||
{
|
||||
return min_ncp_gll_x[cp_type_i][nb_i-2];
|
||||
}
|
||||
else if (b_type_i == 2)
|
||||
{
|
||||
return min_ncp_pos_x[cp_type_i][nb_i-2];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PLBound::Print(std::ostream &outp) const
|
||||
{
|
||||
outp << "PLBound nb: " << nb << std::endl;
|
||||
outp << "PLBound ncp: " << ncp << std::endl;
|
||||
outp << "PLBound b_type: " << b_type << std::endl;
|
||||
outp << "PLBound cp_type: " << cp_type << std::endl;
|
||||
outp << "Print nodes: " << std::endl;
|
||||
nodes.Print(outp);
|
||||
outp << "Print weights: " << std::endl;
|
||||
weights.Print(outp);
|
||||
outp << "Print control_points: " << std::endl;
|
||||
control_points.Print(outp);
|
||||
outp << "Print lower bounds: " << std::endl;
|
||||
lbound.Print(outp);
|
||||
outp << "Print upper bounds: " << std::endl;
|
||||
ubound.Print(outp);
|
||||
}
|
||||
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#ifndef MFEM_BOUND
|
||||
#define MFEM_BOUND
|
||||
|
||||
#include "../config/config.hpp"
|
||||
#include "fespace.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
/** @name Piecewise linear bounds of bases
|
||||
\brief Piecewise linear bounds of bases can be used to compute bounds on the grid function in each element. The bounds for the bases are constructed based on the following parameters:
|
||||
|
||||
(i) @b nb: number of bases/nodes in 1D (i.e. polynomial order+1),
|
||||
|
||||
(ii) @b b_type: bases type, 0 - Lagrange interpolants on Gauss-Legendre nodes, 1 - Lagrange interpolants on Gauss-Lobatto-Legendre nodes, and
|
||||
2 - Positive/Bernstein bases on uniformly distributed nodes,
|
||||
|
||||
(iii) @b ncp: number of control points used to construct the piecewise linear bounds
|
||||
|
||||
(iv) @b cp_type: control point distribution. 0 - GL + end-points,
|
||||
1 - Chebyshev.
|
||||
|
||||
Note: @b nb and @b b_type are inferred directly from the grid-function.
|
||||
|
||||
If the user does not specify @b ncp and @b cp_type, the minimum value of
|
||||
@b ncp is used that would bound the bases for the @b cp_type. We default
|
||||
to @b cp_type = 0 as it requires fewer number of points to bound the bases. Typically, @b ncp = 2 @b nb is sufficient to get fairly compact bounds, and increasing @b ncp results in tighter bounds.
|
||||
|
||||
Finally, only tensor-product elements are currently supported.
|
||||
|
||||
For more technical details see:
|
||||
Mittal et al., "General Field Evaluation in High-Order Meshes on GPUs" &
|
||||
Dzanic et al., "A method for bounding high-order finite element
|
||||
functions: Applications to mesh validity and bounds-preserving limiters".
|
||||
*/
|
||||
class PLBound
|
||||
{
|
||||
private:
|
||||
int nb; // #mesh nodes in 1D
|
||||
int ncp; // #control points in 1D
|
||||
int b_type; // bases type: 0 - GL, 1 - GLL, 2 - Bernstein
|
||||
int cp_type; // control points type: 0 - GL+Ends, 1 - Chebyshev
|
||||
bool proj = true; // Use linear projection to compute bounds.
|
||||
real_t tol = 0.0; // offset bounds to avoid round-off errors
|
||||
Vector nodes, weights, control_points;
|
||||
DenseMatrix lbound, ubound; // nb x ncp matrices with bounds of all bases
|
||||
// Some auxillary storage for computing the bounds with Bernstein
|
||||
DenseMatrix basisMatNodes; // Bernstein bases at equispaced nodes
|
||||
DenseMatrix basisMatInt; // Bernstein bases at GLL nodes
|
||||
Vector nodes_int, weights_int; // Integration nodes and weights
|
||||
DenseMatrix basisMatLU; // Used to compute LU factors for Bernstein
|
||||
mutable Array<int> lu_ip;
|
||||
|
||||
// stores min_ncp for nb = 2..12 for Lagrange interpolants on GL nodes
|
||||
// with GL+end points and Chebyshev points as control points
|
||||
static constexpr int min_ncp_gl_x[2][11]= {{3,5,6,8,9,10,11,11,12,13,14},
|
||||
{3,5,8,9,11,12,14,15,17,18,20}
|
||||
};
|
||||
|
||||
// stores min_ncp for nb = 2..12 for Lagrange interpolants on GLL nodes
|
||||
// with GL+end points and Chebyshev points as control points
|
||||
static constexpr int min_ncp_gll_x[2][11]= {{3,5,7,8,9,10,12,13,14,15,16},
|
||||
{3,5,8,10,12,13,15,17,19,21,22}
|
||||
};
|
||||
|
||||
// stores min_ncp for nb = 2..12 for Bernstein bases with GL+end points
|
||||
// and Chebyshev points as control points
|
||||
static constexpr int min_ncp_pos_x[2][11]= {{3,5,7,8,8,9,10,10,11,12,13},
|
||||
{3,5,8,9,11,12,13,13,14,15,16}
|
||||
};
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
PLBound(const int nb_i, const int ncp_i, const int b_type_i,
|
||||
const int cp_type_i, const real_t tol_i)
|
||||
{
|
||||
Setup(nb_i, ncp_i, b_type_i, cp_type_i, tol_i);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
PLBound(FiniteElementSpace *fes, int ncp_i = -1, int cp_type_i = 0);
|
||||
|
||||
// Get minimum number of control points needed to bound the given bases
|
||||
int GetMinimumPointsForGivenBases(int nb_i, int b_type_i,
|
||||
int cp_type_i) const;
|
||||
|
||||
// Print information about the bounds
|
||||
void Print(std::ostream &outp = mfem::out) const;
|
||||
|
||||
// Enable (default) or disable linear projection before bounding.
|
||||
// This projection increases the computational cost but results in tighter
|
||||
// bounds.
|
||||
void SetProjectionFlagForBounding(bool proj_) { proj = proj_; }
|
||||
|
||||
/// Compute piecewise linear bounds for the lexicographically-ordered
|
||||
/// coefficients in @a coeff in 1D/2D/3D.
|
||||
void GetNDBounds(int rdim, Vector &coeff,
|
||||
Vector &intmin, Vector &intmax) const;
|
||||
|
||||
/// Get number of control points used to compute the bounds.
|
||||
int GetNControlPoints() const { return ncp; }
|
||||
private:
|
||||
/// Compute piecewise linear bounds for the lexicographically-ordered
|
||||
/// coefficients in @a coeff in 1D.
|
||||
void Get1DBounds(Vector &coeff, Vector &intmin, Vector &intmax) const;
|
||||
|
||||
/// Compute piecewise linear bounds for the lexicographically-ordered
|
||||
/// coefficients in @a coeff in 2D.
|
||||
void Get2DBounds(Vector &coeff, Vector &intmin, Vector &intmax) const;
|
||||
|
||||
/// Compute piecewise linear bounds for the lexicographically-ordered
|
||||
/// coefficients in @a coeff in 3D.
|
||||
void Get3DBounds(Vector &coeff, Vector &intmin, Vector &intmax) const;
|
||||
|
||||
/// Setup matrix used to compute values at given 1D locations in [0,1]
|
||||
/// for Bernstein bases.
|
||||
void SetupBernsteinBasisMat(DenseMatrix &basisMat, Vector &nodesBern) const;
|
||||
|
||||
void Setup(const int nb_i, const int ncp_i, const int b_type_i,
|
||||
const int cp_type_i, const real_t tol_i);
|
||||
};
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
#endif // MFEM_BOUND
|
||||
@@ -20,6 +20,16 @@ namespace mfem
|
||||
|
||||
using namespace std;
|
||||
|
||||
DofToQuad DofToQuad::Abs() const
|
||||
{
|
||||
DofToQuad d2q(*this);
|
||||
d2q.B.Abs();
|
||||
d2q.Bt.Abs();
|
||||
d2q.G.Abs();
|
||||
d2q.Gt.Abs();
|
||||
return d2q;
|
||||
}
|
||||
|
||||
FiniteElement::FiniteElement(int D, Geometry::Type G,
|
||||
int Do, int O, int F)
|
||||
: Nodes(Do)
|
||||
|
||||
@@ -219,6 +219,9 @@ public:
|
||||
- #ndof x #nqpt, for H(div) vector elements, or
|
||||
- #ndof x #nqpt x cdim, for H(curl) vector elements. */
|
||||
Array<real_t> Gt;
|
||||
|
||||
/// Returns absolute value of the maps
|
||||
DofToQuad Abs() const;
|
||||
};
|
||||
|
||||
/// Describes the function space on each element
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "lor/lor.hpp"
|
||||
#include "dgmassinv.hpp"
|
||||
#include "hyperbolic.hpp"
|
||||
#include "bounds.hpp"
|
||||
|
||||
#include "dfem/doperator.hpp"
|
||||
|
||||
|
||||
@@ -4278,9 +4278,6 @@ void FiniteElementSpace::Update(bool want_transform)
|
||||
void FiniteElementSpace::PRefineAndUpdate(const Array<pRefinement> & refs,
|
||||
bool want_transfer)
|
||||
{
|
||||
MFEM_VERIFY(PRefinementSupported(),
|
||||
"p-refinement is not supported in this space");
|
||||
|
||||
if (want_transfer)
|
||||
{
|
||||
fesPrev.reset(new FiniteElementSpace(mesh, fec, vdim, ordering));
|
||||
|
||||
@@ -4563,4 +4563,135 @@ GridFunction *Extrude1DGridFunction(Mesh *mesh, Mesh *mesh2d,
|
||||
return sol2d;
|
||||
}
|
||||
|
||||
void GridFunction::GetElementBoundsAtControlPoints(const int elem,
|
||||
const PLBound &plb,
|
||||
Vector &lower, Vector &upper,
|
||||
const int vdim)
|
||||
{
|
||||
const FiniteElement *fe = fes->GetFE(elem);
|
||||
int fes_dim = fes->GetVDim();
|
||||
int rdim = fe->GetDim();
|
||||
|
||||
const TensorBasisElement *tbe =
|
||||
dynamic_cast<const TensorBasisElement *>(fe);
|
||||
MFEM_VERIFY(tbe != NULL, "TensorBasis FiniteElement expected.");
|
||||
const Array<int> &dof_map = tbe->GetDofMap();
|
||||
|
||||
Vector loc_data;
|
||||
Array<int> dof_idx;
|
||||
fes->GetElementDofs(elem, dof_idx);
|
||||
int ndofs = dof_idx.Size();
|
||||
|
||||
int n_c_pts = std::pow(plb.GetNControlPoints(), rdim);
|
||||
lower.SetSize(n_c_pts*(vdim > 0 ? 1 : fes_dim));
|
||||
upper.SetSize(n_c_pts*(vdim > 0 ? 1 : fes_dim));
|
||||
|
||||
for (int d = 0; d < fes_dim; d++)
|
||||
{
|
||||
if (vdim > 0 && d != vdim-1) { continue; }
|
||||
const int d_off = vdim > 0 ? 0 : d;
|
||||
Array<int> dof_idx_c = dof_idx;
|
||||
Vector lowerT(lower, d_off*n_c_pts, n_c_pts);
|
||||
Vector upperT(upper, d_off*n_c_pts, n_c_pts);
|
||||
fes->DofsToVDofs(vdim > 0 ? vdim-1 : d, dof_idx_c);
|
||||
GetSubVector(dof_idx_c, loc_data);
|
||||
Vector nodal_data;
|
||||
if (dof_map.Size() == 0)
|
||||
{
|
||||
nodal_data.SetDataAndSize(loc_data.GetData(), ndofs);
|
||||
}
|
||||
else
|
||||
{
|
||||
nodal_data.SetSize(ndofs);
|
||||
for (int j = 0; j < ndofs; j++)
|
||||
{
|
||||
nodal_data(j) = loc_data(dof_map[j]);
|
||||
}
|
||||
}
|
||||
plb.GetNDBounds(rdim, nodal_data, lowerT, upperT);
|
||||
}
|
||||
}
|
||||
|
||||
void GridFunction::GetElementBounds(const int elem, const PLBound &plb,
|
||||
Vector &lower, Vector &upper,
|
||||
const int vdim)
|
||||
{
|
||||
Vector lowerC, upperC;
|
||||
GetElementBoundsAtControlPoints(elem, plb, lowerC, upperC, vdim);
|
||||
const FiniteElement *fe = fes->GetFE(elem);
|
||||
int rdim = fe->GetDim();
|
||||
int n_c_pts = std::pow(plb.GetNControlPoints(), rdim);
|
||||
int fes_dim = fes->GetVDim();
|
||||
lower.SetSize((vdim > 0 ? 1 :fes_dim));
|
||||
upper.SetSize((vdim > 0 ? 1 :fes_dim));
|
||||
for (int d = 0; d < fes_dim; d++)
|
||||
{
|
||||
if (vdim > 0 && d != vdim-1) { continue; }
|
||||
const int d_off = vdim > 0 ? 0 : d;
|
||||
Vector lowerT(lowerC, d_off*n_c_pts, n_c_pts);
|
||||
Vector upperT(upperC, d_off*n_c_pts, n_c_pts);
|
||||
lower(d_off) = lowerT.Min();
|
||||
upper(d_off) = upperT.Max();
|
||||
}
|
||||
}
|
||||
|
||||
void GridFunction::GetElementBounds(const PLBound &plb,
|
||||
Vector &lower, Vector &upper,
|
||||
const int vdim)
|
||||
{
|
||||
int nel = fes->GetNE();
|
||||
int fes_dim = fes->GetVDim();
|
||||
lower.SetSize(nel*(vdim > 0 ? 1 :fes_dim));
|
||||
upper.SetSize(nel*(vdim > 0 ? 1 :fes_dim));
|
||||
for (int e = 0; e < nel; e++)
|
||||
{
|
||||
Vector lt, ut;
|
||||
GetElementBounds(e, plb, lt, ut, vdim);
|
||||
for (int d = 0; d < fes_dim ; d++)
|
||||
{
|
||||
if (vdim > 0 && d != vdim-1) { continue; }
|
||||
const int d_off = vdim > 0 ? 0 : d;
|
||||
lower(e + d_off*nel) = lt(d_off);
|
||||
upper(e + d_off*nel) = ut(d_off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PLBound GridFunction::GetElementBounds(Vector &lower,
|
||||
Vector &upper,
|
||||
const int ref_factor,
|
||||
const int vdim)
|
||||
{
|
||||
int max_order = fes->GetMaxElementOrder();
|
||||
PLBound plb(fes, ref_factor*(max_order+1));
|
||||
GetElementBounds(plb, lower, upper, vdim);
|
||||
return plb;
|
||||
}
|
||||
|
||||
PLBound GridFunction::GetBounds(Vector &lower, Vector &upper,
|
||||
const int ref_factor, const int vdim)
|
||||
{
|
||||
int max_order = fes->GetMaxElementOrder();
|
||||
PLBound plb(fes, ref_factor*(max_order+1));
|
||||
Vector lel, uel;
|
||||
GetElementBounds(plb, lel, uel, vdim);
|
||||
|
||||
int nel = fes->GetNE();
|
||||
int fes_dim = fes->GetVDim();
|
||||
lower.SetSize(vdim > 0 ? 1 : fes_dim);
|
||||
upper.SetSize(vdim > 0 ? 1 : fes_dim);
|
||||
for (int d = 0; d < fes_dim; d++)
|
||||
{
|
||||
if (vdim > 0 && d != vdim-1) { continue; }
|
||||
const int d_off = vdim > 0 ? 0 : d;
|
||||
Vector lelt(lel, d_off*nel, nel);
|
||||
Vector uelt(uel, d_off*nel, nel);
|
||||
lower(d_off) = lelt.Min();
|
||||
upper(d_off) = uelt.Max();
|
||||
}
|
||||
return plb;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+47
-1
@@ -16,6 +16,7 @@
|
||||
#include "fespace.hpp"
|
||||
#include "coefficient.hpp"
|
||||
#include "bilininteg.hpp"
|
||||
#include "bounds.hpp"
|
||||
#ifdef MFEM_USE_ADIOS2
|
||||
#include "../general/adios2stream.hpp"
|
||||
#endif
|
||||
@@ -1561,11 +1562,56 @@ public:
|
||||
must be 2 and that quad elements will be broken into two triangles.*/
|
||||
void SaveSTL(std::ostream &out, int TimesToRefine = 1);
|
||||
|
||||
/** @name Methods to compute bounds on the grid function
|
||||
\brief See bounds.hpp for \ref PLBound that constructs piecewise linear
|
||||
bounds for a given set of bases. These piecewise bounds can be used to compute bounds on a grid function. Currently tensor-product elements are
|
||||
supported with Lagrange interpolants on Gauss Legendre nodes and Gauss Lobatto Legendre nodes, and Bernstein bases.
|
||||
*/
|
||||
///@{
|
||||
/// Computes the \ref PLBound for the gridfunction with number of control
|
||||
/// points based on @a ref_factor, and returns the overall bounds for each
|
||||
/// vdim (across all elements) in @b lower and @b upper. We also return the
|
||||
/// PLBound object used to compute the bounds.
|
||||
/// We compute the bounds for each vdim if @a vdim < 1.
|
||||
/// Note: For most cases, this method/interface will be sufficient.
|
||||
virtual PLBound GetBounds(Vector &lower, Vector &upper,
|
||||
const int ref_factor=1, const int vdim=-1);
|
||||
|
||||
/// Computes the \ref PLBound for the gridfunction with number of control
|
||||
/// points based on @a ref_factor, and returns the bounds for each element
|
||||
/// ordered byVDim:
|
||||
/// lower_{0,0}, lower_{1,0}, ..., lower_{ne-1,0},
|
||||
/// lower_{0,1}, ..., lower_{ne-1,vdim-1}. We also return the
|
||||
/// PLBound object used to compute the bounds.
|
||||
/// We compute the bounds for each vdim if @a vdim < 1.
|
||||
PLBound GetElementBounds(Vector &lower, Vector &upper,
|
||||
const int ref_factor=1, const int vdim=-1);
|
||||
|
||||
/// Compute piecewise linear bounds on the given element at the grid of
|
||||
/// [plb.ncp x plb.ncp x plb.ncp] control points for each of the vdim
|
||||
/// components of the gridfunction.
|
||||
void GetElementBoundsAtControlPoints(const int elem, const PLBound &plb,
|
||||
Vector &lower, Vector &upper,
|
||||
const int vdim = -1);
|
||||
|
||||
/// Compute bounds on the grid function for the given element.
|
||||
/// The bounds are stored in @b lower and @b upper.
|
||||
void GetElementBounds(const int elem, const PLBound &plb,
|
||||
Vector &lower, Vector &upper,
|
||||
const int vdim = -1);
|
||||
|
||||
/// Compute bounds on the grid function for all the elements. The bounds
|
||||
/// are returned in @b lower and @b upper, ordered byVDim:
|
||||
/// lower_{0,0}, lower_{1,0}, ..., lower_{ne-1,0},
|
||||
/// lower_{0,1}, ..., lower_{ne-1,vdim-1}
|
||||
void GetElementBounds(const PLBound &plb, Vector &lower, Vector &upper,
|
||||
const int vdim=-1);
|
||||
///@}
|
||||
|
||||
/// Destroys grid function.
|
||||
virtual ~GridFunction() { Destroy(); }
|
||||
};
|
||||
|
||||
|
||||
/** Overload operator<< for std::ostream and GridFunction; valid also for the
|
||||
derived class ParGridFunction */
|
||||
std::ostream &operator<<(std::ostream &out, const GridFunction &sol);
|
||||
|
||||
+3
-1
@@ -30,7 +30,9 @@ namespace mfem
|
||||
{
|
||||
|
||||
/** \brief FindPointsGSLIB can robustly evaluate a GridFunction on an arbitrary
|
||||
* collection of points.
|
||||
* collection of points. See Mittal et al., "General Field Evaluation in
|
||||
* High-Order Meshes on GPUs". (2025). Computers & Fluids. for technical
|
||||
* details.
|
||||
*
|
||||
* There are three key functions in FindPointsGSLIB:
|
||||
*
|
||||
|
||||
@@ -202,4 +202,68 @@ void CurlCurlIntegrator::AddMultPA(const Vector &x, Vector &y) const
|
||||
}
|
||||
}
|
||||
|
||||
void CurlCurlIntegrator::AddAbsMultPA(const Vector &x, Vector &y) const
|
||||
{
|
||||
Vector abs_pa_data(pa_data);
|
||||
abs_pa_data.Abs();
|
||||
auto absO = mapsO->Abs();
|
||||
auto absC = mapsC->Abs();
|
||||
|
||||
if (dim == 3)
|
||||
{
|
||||
if (Device::Allows(Backend::DEVICE_MASK))
|
||||
{
|
||||
const int ID = (dofs1D << 4) | quad1D;
|
||||
switch (ID)
|
||||
{
|
||||
case 0x23:
|
||||
return internal::SmemPACurlCurlApply3D<2,3>(
|
||||
dofs1D, quad1D,
|
||||
symmetric, ne,
|
||||
absO.B, absC.B, absO.Bt, absC.Bt,
|
||||
absC.G, absC.Gt, abs_pa_data, x, y, true);
|
||||
case 0x34:
|
||||
return internal::SmemPACurlCurlApply3D<3,4>(
|
||||
dofs1D, quad1D,
|
||||
symmetric, ne,
|
||||
absO.B, absC.B, absO.Bt, absC.Bt,
|
||||
absC.G, absC.Gt, abs_pa_data, x, y, true);
|
||||
case 0x45:
|
||||
return internal::SmemPACurlCurlApply3D<4,5>(
|
||||
dofs1D, quad1D,
|
||||
symmetric, ne,
|
||||
absO.B, absC.B, absO.Bt, absC.Bt,
|
||||
absC.G, absC.Gt, abs_pa_data, x, y, true);
|
||||
case 0x56:
|
||||
return internal::SmemPACurlCurlApply3D<5,6>(
|
||||
dofs1D, quad1D,
|
||||
symmetric, ne,
|
||||
absO.B, absC.B, absO.Bt, absC.Bt,
|
||||
absC.G, absC.Gt, abs_pa_data, x, y, true);
|
||||
default:
|
||||
return internal::SmemPACurlCurlApply3D<0,0>(
|
||||
dofs1D, quad1D, symmetric, ne,
|
||||
absO.B, absC.B, absO.Bt, absC.Bt,
|
||||
absC.G, absC.Gt, abs_pa_data, x, y, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
internal::PACurlCurlApply3D<0,0>(
|
||||
dofs1D, quad1D, symmetric, ne,
|
||||
absO.B, absC.B, absO.Bt, absC.Bt, absC.G, absC.Gt,
|
||||
abs_pa_data, x, y, true);
|
||||
}
|
||||
}
|
||||
else if (dim == 2)
|
||||
{
|
||||
internal::PACurlCurlApply2D(dofs1D, quad1D, ne, absO.B, absO.Bt,
|
||||
absC.G, absC.Gt, abs_pa_data, x, y, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ABORT("Unsupported dimension!");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
@@ -483,19 +483,6 @@ inline void SmemPADiffusionDiagonal3D(const int NE,
|
||||
});
|
||||
}
|
||||
|
||||
void PADiffusionApply(const int dim,
|
||||
const int D1D,
|
||||
const int Q1D,
|
||||
const int NE,
|
||||
const bool symm,
|
||||
const Array<real_t> &B,
|
||||
const Array<real_t> &G,
|
||||
const Array<real_t> &Bt,
|
||||
const Array<real_t> &Gt,
|
||||
const Vector &D,
|
||||
const Vector &X,
|
||||
Vector &Y);
|
||||
|
||||
#ifdef MFEM_USE_OCCA
|
||||
// OCCA PA Diffusion Apply 2D kernel
|
||||
void OccaPADiffusionApply2D(const int D1D,
|
||||
|
||||
@@ -164,6 +164,36 @@ void DiffusionIntegrator::AssemblePatchPA(const int patch,
|
||||
SetupPatchPA(patch, mesh); // For full quadrature, unitWeights = false
|
||||
}
|
||||
|
||||
void DiffusionIntegrator::AddAbsMultPA(const Vector &x, Vector &y) const
|
||||
{
|
||||
if (DeviceCanUseCeed())
|
||||
{
|
||||
MFEM_ABORT("Ceed AbsMult not implemented yet");
|
||||
}
|
||||
Vector abs_pa_data(pa_data);
|
||||
abs_pa_data.Abs();
|
||||
auto abs_maps = maps->Abs();
|
||||
|
||||
ApplyPAKernels::Run(dim, dofs1D, quad1D, ne, symmetric,
|
||||
abs_maps.B, abs_maps.G, abs_maps.Bt, abs_maps.Gt,
|
||||
abs_pa_data, x, y, dofs1D, quad1D);
|
||||
}
|
||||
|
||||
void DiffusionIntegrator::AddAbsMultTransposePA(const Vector &x,
|
||||
Vector &y) const
|
||||
{
|
||||
if (symmetric)
|
||||
{
|
||||
AddAbsMultPA(x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ABORT("DiffusionIntegrator::AddAbsMultTransposePA only implemented "
|
||||
"in the symmetric case.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This version uses full 1D quadrature rules, taking into account the
|
||||
// minimum interaction between basis functions and integration points.
|
||||
void DiffusionIntegrator::AddMultPatchPA(const int patch, const Vector &x,
|
||||
|
||||
@@ -212,7 +212,7 @@ void ElasticityAddMultPA_(const int nDofs, const FiniteElementSpace &fespace,
|
||||
const int iIndex = isComponent ? 0 : i;
|
||||
div += gradx(iIndex,i);
|
||||
}
|
||||
const real_t w = ipWeights[p] /det(invJ);
|
||||
const real_t w = ipWeights[p]/det(invJ);
|
||||
for (int m = 0; m < d; m++)
|
||||
{
|
||||
for (int q = qLower; q < qUpper; q++)
|
||||
@@ -226,8 +226,8 @@ void ElasticityAddMultPA_(const int nDofs, const FiniteElementSpace &fespace,
|
||||
{
|
||||
for (int a = 0; a < d; a++)
|
||||
{
|
||||
contraction += 2*((a == q)*invJ(m,j_block) + (j_block==q)*invJ(m,a))*(gradx(0,
|
||||
a));
|
||||
contraction += 2*((a == q)*invJ(m,j_block)
|
||||
+ (j_block==q)*invJ(m,a))*(gradx(0, a));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -236,7 +236,7 @@ void ElasticityAddMultPA_(const int nDofs, const FiniteElementSpace &fespace,
|
||||
{
|
||||
for (int b = 0; b < d; b++)
|
||||
{
|
||||
contraction += ((a == q)*invJ(m,b) + (b==q)*invJ(m,a))
|
||||
contraction += ((a == q)*invJ(m,b) + (b == q)*invJ(m,a))
|
||||
*(gradx(a,b) + gradx(b, a));
|
||||
}
|
||||
}
|
||||
@@ -244,7 +244,8 @@ void ElasticityAddMultPA_(const int nDofs, const FiniteElementSpace &fespace,
|
||||
// lambda*div(u)*div(v) + 2*mu*sym(grad(u))*sym(grad(v))
|
||||
// contraction = 4*sym(grad(u))sym(grad(v))
|
||||
const int qIndex = isComponent ? 0 : q;
|
||||
Q(p,m,qIndex,e) = w*(lamDev(p, e)*invJ(m,q)*div + 0.5*muDev(p, e)*contraction);
|
||||
Q(p,m,qIndex,e) = w*(lamDev(p, e)*invJ(m,q)*div
|
||||
+ 0.5*muDev(p, e)*contraction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,7 +662,8 @@ void PACurlCurlApply2D(const int D1D,
|
||||
const Array<real_t> &gct,
|
||||
const Vector &pa_data,
|
||||
const Vector &x,
|
||||
Vector &y)
|
||||
Vector &y,
|
||||
const bool useAbs)
|
||||
{
|
||||
|
||||
auto Bo = Reshape(bo.Read(), Q1D, D1D-1);
|
||||
@@ -717,7 +718,8 @@ void PACurlCurlApply2D(const int D1D,
|
||||
|
||||
for (int qy = 0; qy < Q1D; ++qy)
|
||||
{
|
||||
const real_t wy = (c == 0) ? -Gc(qy,dy) : Bo(qy,dy);
|
||||
const int sign = useAbs ? 1 : -1;
|
||||
const real_t wy = (c == 0) ? (sign*Gc(qy,dy)) : Bo(qy,dy);
|
||||
for (int qx = 0; qx < Q1D; ++qx)
|
||||
{
|
||||
curl[qy][qx] += gradX[qx] * wy;
|
||||
@@ -760,7 +762,8 @@ void PACurlCurlApply2D(const int D1D,
|
||||
}
|
||||
for (int dy = 0; dy < D1Dy; ++dy)
|
||||
{
|
||||
const real_t wy = (c == 0) ? -Gct(dy,qy) : Bot(dy,qy);
|
||||
const int sign = useAbs ? 1 : -1;
|
||||
const real_t wy = (c == 0) ? (sign*Gct(dy,qy)) : Bot(dy,qy);
|
||||
|
||||
for (int dx = 0; dx < D1Dx; ++dx)
|
||||
{
|
||||
|
||||
@@ -828,7 +828,7 @@ inline void SmemPACurlCurlAssembleDiagonal3D(const int d1d,
|
||||
}); // end of element loop
|
||||
}
|
||||
|
||||
// PA H(curl) curl-curl Apply 2D kernel
|
||||
// PA H(curl) curl-curl Apply/AbsApply 2D kernel
|
||||
void PACurlCurlApply2D(const int D1D,
|
||||
const int Q1D,
|
||||
const int NE,
|
||||
@@ -838,9 +838,10 @@ void PACurlCurlApply2D(const int D1D,
|
||||
const Array<real_t> &gct,
|
||||
const Vector &pa_data,
|
||||
const Vector &x,
|
||||
Vector &y);
|
||||
Vector &y,
|
||||
const bool useAbs = false);
|
||||
|
||||
// PA H(curl) curl-curl Apply 3D kernel
|
||||
// PA H(curl) curl-curl Apply/AbsApply 3D kernel
|
||||
template<int T_D1D = 0, int T_Q1D = 0>
|
||||
inline void PACurlCurlApply3D(const int d1d,
|
||||
const int q1d,
|
||||
@@ -854,7 +855,8 @@ inline void PACurlCurlApply3D(const int d1d,
|
||||
const Array<real_t> &gct,
|
||||
const Vector &pa_data,
|
||||
const Vector &x,
|
||||
Vector &y)
|
||||
Vector &y,
|
||||
const bool useAbs = false)
|
||||
{
|
||||
MFEM_VERIFY(T_D1D || d1d <= DeviceDofQuadLimits::Get().HCURL_MAX_D1D,
|
||||
"Error: d1d > HCURL_MAX_D1D");
|
||||
@@ -970,7 +972,16 @@ inline void PACurlCurlApply3D(const int d1d,
|
||||
{
|
||||
// \hat{\nabla}\times\hat{u} is [0, (u_0)_{x_2}, -(u_0)_{x_1}]
|
||||
curl[qz][qy][qx][1] += gradXY[qy][qx][1] * wDz; // (u_0)_{x_2}
|
||||
curl[qz][qy][qx][2] -= gradXY[qy][qx][0] * wz; // -(u_0)_{x_1}
|
||||
if (useAbs)
|
||||
{
|
||||
// +(u_0)_{x_1}
|
||||
curl[qz][qy][qx][2] += gradXY[qy][qx][0] * wz;
|
||||
}
|
||||
else
|
||||
{
|
||||
// -(u_0)_{x_1}
|
||||
curl[qz][qy][qx][2] -= gradXY[qy][qx][0] * wz;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1038,7 +1049,16 @@ inline void PACurlCurlApply3D(const int d1d,
|
||||
for (int qx = 0; qx < Q1D; ++qx)
|
||||
{
|
||||
// \hat{\nabla}\times\hat{u} is [-(u_1)_{x_2}, 0, (u_1)_{x_0}]
|
||||
curl[qz][qy][qx][0] -= gradXY[qy][qx][1] * wDz; // -(u_1)_{x_2}
|
||||
if (useAbs)
|
||||
{
|
||||
// +(u_1)_{x_2}
|
||||
curl[qz][qy][qx][0] += gradXY[qy][qx][1] * wDz;
|
||||
}
|
||||
else
|
||||
{
|
||||
// -(u_1)_{x_2}
|
||||
curl[qz][qy][qx][0] -= gradXY[qy][qx][1] * wDz;
|
||||
}
|
||||
curl[qz][qy][qx][2] += gradXY[qy][qx][0] * wz; // (u_1)_{x_0}
|
||||
}
|
||||
}
|
||||
@@ -1109,7 +1129,16 @@ inline void PACurlCurlApply3D(const int d1d,
|
||||
{
|
||||
// \hat{\nabla}\times\hat{u} is [(u_2)_{x_1}, -(u_2)_{x_0}, 0]
|
||||
curl[qz][qy][qx][0] += gradYZ[qz][qy][1] * wx; // (u_2)_{x_1}
|
||||
curl[qz][qy][qx][1] -= gradYZ[qz][qy][0] * wDx; // -(u_2)_{x_0}
|
||||
if (useAbs)
|
||||
{
|
||||
// +(u_2)_{x_0}
|
||||
curl[qz][qy][qx][1] += gradYZ[qz][qy][0] * wDx;
|
||||
}
|
||||
else
|
||||
{
|
||||
// -(u_2)_{x_0}
|
||||
curl[qz][qy][qx][1] -= gradYZ[qz][qy][0] * wDx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1209,9 +1238,21 @@ inline void PACurlCurlApply3D(const int d1d,
|
||||
for (int dx = 0; dx < D1Dx; ++dx)
|
||||
{
|
||||
// \hat{\nabla}\times\hat{u} is [0, (u_0)_{x_2}, -(u_0)_{x_1}]
|
||||
// (u_0)_{x_2} * (op * curl)_1 - (u_0)_{x_1} * (op * curl)_2
|
||||
Y(dx + ((dy + (dz * D1Dy)) * D1Dx) + osc,
|
||||
e) += (gradXY21[dy][dx] * wDz) - (gradXY12[dy][dx] * wz);
|
||||
const int idx = dx + ((dy + (dz * D1Dy)) * D1Dx) + osc;
|
||||
if (useAbs)
|
||||
{
|
||||
// (u_0)_{x_2} * (op * curl)_1 +
|
||||
// (u_0)_{x_1} * (op * curl)_2
|
||||
Y(idx, e) += (gradXY21[dy][dx] * wDz) +
|
||||
(gradXY12[dy][dx] * wz);
|
||||
}
|
||||
else
|
||||
{
|
||||
// (u_0)_{x_2} * (op * curl)_1 -
|
||||
// (u_0)_{x_1} * (op * curl)_2
|
||||
Y(idx, e) += (gradXY21[dy][dx] * wDz) -
|
||||
(gradXY12[dy][dx] * wz);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1278,10 +1319,22 @@ inline void PACurlCurlApply3D(const int d1d,
|
||||
{
|
||||
for (int dx = 0; dx < D1Dx; ++dx)
|
||||
{
|
||||
const int idx = dx + ((dy + (dz * D1Dy)) * D1Dx) + osc;
|
||||
// \hat{\nabla}\times\hat{u} is [-(u_1)_{x_2}, 0, (u_1)_{x_0}]
|
||||
// -(u_1)_{x_2} * (op * curl)_0 + (u_1)_{x_0} * (op * curl)_2
|
||||
Y(dx + ((dy + (dz * D1Dy)) * D1Dx) + osc,
|
||||
e) += (-gradXY20[dy][dx] * wDz) + (gradXY02[dy][dx] * wz);
|
||||
if (useAbs)
|
||||
{
|
||||
// +(u_1)_{x_2} * (op * curl)_0 +
|
||||
// (u_1)_{x_0} * (op * curl)_2
|
||||
Y(idx, e) += (gradXY20[dy][dx] * wDz) +
|
||||
(gradXY02[dy][dx] * wz);
|
||||
}
|
||||
else
|
||||
{
|
||||
// -(u_1)_{x_2} * (op * curl)_0 +
|
||||
// (u_1)_{x_0} * (op * curl)_2
|
||||
Y(idx, e) += (-gradXY20[dy][dx] * wDz) +
|
||||
(gradXY02[dy][dx] * wz);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1351,10 +1404,22 @@ inline void PACurlCurlApply3D(const int d1d,
|
||||
{
|
||||
for (int dz = 0; dz < D1Dz; ++dz)
|
||||
{
|
||||
const int idx = dx + ((dy + (dz * D1Dy)) * D1Dx) + osc;
|
||||
// \hat{\nabla}\times\hat{u} is [(u_2)_{x_1}, -(u_2)_{x_0}, 0]
|
||||
// (u_2)_{x_1} * (op * curl)_0 - (u_2)_{x_0} * (op * curl)_1
|
||||
Y(dx + ((dy + (dz * D1Dy)) * D1Dx) + osc,
|
||||
e) += (gradYZ10[dz][dy] * wx) - (gradYZ01[dz][dy] * wDx);
|
||||
if (useAbs)
|
||||
{
|
||||
// (u_2)_{x_1} * (op * curl)_0 +
|
||||
// (u_2)_{x_0} * (op * curl)_1
|
||||
Y(idx, e) += (gradYZ10[dz][dy] * wx) +
|
||||
(gradYZ01[dz][dy] * wDx);
|
||||
}
|
||||
else
|
||||
{
|
||||
// (u_2)_{x_1} * (op * curl)_0 -
|
||||
// (u_2)_{x_0} * (op * curl)_1
|
||||
Y(idx, e) += (gradYZ10[dz][dy] * wx) -
|
||||
(gradYZ01[dz][dy] * wDx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1363,7 +1428,7 @@ inline void PACurlCurlApply3D(const int d1d,
|
||||
}); // end of element loop
|
||||
}
|
||||
|
||||
// Shared memory PA H(curl) curl-curl Apply 3D kernel
|
||||
// Shared memory PA H(curl) curl-curl Apply/AbsApply 3D kernel
|
||||
template<int T_D1D = 0, int T_Q1D = 0>
|
||||
inline void SmemPACurlCurlApply3D(const int d1d,
|
||||
const int q1d,
|
||||
@@ -1377,7 +1442,8 @@ inline void SmemPACurlCurlApply3D(const int d1d,
|
||||
const Array<real_t> &gct,
|
||||
const Vector &pa_data,
|
||||
const Vector &x,
|
||||
Vector &y)
|
||||
Vector &y,
|
||||
const bool useAbs = false)
|
||||
{
|
||||
MFEM_VERIFY(T_D1D || d1d <= DeviceDofQuadLimits::Get().HCURL_MAX_D1D,
|
||||
"Error: d1d > HCURL_MAX_D1D");
|
||||
@@ -1531,7 +1597,8 @@ inline void SmemPACurlCurlApply3D(const int d1d,
|
||||
}
|
||||
|
||||
curl[qy][qx][1] += v; // (u_0)_{x_2}
|
||||
curl[qy][qx][2] -= u; // -(u_0)_{x_1}
|
||||
if (useAbs) { curl[qy][qx][2] += u; } // +(u_0)_{x_1}
|
||||
else { curl[qy][qx][2] -= u; } // -(u_0)_{x_1}
|
||||
}
|
||||
else if (c == 1) // y component
|
||||
{
|
||||
@@ -1558,7 +1625,8 @@ inline void SmemPACurlCurlApply3D(const int d1d,
|
||||
}
|
||||
}
|
||||
|
||||
curl[qy][qx][0] -= v; // -(u_1)_{x_2}
|
||||
if (useAbs) { curl[qy][qx][0] += v; } // +(u_1)_{x_2}
|
||||
else { curl[qy][qx][0] -= v; } // -(u_1)_{x_2}
|
||||
curl[qy][qx][2] += u; // (u_1)_{x_0}
|
||||
}
|
||||
else // z component
|
||||
@@ -1587,7 +1655,8 @@ inline void SmemPACurlCurlApply3D(const int d1d,
|
||||
}
|
||||
|
||||
curl[qy][qx][0] += v; // (u_2)_{x_1}
|
||||
curl[qy][qx][1] -= u; // -(u_2)_{x_0}
|
||||
if (useAbs) { curl[qy][qx][1] += u; }// +(u_2)_{x_0}
|
||||
else { curl[qy][qx][1] -= u; } // -(u_2)_{x_0}
|
||||
}
|
||||
} // qx
|
||||
} // qy
|
||||
@@ -1642,18 +1711,54 @@ inline void SmemPACurlCurlApply3D(const int d1d,
|
||||
if (dx < D1D-1)
|
||||
{
|
||||
// \hat{\nabla}\times\hat{u} is [0, (u_0)_{x_2}, -(u_0)_{x_1}]
|
||||
// (u_0)_{x_2} * (op * curl)_1 - (u_0)_{x_1} * (op * curl)_2
|
||||
const real_t wx = sBo[dx][qx];
|
||||
dxyz1 += (wx * c2 * wcy * wcDz) - (wx * c3 * wcDy * wcz);
|
||||
if (useAbs)
|
||||
{
|
||||
// (u_0)_{x_2} * (op * curl)_1 +
|
||||
// (u_0)_{x_1} * (op * curl)_2
|
||||
dxyz1 += (wx * c2 * wcy * wcDz) +
|
||||
(wx * c3 * wcDy * wcz);
|
||||
}
|
||||
else
|
||||
{
|
||||
// (u_0)_{x_2} * (op * curl)_1 -
|
||||
// (u_0)_{x_1} * (op * curl)_2
|
||||
dxyz1 += (wx * c2 * wcy * wcDz) -
|
||||
(wx * c3 * wcDy * wcz);
|
||||
}
|
||||
}
|
||||
|
||||
// \hat{\nabla}\times\hat{u} is [-(u_1)_{x_2}, 0, (u_1)_{x_0}]
|
||||
// -(u_1)_{x_2} * (op * curl)_0 + (u_1)_{x_0} * (op * curl)_2
|
||||
dxyz2 += (-wy * c1 * wcx * wcDz) + (wy * c3 * wDx * wcz);
|
||||
if (useAbs)
|
||||
{
|
||||
// +(u_1)_{x_2} * (op * curl)_0 +
|
||||
// (u_1)_{x_0} * (op * curl)_2
|
||||
dxyz2 += (wy * c1 * wcx * wcDz) +
|
||||
(wy * c3 * wDx * wcz);
|
||||
}
|
||||
else
|
||||
{
|
||||
// -(u_1)_{x_2} * (op * curl)_0 +
|
||||
// (u_1)_{x_0} * (op * curl)_2
|
||||
dxyz2 += (-wy * c1 * wcx * wcDz) +
|
||||
(wy * c3 * wDx * wcz);
|
||||
}
|
||||
|
||||
// \hat{\nabla}\times\hat{u} is [(u_2)_{x_1}, -(u_2)_{x_0}, 0]
|
||||
// (u_2)_{x_1} * (op * curl)_0 - (u_2)_{x_0} * (op * curl)_1
|
||||
dxyz3 += (wcDy * wz * c1 * wcx) - (wcy * wz * c2 * wDx);
|
||||
if (useAbs)
|
||||
{
|
||||
// (u_2)_{x_1} * (op * curl)_0 +
|
||||
// (u_2)_{x_0} * (op * curl)_1
|
||||
dxyz3 += (wcDy * wz * c1 * wcx) +
|
||||
(wcy * wz * c2 * wDx);
|
||||
}
|
||||
else
|
||||
{
|
||||
// (u_2)_{x_1} * (op * curl)_0 -
|
||||
// (u_2)_{x_0} * (op * curl)_1
|
||||
dxyz3 += (wcDy * wz * c1 * wcx) -
|
||||
(wcy * wz * c2 * wDx);
|
||||
}
|
||||
} // qx
|
||||
} // qy
|
||||
} // dx
|
||||
|
||||
@@ -199,10 +199,37 @@ void MassIntegrator::AddMultPA(const Vector &x, Vector &y) const
|
||||
}
|
||||
}
|
||||
|
||||
void MassIntegrator::AddAbsMultPA(const Vector &x, Vector &y) const
|
||||
{
|
||||
if (DeviceCanUseCeed())
|
||||
{
|
||||
MFEM_ABORT("AddAbsMultPA not implemented with CEED!");
|
||||
ceedOp->AddMult(x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector abs_pa_data(pa_data);
|
||||
abs_pa_data.Abs();
|
||||
Array<real_t> absB(maps->B);
|
||||
Array<real_t> absBt(maps->Bt);
|
||||
absB.Abs();
|
||||
absBt.Abs();
|
||||
|
||||
ApplyPAKernels::Run(dim, dofs1D, quad1D, ne, absB, absBt, abs_pa_data,
|
||||
x, y, dofs1D, quad1D);
|
||||
}
|
||||
}
|
||||
|
||||
void MassIntegrator::AddMultTransposePA(const Vector &x, Vector &y) const
|
||||
{
|
||||
// Mass integrator is symmetric
|
||||
AddMultPA(x, y);
|
||||
}
|
||||
|
||||
void MassIntegrator::AddAbsMultTransposePA(const Vector &x, Vector &y) const
|
||||
{
|
||||
// Mass integrator is symmetric
|
||||
AddAbsMultPA(x, y);
|
||||
}
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
@@ -313,6 +313,129 @@ void VectorFEMassIntegrator::AddMultPA(const Vector &x, Vector &y) const
|
||||
}
|
||||
}
|
||||
|
||||
void VectorFEMassIntegrator::AddAbsMultPA(const Vector &x, Vector &y) const
|
||||
{
|
||||
const bool trial_curl = (trial_fetype == mfem::FiniteElement::CURL);
|
||||
const bool trial_div = (trial_fetype == mfem::FiniteElement::DIV);
|
||||
const bool test_curl = (test_fetype == mfem::FiniteElement::CURL);
|
||||
const bool test_div = (test_fetype == mfem::FiniteElement::DIV);
|
||||
|
||||
Vector abs_pa_data(pa_data);
|
||||
abs_pa_data.Abs();
|
||||
|
||||
Array<real_t> absBo(mapsO->B);
|
||||
Array<real_t> absBc(mapsC->B);
|
||||
Array<real_t> absBto(mapsO->Bt);
|
||||
Array<real_t> absBtc(mapsC->Bt);
|
||||
Array<real_t> absBto_t(mapsOtest->Bt);
|
||||
Array<real_t> absBtc_t(mapsCtest->Bt);
|
||||
|
||||
absBo.Abs();
|
||||
absBc.Abs();
|
||||
absBto.Abs();
|
||||
absBtc.Abs();
|
||||
absBto_t.Abs();
|
||||
absBtc_t.Abs();
|
||||
|
||||
if (dim == 3)
|
||||
{
|
||||
if (trial_curl && test_curl)
|
||||
{
|
||||
if (Device::Allows(Backend::DEVICE_MASK))
|
||||
{
|
||||
const int ID = (dofs1D << 4) | quad1D;
|
||||
switch (ID)
|
||||
{
|
||||
case 0x23:
|
||||
return internal::SmemPAHcurlMassApply3D<2,3>(
|
||||
dofs1D, quad1D, ne, symmetric,
|
||||
absBo, absBc, absBto, absBtc,
|
||||
abs_pa_data, x, y);
|
||||
case 0x34:
|
||||
return internal::SmemPAHcurlMassApply3D<3,4>(
|
||||
dofs1D, quad1D, ne, symmetric,
|
||||
absBo, absBc, absBto, absBtc,
|
||||
abs_pa_data, x, y);
|
||||
case 0x45:
|
||||
return internal::SmemPAHcurlMassApply3D<4,5>(
|
||||
dofs1D, quad1D, ne, symmetric,
|
||||
absBo, absBc, absBto, absBtc,
|
||||
abs_pa_data, x, y);
|
||||
case 0x56:
|
||||
return internal::SmemPAHcurlMassApply3D<5,6>(
|
||||
dofs1D, quad1D, ne, symmetric,
|
||||
absBo, absBc, absBto, absBtc,
|
||||
abs_pa_data, x, y);
|
||||
default:
|
||||
return internal::SmemPAHcurlMassApply3D(
|
||||
dofs1D, quad1D, ne, symmetric,
|
||||
absBo, absBc, absBto, absBtc,
|
||||
abs_pa_data, x, y);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
internal::PAHcurlMassApply3D(dofs1D, quad1D, ne, symmetric,
|
||||
absBo, absBc, absBto, absBtc,
|
||||
abs_pa_data, x, y);
|
||||
}
|
||||
}
|
||||
else if (trial_div && test_div)
|
||||
{
|
||||
internal::PAHdivMassApply(3, dofs1D, quad1D, ne, symmetric,
|
||||
absBo, absBc, absBto, absBtc,
|
||||
abs_pa_data, x, y);
|
||||
}
|
||||
else if (trial_curl && test_div)
|
||||
{
|
||||
const bool scalarCoeff = !(DQ || MQ);
|
||||
internal::PAHcurlHdivMassApply3D(dofs1D, dofs1Dtest, quad1D, ne,
|
||||
scalarCoeff, true, false,
|
||||
absBo, absBc, absBto_t, absBtc_t,
|
||||
abs_pa_data, x, y);
|
||||
}
|
||||
else if (trial_div && test_curl)
|
||||
{
|
||||
const bool scalarCoeff = !(DQ || MQ);
|
||||
internal::PAHcurlHdivMassApply3D(dofs1D, dofs1Dtest, quad1D, ne,
|
||||
scalarCoeff, false, false,
|
||||
absBo, absBc, absBto_t, absBtc_t,
|
||||
abs_pa_data, x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ABORT("Unknown kernel.");
|
||||
}
|
||||
}
|
||||
else // 2D
|
||||
{
|
||||
if (trial_curl && test_curl)
|
||||
{
|
||||
internal::PAHcurlMassApply2D(dofs1D, quad1D, ne, symmetric,
|
||||
absBo, absBc, absBto, absBtc,
|
||||
abs_pa_data, x, y);
|
||||
}
|
||||
else if (trial_div && test_div)
|
||||
{
|
||||
internal::PAHdivMassApply(2, dofs1D, quad1D, ne, symmetric,
|
||||
absBo, absBc, absBto, absBtc,
|
||||
abs_pa_data, x, y);
|
||||
}
|
||||
else if ((trial_curl && test_div) || (trial_div && test_curl))
|
||||
{
|
||||
const bool scalarCoeff = !(DQ || MQ);
|
||||
internal::PAHcurlHdivMassApply2D(dofs1D, dofs1Dtest, quad1D, ne,
|
||||
scalarCoeff, trial_curl, false,
|
||||
absBo, absBc, absBto_t, absBtc_t,
|
||||
abs_pa_data, x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
MFEM_ABORT("Unknown kernel.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VectorFEMassIntegrator::AddMultTransposePA(const Vector &x,
|
||||
Vector &y) const
|
||||
{
|
||||
|
||||
@@ -577,7 +577,13 @@ public:
|
||||
|
||||
void Mult(const Vector &x, Vector &y) const override;
|
||||
|
||||
void AbsMult(const Vector &x, Vector &y) const override
|
||||
{ Mult(x,y); }
|
||||
|
||||
void MultTranspose(const Vector &x, Vector &y) const override;
|
||||
|
||||
void AbsMultTranspose(const Vector &x, Vector &y) const override
|
||||
{ MultTranspose(x,y); }
|
||||
};
|
||||
|
||||
/// Auxiliary device class used by ParFiniteElementSpace.
|
||||
@@ -628,7 +634,13 @@ public:
|
||||
|
||||
void Mult(const Vector &x, Vector &y) const override;
|
||||
|
||||
void AbsMult(const Vector &x, Vector &y) const override
|
||||
{ Mult(x,y); }
|
||||
|
||||
void MultTranspose(const Vector &x, Vector &y) const override;
|
||||
|
||||
void AbsMultTranspose(const Vector &x, Vector &y) const override
|
||||
{ MultTranspose(x,y); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1406,6 +1406,18 @@ real_t L2ZZErrorEstimator(BilinearFormIntegrator &flux_integrator,
|
||||
return pow(glob_error, 1.0/norm_p);
|
||||
}
|
||||
|
||||
PLBound ParGridFunction::GetBounds(Vector &lower, Vector &upper,
|
||||
const int ref_factor, const int vdim)
|
||||
{
|
||||
PLBound plb = GridFunction::GetBounds(lower, upper, ref_factor, vdim);
|
||||
int siz = vdim > 0 ? 1 : fes->GetVDim();
|
||||
MPI_Allreduce(MPI_IN_PLACE, lower.HostReadWrite(), siz,
|
||||
MFEM_MPI_REAL_T, MPI_MIN, pfes->GetComm());
|
||||
MPI_Allreduce(MPI_IN_PLACE, upper.HostReadWrite(), siz,
|
||||
MFEM_MPI_REAL_T, MPI_MAX, pfes->GetComm());
|
||||
return plb;
|
||||
}
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
#endif // MFEM_USE_MPI
|
||||
|
||||
@@ -581,6 +581,14 @@ public:
|
||||
GridFunction &flux,
|
||||
bool wcoef = true, int subdomain = -1) override;
|
||||
|
||||
/// Computes the PLBound for the gridfunction with number of control
|
||||
/// points based on @a ref_factor, and returns the bounds for each
|
||||
/// vdim across all elements in @b lower and @b upper. We also return the
|
||||
/// PLBound object used to compute the bounds. Note: if vdim < 1, we compute
|
||||
/// the bounds for each vector dimension.
|
||||
PLBound GetBounds(Vector &lower, Vector &upper,
|
||||
const int ref_factor=1, const int vdim=-1) override;
|
||||
|
||||
/** Save the local portion of the ParGridFunction. This differs from the
|
||||
serial GridFunction::Save in that it takes into account the signs of
|
||||
the local dofs. */
|
||||
|
||||
@@ -566,8 +566,8 @@ void QuadratureInterpolator::Mult(const Vector &e_vec,
|
||||
}
|
||||
else // use_tensor_eval == false
|
||||
{
|
||||
EvalKernels::Run(dim, vdim, maps.ndof, maps.nqpt, ne,vdim,q_layout,
|
||||
geom, maps,e_vec, q_val,q_der,q_det,eval_flags);
|
||||
EvalKernels::Run(dim, vdim, maps.ndof, maps.nqpt, ne,vdim, q_layout,
|
||||
geom, maps, e_vec, q_val, q_der, q_det, eval_flags);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-5
@@ -128,7 +128,7 @@ void ElementRestriction::Mult(const Vector& x, Vector& y) const
|
||||
});
|
||||
}
|
||||
|
||||
void ElementRestriction::MultUnsigned(const Vector& x, Vector& y) const
|
||||
void ElementRestriction::AbsMult(const Vector& x, Vector& y) const
|
||||
{
|
||||
// Assumes all elements have the same number of dofs
|
||||
const int nd = dof;
|
||||
@@ -193,7 +193,7 @@ void ElementRestriction::AddMultTranspose(const Vector& x, Vector& y,
|
||||
TAddMultTranspose<ADD>(x, y);
|
||||
}
|
||||
|
||||
void ElementRestriction::MultTransposeUnsigned(const Vector& x, Vector& y) const
|
||||
void ElementRestriction::AbsMultTranspose(const Vector& x, Vector& y) const
|
||||
{
|
||||
// Assumes all elements have the same number of dofs
|
||||
const int nd = dof;
|
||||
@@ -653,7 +653,8 @@ ConformingFaceRestriction::ConformingFaceRestriction(
|
||||
: ConformingFaceRestriction(fes, f_ordering, type, true)
|
||||
{ }
|
||||
|
||||
void ConformingFaceRestriction::Mult(const Vector& x, Vector& y) const
|
||||
void ConformingFaceRestriction::MultInternal(const Vector& x, Vector& y,
|
||||
const bool useAbs) const
|
||||
{
|
||||
if (nf==0) { return; }
|
||||
// Assumes all elements have the same number of dofs
|
||||
@@ -666,7 +667,7 @@ void ConformingFaceRestriction::Mult(const Vector& x, Vector& y) const
|
||||
mfem::forall(nfdofs, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
const int s_idx = d_indices[i];
|
||||
const int sgn = (s_idx >= 0) ? 1 : -1;
|
||||
const int sgn = (useAbs || s_idx >= 0) ? 1 : -1;
|
||||
const int idx = (s_idx >= 0) ? s_idx : -1 - s_idx;
|
||||
const int dof = i % nface_dofs;
|
||||
const int face = i / nface_dofs;
|
||||
@@ -724,7 +725,7 @@ void ConformingFaceRestriction::AddMultTranspose(
|
||||
true, a);
|
||||
}
|
||||
|
||||
void ConformingFaceRestriction::AddMultTransposeUnsigned(
|
||||
void ConformingFaceRestriction::AddAbsMultTranspose(
|
||||
const Vector& x, Vector& y, const real_t a) const
|
||||
{
|
||||
ConformingFaceRestriction_AddMultTranspose(
|
||||
|
||||
+55
-7
@@ -59,9 +59,18 @@ public:
|
||||
const real_t a = 1.0) const override;
|
||||
|
||||
/// Compute Mult without applying signs based on DOF orientations.
|
||||
void MultUnsigned(const Vector &x, Vector &y) const;
|
||||
void AbsMult(const Vector &x, Vector &y) const override;
|
||||
|
||||
/// Compute MultTranspose without applying signs based on DOF orientations.
|
||||
void MultTransposeUnsigned(const Vector &x, Vector &y) const;
|
||||
void AbsMultTranspose(const Vector &x, Vector &y) const override;
|
||||
|
||||
/// @deprecated Use AbsMult() instead.
|
||||
MFEM_DEPRECATED void MultUnsigned(const Vector &x, Vector &y) const
|
||||
{ AbsMult(x, y); }
|
||||
|
||||
/// @deprecated Use AbsMultTranspose() instead.
|
||||
MFEM_DEPRECATED void MultTransposeUnsigned(const Vector &x, Vector &y) const
|
||||
{ AbsMultTranspose(x, y); }
|
||||
|
||||
/// Compute MultTranspose by setting (rather than adding) element
|
||||
/// contributions; this is a left inverse of the Mult() operation
|
||||
@@ -184,12 +193,19 @@ public:
|
||||
|
||||
/** @brief Add the face degrees of freedom @a x to the element degrees of
|
||||
freedom @a y ignoring the signs from DOF orientation. */
|
||||
virtual void AddMultTransposeUnsigned(const Vector &x, Vector &y,
|
||||
const real_t a = 1.0) const
|
||||
virtual void AddAbsMultTranspose(const Vector &x, Vector &y,
|
||||
const real_t a = 1.0) const
|
||||
{
|
||||
AddMultTranspose(x, y, a);
|
||||
}
|
||||
|
||||
/// @deprecated Use AddAbsMultTranspose() instead.
|
||||
MFEM_DEPRECATED void AddMultTransposeUnsigned(const Vector &x, Vector &y,
|
||||
const real_t a = 1.0) const
|
||||
{
|
||||
AddAbsMultTranspose(x, y, a);
|
||||
}
|
||||
|
||||
/** @brief Add the face degrees of freedom @a x to the element degrees of
|
||||
freedom @a y. Perform the same computation as AddMultTranspose, but
|
||||
@a x is invalid after calling this method.
|
||||
@@ -219,6 +235,12 @@ public:
|
||||
AddMultTranspose(x, y);
|
||||
}
|
||||
|
||||
void AbsMultTranspose(const Vector &x, Vector &y) const override
|
||||
{
|
||||
y = 0.0;
|
||||
AddAbsMultTranspose(x, y);
|
||||
}
|
||||
|
||||
/** @brief For each face, sets @a y to the partial derivative of @a x with
|
||||
respect to the reference coordinate whose direction is
|
||||
perpendicular to the face on the reference element.
|
||||
@@ -319,7 +341,16 @@ public:
|
||||
requested by @a type in the constructor.
|
||||
The face_dofs are ordered according to the given
|
||||
ElementDofOrdering. */
|
||||
void Mult(const Vector &x, Vector &y) const override;
|
||||
void Mult(const Vector &x, Vector &y) const override
|
||||
{ MultInternal(x, y); }
|
||||
|
||||
/// Compute Mult without applying signs based on DOF orientations.
|
||||
void AbsMult(const Vector &x, Vector &y) const override
|
||||
{ MultInternal(x, y, true); }
|
||||
|
||||
/// @deprecated Use AbsMult() instead.
|
||||
MFEM_DEPRECATED void MultUnsigned(const Vector &x, Vector &y) const
|
||||
{ AbsMult(x, y); }
|
||||
|
||||
using FaceRestriction::AddMultTransposeInPlace;
|
||||
|
||||
@@ -341,8 +372,20 @@ public:
|
||||
L-Vector @b not taking into account signs from DOF orientations.
|
||||
|
||||
@sa AddMultTranspose(). */
|
||||
void AddMultTransposeUnsigned(const Vector &x, Vector &y,
|
||||
const real_t a = 1.0) const override;
|
||||
void AddAbsMultTranspose(const Vector &x, Vector &y,
|
||||
const real_t a = 1.0) const override;
|
||||
|
||||
/// @deprecated Use AddAbsMultTranspose() instead.
|
||||
MFEM_DEPRECATED void AddMultTransposeUnsigned(const Vector &x, Vector &y) const
|
||||
{
|
||||
AddAbsMultTranspose(x, y);
|
||||
}
|
||||
|
||||
void AbsMultTranspose(const Vector &x, Vector &y) const override
|
||||
{
|
||||
y = 0.0;
|
||||
AddAbsMultTranspose(x, y);
|
||||
}
|
||||
|
||||
private:
|
||||
/** @brief Compute the scatter indices: L-vector to E-vector, and the offsets
|
||||
@@ -395,6 +438,11 @@ protected:
|
||||
void SetFaceDofsGatherIndices(const Mesh::FaceInformation &face,
|
||||
const int face_index,
|
||||
const ElementDofOrdering f_ordering);
|
||||
|
||||
public:
|
||||
// This method needs to be public due to 'nvcc' restriction.
|
||||
void MultInternal(const Vector &x, Vector &y,
|
||||
const bool useAbs = false) const;
|
||||
};
|
||||
|
||||
/// @brief Alias for ConformingFaceRestriction, for backwards compatibility and
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "array.hpp"
|
||||
#include "../general/forall.hpp"
|
||||
#include <fstream>
|
||||
#include <type_traits>
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
@@ -110,6 +111,19 @@ void Array<T>::PartialSum()
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Array<T>::Abs()
|
||||
{
|
||||
static_assert(std::is_arithmetic<T>::value, "Use with arithmetic types!");
|
||||
const bool useDevice = UseDevice();
|
||||
const int N = size;
|
||||
auto y = ReadWrite(useDevice);
|
||||
mfem::forall_switch(useDevice, N, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
y[i] = std::abs(y[i]);
|
||||
});
|
||||
}
|
||||
|
||||
// Sum
|
||||
template <class T>
|
||||
T Array<T>::Sum() const
|
||||
|
||||
@@ -305,6 +305,9 @@ public:
|
||||
/// Fill the entries of the array with the cumulative sum of the entries.
|
||||
void PartialSum();
|
||||
|
||||
/// Replace each entry of the array with its absolute value.
|
||||
void Abs();
|
||||
|
||||
/// Return the sum of all the array entries using the '+'' operator for class 'T'.
|
||||
T Sum() const;
|
||||
|
||||
|
||||
@@ -23,9 +23,6 @@
|
||||
#include <_hypre_utilities.h>
|
||||
#endif
|
||||
|
||||
#include "array.hpp"
|
||||
#include "reducers.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
@@ -853,159 +850,6 @@ inline MemoryClass GetHypreForallMemoryClass()
|
||||
|
||||
#endif // MFEM_USE_MPI
|
||||
|
||||
namespace internal
|
||||
{
|
||||
/**
|
||||
@brief Device portion of a reduction over a 1D sequence [0, N)
|
||||
@tparam B Reduction body. Must be callable with the signature void(int i, value_type&
|
||||
v), where i is the index to evaluate and v is the value to update.
|
||||
@tparam R Reducer capable of combining values of type value_type. See reducers.hpp for
|
||||
pre-defined reducers.
|
||||
*/
|
||||
template<class B, class R> struct reduction_kernel
|
||||
{
|
||||
/// value type body and reducer operate on.
|
||||
using value_type = typename R::value_type;
|
||||
/// workspace for the intermediate reduction results
|
||||
mutable value_type *work;
|
||||
B body;
|
||||
R reducer;
|
||||
/// Length of sequence to reduce over.
|
||||
int N;
|
||||
/// How many items is each thread responsible for during the serial phase
|
||||
int items_per_thread;
|
||||
|
||||
constexpr static MFEM_HOST_DEVICE int max_blocksize() { return 256; }
|
||||
|
||||
/// helper for computing the reduction block size
|
||||
static int block_log2(unsigned N)
|
||||
{
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return N ? (sizeof(unsigned) * 8 - __builtin_clz(N)) : 0;
|
||||
#elif defined(_MSC_VER)
|
||||
return sizeof(unsigned) * 8 - __lzclz(N);
|
||||
#else
|
||||
int res = 0;
|
||||
while (N)
|
||||
{
|
||||
N >>= 1;
|
||||
++res;
|
||||
}
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
MFEM_HOST_DEVICE void operator()(int work_idx) const
|
||||
{
|
||||
MFEM_SHARED value_type buffer[max_blocksize()];
|
||||
reducer.SetInitialValue(buffer[MFEM_THREAD_ID(x)]);
|
||||
// serial part
|
||||
for (int idx = 0; idx < items_per_thread; ++idx)
|
||||
{
|
||||
int i = MFEM_THREAD_ID(x) +
|
||||
(idx + work_idx * items_per_thread) * MFEM_THREAD_SIZE(x);
|
||||
if (i < N)
|
||||
{
|
||||
body(i, buffer[MFEM_THREAD_ID(x)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// binary tree reduction
|
||||
for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1)
|
||||
{
|
||||
MFEM_SYNC_THREAD;
|
||||
if (MFEM_THREAD_ID(x) < i)
|
||||
{
|
||||
reducer.Join(buffer[MFEM_THREAD_ID(x)], buffer[MFEM_THREAD_ID(x) + i]);
|
||||
}
|
||||
}
|
||||
if (MFEM_THREAD_ID(x) == 0)
|
||||
{
|
||||
work[work_idx] = buffer[0];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Performs a 1D reduction on the range [0,N).
|
||||
@a res initial value and where the result will be written.
|
||||
@a body reduction function body.
|
||||
@a reducer helper for joining two reduced values.
|
||||
@a use_dev true to perform the reduction on the device, if possible.
|
||||
@a workspace temporary workspace used for device reductions. May be resized to
|
||||
a larger capacity as needed. Preferably should have MemoryType::MANAGED or
|
||||
MemoryType::HOST_PINNED. TODO: replace with internal temporary workspace
|
||||
vectors once that's added to the memory manager.
|
||||
@tparam T value_type to operate on
|
||||
*/
|
||||
template <class T, class B, class R>
|
||||
void reduce(int N, T &res, B &&body, const R &reducer, bool use_dev,
|
||||
Array<T> &workspace)
|
||||
{
|
||||
if (N == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(MFEM_USE_HIP) || defined(MFEM_USE_CUDA)
|
||||
if (use_dev &&
|
||||
mfem::Device::Allows(Backend::CUDA | Backend::HIP | Backend::RAJA_CUDA |
|
||||
Backend::RAJA_HIP))
|
||||
{
|
||||
using red_type = internal::reduction_kernel<typename std::decay<B>::type,
|
||||
typename std::decay<R>::type>;
|
||||
// max block size is 256, but can be smaller
|
||||
int block_size = std::min<int>(red_type::max_blocksize(),
|
||||
1ll << red_type::block_log2(N));
|
||||
|
||||
int num_mp = Device::NumMultiprocessors(Device::GetId());
|
||||
#if defined(MFEM_USE_CUDA)
|
||||
// good value of mp_sat found experimentally on Lassen
|
||||
constexpr int mp_sat = 8;
|
||||
#elif defined(MFEM_USE_HIP)
|
||||
// good value of mp_sat found experimentally on Tuolumne
|
||||
constexpr int mp_sat = 4;
|
||||
#else
|
||||
num_mp = 1;
|
||||
constexpr int mp_sat = 1;
|
||||
#endif
|
||||
// determine how many items each thread should sum during the serial
|
||||
// portion
|
||||
int nblocks = std::min(mp_sat * num_mp, (N + block_size - 1) / block_size);
|
||||
int items_per_thread =
|
||||
(N + block_size * nblocks - 1) / (block_size * nblocks);
|
||||
|
||||
red_type red{nullptr, std::forward<B>(body), reducer, N, items_per_thread};
|
||||
// allocate res to fit block_size entries
|
||||
auto mt = workspace.GetMemory().GetMemoryType();
|
||||
if (mt != MemoryType::HOST_PINNED && mt != MemoryType::MANAGED)
|
||||
{
|
||||
mt = MemoryType::HOST_PINNED;
|
||||
}
|
||||
workspace.SetSize(nblocks, mt);
|
||||
auto work = workspace.HostWrite();
|
||||
red.work = work;
|
||||
forall_2D(nblocks, block_size, 1, std::move(red));
|
||||
// wait for results
|
||||
MFEM_DEVICE_SYNC;
|
||||
for (int i = 0; i < nblocks; ++i)
|
||||
{
|
||||
reducer.Join(res, work[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
body(i, res);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
#endif // MFEM_FORALL_HPP
|
||||
|
||||
@@ -641,7 +641,7 @@ public:
|
||||
UmpireMemorySpace(name, "DEVICE") {}
|
||||
void Alloc(Memory &base) override
|
||||
{ base.d_ptr = allocator.allocate(base.bytes); }
|
||||
void Dealloc(Memory &base) override { rm.deallocate(base.d_ptr); }
|
||||
void Dealloc(Memory &base) override { allocator.deallocate(base.d_ptr); }
|
||||
void *HtoD(void *dst, const void *src, size_t bytes) override
|
||||
{
|
||||
#ifdef MFEM_USE_CUDA
|
||||
|
||||
+156
-3
@@ -12,11 +12,10 @@
|
||||
#ifndef MFEM_REDUCERS_HPP
|
||||
#define MFEM_REDUCERS_HPP
|
||||
|
||||
#include "array.hpp"
|
||||
#include "forall.hpp"
|
||||
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
@@ -439,6 +438,160 @@ template <class I> struct ArgMinMaxReducer<double, I>
|
||||
}
|
||||
};
|
||||
|
||||
namespace internal
|
||||
{
|
||||
|
||||
/**
|
||||
@brief Device portion of a reduction over a 1D sequence [0, N)
|
||||
@tparam B Reduction body. Must be callable with the signature void(int i, value_type&
|
||||
v), where i is the index to evaluate and v is the value to update.
|
||||
@tparam R Reducer capable of combining values of type value_type. See reducers.hpp for
|
||||
pre-defined reducers.
|
||||
*/
|
||||
template<class B, class R> struct reduction_kernel
|
||||
{
|
||||
/// value type body and reducer operate on.
|
||||
using value_type = typename R::value_type;
|
||||
/// workspace for the intermediate reduction results
|
||||
mutable value_type *work;
|
||||
B body;
|
||||
R reducer;
|
||||
/// Length of sequence to reduce over.
|
||||
int N;
|
||||
/// How many items is each thread responsible for during the serial phase
|
||||
int items_per_thread;
|
||||
|
||||
constexpr static MFEM_HOST_DEVICE int max_blocksize() { return 256; }
|
||||
|
||||
/// helper for computing the reduction block size
|
||||
static int block_log2(unsigned N)
|
||||
{
|
||||
#if defined(__GNUC__) or defined(__clang__)
|
||||
return N ? (sizeof(unsigned) * 8 - __builtin_clz(N)) : 0;
|
||||
#elif defined(_MSC_VER)
|
||||
return sizeof(unsigned) * 8 - __lzclz(N);
|
||||
#else
|
||||
int res = 0;
|
||||
while (N)
|
||||
{
|
||||
N >>= 1;
|
||||
++res;
|
||||
}
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
MFEM_HOST_DEVICE void operator()(int work_idx) const
|
||||
{
|
||||
MFEM_SHARED value_type buffer[max_blocksize()];
|
||||
reducer.SetInitialValue(buffer[MFEM_THREAD_ID(x)]);
|
||||
// serial part
|
||||
for (int idx = 0; idx < items_per_thread; ++idx)
|
||||
{
|
||||
int i = MFEM_THREAD_ID(x) +
|
||||
(idx + work_idx * items_per_thread) * MFEM_THREAD_SIZE(x);
|
||||
if (i < N)
|
||||
{
|
||||
body(i, buffer[MFEM_THREAD_ID(x)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// binary tree reduction
|
||||
for (int i = (MFEM_THREAD_SIZE(x) >> 1); i > 0; i >>= 1)
|
||||
{
|
||||
MFEM_SYNC_THREAD;
|
||||
if (MFEM_THREAD_ID(x) < i)
|
||||
{
|
||||
reducer.Join(buffer[MFEM_THREAD_ID(x)], buffer[MFEM_THREAD_ID(x) + i]);
|
||||
}
|
||||
}
|
||||
if (MFEM_THREAD_ID(x) == 0)
|
||||
{
|
||||
work[work_idx] = buffer[0];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Performs a 1D reduction on the range [0,N).
|
||||
@a res initial value and where the result will be written.
|
||||
@a body reduction function body.
|
||||
@a reducer helper for joining two reduced values.
|
||||
@a use_dev true to perform the reduction on the device, if possible.
|
||||
@a workspace temporary workspace used for device reductions. May be resized to
|
||||
a larger capacity as needed. Preferably should have MemoryType::MANAGED or
|
||||
MemoryType::HOST_PINNED. TODO: replace with internal temporary workspace
|
||||
vectors once that's added to the memory manager.
|
||||
@tparam T value_type to operate on
|
||||
*/
|
||||
template <class T, class B, class R>
|
||||
void reduce(int N, T &res, B &&body, const R &reducer, bool use_dev,
|
||||
Array<T> &workspace)
|
||||
{
|
||||
if (N == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(MFEM_USE_HIP) || defined(MFEM_USE_CUDA)
|
||||
if (use_dev &&
|
||||
mfem::Device::Allows(Backend::CUDA | Backend::HIP | Backend::RAJA_CUDA |
|
||||
Backend::RAJA_HIP))
|
||||
{
|
||||
using red_type = internal::reduction_kernel<typename std::decay<B>::type,
|
||||
typename std::decay<R>::type>;
|
||||
// max block size is 256, but can be smaller
|
||||
int block_size = std::min<int>(red_type::max_blocksize(),
|
||||
1ll << red_type::block_log2(N));
|
||||
|
||||
int num_mp = Device::NumMultiprocessors(Device::GetId());
|
||||
#if defined(MFEM_USE_CUDA)
|
||||
// good value of mp_sat found experimentally on Lassen
|
||||
constexpr int mp_sat = 8;
|
||||
#elif defined(MFEM_USE_HIP)
|
||||
// good value of mp_sat found experimentally on Tuolumne
|
||||
constexpr int mp_sat = 4;
|
||||
#else
|
||||
num_mp = 1;
|
||||
constexpr int mp_sat = 1;
|
||||
#endif
|
||||
// determine how many items each thread should sum during the serial
|
||||
// portion
|
||||
int nblocks = std::min(mp_sat * num_mp, (N + block_size - 1) / block_size);
|
||||
int items_per_thread =
|
||||
(N + block_size * nblocks - 1) / (block_size * nblocks);
|
||||
|
||||
red_type red{nullptr, std::forward<B>(body), reducer, N, items_per_thread};
|
||||
// allocate res to fit block_size entries
|
||||
auto mt = workspace.GetMemory().GetMemoryType();
|
||||
if (mt != MemoryType::HOST_PINNED && mt != MemoryType::MANAGED)
|
||||
{
|
||||
mt = MemoryType::HOST_PINNED;
|
||||
}
|
||||
workspace.SetSize(nblocks, mt);
|
||||
auto work = workspace.HostWrite();
|
||||
red.work = work;
|
||||
forall_2D(nblocks, block_size, 1, std::move(red));
|
||||
// wait for results
|
||||
MFEM_DEVICE_SYNC;
|
||||
for (int i = 0; i < nblocks; ++i)
|
||||
{
|
||||
reducer.Join(res, work[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
body(i, res);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
#endif
|
||||
#endif // MFEM_REDUCERS_HPP
|
||||
|
||||
+25
-28
@@ -124,24 +124,21 @@ const real_t &DenseMatrix::Elem(int i, int j) const
|
||||
|
||||
void DenseMatrix::Mult(const real_t *x, real_t *y) const
|
||||
{
|
||||
HostRead();
|
||||
kernels::Mult(height, width, Data(), x, y);
|
||||
kernels::Mult(height, width, HostRead(), x, y);
|
||||
}
|
||||
|
||||
void DenseMatrix::Mult(const real_t *x, Vector &y) const
|
||||
{
|
||||
MFEM_ASSERT(height == y.Size(), "incompatible dimensions");
|
||||
|
||||
y.HostReadWrite();
|
||||
Mult(x, y.GetData());
|
||||
Mult(x, y.HostWrite());
|
||||
}
|
||||
|
||||
void DenseMatrix::Mult(const Vector &x, real_t *y) const
|
||||
{
|
||||
MFEM_ASSERT(width == x.Size(), "incompatible dimensions");
|
||||
|
||||
x.HostRead();
|
||||
Mult(x.GetData(), y);
|
||||
Mult(x.HostRead(), y);
|
||||
}
|
||||
|
||||
void DenseMatrix::Mult(const Vector &x, Vector &y) const
|
||||
@@ -149,9 +146,15 @@ void DenseMatrix::Mult(const Vector &x, Vector &y) const
|
||||
MFEM_ASSERT(height == y.Size() && width == x.Size(),
|
||||
"incompatible dimensions");
|
||||
|
||||
x.HostRead();
|
||||
y.HostReadWrite();
|
||||
Mult(x.GetData(), y.GetData());
|
||||
Mult(x.HostRead(), y.HostWrite());
|
||||
}
|
||||
|
||||
void DenseMatrix::AbsMult(const Vector &x, Vector &y) const
|
||||
{
|
||||
MFEM_ASSERT(height == y.Size() && width == x.Size(),
|
||||
"incompatible dimensions");
|
||||
|
||||
kernels::AbsMult(height, width, HostRead(), x.HostRead(), y.HostWrite());
|
||||
}
|
||||
|
||||
real_t DenseMatrix::operator *(const DenseMatrix &m) const
|
||||
@@ -171,34 +174,21 @@ real_t DenseMatrix::operator *(const DenseMatrix &m) const
|
||||
|
||||
void DenseMatrix::MultTranspose(const real_t *x, real_t *y) const
|
||||
{
|
||||
HostRead();
|
||||
real_t *d_col = Data();
|
||||
for (int col = 0; col < width; col++)
|
||||
{
|
||||
real_t y_col = 0.0;
|
||||
for (int row = 0; row < height; row++)
|
||||
{
|
||||
y_col += x[row]*d_col[row];
|
||||
}
|
||||
y[col] = y_col;
|
||||
d_col += height;
|
||||
}
|
||||
kernels::MultTranspose(height, width, HostRead(), x, y);
|
||||
}
|
||||
|
||||
void DenseMatrix::MultTranspose(const real_t *x, Vector &y) const
|
||||
{
|
||||
MFEM_ASSERT(width == y.Size(), "incompatible dimensions");
|
||||
|
||||
y.HostReadWrite();
|
||||
MultTranspose(x, y.GetData());
|
||||
MultTranspose(x, y.HostWrite());
|
||||
}
|
||||
|
||||
void DenseMatrix::MultTranspose(const Vector &x, real_t *y) const
|
||||
{
|
||||
MFEM_ASSERT(height == x.Size(), "incompatible dimensions");
|
||||
|
||||
x.HostRead();
|
||||
MultTranspose(x.GetData(), y);
|
||||
MultTranspose(x.HostRead(), y);
|
||||
}
|
||||
|
||||
void DenseMatrix::MultTranspose(const Vector &x, Vector &y) const
|
||||
@@ -206,9 +196,16 @@ void DenseMatrix::MultTranspose(const Vector &x, Vector &y) const
|
||||
MFEM_ASSERT(height == x.Size() && width == y.Size(),
|
||||
"incompatible dimensions");
|
||||
|
||||
x.HostRead();
|
||||
y.HostReadWrite();
|
||||
MultTranspose(x.GetData(), y.GetData());
|
||||
MultTranspose(x.HostRead(), y.HostWrite());
|
||||
}
|
||||
|
||||
void DenseMatrix::AbsMultTranspose(const Vector &x, Vector &y) const
|
||||
{
|
||||
MFEM_ASSERT(height == x.Size() && width == y.Size(),
|
||||
"incompatible dimensions");
|
||||
|
||||
kernels::AbsMultTranspose(height, width, HostRead(),
|
||||
x.HostRead(), y.HostWrite());
|
||||
}
|
||||
|
||||
void DenseMatrix::AddMult(const Vector &x, Vector &y, const real_t a) const
|
||||
|
||||
@@ -153,6 +153,9 @@ public:
|
||||
/// Matrix vector multiplication.
|
||||
void Mult(const Vector &x, Vector &y) const override;
|
||||
|
||||
/// Absolute-value matrix vector multiplication.
|
||||
void AbsMult(const Vector &x, Vector &y) const override;
|
||||
|
||||
/// Multiply a vector with the transpose matrix.
|
||||
void MultTranspose(const real_t *x, real_t *y) const;
|
||||
|
||||
@@ -165,6 +168,9 @@ public:
|
||||
/// Multiply a vector with the transpose matrix.
|
||||
void MultTranspose(const Vector &x, Vector &y) const override;
|
||||
|
||||
/// Multiply a vector with the absolute-value transpose matrix.
|
||||
void AbsMultTranspose(const Vector &x, Vector &y) const override;
|
||||
|
||||
using Operator::Mult;
|
||||
using Operator::MultTranspose;
|
||||
|
||||
|
||||
@@ -778,10 +778,19 @@ public:
|
||||
of the matrix A. */
|
||||
void AbsMult(real_t a, const Vector &x, real_t b, Vector &y) const;
|
||||
|
||||
/// @brief Computes y = |A| * x, using entry-wise absolute values of the matrix A.
|
||||
void AbsMult(const Vector &x, Vector &y) const override
|
||||
{ AbsMult(1.0, x, 0.0, y); }
|
||||
|
||||
/** @brief Computes y = a * |At| * x + b * y, using entry-wise absolute
|
||||
values of the transpose of the matrix A. */
|
||||
void AbsMultTranspose(real_t a, const Vector &x, real_t b, Vector &y) const;
|
||||
|
||||
/** @brief Computes y = |At| * x, using entry-wise absolute values of the
|
||||
matrix A. */
|
||||
void AbsMultTranspose(const Vector &x, Vector &y) const override
|
||||
{ AbsMultTranspose(1.0, x, 0.0, y); }
|
||||
|
||||
/** @brief The "Boolean" analog of y = alpha * A * x + beta * y, where
|
||||
elements in the sparsity pattern of the matrix are treated as "true". */
|
||||
void BooleanMult(int alpha, const int *x, int beta, int *y)
|
||||
|
||||
@@ -188,6 +188,40 @@ void Mult(const int height, const int width, const TA *data, const TX *x, TY *y)
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Absolute-value matrix vector multiplication: y = |A| x, where the
|
||||
matrix A is of size @a height x @a width with given @a data, while @a x and
|
||||
@a y specify the data of the input and output vectors. */
|
||||
template<typename TA, typename TX, typename TY>
|
||||
MFEM_HOST_DEVICE inline
|
||||
void AbsMult(const int height, const int width, const TA *data,
|
||||
const TX *x, TY *y)
|
||||
{
|
||||
if (width == 0)
|
||||
{
|
||||
for (int row = 0; row < height; row++)
|
||||
{
|
||||
y[row] = 0.0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const TA *d_col = data;
|
||||
TX x_col = x[0];
|
||||
for (int row = 0; row < height; row++)
|
||||
{
|
||||
y[row] = x_col*std::fabs(d_col[row]);
|
||||
}
|
||||
d_col += height;
|
||||
for (int col = 1; col < width; col++)
|
||||
{
|
||||
x_col = x[col];
|
||||
for (int row = 0; row < height; row++)
|
||||
{
|
||||
y[row] += x_col*std::fabs(d_col[row]);
|
||||
}
|
||||
d_col += height;
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Matrix transpose vector multiplication: y = At x, where the matrix A
|
||||
is of size @a height x @a width with given @a data, while @a x and @a y
|
||||
specify the data of the input and output vectors. */
|
||||
@@ -217,6 +251,35 @@ void MultTranspose(const int height, const int width, const TA *data,
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Absolute-value matrix transpose vector multiplication: y = |At| x,
|
||||
where the matrix A is of size @a height x @a width with given @a data, while
|
||||
@a x and @a y specify the data of the input and output vectors. */
|
||||
template<typename TA, typename TX, typename TY>
|
||||
MFEM_HOST_DEVICE inline
|
||||
void AbsMultTranspose(const int height, const int width, const TA *data,
|
||||
const TX *x, TY *y)
|
||||
{
|
||||
if (height == 0)
|
||||
{
|
||||
for (int row = 0; row < width; row++)
|
||||
{
|
||||
y[row] = 0.0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
TY *y_off = y;
|
||||
for (int i = 0; i < width; ++i)
|
||||
{
|
||||
TY val = 0.0;
|
||||
for (int j = 0; j < height; ++j)
|
||||
{
|
||||
val += x[j] * std::fabs(data[i * height + j]);
|
||||
}
|
||||
*y_off = val;
|
||||
y_off++;
|
||||
}
|
||||
}
|
||||
|
||||
/// Symmetrize a square matrix with given @a size and @a data: A -> (A+A^T)/2.
|
||||
template<typename T>
|
||||
MFEM_HOST_DEVICE inline
|
||||
|
||||
@@ -645,18 +645,92 @@ void ConstrainedOperator::ConstrainedMult(const Vector &x, Vector &y,
|
||||
}
|
||||
}
|
||||
|
||||
void ConstrainedOperator::ConstrainedAbsMult(const Vector &x, Vector &y,
|
||||
const bool transpose) const
|
||||
{
|
||||
const int csz = constraint_list.Size();
|
||||
if (csz == 0)
|
||||
{
|
||||
if (transpose)
|
||||
{
|
||||
A->AbsMultTranspose(x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
A->AbsMult(x, y);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
z = x;
|
||||
|
||||
auto idx = constraint_list.Read();
|
||||
// Use read+write access - we are modifying sub-vector of z
|
||||
auto d_z = z.ReadWrite();
|
||||
mfem::forall(csz, [=] MFEM_HOST_DEVICE (int i) { d_z[idx[i]] = 0.0; });
|
||||
|
||||
if (transpose)
|
||||
{
|
||||
A->AbsMultTranspose(z, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
A->AbsMult(z, y);
|
||||
}
|
||||
|
||||
auto d_x = x.Read();
|
||||
// Use read+write access - we are modifying sub-vector of y
|
||||
auto d_y = y.ReadWrite();
|
||||
switch (diag_policy)
|
||||
{
|
||||
case DIAG_ONE:
|
||||
mfem::forall(csz, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
const int id = idx[i];
|
||||
d_y[id] = d_x[id];
|
||||
});
|
||||
break;
|
||||
case DIAG_ZERO:
|
||||
mfem::forall(csz, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
const int id = idx[i];
|
||||
d_y[id] = 0.0;
|
||||
});
|
||||
break;
|
||||
case DIAG_KEEP:
|
||||
// Needs action of the operator diagonal on vector
|
||||
mfem_error("ConstrainedOperator::AbsMult #1");
|
||||
break;
|
||||
default:
|
||||
mfem_error("ConstrainedOperator::AbsMult #2");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ConstrainedOperator::Mult(const Vector &x, Vector &y) const
|
||||
{
|
||||
constexpr bool transpose = false;
|
||||
ConstrainedMult(x, y, transpose);
|
||||
}
|
||||
|
||||
void ConstrainedOperator::AbsMult(const Vector &x, Vector &y) const
|
||||
{
|
||||
constexpr bool transpose = false;
|
||||
ConstrainedAbsMult(x, y, transpose);
|
||||
}
|
||||
|
||||
void ConstrainedOperator::MultTranspose(const Vector &x, Vector &y) const
|
||||
{
|
||||
constexpr bool transpose = true;
|
||||
ConstrainedMult(x, y, transpose);
|
||||
}
|
||||
|
||||
void ConstrainedOperator::AbsMultTranspose(const Vector &x, Vector &y) const
|
||||
{
|
||||
constexpr bool transpose = true;
|
||||
ConstrainedAbsMult(x, y, transpose);
|
||||
}
|
||||
|
||||
void ConstrainedOperator::AddMult(const Vector &x, Vector &y,
|
||||
const real_t a) const
|
||||
{
|
||||
|
||||
+38
-6
@@ -88,10 +88,22 @@ public:
|
||||
/// Operator application: `y=A(x)`.
|
||||
virtual void Mult(const Vector &x, Vector &y) const = 0;
|
||||
|
||||
/** @brief Action of the absolute-value operator: `y=|A|(x)`. The default
|
||||
behavior in class Operator is to generate an error. If the Operator is a
|
||||
composition of several operators, the composition unfold into a product
|
||||
of absolute-value operators too. */
|
||||
virtual void AbsMult(const Vector &x, Vector &y) const
|
||||
{ MFEM_ABORT("Operator::AbsMult() is not overridden!"); }
|
||||
|
||||
/** @brief Action of the transpose operator: `y=A^t(x)`. The default behavior
|
||||
in class Operator is to generate an error. */
|
||||
virtual void MultTranspose(const Vector &x, Vector &y) const
|
||||
{ mfem_error("Operator::MultTranspose() is not overridden!"); }
|
||||
{ MFEM_ABORT("Operator::MultTranspose() is not overridden!"); }
|
||||
|
||||
/** @brief Action of the transpose absolute-value operator: `y=|A|^t(x)`.
|
||||
The default behavior in class Operator is to generate an error. */
|
||||
virtual void AbsMultTranspose(const Vector &x, Vector &y) const
|
||||
{ MFEM_ABORT("Operator::AbsMultTranspose() is not overridden!"); }
|
||||
|
||||
/// Operator application: `y+=A(x)` (default) or `y+=a*A(x)`.
|
||||
virtual void AddMult(const Vector &x, Vector &y, const real_t a = 1.0) const;
|
||||
@@ -121,7 +133,7 @@ public:
|
||||
behavior in class Operator is to generate an error. */
|
||||
virtual Operator &GetGradient(const Vector &x) const
|
||||
{
|
||||
mfem_error("Operator::GetGradient() is not overridden!");
|
||||
MFEM_ABORT("Operator::GetGradient() is not overridden!");
|
||||
return const_cast<Operator &>(*this);
|
||||
}
|
||||
|
||||
@@ -691,7 +703,7 @@ public:
|
||||
const Vector &xB, const Vector &fxB,
|
||||
int jokB, int *jcurB, real_t gammaB)
|
||||
{
|
||||
mfem_error("TimeDependentAdjointOperator::SUNImplicitSetupB() is not "
|
||||
MFEM_ABORT("TimeDependentAdjointOperator::SUNImplicitSetupB() is not "
|
||||
"overridden!");
|
||||
return (-1);
|
||||
}
|
||||
@@ -709,7 +721,7 @@ public:
|
||||
see the SUNDIALS User Guides. */
|
||||
virtual int SUNImplicitSolveB(Vector &x, const Vector &b, real_t tol)
|
||||
{
|
||||
mfem_error("TimeDependentAdjointOperator::SUNImplicitSolveB() is not "
|
||||
MFEM_ABORT("TimeDependentAdjointOperator::SUNImplicitSolveB() is not "
|
||||
"overridden!");
|
||||
return (-1);
|
||||
}
|
||||
@@ -930,6 +942,10 @@ public:
|
||||
void Mult(const Vector & x, Vector & y) const override
|
||||
{ P.Mult(x, Px); A.Mult(Px, APx); Rt.MultTranspose(APx, y); }
|
||||
|
||||
/// Operator-wise absolute-value application.
|
||||
void AbsMult(const Vector & x, Vector & y) const override
|
||||
{ P.AbsMult(x, Px); A.AbsMult(Px, APx); Rt.AbsMultTranspose(APx, y); }
|
||||
|
||||
/// Approximate diagonal of the RAP Operator.
|
||||
/** Returns the diagonal of A, as returned by its AssembleDiagonal method,
|
||||
multiplied be P^T.
|
||||
@@ -950,6 +966,14 @@ public:
|
||||
/// Application of the transpose.
|
||||
void MultTranspose(const Vector & x, Vector & y) const override
|
||||
{ Rt.Mult(x, APx); A.MultTranspose(APx, Px); P.MultTranspose(Px, y); }
|
||||
|
||||
/// Operator-wise absolute-value application of the transpose
|
||||
void AbsMultTranspose(const Vector & x, Vector & y) const override
|
||||
{
|
||||
Rt.AbsMult(x, APx);
|
||||
A.AbsMultTranspose(APx, Px);
|
||||
P.AbsMultTranspose(Px, y);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1045,13 +1069,21 @@ public:
|
||||
|
||||
void AddMult(const Vector &x, Vector &y, const real_t a = 1.0) const override;
|
||||
|
||||
void AbsMult(const Vector &x, Vector &y) const override;
|
||||
|
||||
void MultTranspose(const Vector &x, Vector &y) const override;
|
||||
|
||||
void AbsMultTranspose(const Vector &x, Vector &y) const override;
|
||||
|
||||
/** @brief Implementation of Mult or MultTranspose.
|
||||
* TODO - Generalize to allow constraining rows and columns differently.
|
||||
*/
|
||||
TODO - Generalize to allow constraining rows and columns differently. */
|
||||
void ConstrainedMult(const Vector &x, Vector &y, const bool transpose) const;
|
||||
|
||||
/** @brief Implementation of AbsMult or AbsMultTranspose.
|
||||
TODO - Generalize to allow constraining rows and columns differently. */
|
||||
void ConstrainedAbsMult(const Vector &x, Vector &y,
|
||||
const bool transpose) const;
|
||||
|
||||
/// Destructor: destroys the unconstrained Operator, if owned.
|
||||
~ConstrainedOperator() override { if (own_A) { delete A; } }
|
||||
};
|
||||
|
||||
+8
-7
@@ -624,7 +624,7 @@ void SLISolver::Mult(const Vector &b, Vector &x) const
|
||||
}
|
||||
|
||||
r0 = std::max(nom*rel_tol, abs_tol);
|
||||
if (nom <= r0)
|
||||
if (Monitor(0, nom, r, x) || nom <= r0)
|
||||
{
|
||||
converged = true;
|
||||
final_iter = 0;
|
||||
@@ -665,18 +665,13 @@ void SLISolver::Mult(const Vector &b, Vector &x) const
|
||||
nomold = nom;
|
||||
|
||||
bool done = false;
|
||||
if (nom < r0)
|
||||
if (Monitor(i, nom, r, x) || nom < r0)
|
||||
{
|
||||
converged = true;
|
||||
final_iter = i;
|
||||
done = true;
|
||||
}
|
||||
|
||||
if (++i > max_iter)
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
|
||||
if (print_options.iterations || (done && print_options.first_and_last))
|
||||
{
|
||||
mfem::out << " Iteration : " << setw(3) << right << (i-1)
|
||||
@@ -684,6 +679,11 @@ void SLISolver::Mult(const Vector &b, Vector &x) const
|
||||
<< "\tConv. rate: " << cf << '\n';
|
||||
}
|
||||
|
||||
if (++i > max_iter)
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
|
||||
if (done) { break; }
|
||||
}
|
||||
|
||||
@@ -700,6 +700,7 @@ void SLISolver::Mult(const Vector &b, Vector &x) const
|
||||
}
|
||||
|
||||
final_norm = nom;
|
||||
Monitor(final_iter, final_norm, r, x, true);
|
||||
}
|
||||
|
||||
void SLI(const Operator &A, const Vector &b, Vector &x,
|
||||
|
||||
@@ -422,13 +422,13 @@ public:
|
||||
void BooleanMultTranspose(const Array<int> &x, Array<int> &y) const;
|
||||
|
||||
/// y = |A| * x, using entry-wise absolute values of matrix A
|
||||
void AbsMult(const Vector &x, Vector &y) const;
|
||||
void AbsMult(const Vector &x, Vector &y) const override;
|
||||
|
||||
/// y = |At| * x, using entry-wise absolute values of the transpose of matrix A
|
||||
/** If the matrix is modified, call ResetTranspose() and optionally
|
||||
EnsureMultTranspose() to make sure this method uses the correct updated
|
||||
transpose. */
|
||||
void AbsMultTranspose(const Vector &x, Vector &y) const;
|
||||
void AbsMultTranspose(const Vector &x, Vector &y) const override;
|
||||
|
||||
/// Compute y^t A x
|
||||
real_t InnerProduct(const Vector &x, const Vector &y) const;
|
||||
|
||||
+219
-194
@@ -11,19 +11,18 @@
|
||||
|
||||
// Implementation of data type vector
|
||||
|
||||
#include "kernels.hpp"
|
||||
#include "vector.hpp"
|
||||
#include "../general/forall.hpp"
|
||||
#include "../general/reducers.hpp"
|
||||
#include "../general/hash.hpp"
|
||||
#include "vector.hpp"
|
||||
|
||||
#ifdef MFEM_USE_OPENMP
|
||||
#include <omp.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
#include <limits>
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
@@ -207,7 +206,7 @@ Vector &Vector::operator=(const Vector &v)
|
||||
UseDevice(v.UseDevice());
|
||||
#else
|
||||
SetSize(v.Size());
|
||||
bool vuse = v.UseDevice();
|
||||
const bool vuse = v.UseDevice();
|
||||
const bool use_dev = UseDevice() || vuse;
|
||||
v.UseDevice(use_dev);
|
||||
// keep 'data' where it is, unless 'use_dev' is true
|
||||
@@ -249,8 +248,8 @@ Vector &Vector::operator*=(const Vector &v)
|
||||
|
||||
const bool use_dev = UseDevice() || v.UseDevice();
|
||||
const int N = size;
|
||||
const auto x = v.Read(use_dev);
|
||||
auto y = ReadWrite(use_dev);
|
||||
auto x = v.Read(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] *= x[i]; });
|
||||
return *this;
|
||||
}
|
||||
@@ -271,8 +270,8 @@ Vector &Vector::operator/=(const Vector &v)
|
||||
|
||||
const bool use_dev = UseDevice() || v.UseDevice();
|
||||
const int N = size;
|
||||
const auto x = v.Read(use_dev);
|
||||
auto y = ReadWrite(use_dev);
|
||||
auto x = v.Read(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] /= x[i]; });
|
||||
return *this;
|
||||
}
|
||||
@@ -292,8 +291,8 @@ Vector &Vector::operator-=(const Vector &v)
|
||||
|
||||
const bool use_dev = UseDevice() || v.UseDevice();
|
||||
const int N = size;
|
||||
const auto x = v.Read(use_dev);
|
||||
auto y = ReadWrite(use_dev);
|
||||
auto x = v.Read(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] -= x[i]; });
|
||||
return *this;
|
||||
}
|
||||
@@ -313,8 +312,8 @@ Vector &Vector::operator+=(const Vector &v)
|
||||
|
||||
const bool use_dev = UseDevice() || v.UseDevice();
|
||||
const int N = size;
|
||||
const auto x = v.Read(use_dev);
|
||||
auto y = ReadWrite(use_dev);
|
||||
auto x = v.Read(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] += x[i]; });
|
||||
return *this;
|
||||
}
|
||||
@@ -327,8 +326,8 @@ Vector &Vector::Add(const real_t a, const Vector &Va)
|
||||
{
|
||||
const int N = size;
|
||||
const bool use_dev = UseDevice() || Va.UseDevice();
|
||||
const auto x = Va.Read(use_dev);
|
||||
auto y = ReadWrite(use_dev);
|
||||
auto x = Va.Read(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] += a * x[i]; });
|
||||
}
|
||||
return *this;
|
||||
@@ -340,7 +339,7 @@ Vector &Vector::Set(const real_t a, const Vector &Va)
|
||||
|
||||
const bool use_dev = UseDevice() || Va.UseDevice();
|
||||
const int N = size;
|
||||
auto x = Va.Read(use_dev);
|
||||
const auto x = Va.Read(use_dev);
|
||||
auto y = Write(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] = a * x[i]; });
|
||||
return *this;
|
||||
@@ -352,9 +351,9 @@ void Vector::SetVector(const Vector &v, int offset)
|
||||
|
||||
const bool use_dev = UseDevice() || v.UseDevice();
|
||||
const int vs = v.Size();
|
||||
const real_t *vp = v.Read(use_dev);
|
||||
const auto vp = v.Read(use_dev);
|
||||
// Use read+write access for *this - we only modify some of its entries
|
||||
real_t *p = ReadWrite(use_dev) + offset;
|
||||
auto p = ReadWrite(use_dev) + offset;
|
||||
mfem::forall_switch(use_dev, vs, [=] MFEM_HOST_DEVICE (int i) { p[i] = vp[i]; });
|
||||
}
|
||||
|
||||
@@ -364,8 +363,8 @@ void Vector::AddSubVector(const Vector &v, int offset)
|
||||
|
||||
const bool use_dev = UseDevice() || v.UseDevice();
|
||||
const int vs = v.Size();
|
||||
const real_t *vp = v.Read(use_dev);
|
||||
real_t *p = ReadWrite(use_dev) + offset;
|
||||
const auto vp = v.Read(use_dev);
|
||||
auto p = ReadWrite(use_dev) + offset;
|
||||
mfem::forall_switch(use_dev, vs, [=] MFEM_HOST_DEVICE (int i) { p[i] += vp[i]; });
|
||||
}
|
||||
|
||||
@@ -385,6 +384,28 @@ void Vector::Reciprocal()
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] = 1.0/y[i]; });
|
||||
}
|
||||
|
||||
void Vector::Abs()
|
||||
{
|
||||
const bool use_dev = UseDevice();
|
||||
const int N = size;
|
||||
auto y = ReadWrite(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
y[i] = std::abs(y[i]);
|
||||
});
|
||||
}
|
||||
|
||||
void Vector::Pow(const real_t p)
|
||||
{
|
||||
const bool use_dev = UseDevice();
|
||||
const int N = size;
|
||||
auto y = ReadWrite(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
y[i] = std::pow(y[i], p);
|
||||
});
|
||||
}
|
||||
|
||||
void add(const Vector &v1, const Vector &v2, Vector &v)
|
||||
{
|
||||
MFEM_ASSERT(v.size == v1.size && v.size == v2.size,
|
||||
@@ -394,8 +415,8 @@ void add(const Vector &v1, const Vector &v2, Vector &v)
|
||||
const bool use_dev = v1.UseDevice() || v2.UseDevice() || v.UseDevice();
|
||||
const int N = v.size;
|
||||
// Note: get read access first, in case v is the same as v1/v2.
|
||||
auto x1 = v1.Read(use_dev);
|
||||
auto x2 = v2.Read(use_dev);
|
||||
const auto x1 = v1.Read(use_dev);
|
||||
const auto x2 = v2.Read(use_dev);
|
||||
auto y = v.Write(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { y[i] = x1[i] + x2[i]; });
|
||||
#else
|
||||
@@ -426,8 +447,8 @@ void add(const Vector &v1, real_t alpha, const Vector &v2, Vector &v)
|
||||
const bool use_dev = v1.UseDevice() || v2.UseDevice() || v.UseDevice();
|
||||
const int N = v.size;
|
||||
// Note: get read access first, in case v is the same as v1/v2.
|
||||
auto d_x = v1.Read(use_dev);
|
||||
auto d_y = v2.Read(use_dev);
|
||||
const auto d_x = v1.Read(use_dev);
|
||||
const auto d_y = v2.Read(use_dev);
|
||||
auto d_z = v.Write(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
@@ -465,8 +486,8 @@ void add(const real_t a, const Vector &x, const Vector &y, Vector &z)
|
||||
const bool use_dev = x.UseDevice() || y.UseDevice() || z.UseDevice();
|
||||
const int N = x.size;
|
||||
// Note: get read access first, in case z is the same as x/y.
|
||||
auto xd = x.Read(use_dev);
|
||||
auto yd = y.Read(use_dev);
|
||||
const auto xd = x.Read(use_dev);
|
||||
const auto yd = y.Read(use_dev);
|
||||
auto zd = z.Write(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
@@ -520,8 +541,8 @@ void add(const real_t a, const Vector &x,
|
||||
const bool use_dev = x.UseDevice() || y.UseDevice() || z.UseDevice();
|
||||
const int N = x.size;
|
||||
// Note: get read access first, in case z is the same as x/y.
|
||||
auto xd = x.Read(use_dev);
|
||||
auto yd = y.Read(use_dev);
|
||||
const auto xd = x.Read(use_dev);
|
||||
const auto yd = y.Read(use_dev);
|
||||
auto zd = z.Write(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
@@ -550,8 +571,8 @@ void subtract(const Vector &x, const Vector &y, Vector &z)
|
||||
const bool use_dev = x.UseDevice() || y.UseDevice() || z.UseDevice();
|
||||
const int N = x.size;
|
||||
// Note: get read access first, in case z is the same as x/y.
|
||||
auto xd = x.Read(use_dev);
|
||||
auto yd = y.Read(use_dev);
|
||||
const auto xd = x.Read(use_dev);
|
||||
const auto yd = y.Read(use_dev);
|
||||
auto zd = z.Write(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
@@ -589,8 +610,8 @@ void subtract(const real_t a, const Vector &x, const Vector &y, Vector &z)
|
||||
const bool use_dev = x.UseDevice() || y.UseDevice() || z.UseDevice();
|
||||
const int N = x.size;
|
||||
// Note: get read access first, in case z is the same as x/y.
|
||||
auto xd = x.Read(use_dev);
|
||||
auto yd = y.Read(use_dev);
|
||||
const auto xd = x.Read(use_dev);
|
||||
const auto yd = y.Read(use_dev);
|
||||
auto zd = z.Write(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
@@ -631,8 +652,8 @@ void Vector::median(const Vector &lo, const Vector &hi)
|
||||
const bool use_dev = UseDevice() || lo.UseDevice() || hi.UseDevice();
|
||||
const int N = size;
|
||||
// Note: get read access first, in case *this is the same as lo/hi.
|
||||
auto l = lo.Read(use_dev);
|
||||
auto h = hi.Read(use_dev);
|
||||
const auto l = lo.Read(use_dev);
|
||||
const auto h = hi.Read(use_dev);
|
||||
auto m = Write(use_dev);
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
@@ -652,9 +673,9 @@ void Vector::GetSubVector(const Array<int> &dofs, Vector &elemvect) const
|
||||
const int n = dofs.Size();
|
||||
elemvect.SetSize(n);
|
||||
const bool use_dev = dofs.UseDevice() || elemvect.UseDevice();
|
||||
const auto d_X = Read(use_dev);
|
||||
const auto d_dofs = dofs.Read(use_dev);
|
||||
auto d_y = elemvect.Write(use_dev);
|
||||
auto d_X = Read(use_dev);
|
||||
auto d_dofs = dofs.Read(use_dev);
|
||||
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
const int dof_i = d_dofs[i];
|
||||
@@ -664,7 +685,7 @@ void Vector::GetSubVector(const Array<int> &dofs, Vector &elemvect) const
|
||||
|
||||
void Vector::GetSubVector(const Array<int> &dofs, real_t *elem_data) const
|
||||
{
|
||||
data.Read(MemoryClass::HOST, size);
|
||||
HostRead();
|
||||
const int n = dofs.Size();
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
@@ -679,7 +700,7 @@ void Vector::SetSubVector(const Array<int> &dofs, const real_t value)
|
||||
const int n = dofs.Size();
|
||||
// Use read+write access for *this - we only modify some of its entries
|
||||
auto d_X = ReadWrite(use_dev);
|
||||
auto d_dofs = dofs.Read(use_dev);
|
||||
const auto d_dofs = dofs.Read(use_dev);
|
||||
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
const int j = d_dofs[i];
|
||||
@@ -721,8 +742,8 @@ void Vector::SetSubVector(const Array<int> &dofs, const Vector &elemvect)
|
||||
const int n = dofs.Size();
|
||||
// Use read+write access for X - we only modify some of its entries
|
||||
auto d_X = ReadWrite(use_dev);
|
||||
auto d_y = elemvect.Read(use_dev);
|
||||
auto d_dofs = dofs.Read(use_dev);
|
||||
const auto d_y = elemvect.Read(use_dev);
|
||||
const auto d_dofs = dofs.Read(use_dev);
|
||||
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
const int dof_i = d_dofs[i];
|
||||
@@ -740,7 +761,7 @@ void Vector::SetSubVector(const Array<int> &dofs, const Vector &elemvect)
|
||||
void Vector::SetSubVector(const Array<int> &dofs, real_t *elem_data)
|
||||
{
|
||||
// Use read+write access because we overwrite only part of the data.
|
||||
data.ReadWrite(MemoryClass::HOST, size);
|
||||
HostReadWrite();
|
||||
const int n = dofs.Size();
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
@@ -764,9 +785,9 @@ void Vector::AddElementVector(const Array<int> &dofs, const Vector &elemvect)
|
||||
|
||||
const bool use_dev = dofs.UseDevice() || elemvect.UseDevice();
|
||||
const int n = dofs.Size();
|
||||
auto d_y = elemvect.Read(use_dev);
|
||||
const auto d_y = elemvect.Read(use_dev);
|
||||
const auto d_dofs = dofs.Read(use_dev);
|
||||
auto d_X = ReadWrite(use_dev);
|
||||
auto d_dofs = dofs.Read(use_dev);
|
||||
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
const int j = d_dofs[i];
|
||||
@@ -783,7 +804,7 @@ void Vector::AddElementVector(const Array<int> &dofs, const Vector &elemvect)
|
||||
|
||||
void Vector::AddElementVector(const Array<int> &dofs, real_t *elem_data)
|
||||
{
|
||||
data.ReadWrite(MemoryClass::HOST, size);
|
||||
HostReadWrite();
|
||||
const int n = dofs.Size();
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
@@ -808,9 +829,9 @@ void Vector::AddElementVector(const Array<int> &dofs, const real_t a,
|
||||
|
||||
const bool use_dev = dofs.UseDevice() || elemvect.UseDevice();
|
||||
const int n = dofs.Size();
|
||||
const auto d_x = elemvect.Read(use_dev);
|
||||
const auto d_dofs = dofs.Read(use_dev);
|
||||
auto d_y = ReadWrite(use_dev);
|
||||
auto d_x = elemvect.Read(use_dev);
|
||||
auto d_dofs = dofs.Read(use_dev);
|
||||
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i)
|
||||
{
|
||||
const int j = d_dofs[i];
|
||||
@@ -835,7 +856,7 @@ void Vector::SetSubVectorComplement(const Array<int> &dofs, const real_t val)
|
||||
Device::GetHostMemoryType());
|
||||
auto d_data = ReadWrite(use_dev);
|
||||
auto d_dofs_vals = dofs_vals.Write(use_dev);
|
||||
auto d_dofs = dofs.Read(use_dev);
|
||||
const auto d_dofs = dofs.Read(use_dev);
|
||||
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i) { d_dofs_vals[i] = d_data[d_dofs[i]]; });
|
||||
mfem::forall_switch(use_dev, N, [=] MFEM_HOST_DEVICE (int i) { d_data[i] = val; });
|
||||
mfem::forall_switch(use_dev, n, [=] MFEM_HOST_DEVICE (int i) { d_data[d_dofs[i]] = d_dofs_vals[i]; });
|
||||
@@ -844,7 +865,7 @@ void Vector::SetSubVectorComplement(const Array<int> &dofs, const real_t val)
|
||||
void Vector::Print(std::ostream &os, int width) const
|
||||
{
|
||||
if (!size) { return; }
|
||||
data.Read(MemoryClass::HOST, size);
|
||||
HostRead();
|
||||
for (int i = 0; 1; )
|
||||
{
|
||||
os << ZeroSubnormal(data[i]);
|
||||
@@ -870,7 +891,7 @@ void Vector::Print(adios2stream &os,
|
||||
const std::string& variable_name) const
|
||||
{
|
||||
if (!size) { return; }
|
||||
data.Read(MemoryClass::HOST, size);
|
||||
HostRead();
|
||||
os.engine.Put(variable_name, &data[0] );
|
||||
}
|
||||
#endif
|
||||
@@ -928,10 +949,7 @@ void Vector::PrintHash(std::ostream &os) const
|
||||
|
||||
void Vector::Randomize(int seed)
|
||||
{
|
||||
if (seed == 0)
|
||||
{
|
||||
seed = (int)time(0);
|
||||
}
|
||||
if (seed == 0) { seed = (int)time(0); }
|
||||
|
||||
srand((unsigned)seed);
|
||||
|
||||
@@ -947,20 +965,15 @@ real_t Vector::Norml2() const
|
||||
// Scale entries of Vector on the fly, using algorithms from
|
||||
// std::hypot() and LAPACK's drm2. This scaling ensures that the
|
||||
// argument of each call to std::pow is <= 1 to avoid overflow.
|
||||
if (size == 0)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
if (size == 0) { return 0.0; }
|
||||
|
||||
auto m_data = Read(UseDevice());
|
||||
const auto m_data = Read(UseDevice());
|
||||
using value_type = DevicePair<real_t, real_t>;
|
||||
value_type res;
|
||||
res.first = 0;
|
||||
res.second = 0;
|
||||
// first compute sum (|m_data|/scale)^2
|
||||
reduce(
|
||||
size, res,
|
||||
[=] MFEM_HOST_DEVICE(int i, value_type &r)
|
||||
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, value_type &r)
|
||||
{
|
||||
real_t n = fabs(m_data[i]);
|
||||
if (n > 0)
|
||||
@@ -987,11 +1000,12 @@ real_t Vector::Normlinf() const
|
||||
{
|
||||
if (size == 0) { return 0; }
|
||||
|
||||
auto m_data = Read(UseDevice());
|
||||
real_t res = 0;
|
||||
reduce(
|
||||
size, res,
|
||||
[=] MFEM_HOST_DEVICE(int i, real_t &r) { r = fmax(r, fabs(m_data[i])); },
|
||||
const auto m_data = Read(UseDevice());
|
||||
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, real_t &r)
|
||||
{
|
||||
r = fmax(r, fabs(m_data[i]));
|
||||
},
|
||||
MaxReducer<real_t> {}, UseDevice(), vector_workspace());
|
||||
return res;
|
||||
}
|
||||
@@ -1000,11 +1014,12 @@ real_t Vector::Norml1() const
|
||||
{
|
||||
if (size == 0) { return 0.0; }
|
||||
|
||||
auto m_data = Read(UseDevice());
|
||||
real_t res = 0;
|
||||
reduce(
|
||||
size, res,
|
||||
[=] MFEM_HOST_DEVICE(int i, real_t &r) { r += fabs(m_data[i]); },
|
||||
const auto m_data = Read(UseDevice());
|
||||
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, real_t &r)
|
||||
{
|
||||
r += fabs(m_data[i]);
|
||||
},
|
||||
SumReducer<real_t> {}, UseDevice(), vector_workspace());
|
||||
return res;
|
||||
}
|
||||
@@ -1013,33 +1028,24 @@ real_t Vector::Normlp(real_t p) const
|
||||
{
|
||||
MFEM_ASSERT(p > 0.0, "Vector::Normlp");
|
||||
|
||||
if (p == 1.0)
|
||||
{
|
||||
return Norml1();
|
||||
}
|
||||
if (p == 2.0)
|
||||
{
|
||||
return Norml2();
|
||||
}
|
||||
if (p == 1.0) { return Norml1(); }
|
||||
|
||||
if (p == 2.0) { return Norml2(); }
|
||||
|
||||
if (p < infinity())
|
||||
{
|
||||
// Scale entries of Vector on the fly, using algorithms from
|
||||
// std::hypot() and LAPACK's drm2. This scaling ensures that the
|
||||
// argument of each call to std::pow is <= 1 to avoid overflow.
|
||||
if (size == 0)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
if (size == 0) { return 0.0; }
|
||||
|
||||
auto m_data = Read(UseDevice());
|
||||
using value_type = DevicePair<real_t, real_t>;
|
||||
value_type res;
|
||||
res.first = 0;
|
||||
res.second = 0;
|
||||
const auto m_data = Read(UseDevice());
|
||||
// first compute sum (|m_data|/scale)^p
|
||||
reduce(
|
||||
size, res,
|
||||
[=] MFEM_HOST_DEVICE(int i, value_type &r)
|
||||
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, value_type &r)
|
||||
{
|
||||
real_t n = fabs(m_data[i]);
|
||||
if (n > 0)
|
||||
@@ -1068,163 +1074,182 @@ real_t Vector::Normlp(real_t p) const
|
||||
real_t Vector::operator*(const Vector &v) const
|
||||
{
|
||||
MFEM_ASSERT(size == v.size, "incompatible Vectors!");
|
||||
|
||||
if (size == 0) { return 0.0; }
|
||||
|
||||
const bool use_dev = UseDevice() || v.UseDevice();
|
||||
const auto m_data = Read(use_dev), v_data = v.Read(use_dev);
|
||||
|
||||
auto m_data = Read(use_dev);
|
||||
auto v_data = v.Read(use_dev);
|
||||
|
||||
if (use_dev)
|
||||
{
|
||||
// special path for OCCA and OpenMP
|
||||
// If OCCA is enabled, it handles all selected backends
|
||||
#ifdef MFEM_USE_OCCA
|
||||
if (DeviceCanUseOcca())
|
||||
{
|
||||
return occa::linalg::dot<real_t, real_t, real_t>(
|
||||
OccaMemoryRead(data, size), OccaMemoryRead(v.data, size));
|
||||
}
|
||||
if (use_dev && DeviceCanUseOcca())
|
||||
{
|
||||
return occa::linalg::dot<real_t, real_t, real_t>(
|
||||
OccaMemoryRead(data, size), OccaMemoryRead(v.data, size));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MFEM_USE_OPENMP
|
||||
if (Device::Allows(Backend::OMP_MASK))
|
||||
const auto compute_dot = [&]()
|
||||
{
|
||||
real_t res = 0;
|
||||
reduce(size, res, [=] MFEM_HOST_DEVICE (int i, real_t &r)
|
||||
{
|
||||
r += m_data[i] * v_data[i];
|
||||
},
|
||||
SumReducer<real_t> {}, use_dev, vector_workspace());
|
||||
return res;
|
||||
};
|
||||
|
||||
// Device backends have top priority
|
||||
if (Device::Allows(Backend::DEVICE_MASK)) { return compute_dot(); }
|
||||
|
||||
// Special path for OpenMP
|
||||
#ifdef MFEM_USE_OPENMP
|
||||
if (use_dev && Device::Allows(Backend::OMP_MASK))
|
||||
{
|
||||
// By default, use a deterministic way of computing the dot product
|
||||
#define MFEM_USE_OPENMP_DETERMINISTIC_DOT
|
||||
#ifdef MFEM_USE_OPENMP_DETERMINISTIC_DOT
|
||||
// By default, use a deterministic way of computing the dot product
|
||||
static Vector th_dot;
|
||||
#pragma omp parallel
|
||||
static Vector th_dot;
|
||||
#pragma omp parallel
|
||||
{
|
||||
const int nt = omp_get_num_threads();
|
||||
#pragma omp master
|
||||
th_dot.SetSize(nt);
|
||||
const int tid = omp_get_thread_num();
|
||||
const int stride = (size + nt - 1) / nt;
|
||||
const int start = tid * stride;
|
||||
const int stop = std::min(start + stride, size);
|
||||
real_t my_dot = 0.0;
|
||||
for (int i = start; i < stop; i++)
|
||||
{
|
||||
const int nt = omp_get_num_threads();
|
||||
#pragma omp master
|
||||
th_dot.SetSize(nt);
|
||||
const int tid = omp_get_thread_num();
|
||||
const int stride = (size + nt - 1) / nt;
|
||||
const int start = tid * stride;
|
||||
const int stop = std::min(start + stride, size);
|
||||
real_t my_dot = 0.0;
|
||||
for (int i = start; i < stop; i++)
|
||||
{
|
||||
my_dot += m_data[i] * v_data[i];
|
||||
}
|
||||
#pragma omp barrier
|
||||
th_dot(tid) = my_dot;
|
||||
my_dot += m_data[i] * v_data[i];
|
||||
}
|
||||
return th_dot.Sum();
|
||||
#else
|
||||
// The standard way of computing the dot product is non-deterministic
|
||||
real_t prod = 0.0;
|
||||
#pragma omp parallel for reduction(+ : prod)
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
prod += m_data[i] * v_data[i];
|
||||
}
|
||||
return prod;
|
||||
#endif // MFEM_USE_OPENMP_DETERMINISTIC_DOT
|
||||
#pragma omp barrier
|
||||
th_dot(tid) = my_dot;
|
||||
}
|
||||
#endif // MFEM_USE_OPENMP
|
||||
return th_dot.Sum();
|
||||
#else
|
||||
// The standard way of computing the dot product is non-deterministic
|
||||
real_t prod = 0.0;
|
||||
#pragma omp parallel for reduction(+ : prod)
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
prod += m_data[i] * v_data[i];
|
||||
}
|
||||
return prod;
|
||||
#endif // MFEM_USE_OPENMP_DETERMINISTIC_DOT
|
||||
}
|
||||
#endif // MFEM_USE_OPENMP
|
||||
|
||||
// normal path for everything else (cuda, hip, debug, cpu)
|
||||
real_t res = 0;
|
||||
reduce(
|
||||
size, res,
|
||||
[=] MFEM_HOST_DEVICE(int i, real_t &r) { r += m_data[i] * v_data[i]; },
|
||||
SumReducer<real_t> {}, use_dev, vector_workspace());
|
||||
return res;
|
||||
// All other CPU backends
|
||||
return compute_dot();
|
||||
}
|
||||
|
||||
real_t Vector::Min() const
|
||||
{
|
||||
if (size == 0) { return infinity(); }
|
||||
|
||||
const bool use_dev = UseDevice();
|
||||
auto m_data = Read(use_dev);
|
||||
|
||||
if (use_dev)
|
||||
{
|
||||
// special case for OCCA and OpenMP
|
||||
const auto use_dev = UseDevice();
|
||||
const auto m_data = Read(use_dev);
|
||||
|
||||
#ifdef MFEM_USE_OCCA
|
||||
if (DeviceCanUseOcca())
|
||||
{
|
||||
return occa::linalg::min<real_t,real_t>(OccaMemoryRead(data, size));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MFEM_USE_OPENMP
|
||||
if (Device::Allows(Backend::OMP_MASK))
|
||||
{
|
||||
real_t minimum = m_data[0];
|
||||
#pragma omp parallel for reduction(min:minimum)
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
minimum = std::min(minimum, m_data[i]);
|
||||
}
|
||||
return minimum;
|
||||
}
|
||||
#endif
|
||||
if (use_dev && DeviceCanUseOcca())
|
||||
{
|
||||
return occa::linalg::min<real_t,real_t>(OccaMemoryRead(data, size));
|
||||
}
|
||||
#endif
|
||||
|
||||
// normal path for everything else (cuda, hip, debug, cpu)
|
||||
real_t res = infinity();
|
||||
reduce(
|
||||
size, res,
|
||||
[=] MFEM_HOST_DEVICE(int i, real_t &r) { r = fmin(r, m_data[i]); },
|
||||
MinReducer<real_t> {}, use_dev, vector_workspace());
|
||||
return res;
|
||||
const auto compute_min = [&]()
|
||||
{
|
||||
real_t res = infinity();
|
||||
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, real_t &r)
|
||||
{
|
||||
r = fmin(r, m_data[i]);
|
||||
},
|
||||
MinReducer<real_t> {}, use_dev, vector_workspace());
|
||||
return res;
|
||||
};
|
||||
|
||||
// Device backends have top priority
|
||||
if (Device::Allows(Backend::DEVICE_MASK)) { return compute_min(); }
|
||||
|
||||
// Special path for OpenMP
|
||||
#ifdef MFEM_USE_OPENMP
|
||||
if (use_dev && Device::Allows(Backend::OMP_MASK))
|
||||
{
|
||||
real_t minimum = m_data[0];
|
||||
#pragma omp parallel for reduction(min:minimum)
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
minimum = std::min(minimum, m_data[i]);
|
||||
}
|
||||
return minimum;
|
||||
}
|
||||
#endif
|
||||
|
||||
// All other CPU backends
|
||||
return compute_min();
|
||||
}
|
||||
|
||||
real_t Vector::Max() const
|
||||
{
|
||||
if (size == 0) { return -infinity(); }
|
||||
|
||||
const bool use_dev = UseDevice();
|
||||
auto m_data = Read(use_dev);
|
||||
const auto use_dev = UseDevice();
|
||||
const auto m_data = Read(use_dev);
|
||||
|
||||
if (use_dev)
|
||||
{
|
||||
// special cases where OCCA or OenMP are used
|
||||
#ifdef MFEM_USE_OCCA
|
||||
if (DeviceCanUseOcca())
|
||||
{
|
||||
return occa::linalg::max<real_t, real_t>(OccaMemoryRead(data, size));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MFEM_USE_OPENMP
|
||||
if (Device::Allows(Backend::OMP_MASK))
|
||||
{
|
||||
real_t maximum = m_data[0];
|
||||
#pragma omp parallel for reduction(max : maximum)
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
maximum = fmax(maximum, m_data[i]);
|
||||
}
|
||||
return maximum;
|
||||
}
|
||||
#endif
|
||||
if (use_dev && DeviceCanUseOcca())
|
||||
{
|
||||
return occa::linalg::max<real_t, real_t>(OccaMemoryRead(data, size));
|
||||
}
|
||||
#endif
|
||||
|
||||
// normal path for everything else (cuda, hip, debug, cpu)
|
||||
real_t res = -infinity();
|
||||
reduce(
|
||||
size, res,
|
||||
[=] MFEM_HOST_DEVICE(int i, real_t &r) { r = fmax(r, m_data[i]); },
|
||||
MaxReducer<real_t> {}, use_dev, vector_workspace());
|
||||
return res;
|
||||
const auto compute_max = [&]()
|
||||
{
|
||||
real_t res = -infinity();
|
||||
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, real_t &r)
|
||||
{
|
||||
r = fmax(r, m_data[i]);
|
||||
},
|
||||
MaxReducer<real_t> {}, use_dev, vector_workspace());
|
||||
return res;
|
||||
};
|
||||
|
||||
// Device backends have top priority
|
||||
if (Device::Allows(Backend::DEVICE_MASK)) { return compute_max(); }
|
||||
|
||||
// Special path for OpenMP
|
||||
#ifdef MFEM_USE_OPENMP
|
||||
if (use_dev && Device::Allows(Backend::OMP_MASK))
|
||||
{
|
||||
real_t maximum = m_data[0];
|
||||
#pragma omp parallel for reduction(max : maximum)
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
maximum = fmax(maximum, m_data[i]);
|
||||
}
|
||||
return maximum;
|
||||
}
|
||||
#endif
|
||||
|
||||
// All other CPU backends
|
||||
return compute_max();
|
||||
}
|
||||
|
||||
real_t Vector::Sum() const
|
||||
{
|
||||
if (size == 0) { return 0.0; }
|
||||
|
||||
auto m_data = Read(UseDevice());
|
||||
real_t res = 0;
|
||||
reduce(
|
||||
size, res, [=] MFEM_HOST_DEVICE(int i, real_t &r) { r += m_data[i]; },
|
||||
const auto m_data = Read(UseDevice());
|
||||
reduce(size, res, [=] MFEM_HOST_DEVICE(int i, real_t &r)
|
||||
{
|
||||
r += m_data[i];
|
||||
},
|
||||
SumReducer<real_t> {}, UseDevice(), vector_workspace());
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace mfem
|
||||
|
||||
@@ -360,6 +360,12 @@ public:
|
||||
/// (*this)(i) = 1.0 / (*this)(i)
|
||||
void Reciprocal();
|
||||
|
||||
/// (*this)(i) = abs((*this)(i))
|
||||
void Abs();
|
||||
|
||||
/// (*this)(i) = pow((*this)(i), p)
|
||||
void Pow(const real_t p);
|
||||
|
||||
/// Swap the contents of two Vectors
|
||||
inline void Swap(Vector &other);
|
||||
|
||||
|
||||
@@ -125,11 +125,11 @@ EXAMPLE_TEST_DIRS := examples
|
||||
|
||||
MINIAPP_SUBDIRS = common electromagnetics meshing navier performance tools \
|
||||
toys nurbs gslib adjoint solvers shifted mtop parelag tribol autodiff dfem \
|
||||
hooke multidomain dpg hdiv-linear-solver spde
|
||||
hooke multidomain dpg hdiv-linear-solver spde diag-smoothers
|
||||
MINIAPP_DIRS := $(addprefix miniapps/,$(MINIAPP_SUBDIRS))
|
||||
MINIAPP_TEST_DIRS := $(filter-out %/common,$(MINIAPP_DIRS))
|
||||
MINIAPP_USE_COMMON := $(addprefix miniapps/,electromagnetics meshing tools \
|
||||
toys shifted dpg)
|
||||
toys shifted dpg diag-smoothers)
|
||||
|
||||
EM_DIRS = $(EXAMPLE_DIRS) $(MINIAPP_DIRS)
|
||||
|
||||
|
||||
+127
-181
@@ -3249,6 +3249,19 @@ int Mesh::GetPatchBdrAttribute(int i) const
|
||||
return NURBSext->GetPatchBdrAttribute(i);
|
||||
}
|
||||
|
||||
void Mesh::GetNURBSPatches(Array<NURBSPatch*> &patches)
|
||||
{
|
||||
MFEM_VERIFY(NURBSext, "Must be a NURBS mesh");
|
||||
// This sets the data in NURBSPatch(es) from the control points (Nodes)
|
||||
NURBSext->ConvertToPatches(*Nodes);
|
||||
|
||||
// Deep copy patches
|
||||
NURBSext->GetPatches(patches);
|
||||
|
||||
// Among other things, this deletes patches in NURBSext
|
||||
UpdateNURBS();
|
||||
}
|
||||
|
||||
void Mesh::FinalizeTetMesh(int generate_edges, int refine, bool fix_orientation)
|
||||
{
|
||||
FinalizeCheck();
|
||||
@@ -6273,7 +6286,7 @@ void Mesh::UpdateNURBS()
|
||||
GenerateFaces();
|
||||
}
|
||||
|
||||
void Mesh::LoadPatchTopo(std::istream &input, Array<int> &edge_to_knot)
|
||||
void Mesh::LoadPatchTopo(std::istream &input, Array<int> &edge_to_ukv)
|
||||
{
|
||||
SetEmpty();
|
||||
|
||||
@@ -6313,20 +6326,20 @@ void Mesh::LoadPatchTopo(std::istream &input, Array<int> &edge_to_knot)
|
||||
if (NumOfEdges > 0)
|
||||
{
|
||||
edge_vertex = new Table(NumOfEdges, 2);
|
||||
edge_to_knot.SetSize(NumOfEdges);
|
||||
edge_to_ukv.SetSize(NumOfEdges);
|
||||
for (int j = 0; j < NumOfEdges; j++)
|
||||
{
|
||||
int *v = edge_vertex->GetRow(j);
|
||||
input >> edge_to_knot[j] >> v[0] >> v[1];
|
||||
input >> edge_to_ukv[j] >> v[0] >> v[1];
|
||||
if (v[0] > v[1])
|
||||
{
|
||||
edge_to_knot[j] = -1 - edge_to_knot[j];
|
||||
edge_to_ukv[j] = -1 - edge_to_ukv[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
edge_to_knot.SetSize(0);
|
||||
edge_to_ukv.SetSize(0);
|
||||
}
|
||||
|
||||
skip_comment_lines(input, '#');
|
||||
@@ -6338,196 +6351,129 @@ void Mesh::LoadPatchTopo(std::istream &input, Array<int> &edge_to_knot)
|
||||
FinalizeTopology();
|
||||
CheckBdrElementOrientation(); // check and fix boundary element orientation
|
||||
|
||||
/* Generate knot 2 edge mapping -- if edges are not specified in the mesh file
|
||||
See data/two-squares-nurbs-autoedge.mesh for an example */
|
||||
if (edge_to_knot.Size() == 0)
|
||||
/* Generate edge to knotvector mapping if edges are not specified in the
|
||||
mesh file. See miniapps/nurbs/meshes/two-squares-nurbs-autoedge.mesh
|
||||
for an example */
|
||||
if (edge_to_ukv.Size() == 0)
|
||||
{
|
||||
edge_vertex = new Table(NumOfEdges, 2);
|
||||
edge_to_knot.SetSize(NumOfEdges);
|
||||
constexpr int notset = -9999999;
|
||||
edge_to_knot = notset;
|
||||
Array<int> edges;
|
||||
Array<int> oedge;
|
||||
int knot = 0;
|
||||
Array<int> ukv_to_rpkv;
|
||||
GetEdgeToUniqueKnotvector(edge_to_ukv, ukv_to_rpkv);
|
||||
}
|
||||
}
|
||||
|
||||
Array<int> edge0, edge1;
|
||||
int flip = 1;
|
||||
if (Dimension() == 2)
|
||||
void Mesh::GetEdgeToUniqueKnotvector(Array<int> &edge_to_ukv,
|
||||
Array<int> &ukv_to_rpkv) const
|
||||
{
|
||||
const int dim = Dimension(); // topological (not physical) dimension
|
||||
const int NP = NumOfElements; // number of patches
|
||||
const int NPKV = NP * dim; // number of patch knotvectors
|
||||
constexpr int notset = -9999999;
|
||||
// Sign convention
|
||||
auto sign = [](int i) { return -1 - i; };
|
||||
auto unsign = [](int i) { return (i < 0) ? -1 - i : i; };
|
||||
// Edge index -> dimension convention
|
||||
auto edge_to_dim = [](int i) { return (i < 8) ? ((i & 1) ? 1 : 0) : 2; };
|
||||
|
||||
Array<int> v(2); // vertices of an edge
|
||||
|
||||
// 1D case is special: edge index = signed element index
|
||||
// ukv_to_rpkv = Identity
|
||||
if (dim == 1)
|
||||
{
|
||||
edge_to_ukv.SetSize(NP);
|
||||
ukv_to_rpkv.SetSize(NP);
|
||||
for (int i = 0; i < NP; i++)
|
||||
{
|
||||
edge0.SetSize(2);
|
||||
edge1.SetSize(2);
|
||||
|
||||
edge0[0] = 0; edge1[0] = 2;
|
||||
edge0[1] = 1; edge1[1] = 3;
|
||||
flip = 1;
|
||||
GetElementVertices(i, v);
|
||||
// Sign is based on the edge's vertex indices
|
||||
edge_to_ukv[i] = (v[1] > v[0]) ? i : sign(i);
|
||||
ukv_to_rpkv[i] = i;
|
||||
}
|
||||
else if (Dimension() == 3)
|
||||
return;
|
||||
}
|
||||
|
||||
// Local (per-patch) variables
|
||||
Array<int> edges, oedges;
|
||||
// Edge index -> signed patch knotvector index (p*dim + d)
|
||||
Array<int> edge_to_pkv(NumOfEdges);
|
||||
edge_to_pkv.SetSize(NumOfEdges);
|
||||
edge_to_pkv = notset;
|
||||
|
||||
// Initialize pkv_map as identity - this is the storage for the
|
||||
// disjoint-set/union-find algorithm which will later be used
|
||||
// to get the map pkv_to_rpkv
|
||||
Array<int> pkv_map(NPKV);
|
||||
for (int i = 0; i < NPKV; i++)
|
||||
{
|
||||
pkv_map[i] = i;
|
||||
}
|
||||
std::function<int(int)> get_root;
|
||||
get_root = [&pkv_map, &get_root](int i) -> int
|
||||
{
|
||||
return (pkv_map[i] == i) ? i : get_root(pkv_map[i]);
|
||||
};
|
||||
auto unite = [&pkv_map, &get_root](int i, int j)
|
||||
{
|
||||
const int ri = get_root(i);
|
||||
const int rj = get_root(j);
|
||||
if (ri == rj) return;
|
||||
// keep the lowest index
|
||||
(ri < rj) ? pkv_map[rj] = ri : pkv_map[ri] = rj;
|
||||
};
|
||||
|
||||
// Get edge_to_pkv (one edge can link to multiple pkv) and pkv_map
|
||||
for (int p = 0; p < NP; p++)
|
||||
{
|
||||
GetElementEdges(p, edges, oedges);
|
||||
|
||||
// First loop checks for if edge has already been set
|
||||
for (int i = 0; i < edges.Size(); i++)
|
||||
{
|
||||
edge0.SetSize(9);
|
||||
edge1.SetSize(9);
|
||||
const int edge = edges[i];
|
||||
const int d = edge_to_dim(i);
|
||||
const int pkv = p*dim+d;
|
||||
|
||||
edge0[0] = 0; edge1[0] = 2;
|
||||
edge0[1] = 0; edge1[1] = 4;
|
||||
edge0[2] = 0; edge1[2] = 6;
|
||||
|
||||
edge0[3] = 1; edge1[3] = 3;
|
||||
edge0[4] = 1; edge1[4] = 5;
|
||||
edge0[5] = 1; edge1[5] = 7;
|
||||
|
||||
edge0[6] = 8; edge1[6] = 9;
|
||||
edge0[7] = 8; edge1[7] = 10;
|
||||
edge0[8] = 8; edge1[8] = 11;
|
||||
flip = -1;
|
||||
}
|
||||
|
||||
/* Initial assignment of knots to edges. This is an algorithm that loops over the
|
||||
patches and assigns knot vectors to edges. It starts with assigning knot vector 0
|
||||
and 1 to the edges of the first patch. Then it uses: 1) patches can share edges
|
||||
2) knot vectors on opposing edges in a patch are equal, to create edge_to_knot */
|
||||
int e0, e1, v0, v1, df;
|
||||
int p,j,k;
|
||||
for (p = 0; p < GetNE(); p++)
|
||||
{
|
||||
GetElementEdges(p, edges, oedge);
|
||||
|
||||
const int *v = elements[p]->GetVertices();
|
||||
for (j = 0; j < edges.Size(); j++)
|
||||
// We've set this edge already - link this index to it
|
||||
if (edge_to_pkv[edge] != notset)
|
||||
{
|
||||
int *vv = edge_vertex->GetRow(edges[j]);
|
||||
const int *e = elements[p]->GetEdgeVertices(j);
|
||||
if (oedge[j] == 1)
|
||||
{
|
||||
vv[0] = v[e[0]];
|
||||
vv[1] = v[e[1]];
|
||||
}
|
||||
else
|
||||
{
|
||||
vv[0] = v[e[1]];
|
||||
vv[1] = v[e[0]];
|
||||
}
|
||||
const int pkv_other = unsign(edge_to_pkv[edge]);
|
||||
unite(pkv, pkv_other);
|
||||
}
|
||||
|
||||
for (j = 0; j < edge1.Size(); j++)
|
||||
else
|
||||
{
|
||||
e0 = edges[edge0[j]];
|
||||
e1 = edges[edge1[j]];
|
||||
v0 = edge_to_knot[e0];
|
||||
v1 = edge_to_knot[e1];
|
||||
df = flip*oedge[edge0[j]]*oedge[edge1[j]];
|
||||
|
||||
// Case 1: knot vector is not set
|
||||
if ((v0 == notset) && (v1 == notset))
|
||||
{
|
||||
edge_to_knot[e0] = knot;
|
||||
edge_to_knot[e1] = knot;
|
||||
knot++;
|
||||
}
|
||||
// Case 2 & 3: knot vector on one of the two edges
|
||||
// is set earlier (in another patch). We just have
|
||||
// to copy it for the opposing edge.
|
||||
else if ((v0 != notset) && (v1 == notset))
|
||||
{
|
||||
edge_to_knot[e1] = (df >= 0 ? -v0-1 : v0);
|
||||
}
|
||||
else if ((v0 == notset) && (v1 != notset))
|
||||
{
|
||||
edge_to_knot[e0] = (df >= 0 ? -v1-1 : v1);
|
||||
}
|
||||
GetEdgeVertices(edge, v);
|
||||
// Sign is based on the edge's vertex indices
|
||||
edge_to_pkv[edge] = (v[1] > v[0]) ? pkv : sign(pkv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Verify correct assignment, make sure that corresponding edges
|
||||
within patch point to same knot vector. If not assign the lowest number.
|
||||
// Construct the pkv_to_rpkv map by finding the lowest/root index
|
||||
Array<int> pkv_to_rpkv(NPKV);
|
||||
ukv_to_rpkv.SetSize(NPKV);
|
||||
for (int i = 0; i < NPKV; i++)
|
||||
{
|
||||
pkv_to_rpkv[i] = get_root(pkv_map[i]);
|
||||
ukv_to_rpkv[i] = pkv_to_rpkv[i];
|
||||
}
|
||||
ukv_to_rpkv.Sort(); // ukv is just a renumbering of rpkv
|
||||
ukv_to_rpkv.Unique();
|
||||
|
||||
We bound the while by GetNE() + 1 as this is probably the most unlucky
|
||||
case. +1 to finish without corrections. Note that this is a check and
|
||||
in general the initial assignment is correct. Then the while is performed
|
||||
only once. Only on very tricky meshes it might need corrections.*/
|
||||
int corrections;
|
||||
int passes = 0;
|
||||
do
|
||||
{
|
||||
corrections = 0;
|
||||
for (p = 0; p < GetNE(); p++)
|
||||
{
|
||||
GetElementEdges(p, edges, oedge);
|
||||
for (j = 0; j < edge1.Size(); j++)
|
||||
{
|
||||
e0 = edges[edge0[j]];
|
||||
e1 = edges[edge1[j]];
|
||||
v0 = edge_to_knot[e0];
|
||||
v1 = edge_to_knot[e1];
|
||||
v0 = ( v0 >= 0 ? v0 : -v0-1);
|
||||
v1 = ( v1 >= 0 ? v1 : -v1-1);
|
||||
if (v0 != v1)
|
||||
{
|
||||
corrections++;
|
||||
if (v0 < v1)
|
||||
{
|
||||
edge_to_knot[e1] = (oedge[edge1[j]] >= 0 ? v0 : -v0-1);
|
||||
}
|
||||
else if (v1 < v0)
|
||||
{
|
||||
edge_to_knot[e0] = (oedge[edge0[j]] >= 0 ? v1 : -v1-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create inverse map
|
||||
std::map<int, int> rpkv_to_ukv;
|
||||
for (int i = 0; i < ukv_to_rpkv.Size(); i++)
|
||||
{
|
||||
rpkv_to_ukv[ukv_to_rpkv[i]] = i;
|
||||
}
|
||||
|
||||
passes++;
|
||||
}
|
||||
while (corrections > 0 && passes < GetNE() + 1);
|
||||
|
||||
// Check the validity of corrections applied
|
||||
if (corrections > 0)
|
||||
{
|
||||
mfem::err<<"Edge_to_knot mapping potentially incorrect"<<endl;
|
||||
mfem::err<<" passes = "<<passes<<endl;
|
||||
mfem::err<<" corrections = "<<corrections<<endl;
|
||||
}
|
||||
|
||||
/* Renumber knotvectors, such that:
|
||||
-- numbering is consecutive
|
||||
-- starts at zero */
|
||||
Array<int> cnt(NumOfEdges);
|
||||
cnt = 0;
|
||||
for (j = 0; j < NumOfEdges; j++)
|
||||
{
|
||||
k = edge_to_knot[j];
|
||||
cnt[(k >= 0 ? k : -k-1)]++;
|
||||
}
|
||||
|
||||
k = 0;
|
||||
for (j = 0; j < cnt.Size(); j++)
|
||||
{
|
||||
cnt[j] = (cnt[j] > 0 ? k++ : -1);
|
||||
}
|
||||
|
||||
for (j = 0; j < NumOfEdges; j++)
|
||||
{
|
||||
k = edge_to_knot[j];
|
||||
edge_to_knot[j] = (k >= 0 ? cnt[k]:-cnt[-k-1]-1);
|
||||
}
|
||||
|
||||
// Print knot to edge mapping
|
||||
mfem::out<<"Generated edge to knot mapping:"<<endl;
|
||||
for (j = 0; j < NumOfEdges; j++)
|
||||
{
|
||||
int *v = edge_vertex->GetRow(j);
|
||||
k = edge_to_knot[j];
|
||||
|
||||
v0 = v[0];
|
||||
v1 = v[1];
|
||||
if (k < 0)
|
||||
{
|
||||
v[0] = v1;
|
||||
v[1] = v0;
|
||||
}
|
||||
mfem::out<<(k >= 0 ? k:-k-1)<<" "<< v[0] <<" "<<v[1]<<endl;
|
||||
}
|
||||
|
||||
// Terminate here upon failure after printing to have an idea of edge_to_knot.
|
||||
if (corrections > 0 ) {mfem_error("Mesh::LoadPatchTopo");}
|
||||
// Get edge_to_ukv = edge_to_pkv -> pkv_to_rpkv -> rpkv_to_ukv
|
||||
edge_to_ukv.SetSize(NumOfEdges);
|
||||
for (int i = 0; i < NumOfEdges; i++)
|
||||
{
|
||||
const int pkv = unsign(edge_to_pkv[i]);
|
||||
const int rpkv = pkv_to_rpkv[pkv];
|
||||
const int ukv = rpkv_to_ukv[rpkv];
|
||||
edge_to_ukv[i] = (edge_to_pkv[i] < 0) ? sign(ukv) : ukv;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+31
-1
@@ -39,6 +39,7 @@ namespace mfem
|
||||
class GeometricFactors;
|
||||
class FaceGeometricFactors;
|
||||
class KnotVector;
|
||||
class NURBSPatch;
|
||||
class NURBSExtension;
|
||||
class FiniteElementSpace;
|
||||
class GridFunction;
|
||||
@@ -472,7 +473,7 @@ protected:
|
||||
const int *fine, int nfine, int op);
|
||||
|
||||
/// Read NURBS patch/macro-element mesh
|
||||
void LoadPatchTopo(std::istream &input, Array<int> &edge_to_knot);
|
||||
void LoadPatchTopo(std::istream &input, Array<int> &edge_to_ukv);
|
||||
|
||||
void UpdateNURBS();
|
||||
|
||||
@@ -789,6 +790,29 @@ public:
|
||||
/// Destroys Mesh.
|
||||
virtual ~Mesh() { DestroyPointers(); }
|
||||
|
||||
/** Get the edge to unique knotvector map used by NURBS patch topology meshes
|
||||
Various index maps are defined using the following indices:
|
||||
|
||||
edge: Edge index in the patch topology mesh
|
||||
pkv: Patch knotvector index, equivalent to (p * dim + d) where
|
||||
p is the patch index, dim is the topological dimension of
|
||||
the patch, and d is the local dimension
|
||||
rpkv: Root patch knotvector index; the lowest index pkv for all
|
||||
equivalent pkv.
|
||||
ukv: (signed) Unique knotvector index. Equivalent to rpkv reordered
|
||||
from 0 to N-1, where N is the number of unique knotvectors +
|
||||
sign, which indicates the orientation of the edge.
|
||||
@param[in,out] edge_to_ukv Array<int> Map from edge index to (signed)
|
||||
unique knotvector index. Will be resized
|
||||
to the number of edges.
|
||||
@param[in,out] ukv_to_rpkv Array<int> Map from (unsigned) unique
|
||||
knotvector index to the (unsigned) root
|
||||
patch knotvector index. Will be resized
|
||||
to the number of unique knotvectors.
|
||||
*/
|
||||
void GetEdgeToUniqueKnotvector(Array<int> &edge_to_ukv,
|
||||
Array<int> &ukv_to_rpkv) const;
|
||||
|
||||
/// @}
|
||||
|
||||
/** @anchor mfem_Mesh_named_ctors @name Named mesh constructors.
|
||||
@@ -1435,6 +1459,12 @@ public:
|
||||
/// Set the attribute of patch boundary element i, for a NURBS mesh.
|
||||
void SetPatchBdrAttribute(int i, int attr);
|
||||
|
||||
/** Returns a deep copy of all patches. This method is not const
|
||||
as it first sets the patches in NURBSext using control points
|
||||
defined by Nodes. Caller gets ownership of the returned object,
|
||||
and is responsible for deletion.*/
|
||||
void GetNURBSPatches(Array<NURBSPatch*> &patches);
|
||||
|
||||
/// Returns the type of element i.
|
||||
Element::Type GetElementType(int i) const;
|
||||
|
||||
|
||||
+61
-60
@@ -1984,7 +1984,7 @@ NURBSExtension::NURBSExtension(const NURBSExtension &orig)
|
||||
activeDof(orig.activeDof),
|
||||
patchTopo(new Mesh(*orig.patchTopo)),
|
||||
own_topo(true),
|
||||
edge_to_knot(orig.edge_to_knot),
|
||||
edge_to_ukv(orig.edge_to_ukv),
|
||||
knotVectors(orig.knotVectors.Size()), // knotVectors are copied in the body
|
||||
knotVectorsCompr(orig.knotVectorsCompr.Size()),
|
||||
weights(orig.weights),
|
||||
@@ -2025,7 +2025,7 @@ NURBSExtension::NURBSExtension(std::istream &input, bool spacing)
|
||||
{
|
||||
// Read topology
|
||||
patchTopo = new Mesh;
|
||||
patchTopo->LoadPatchTopo(input, edge_to_knot);
|
||||
patchTopo->LoadPatchTopo(input, edge_to_ukv);
|
||||
own_topo = true;
|
||||
|
||||
CheckPatches();
|
||||
@@ -2227,7 +2227,7 @@ NURBSExtension::NURBSExtension(NURBSExtension *parent, int newOrder)
|
||||
patchTopo = parent->patchTopo;
|
||||
own_topo = false;
|
||||
|
||||
parent->edge_to_knot.Copy(edge_to_knot);
|
||||
parent->edge_to_ukv.Copy(edge_to_ukv);
|
||||
|
||||
NumOfKnotVectors = parent->GetNKV();
|
||||
knotVectors.SetSize(NumOfKnotVectors);
|
||||
@@ -2285,7 +2285,7 @@ NURBSExtension::NURBSExtension(NURBSExtension *parent,
|
||||
patchTopo = parent->patchTopo;
|
||||
own_topo = false;
|
||||
|
||||
parent->edge_to_knot.Copy(edge_to_knot);
|
||||
parent->edge_to_ukv.Copy(edge_to_ukv);
|
||||
|
||||
NumOfKnotVectors = parent->GetNKV();
|
||||
MFEM_VERIFY(mOrders.Size() == NumOfKnotVectors, "invalid newOrders array");
|
||||
@@ -2344,7 +2344,7 @@ NURBSExtension::NURBSExtension(Mesh *mesh_array[], int num_pieces)
|
||||
own_topo = true;
|
||||
parent->own_topo = false;
|
||||
|
||||
parent->edge_to_knot.Copy(edge_to_knot);
|
||||
parent->edge_to_ukv.Copy(edge_to_ukv);
|
||||
|
||||
parent->GetOrders().Copy(mOrders);
|
||||
mOrder = parent->GetOrder();
|
||||
@@ -2377,70 +2377,61 @@ NURBSExtension::NURBSExtension(Mesh *mesh_array[], int num_pieces)
|
||||
}
|
||||
|
||||
NURBSExtension::NURBSExtension(const Mesh *patch_topology,
|
||||
const Array<const NURBSPatch*> p)
|
||||
const Array<const NURBSPatch*> patches_)
|
||||
{
|
||||
// Basic topology checks
|
||||
MFEM_VERIFY(patches_.Size() > 0, "Must have at least one patch");
|
||||
MFEM_VERIFY(patches_.Size() == patch_topology->GetNE(),
|
||||
"Number of patches must equal number of elements in patch_topology");
|
||||
|
||||
// Copy patch_topology mesh and NURBSPatch(es)
|
||||
patchTopo = new Mesh( *patch_topology );
|
||||
patchTopo->GetEdgeVertexTable();
|
||||
own_topo = 1;
|
||||
patches.Reserve(p.Size());
|
||||
Array<int> edges;
|
||||
Array<int> oedges;
|
||||
Array<int> kvs(3);
|
||||
edge_to_knot.SetSize(patch_topology->GetNEdges());
|
||||
NumOfKnotVectors = 0;
|
||||
NumOfElements = 0;
|
||||
for (int ielem = 0; ielem < patch_topology->GetNE(); ++ielem)
|
||||
patches.SetSize(patches_.Size());
|
||||
for (int p = 0; p < patches.Size(); p++)
|
||||
{
|
||||
patches.Append(new NURBSPatch(*p[ielem]));
|
||||
NURBSPatch& patch = *patches[ielem];
|
||||
int num_patch_elems = 1;
|
||||
for (int ikv = 0; ikv < patch.GetNKV(); ++ikv)
|
||||
{
|
||||
kvs[ikv] = knotVectors.Size();
|
||||
knotVectors.Append(new KnotVector(*patch.GetKV(ikv)));
|
||||
num_patch_elems *= patch.GetKV(ikv)->GetNE();
|
||||
++NumOfKnotVectors;
|
||||
}
|
||||
NumOfElements += num_patch_elems;
|
||||
patch_topology->GetElementEdges(ielem, edges, oedges);
|
||||
for (int iedge = 0; iedge < edges.Size(); ++iedge)
|
||||
{
|
||||
if (iedge < 8)
|
||||
{
|
||||
if (iedge & 1)
|
||||
{
|
||||
edge_to_knot[edges[iedge]] = kvs[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
edge_to_knot[edges[iedge]] = kvs[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
edge_to_knot[edges[iedge]] = kvs[2];
|
||||
}
|
||||
}
|
||||
patches[p] = new NURBSPatch(*patches_[p]);
|
||||
}
|
||||
|
||||
GenerateOffsets();
|
||||
CountBdrElements();
|
||||
NumOfActiveElems = NumOfElements;
|
||||
activeElem.SetSize(NumOfElements);
|
||||
activeElem = true;
|
||||
Array<int> ukv_to_rpkv;
|
||||
patchTopo->GetEdgeToUniqueKnotvector(edge_to_ukv, ukv_to_rpkv);
|
||||
own_topo = true;
|
||||
|
||||
CheckPatches(); // This is checking the edge_to_ukv mapping
|
||||
|
||||
// Set number of unique (not comprehensive) knot vectors
|
||||
NumOfKnotVectors = ukv_to_rpkv.Size();
|
||||
knotVectors.SetSize(NumOfKnotVectors);
|
||||
knotVectors = NULL;
|
||||
|
||||
// Assign the unique knot vectors from patches
|
||||
for (int i = 0; i < NumOfKnotVectors; i++)
|
||||
{
|
||||
// pkv = p*dim + d for an arbitrarily chosen patch p,
|
||||
// in its reference direction d
|
||||
const int pkv = ukv_to_rpkv[i];
|
||||
const int p = pkv / Dimension();
|
||||
const int d = pkv % Dimension();
|
||||
knotVectors[i] = new KnotVector(*patches[p]->GetKV(d));
|
||||
}
|
||||
|
||||
CreateComprehensiveKV();
|
||||
SetOrdersFromKnotVectors();
|
||||
|
||||
GenerateOffsets();
|
||||
CountElements();
|
||||
CountBdrElements();
|
||||
|
||||
NumOfActiveElems = NumOfElements;
|
||||
activeElem.SetSize(NumOfElements);
|
||||
activeElem = true;
|
||||
|
||||
GenerateActiveVertices();
|
||||
InitDofMap();
|
||||
GenerateElementDofTable();
|
||||
GenerateActiveBdrElems();
|
||||
GenerateBdrElementDofTable();
|
||||
|
||||
weights.SetSize(GetNDof());
|
||||
|
||||
CheckPatches();
|
||||
ConnectBoundaries();
|
||||
}
|
||||
|
||||
NURBSExtension::~NURBSExtension()
|
||||
@@ -2481,7 +2472,7 @@ void NURBSExtension::Print(std::ostream &os, const std::string &comments) const
|
||||
}
|
||||
|
||||
const int version = kvSpacing.Size() > 0 ? 11 : 10; // v1.0 or v1.1
|
||||
patchTopo->PrintTopo(os, edge_to_knot, version, comments);
|
||||
patchTopo->PrintTopo(os, edge_to_ukv, version, comments);
|
||||
if (patches.Size() == 0)
|
||||
{
|
||||
os << "\nknotvectors\n" << NumOfKnotVectors << '\n';
|
||||
@@ -2936,7 +2927,7 @@ void NURBSExtension::CheckPatches()
|
||||
|
||||
for (int i = 0; i < edges.Size(); i++)
|
||||
{
|
||||
edges[i] = edge_to_knot[edges[i]];
|
||||
edges[i] = edge_to_ukv[edges[i]];
|
||||
if (oedge[i] < 0)
|
||||
{
|
||||
edges[i] = -1 - edges[i];
|
||||
@@ -2954,7 +2945,7 @@ void NURBSExtension::CheckPatches()
|
||||
edges[8] != edges[11])))
|
||||
{
|
||||
mfem::err << "NURBSExtension::CheckPatch (patch = " << p
|
||||
<< ")\n Inconsistent edge-to-knot mapping!\n";
|
||||
<< ")\n Inconsistent edge-to-knotvector mapping!";
|
||||
mfem_error();
|
||||
}
|
||||
}
|
||||
@@ -2971,7 +2962,7 @@ void NURBSExtension::CheckBdrPatches()
|
||||
|
||||
for (int i = 0; i < edges.Size(); i++)
|
||||
{
|
||||
edges[i] = edge_to_knot[edges[i]];
|
||||
edges[i] = edge_to_ukv[edges[i]];
|
||||
if (oedge[i] < 0)
|
||||
{
|
||||
edges[i] = -1 - edges[i];
|
||||
@@ -4878,6 +4869,16 @@ void NURBSExtension::GetElementIJK(int elem, Array<int> & ijk)
|
||||
el_to_IJK.GetRow(elem, ijk);
|
||||
}
|
||||
|
||||
void NURBSExtension::GetPatches(Array<NURBSPatch*> &patches_copy)
|
||||
{
|
||||
const int NP = patches.Size();
|
||||
patches_copy.SetSize(NP);
|
||||
for (int p = 0; p < NP; p++)
|
||||
{
|
||||
patches_copy[p] = new NURBSPatch(*GetPatch(p));
|
||||
}
|
||||
}
|
||||
|
||||
void NURBSExtension::SetPatchToElements()
|
||||
{
|
||||
const int np = GetNP();
|
||||
@@ -4982,7 +4983,7 @@ ParNURBSExtension::ParNURBSExtension(MPI_Comm comm, NURBSExtension *parent,
|
||||
own_topo = true;
|
||||
parent->own_topo = false;
|
||||
|
||||
parent->edge_to_knot.Copy(edge_to_knot);
|
||||
parent->edge_to_ukv.Copy(edge_to_ukv);
|
||||
|
||||
parent->GetOrders().Copy(mOrders);
|
||||
mOrder = parent->GetOrder();
|
||||
@@ -5045,7 +5046,7 @@ ParNURBSExtension::ParNURBSExtension(NURBSExtension *parent,
|
||||
own_topo = parent->own_topo;
|
||||
parent->own_topo = false;
|
||||
|
||||
Swap(edge_to_knot, parent->edge_to_knot);
|
||||
Swap(edge_to_ukv, parent->edge_to_ukv);
|
||||
|
||||
NumOfKnotVectors = parent->NumOfKnotVectors;
|
||||
Swap(knotVectors, parent->knotVectors);
|
||||
|
||||
+17
-6
@@ -469,7 +469,7 @@ protected:
|
||||
/// Orders of all KnotVectors
|
||||
Array<int> mOrders;
|
||||
|
||||
/// Number of KnotVectors
|
||||
/// Number of unique (not comprehensive) KnotVectors
|
||||
int NumOfKnotVectors;
|
||||
|
||||
/// Global entity counts
|
||||
@@ -490,8 +490,8 @@ protected:
|
||||
/// Whether this object owns patchTopo
|
||||
bool own_topo;
|
||||
|
||||
/// Map from edge indices to KnotVector indices
|
||||
Array<int> edge_to_knot;
|
||||
/// Map from patchTopo edge indices to unique KnotVector indices
|
||||
Array<int> edge_to_ukv;
|
||||
|
||||
/// Set of unique KnotVectors
|
||||
Array<KnotVector *> knotVectors;
|
||||
@@ -555,7 +555,7 @@ protected:
|
||||
if the KnotVector index associated with edge @a edge is negative. */
|
||||
inline const KnotVector *KnotVec(int edge, int oedge, int *okv) const;
|
||||
|
||||
/// Throw an error if any patch has an inconsistent edge-to-knot mapping.
|
||||
/// Throw an error if any patch has an inconsistent edge_to_ukv mapping.
|
||||
void CheckPatches();
|
||||
|
||||
/// Throw an error if any boundary patch has invalid KnotVector orientation.
|
||||
@@ -673,6 +673,9 @@ protected:
|
||||
/// Set @a patch_to_bel.
|
||||
void SetPatchToBdrElements();
|
||||
|
||||
/// Return NURBSPatch object; returned object should NOT be deleted.
|
||||
const NURBSPatch* GetPatch(int patch) const { return patches[patch]; }
|
||||
|
||||
/// To be used by ParNURBSExtension constructor(s)
|
||||
NURBSExtension() : el_dof(nullptr), bel_dof(nullptr) { }
|
||||
|
||||
@@ -911,6 +914,14 @@ public:
|
||||
Cartesian order. */
|
||||
void GetPatchDofs(const int patch, Array<int> &dofs);
|
||||
|
||||
/// Returns a deep copy of the patch topology mesh
|
||||
Mesh GetPatchTopology() const { return Mesh(*patchTopo); }
|
||||
|
||||
/** Returns a deep copy of all instantiated patches. To ensure that patches
|
||||
are instantiated, use Mesh::GetNURBSPatches() instead. Caller gets
|
||||
ownership of the returned object, and is responsible for deletion.*/
|
||||
void GetPatches(Array<NURBSPatch*> &patches);
|
||||
|
||||
/// Return the array of indices of all elements in patch @a patch.
|
||||
const Array<int>& GetPatchElements(int patch);
|
||||
/// Return the array of indices of all boundary elements in patch @a patch.
|
||||
@@ -1138,7 +1149,7 @@ inline const real_t &NURBSPatch::operator()(int i, int j, int k, int l) const
|
||||
|
||||
inline int NURBSExtension::KnotInd(int edge) const
|
||||
{
|
||||
int kv = edge_to_knot[edge];
|
||||
int kv = edge_to_ukv[edge];
|
||||
return (kv >= 0) ? kv : (-1-kv);
|
||||
}
|
||||
|
||||
@@ -1155,7 +1166,7 @@ inline const KnotVector *NURBSExtension::KnotVec(int edge) const
|
||||
inline const KnotVector *NURBSExtension::KnotVec(int edge, int oedge, int *okv)
|
||||
const
|
||||
{
|
||||
int kv = edge_to_knot[edge];
|
||||
int kv = edge_to_ukv[edge];
|
||||
if (kv >= 0)
|
||||
{
|
||||
*okv = oedge;
|
||||
|
||||
@@ -38,3 +38,4 @@ add_subdirectory(hooke)
|
||||
add_subdirectory(dpg)
|
||||
add_subdirectory(hdiv-linear-solver)
|
||||
add_subdirectory(dfem)
|
||||
add_subdirectory(diag-smoothers)
|
||||
|
||||
@@ -253,6 +253,23 @@ void AttrToMarker(int max_attr, const Array<int> &attrs, Array<int> &marker)
|
||||
}
|
||||
}
|
||||
|
||||
void AffineTransformation::Eval(Vector &V, ElementTransformation &T,
|
||||
const IntegrationPoint &ip)
|
||||
{
|
||||
V = 0.0;
|
||||
T.Transform(ip, x);
|
||||
|
||||
if (A.Height() == vdim)
|
||||
{
|
||||
A.Mult(x, V);
|
||||
}
|
||||
|
||||
if (b.Size() == vdim)
|
||||
{
|
||||
V.Add(1.0, b);
|
||||
}
|
||||
}
|
||||
|
||||
void KershawTransformation::Eval(Vector &V, ElementTransformation &T,
|
||||
const IntegrationPoint &ip)
|
||||
{
|
||||
|
||||
@@ -37,6 +37,38 @@ void MergeMeshNodes(Mesh * mesh, int logging);
|
||||
marker array will contain all ones. */
|
||||
void AttrToMarker(int max_attr, const Array<int> &attrs, Array<int> &marker);
|
||||
|
||||
/// Transform a mesh according to an arbitrary affine transformation
|
||||
/// y = A x + b
|
||||
/// Where A is a spaceDim x spaceDim matrix and b is a vector of size spaceDim.
|
||||
/// If A is of size zero the transformation will be y = b.
|
||||
/// If b is of size zero the transformation will be y = A x.
|
||||
///
|
||||
/// Note that no error checking related to the determinant of A is performed.
|
||||
/// If A has a non-positive determinant it is likely to produce an invalid
|
||||
/// transformed mesh.
|
||||
class AffineTransformation : public VectorCoefficient
|
||||
{
|
||||
private:
|
||||
DenseMatrix A;
|
||||
Vector b;
|
||||
Vector x;
|
||||
|
||||
public:
|
||||
AffineTransformation(int dim_, const DenseMatrix &A_, const Vector & b_)
|
||||
: VectorCoefficient(dim_), A(A_), b(b_), x(dim_)
|
||||
{
|
||||
MFEM_VERIFY((A.Height() == dim_ && A.Width() == dim_) ||
|
||||
(A.Height() == 0 && A.Width() == 0),
|
||||
"Affine transformation given an invalid matrix");
|
||||
MFEM_VERIFY(b.Size() == dim_ || b.Size() == 0,
|
||||
"Affine transformation given an invalid vector");
|
||||
}
|
||||
|
||||
void Eval(Vector &V, ElementTransformation &T,
|
||||
const IntegrationPoint &ip) override;
|
||||
|
||||
using VectorCoefficient::Eval;
|
||||
};
|
||||
|
||||
/// Generalized Kershaw mesh transformation in 2D and 3D, see D. Kershaw,
|
||||
/// "Differencing of the diffusion equation in Lagrangian hydrodynamic codes",
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
# at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
# LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
#
|
||||
# This file is part of the MFEM library. For more information and source code
|
||||
# availability visit https://mfem.org.
|
||||
#
|
||||
# MFEM is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
if(MFEM_USE_MPI)
|
||||
add_custom_target(clean_common
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Cleaning common directory..."
|
||||
COMMAND make -C ${CMAKE_CURRENT_SOURCE_DIR}/../common clean
|
||||
COMMENT "Cleaning common directory..."
|
||||
)
|
||||
|
||||
# Define miniapps
|
||||
set(MINIAPPS
|
||||
abs-l1-jacobi
|
||||
mg-abs-l1-jacobi
|
||||
)
|
||||
|
||||
# Add miniapps
|
||||
foreach(APP ${MINIAPPS})
|
||||
add_mfem_miniapp(${APP}
|
||||
MAIN ${APP}.cpp
|
||||
${MFEM_MINIAPPS_COMMON_HEADERS}
|
||||
EXTRA_SOURCES ds-common.cpp
|
||||
EXTRA_HEADERS ds-common.hpp
|
||||
LIBRARIES mfem-common mfem
|
||||
)
|
||||
endforeach()
|
||||
|
||||
# Add the corresponding tests to the "test" target
|
||||
if (MFEM_ENABLE_TESTING)
|
||||
add_test(NAME abs-l1-jacobi_np${MFEM_MPI_NP}
|
||||
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
|
||||
${MPIEXEC_PREFLAGS}
|
||||
$<TARGET_FILE:abs-l1-jacobi> -m ../../data/ref-cube.mesh -rs 2 -rp 2
|
||||
-s 1 -i 1 -a 3 -pc 1 -no-mon -no-vis
|
||||
${MPIEXEC_POSTFLAGS})
|
||||
|
||||
add_test(NAME mg-abs-l1-jacobi_np${MFEM_MPI_NP}
|
||||
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
|
||||
${MPIEXEC_PREFLAGS}
|
||||
$<TARGET_FILE:mg-abs-l1-jacobi> -m ../../data/ref-cube.mesh -rs 2 -rp 1
|
||||
-ol 1 -gl 1 -s 1 -i 1 -a 3 -no-mon -no-vis
|
||||
${MPIEXEC_POSTFLAGS})
|
||||
endif()
|
||||
|
||||
# Add custom target for cleaning everything
|
||||
add_custom_target(clean_all
|
||||
COMMAND ${CMAKE_MAKE_PROGRAM} clean
|
||||
COMMAND ${CMAKE_MAKE_PROGRAM} -C ${CMAKE_CURRENT_SOURCE_DIR}/../common clean
|
||||
COMMENT "Cleaning all build artifacts..."
|
||||
)
|
||||
endif()
|
||||
@@ -0,0 +1,48 @@
|
||||
```
|
||||
Finite Element Discretization Library
|
||||
__
|
||||
_ __ ___ / _| ___ _ __ ___
|
||||
| '_ ` _ \ | |_ / _ \| '_ ` _ \
|
||||
| | | | | || _|| __/| | | | | |
|
||||
|_| |_| |_||_| \___||_| |_| |_|
|
||||
https://mfem.org
|
||||
```
|
||||
|
||||
This directory contains some drivers reimplementing basic examples in MFEM,
|
||||
making use of the Abs-Value-L(1)-Jacobi family of preconditioners/smoothers.
|
||||
|
||||
Make sure you are familiar with the following examples:
|
||||
- `ex1p` [Laplace Problem](https://github.com/mfem/mfem/blob/master/examples/ex1p.cpp)
|
||||
- `ex2p` [Linear Elasticity](https://github.com/mfem/mfem/blob/master/examples/ex2p.cpp)
|
||||
- `ex3p` [Definite Maxwell Problem](https://github.com/mfem/mfem/blob/master/examples/ex3p.cpp)
|
||||
- `ex26p` [Multigrid Preconditioner](https://github.com/mfem/mfem/blob/master/examples/ex26p.cpp)
|
||||
|
||||
The code has *two* drivers: `abs-l1-jacobi` and `mg-abs-l1-jacobi`. All these
|
||||
drivers have the capability to solve the following problems:
|
||||
- An L2-projection into a conforming H1-space.
|
||||
- A diffusion problem.
|
||||
- A linear elasticity problem.
|
||||
- A definite Maxwell problem.
|
||||
|
||||
For later reference, we say a smoother `M` is `A`-convergent if `M + M^T - A` is
|
||||
SPD, this is `(Ax,x) < (Mx, x) + (M^T x, x) = 2 (Mx,x)`. It suffices to find a
|
||||
constant `c < 2` such that `(Ax,x) < c(Mx,x)` to say that `M` is `A`-convergent.
|
||||
|
||||
# Absolute-value L(1)-Jacobi preconditioner for different assembly levels
|
||||
|
||||
Our interest lies on `AssemblyLevel::PARTIAL`. As the FEM operator has the
|
||||
structure `A = P^T G^T B^T D B G P` (see [this](https://mfem.org/performance/)),
|
||||
and the standard L(1)-Jacobi can be writen as `D_1 = diag( |A|1 ). A triangle
|
||||
inequality implies `D_{abs} = diag( |P^T| |G^T| |B^T| |D| |B| |G| |P| 1 )` is
|
||||
also `A`-convergent.
|
||||
|
||||
The MFEM interface allows to make use of `AbsMult` with the purpose of unwrap a
|
||||
composition (of different kind) of operators as their absolute-value
|
||||
application. Similar run-time options are available.
|
||||
|
||||
# Multigrid wrapper
|
||||
|
||||
The driver `mg-abs-l1-jacobi` is basically the multigrid counterparts of the
|
||||
previously mentioned driver. The wrapper (akin to
|
||||
[`ex26p`](https://github.com/mfem/mfem/blob/master/examples/ex26p.cpp))
|
||||
allows the user to do geometric refinement or order refinement.
|
||||
@@ -0,0 +1,432 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
//
|
||||
// -----------------------------------------
|
||||
// Absolute L(1)-Jacobi smoothers miniapp
|
||||
// -----------------------------------------
|
||||
//
|
||||
// This miniapp illustrates the implementation of an (slightly generalized)
|
||||
// absolute-L(1) Jacobi preconditioner. This preconditioner is tested in
|
||||
// different settings. We use Stationary Linear Iterations and Preconditioned
|
||||
// Conjugate Gradient as the main solvers.
|
||||
// We consider a H1-mass matrix, a diffusion matrix, and a definite Maxwell system.
|
||||
//
|
||||
// The preconditioner can be defined at run-time. Similarly, the mesh can be
|
||||
// modified by a Kershaw transformation at run-time. Relative tolerance and
|
||||
// maximum number of iterations can be modified as well.
|
||||
//
|
||||
// Compile with: make abs-l1-jacobi
|
||||
//
|
||||
// Sample runs:
|
||||
// mpirun -np 4 abs-l1-jacobi
|
||||
// mpirun -np 4 abs-l1-jacobi -s 0 -i 0
|
||||
// mpirun -np 4 abs-l1-jacobi -m ../meshing/icf.mesh -f 0.5
|
||||
// mpirun -np 4 abs-l1-jacobi -rs 3 -rp 1
|
||||
// mpirun -np 4 abs-l1-jacobi -t 1e-5 -ni 100
|
||||
// mpirun -np 4 abs-l1-jacobi -m ../../data/beam-quad.mesh -a 3 -Ky 0.5 -Kz 0.5
|
||||
// mpirun -np 4 abs-l1-jacobi --device cuda
|
||||
|
||||
#include "ds-common.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
using namespace ds_common;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// 1. Initialize MPI and HYPRE.
|
||||
Mpi::Init(argc, argv);
|
||||
Hypre::Init();
|
||||
|
||||
// 2. Parse command line options.
|
||||
string mesh_file = "../../data/ref-cube.mesh";
|
||||
// System properties
|
||||
int order = 1;
|
||||
SolverType solver_type = cg;
|
||||
IntegratorType integrator_type = diffusion;
|
||||
PCType pc_type = abs_global;
|
||||
int assembly_type_int = 3; // Default is PARTIAL
|
||||
AssemblyLevel assembly_type;
|
||||
// Number of refinements
|
||||
int refine_serial = 4;
|
||||
int refine_parallel = 0;
|
||||
// Preconditioner parameters, only for L(p,q)-Jacobi
|
||||
real_t p_order = 1.0;
|
||||
real_t q_order = 0.0;
|
||||
// Solver parameters
|
||||
real_t rel_tol = 1e-10;
|
||||
real_t max_iter = 3000;
|
||||
// Kershaw Transformation
|
||||
real_t eps_y = 0.0;
|
||||
real_t eps_z = 0.0;
|
||||
// Other options
|
||||
string device_config = "cpu";
|
||||
bool use_monitor = false;
|
||||
bool visualization = true;
|
||||
|
||||
// Construct argument parser
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&mesh_file, "-m", "--mesh",
|
||||
"Mesh file to use.");
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
"Finite element order (polynomial degree)");
|
||||
args.AddOption((int*)&solver_type, "-s", "--solver",
|
||||
"Solvers to be considered:"
|
||||
"\n\t0: Stationary Linear Iteration"
|
||||
"\n\t1: Preconditioned Conjugate Gradient");
|
||||
args.AddOption((int*)&integrator_type, "-i", "--integrator",
|
||||
"Integrators to be considered:"
|
||||
"\n\t0: MassIntegrator"
|
||||
"\n\t1: DiffusionIntegrator"
|
||||
"\n\t2: CurlCurlIntegrator + VectorFEMassIntegrator");
|
||||
args.AddOption(&assembly_type_int, "-a", "--assembly",
|
||||
"Assembly level to be considered:"
|
||||
"\n\t0: LEGACY"
|
||||
"\n\t1: FULL"
|
||||
"\n\t2: ELEMENT"
|
||||
"\n\t3: PARTIAL"
|
||||
"\n\t4: NONE");
|
||||
args.AddOption((int*)&pc_type, "-pc", "--preconditioner",
|
||||
"Preconditioners to be considered:"
|
||||
"\n\t0: No preconditioner"
|
||||
"\n\t1: Absolute L(1)-Jacobi preconditioner"
|
||||
"\n\t2: Element L(p,q)-Jacobi preconditioner");
|
||||
args.AddOption(&refine_serial, "-rs", "--refine-serial",
|
||||
"Number of serial refinements");
|
||||
args.AddOption(&refine_parallel, "-rp", "--refine-parallel",
|
||||
"Number of parallel refinements");
|
||||
args.AddOption(&p_order, "-p", "--p-order",
|
||||
"P-order for L(p,q)-Jacobi preconditioner");
|
||||
args.AddOption(&q_order, "-q", "--q-order",
|
||||
"Q-order for L(p,q)-Jacobi preconditioner");
|
||||
args.AddOption(&rel_tol, "-t", "--tolerance",
|
||||
"Relative tolerance for the iterative solver");
|
||||
args.AddOption(&max_iter, "-ni", "--iterations",
|
||||
"Maximum number of iterations");
|
||||
args.AddOption(&eps_y, "-Ky", "--Kershaw-y",
|
||||
"Kershaw transform factor, eps_y in (0,1]");
|
||||
args.AddOption(&eps_z, "-Kz", "--Kershaw-z",
|
||||
"Kershaw transform factor, eps_z in (0,1]");
|
||||
args.AddOption(&freq, "-f", "--frequency", "Set the frequency for the exact"
|
||||
" solution.");
|
||||
args.AddOption(&device_config, "-d", "--device",
|
||||
"Device configuration string, see Device::Configure().");
|
||||
args.AddOption(&use_monitor, "-mon", "--monitor", "-no-mon",
|
||||
"--no-monitor",
|
||||
"Enable or disable Data Monitor.");
|
||||
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.ParseCheck();
|
||||
|
||||
MFEM_VERIFY(p_order > 0.0, "p needs to be positive");
|
||||
MFEM_VERIFY((0 <= solver_type) && (solver_type < num_solvers),
|
||||
"invalid solver type: " << solver_type);
|
||||
MFEM_VERIFY((0 <= integrator_type) && (integrator_type < num_integrators),
|
||||
"invalid integrator type: " << integrator_type);
|
||||
MFEM_VERIFY((0 <= assembly_type_int) && (assembly_type_int < 5),
|
||||
"invalid assembly type: " << assembly_type_int);
|
||||
MFEM_VERIFY((0 <= pc_type) && (pc_type < num_pc),
|
||||
"invalid preconditioner type: " << pc_type);
|
||||
MFEM_VERIFY((0.0 <= eps_y) && (eps_y <= 1.0), "eps_y must be in [0,1]");
|
||||
MFEM_VERIFY((0.0 <= eps_z) && (eps_z <= 1.0), "eps_z must be in [0,1]");
|
||||
|
||||
kappa = freq * M_PI;
|
||||
|
||||
ostringstream file_name;
|
||||
if (use_monitor)
|
||||
{
|
||||
file_name << "ABS-"
|
||||
<< "O" << order
|
||||
<< "I" << (int) integrator_type
|
||||
<< "S" << (int) solver_type
|
||||
<< "A" << assembly_type_int
|
||||
<< ".csv";
|
||||
}
|
||||
|
||||
switch (assembly_type_int)
|
||||
{
|
||||
case 0:
|
||||
assembly_type = AssemblyLevel::LEGACY;
|
||||
break;
|
||||
case 1:
|
||||
assembly_type = AssemblyLevel::FULL;
|
||||
break;
|
||||
case 2:
|
||||
assembly_type = AssemblyLevel::ELEMENT;
|
||||
break;
|
||||
case 3:
|
||||
assembly_type = AssemblyLevel::PARTIAL;
|
||||
break;
|
||||
case 4:
|
||||
assembly_type = AssemblyLevel::NONE;
|
||||
break;
|
||||
default:
|
||||
MFEM_ABORT("Unsupported option!");
|
||||
}
|
||||
|
||||
Device device(device_config);
|
||||
if (Mpi::Root()) { device.Print(); }
|
||||
|
||||
// 3. Read the serial mesh from the given mesh file. The number of serial and
|
||||
// parallel refinements can be set by the user on the command line.
|
||||
Mesh *serial_mesh = new Mesh(mesh_file);
|
||||
for (int ls = 0; ls < refine_serial; ls++)
|
||||
{
|
||||
serial_mesh->UniformRefinement();
|
||||
}
|
||||
|
||||
// 4. Define a parallel mesh by a partitioning of the serial mesh. The number
|
||||
// of parallel refinements can be set by the user. If defined, apply
|
||||
// Kershaw transformation.
|
||||
ParMesh *mesh = new ParMesh(MPI_COMM_WORLD, *serial_mesh);
|
||||
delete serial_mesh;
|
||||
for (int lp = 0; lp < refine_parallel; lp++)
|
||||
{
|
||||
mesh->UniformRefinement();
|
||||
}
|
||||
|
||||
dim = mesh->Dimension();
|
||||
space_dim = mesh->SpaceDimension();
|
||||
|
||||
bool cond_z = (dim < 3) ? true : (eps_z != 0); // lazy check
|
||||
if (eps_y != 0.0 && cond_z)
|
||||
{
|
||||
if (dim < 3) { eps_z = 0.0; }
|
||||
common::KershawTransformation kershawT(dim, eps_y, eps_z);
|
||||
mesh->Transform(kershawT);
|
||||
}
|
||||
|
||||
// 5. Define a finite element space on the mesh. We use different spaces and
|
||||
// collections for different systems.
|
||||
// - H1-conforming Lagrange elements for the H1-mass matrix and the
|
||||
// diffusion problem.
|
||||
// - H(curl)-conforming Nedelec elements for the definite Maxwell problem.
|
||||
FiniteElementCollection *fec;
|
||||
ParFiniteElementSpace *fespace;
|
||||
|
||||
switch (integrator_type)
|
||||
{
|
||||
case mass:
|
||||
case diffusion:
|
||||
fec = new H1_FECollection(order, dim);
|
||||
fespace = new ParFiniteElementSpace(mesh, fec);
|
||||
break;
|
||||
case maxwell:
|
||||
fec = new ND_FECollection(order, dim);
|
||||
fespace = new ParFiniteElementSpace(mesh, fec);
|
||||
break;
|
||||
default:
|
||||
mfem_error("Invalid integrator type! Check FiniteElementCollection");
|
||||
}
|
||||
|
||||
HYPRE_BigInt sys_size = fespace->GlobalTrueVSize();
|
||||
if (Mpi::Root())
|
||||
{
|
||||
mfem::out << "Number of unknowns: " << sys_size << endl;
|
||||
}
|
||||
|
||||
// 6. Extract the list of the essential boundary DoFs. We mark all boundary
|
||||
// attibutes as essential. Then we get the list of essential DoFs.
|
||||
Array<int> ess_tdof_list;
|
||||
Array<int> ess_bdr(mesh->bdr_attributes.Max());
|
||||
if (mesh->bdr_attributes.Size())
|
||||
{
|
||||
ess_bdr = 1;
|
||||
fespace->GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
|
||||
}
|
||||
|
||||
// 7. Define the linear system. Set up the bilinear form a(.,.) and the
|
||||
// linear form b(.). The currently implemented systems are the following:
|
||||
// - (u,v), i.e., L2-projection.
|
||||
// - (grad(u), grad(v)), i.e., diffusion operator.
|
||||
// - (curl(u), curl(v)) + (u,v), i.e., definite Maxwell operator.
|
||||
// The linear form has the standard form (f,v).
|
||||
// Also, define the matrices and vectors associated with the forms, and
|
||||
// project the required boundary data into the GridFunction solution.
|
||||
ParBilinearForm *a = new ParBilinearForm(fespace);
|
||||
ParLinearForm *b = new ParLinearForm(fespace);
|
||||
|
||||
// These pointers are owned by the forms
|
||||
LinearFormIntegrator *lfi = nullptr;
|
||||
BilinearFormIntegrator *bfi = nullptr;
|
||||
// Required for a static_cast
|
||||
SumIntegrator *sum_bfi = nullptr;
|
||||
|
||||
// These pointers are not owned by the integrators
|
||||
FunctionCoefficient *scalar_u = nullptr;
|
||||
FunctionCoefficient *scalar_f = nullptr;
|
||||
VectorFunctionCoefficient *vector_u = nullptr;
|
||||
VectorFunctionCoefficient *vector_f = nullptr;
|
||||
|
||||
ConstantCoefficient one(1.0);
|
||||
|
||||
// These variables will define the linear system
|
||||
ParGridFunction x(fespace), y(fespace);
|
||||
OperatorPtr A;
|
||||
Vector B, X;
|
||||
|
||||
x = 0.0;
|
||||
|
||||
switch (integrator_type)
|
||||
{
|
||||
case mass:
|
||||
scalar_u = new FunctionCoefficient(diffusion_solution);
|
||||
lfi = new DomainLFIntegrator(*scalar_u);
|
||||
bfi = new MassIntegrator(one);
|
||||
x.ProjectBdrCoefficient(*scalar_u, ess_bdr);
|
||||
break;
|
||||
case diffusion:
|
||||
scalar_u = new FunctionCoefficient(diffusion_solution);
|
||||
scalar_f = new FunctionCoefficient(diffusion_source);
|
||||
lfi = new DomainLFIntegrator(*scalar_f);
|
||||
bfi = new DiffusionIntegrator(one);
|
||||
x.ProjectBdrCoefficient(*scalar_u, ess_bdr);
|
||||
break;
|
||||
case maxwell:
|
||||
vector_u = new VectorFunctionCoefficient(space_dim, maxwell_solution);
|
||||
vector_f = new VectorFunctionCoefficient(space_dim, maxwell_source);
|
||||
lfi = new VectorFEDomainLFIntegrator(*vector_f);
|
||||
bfi = new SumIntegrator();
|
||||
sum_bfi = static_cast<SumIntegrator*>(bfi);
|
||||
sum_bfi->AddIntegrator(new CurlCurlIntegrator(one));
|
||||
sum_bfi->AddIntegrator(new VectorFEMassIntegrator(one));
|
||||
x.ProjectBdrCoefficientTangent(*vector_u, ess_bdr);
|
||||
break;
|
||||
default:
|
||||
mfem_error("Invalid integrator type! Check ParLinearForm");
|
||||
}
|
||||
|
||||
a->SetAssemblyLevel(assembly_type);
|
||||
a->AddDomainIntegrator(bfi);
|
||||
a->Assemble();
|
||||
|
||||
b->AddDomainIntegrator(lfi);
|
||||
b->Assemble();
|
||||
|
||||
a->FormLinearSystem(ess_tdof_list, x, *b, A, X, B);
|
||||
|
||||
// 8. Construct the preconditioner. Uses AbsMult to construct an appoximation
|
||||
// of the diagonal of the matrix.
|
||||
|
||||
Solver *jacobi = nullptr;
|
||||
Vector ones(fespace->GetTrueVSize());
|
||||
Vector diag(fespace->GetTrueVSize());
|
||||
|
||||
switch (pc_type)
|
||||
{
|
||||
case none:
|
||||
break;
|
||||
case abs_global:
|
||||
ones = 1.0;
|
||||
A->AbsMult(ones, diag);
|
||||
jacobi = new OperatorJacobiSmoother(diag, ess_tdof_list);
|
||||
break;
|
||||
case pq_element:
|
||||
AssembleElementLpqJacobiDiag(*a, p_order, q_order, diag);
|
||||
jacobi = new OperatorJacobiSmoother(diag, ess_tdof_list);
|
||||
break;
|
||||
default:
|
||||
mfem_error("Invalid preconditioner type!");
|
||||
}
|
||||
|
||||
// 9. Construct the solver. The implemented solvers are the following:
|
||||
// - Stationary Linear Iteration
|
||||
// - Preconditioned Conjugate Gradient
|
||||
// Then, solve the system with the used-selected solver.
|
||||
Solver *solver = nullptr;
|
||||
DataMonitor *monitor = nullptr;
|
||||
|
||||
switch (solver_type)
|
||||
{
|
||||
case sli:
|
||||
solver = new SLISolver(MPI_COMM_WORLD);
|
||||
break;
|
||||
case cg:
|
||||
solver = new CGSolver(MPI_COMM_WORLD);
|
||||
break;
|
||||
default:
|
||||
mfem_error("Invalid solver type!");
|
||||
}
|
||||
solver->SetOperator(*A);
|
||||
|
||||
IterativeSolver *it_solver = dynamic_cast<IterativeSolver *>(solver);
|
||||
if (it_solver)
|
||||
{
|
||||
it_solver->SetRelTol(rel_tol);
|
||||
it_solver->SetMaxIter(max_iter);
|
||||
it_solver->SetPrintLevel(1);
|
||||
if (use_monitor)
|
||||
{
|
||||
monitor = new DataMonitor(file_name.str(), MONITOR_DIGITS);
|
||||
it_solver->SetMonitor(*monitor);
|
||||
}
|
||||
if (jacobi)
|
||||
{
|
||||
it_solver->SetPreconditioner(*jacobi);
|
||||
}
|
||||
}
|
||||
|
||||
solver->Mult(B, X);
|
||||
|
||||
// 10. Recover the solution x as a grid function. Send the data by socket to
|
||||
// a GLVis server.
|
||||
a->RecoverFEMSolution(X, *b, x);
|
||||
|
||||
if (visualization)
|
||||
{
|
||||
char vishost[] = "localhost";
|
||||
int visport = 19916;
|
||||
socketstream sol_sock(vishost, visport);
|
||||
|
||||
sol_sock << "parallel " << Mpi::WorldSize() << " " << Mpi::WorldRank()
|
||||
<< "\n";
|
||||
sol_sock.precision(8);
|
||||
sol_sock << "solution\n" << *mesh << x << flush;
|
||||
}
|
||||
|
||||
// 11. Compute and print the L^2 norm of the error.
|
||||
{
|
||||
real_t error = 0.0;
|
||||
switch (integrator_type)
|
||||
{
|
||||
case mass:
|
||||
case diffusion:
|
||||
error = x.ComputeL2Error(*scalar_u);
|
||||
break;
|
||||
case maxwell:
|
||||
error = x.ComputeL2Error(*vector_u);
|
||||
break;
|
||||
default:
|
||||
mfem_error("Invalid integrator type! Check ComputeL2Error");
|
||||
}
|
||||
if (Mpi::Root())
|
||||
{
|
||||
mfem::out << "\n|| u_h - u ||_{L^2} = " << error << "\n" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
// 12. Free the memory used.
|
||||
delete solver;
|
||||
if (jacobi) { delete jacobi; }
|
||||
delete a;
|
||||
delete b;
|
||||
delete fespace;
|
||||
delete fec;
|
||||
delete mesh;
|
||||
if (monitor) { delete monitor; }
|
||||
if (scalar_u) { delete scalar_u; }
|
||||
if (scalar_f) { delete scalar_f; }
|
||||
if (vector_u) { delete vector_u; }
|
||||
if (vector_f) { delete vector_f; }
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#include "ds-common.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
|
||||
namespace ds_common
|
||||
{
|
||||
|
||||
int MONITOR_DIGITS = 20;
|
||||
int MG_MAX_ITER = 10;
|
||||
real_t MG_REL_TOL = std::sqrt(1e-10);
|
||||
|
||||
int dim = 0;
|
||||
int space_dim = 0;
|
||||
real_t freq = 1.0;
|
||||
real_t kappa = 1.0;
|
||||
|
||||
// Custom monitor that prints a csv-formatted file
|
||||
DataMonitor::DataMonitor(string file_name, int ndigits)
|
||||
: os(file_name),
|
||||
precision(ndigits)
|
||||
{
|
||||
if (Mpi::Root())
|
||||
{
|
||||
mfem::out << "Saving iterations into: " << file_name << endl;
|
||||
}
|
||||
os << "it,res,sol" << endl;
|
||||
os << fixed << setprecision(precision);
|
||||
}
|
||||
|
||||
void DataMonitor::MonitorResidual(int it, real_t norm, const Vector &x,
|
||||
bool final)
|
||||
{
|
||||
os << it << "," << norm << ",";
|
||||
}
|
||||
|
||||
void DataMonitor::MonitorSolution(int it, real_t norm, const Vector &x,
|
||||
bool final)
|
||||
{
|
||||
os << norm << endl;
|
||||
}
|
||||
|
||||
|
||||
// Abs-L(1) general geometric multigrid method, derived from GeometricMultigrid
|
||||
AbsL1GeometricMultigrid::AbsL1GeometricMultigrid(
|
||||
ParFiniteElementSpaceHierarchy& fes_hierarchy,
|
||||
Array<int>& ess_bdr,
|
||||
IntegratorType it,
|
||||
SolverType st,
|
||||
AssemblyLevel al)
|
||||
: GeometricMultigrid(fes_hierarchy, ess_bdr),
|
||||
integrator_type(it),
|
||||
solver_type(st),
|
||||
assembly_level(al),
|
||||
coarse_pc(nullptr),
|
||||
one(1.0)
|
||||
{
|
||||
// BilinearForm::FormSystemMatrix does not handle the ownership of A_l.
|
||||
// GeometricMultigrid owns the forms, and deletes them.
|
||||
mg_owned = !(AssemblyLevel::LEGACY == assembly_level);
|
||||
|
||||
ConstructCoarseOperatorAndSolver(fes_hierarchy.GetFESpaceAtLevel(0));
|
||||
for (int l = 1; l < fes_hierarchy.GetNumLevels(); ++l)
|
||||
{
|
||||
ConstructOperatorAndSmoother(fes_hierarchy.GetFESpaceAtLevel(l), l);
|
||||
}
|
||||
}
|
||||
|
||||
void AbsL1GeometricMultigrid::ConstructCoarseOperatorAndSolver(
|
||||
ParFiniteElementSpace& coarse_fespace)
|
||||
{
|
||||
ConstructBilinearForm(coarse_fespace);
|
||||
|
||||
OperatorPtr coarse_mat;
|
||||
coarse_mat.SetType(Operator::ANY_TYPE);
|
||||
bfs[0]->FormSystemMatrix(*essentialTrueDofs[0], coarse_mat);
|
||||
coarse_mat.SetOperatorOwner(false);
|
||||
|
||||
// Create smoother
|
||||
Vector local_ones(coarse_mat->Height());
|
||||
Vector result(coarse_mat->Height());
|
||||
|
||||
local_ones = 1.0;
|
||||
coarse_mat->AbsMult(local_ones, result);
|
||||
|
||||
coarse_pc = new OperatorJacobiSmoother(result, *essentialTrueDofs[0]);
|
||||
|
||||
Solver* coarse_solver = nullptr;
|
||||
switch (solver_type)
|
||||
{
|
||||
case sli:
|
||||
coarse_solver = new SLISolver(MPI_COMM_WORLD);
|
||||
break;
|
||||
case cg:
|
||||
coarse_solver = new CGSolver(MPI_COMM_WORLD);
|
||||
break;
|
||||
default:
|
||||
mfem_error("Invalid solver type!");
|
||||
}
|
||||
coarse_solver->SetOperator(*coarse_mat);
|
||||
|
||||
IterativeSolver *it_solver = dynamic_cast<IterativeSolver*>(coarse_solver);
|
||||
if (it_solver)
|
||||
{
|
||||
it_solver->SetRelTol(MG_REL_TOL);
|
||||
it_solver->SetMaxIter(MG_MAX_ITER);
|
||||
it_solver->SetPrintLevel(-1);
|
||||
it_solver->SetPreconditioner(*coarse_pc);
|
||||
}
|
||||
|
||||
AddLevel(coarse_mat.Ptr(), coarse_solver, mg_owned, true);
|
||||
}
|
||||
|
||||
void AbsL1GeometricMultigrid::ConstructOperatorAndSmoother(
|
||||
ParFiniteElementSpace& fespace, int level)
|
||||
{
|
||||
const Array<int> &ess_tdof_list = *essentialTrueDofs[level];
|
||||
ConstructBilinearForm(fespace);
|
||||
|
||||
OperatorPtr level_mat;
|
||||
level_mat.SetType(Operator::ANY_TYPE);
|
||||
bfs.Last()->FormSystemMatrix(ess_tdof_list, level_mat);
|
||||
level_mat.SetOperatorOwner(false);
|
||||
|
||||
// Create smoother
|
||||
Vector local_ones(level_mat->Height());
|
||||
Vector result(level_mat->Height());
|
||||
|
||||
local_ones = 1.0;
|
||||
level_mat->AbsMult(local_ones, result);
|
||||
|
||||
Solver* smoother = new OperatorJacobiSmoother(result, ess_tdof_list);
|
||||
|
||||
AddLevel(level_mat.Ptr(), smoother, mg_owned, true);
|
||||
}
|
||||
|
||||
void AbsL1GeometricMultigrid::ConstructBilinearForm(
|
||||
ParFiniteElementSpace &fespace)
|
||||
{
|
||||
ParBilinearForm* form = new ParBilinearForm(&fespace);
|
||||
form->SetAssemblyLevel(assembly_level);
|
||||
switch (integrator_type)
|
||||
{
|
||||
case mass:
|
||||
form->AddDomainIntegrator(new MassIntegrator(one));
|
||||
break;
|
||||
case diffusion:
|
||||
form->AddDomainIntegrator(new DiffusionIntegrator(one));
|
||||
break;
|
||||
case maxwell:
|
||||
form->AddDomainIntegrator(new CurlCurlIntegrator(one));
|
||||
form->AddDomainIntegrator(new VectorFEMassIntegrator(one));
|
||||
break;
|
||||
default:
|
||||
mfem_error("Invalid integrator type! Check ParBilinearForm");
|
||||
}
|
||||
form->Assemble();
|
||||
bfs.Append(form);
|
||||
}
|
||||
|
||||
|
||||
void AssembleElementLpqJacobiDiag(ParBilinearForm& form, real_t p, real_t q,
|
||||
Vector& diag)
|
||||
{
|
||||
ParBilinearForm temp_form(form.ParFESpace());
|
||||
temp_form.AllocateMatrix();
|
||||
for (int i = 0; i < form.ParFESpace()->GetNE(); ++i)
|
||||
{
|
||||
DenseMatrix emat_i;
|
||||
form.ComputeElementMatrix(i, emat_i);
|
||||
Vector right(emat_i.Height());
|
||||
Vector temp(emat_i.Height());
|
||||
Vector left(emat_i.Height());
|
||||
|
||||
DenseMatrix temp_emat_i = emat_i;
|
||||
for (int j = 0; j < emat_i.Height(); ++j)
|
||||
{
|
||||
for (int k = 0; k < emat_i.Width(); ++k)
|
||||
{
|
||||
temp_emat_i(j, k) = std::pow(std::abs(emat_i(j, k)), p);
|
||||
}
|
||||
}
|
||||
|
||||
if (q!=0.0)
|
||||
{
|
||||
emat_i.GetDiag(right);
|
||||
right.Abs();
|
||||
right.Pow(q);
|
||||
}
|
||||
else
|
||||
{
|
||||
right = 1.0;
|
||||
}
|
||||
|
||||
temp_emat_i.Mult(right, temp);
|
||||
|
||||
if (1.0 + q - p!= 0.0)
|
||||
{
|
||||
emat_i.GetDiag(left);
|
||||
left.Abs();
|
||||
left.Pow(1.0 + q - p);
|
||||
left *= temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = temp;
|
||||
}
|
||||
|
||||
temp_emat_i.Clear();
|
||||
temp_emat_i.Diag(left.GetData(), left.Size());
|
||||
temp_form.AssembleElementMatrix(i, temp_emat_i, 1);
|
||||
}
|
||||
temp_form.Finalize();
|
||||
auto mat = temp_form.ParallelAssemble();
|
||||
mat->AssembleDiagonal(diag);
|
||||
delete mat;
|
||||
}
|
||||
|
||||
real_t diffusion_solution(const Vector &x)
|
||||
{
|
||||
if (dim == 3)
|
||||
{
|
||||
return sin(kappa * x(0)) * sin(kappa * x(1)) * sin(kappa * x(2)) + 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return sin(kappa * x(0)) * sin(kappa * x(1)) + 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
real_t diffusion_source(const Vector &x)
|
||||
{
|
||||
if (dim == 3)
|
||||
{
|
||||
return dim * kappa * kappa * sin(kappa * x(0)) * sin(kappa * x(1)) *
|
||||
sin(kappa * x(2));
|
||||
}
|
||||
else
|
||||
{
|
||||
return dim * kappa * kappa * sin(kappa * x(0)) * sin(kappa * x(1));
|
||||
}
|
||||
}
|
||||
|
||||
void maxwell_solution(const Vector &x, Vector &u)
|
||||
{
|
||||
if (dim == 3)
|
||||
{
|
||||
u(0) = sin(kappa * x(1));
|
||||
u(1) = sin(kappa * x(2));
|
||||
u(2) = sin(kappa * x(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
u(0) = sin(kappa * x(1));
|
||||
u(1) = sin(kappa * x(0));
|
||||
if (x.Size() == 3) { u(2) = 0.0; }
|
||||
}
|
||||
}
|
||||
|
||||
void maxwell_source(const Vector &x, Vector &f)
|
||||
{
|
||||
if (dim == 3)
|
||||
{
|
||||
f(0) = (1. + kappa * kappa) * sin(kappa * x(1));
|
||||
f(1) = (1. + kappa * kappa) * sin(kappa * x(2));
|
||||
f(2) = (1. + kappa * kappa) * sin(kappa * x(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
f(0) = (1. + kappa * kappa) * sin(kappa * x(1));
|
||||
f(1) = (1. + kappa * kappa) * sin(kappa * x(0));
|
||||
if (x.Size() == 3) { f(2) = 0.0; }
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace ds_common
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#ifndef MFEM_DS_COMMON_HPP
|
||||
#define MFEM_DS_COMMON_HPP
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "../common/mfem-common.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
|
||||
namespace ds_common
|
||||
{
|
||||
|
||||
extern int MONITOR_DIGITS;
|
||||
extern int MG_MAX_ITER;
|
||||
extern real_t MG_REL_TOL;
|
||||
|
||||
extern int dim;
|
||||
extern int space_dim;
|
||||
extern real_t freq;
|
||||
extern real_t kappa;
|
||||
|
||||
// Enumerator for the different solvers to implement
|
||||
enum SolverType
|
||||
{
|
||||
sli,
|
||||
cg,
|
||||
num_solvers, // last
|
||||
};
|
||||
|
||||
// Enumerator for the different integrators to implement
|
||||
enum IntegratorType
|
||||
{
|
||||
mass,
|
||||
diffusion,
|
||||
maxwell,
|
||||
num_integrators, // last
|
||||
};
|
||||
|
||||
// Enumerator for the different types of preconditioners
|
||||
enum PCType
|
||||
{
|
||||
none,
|
||||
abs_global,
|
||||
pq_element,
|
||||
num_pc, // last
|
||||
};
|
||||
|
||||
/// @brief Custom monitor that prints a csv-formatted file
|
||||
class DataMonitor : public IterativeSolverMonitor
|
||||
{
|
||||
private:
|
||||
ofstream os;
|
||||
int precision;
|
||||
public:
|
||||
DataMonitor(string file_name, int ndigits);
|
||||
void MonitorResidual(int it, real_t norm, const Vector &x, bool final);
|
||||
void MonitorSolution(int it, real_t norm, const Vector &x, bool final);
|
||||
};
|
||||
|
||||
|
||||
/// @brief Abs-L(1)-Jacobi custom general geometric multigrid method.
|
||||
///
|
||||
/// Intermediate levels use Abs-L(1)-Jacobi preconditioner by applying the
|
||||
/// level matrix to the constant vector one. These are wrapped by an
|
||||
/// OperatorJacobiSmoother. Coarsest level uses a used-selected solver
|
||||
/// with an Abs-L(1)-Jacobi smoother. The assembly level is user-defined.
|
||||
///
|
||||
/// @warning The construction of the smoother is based on the application of
|
||||
/// AbsMult, which usually unfolds component-wise. E.g., if `A = B C`, then
|
||||
/// `|A|x = |B|(|C| x)`.
|
||||
class AbsL1GeometricMultigrid : public GeometricMultigrid
|
||||
{
|
||||
public:
|
||||
AbsL1GeometricMultigrid(ParFiniteElementSpaceHierarchy& fes_hierarchy,
|
||||
Array<int>& ess_bdr,
|
||||
IntegratorType it,
|
||||
SolverType st,
|
||||
AssemblyLevel al);
|
||||
|
||||
~AbsL1GeometricMultigrid() { delete coarse_pc; }
|
||||
|
||||
bool GetOwnershipLevelOperators() const { return mg_owned; }
|
||||
|
||||
private:
|
||||
IntegratorType integrator_type;
|
||||
SolverType solver_type;
|
||||
AssemblyLevel assembly_level;
|
||||
bool mg_owned;
|
||||
OperatorJacobiSmoother* coarse_pc;
|
||||
ConstantCoefficient one;
|
||||
|
||||
void ConstructCoarseOperatorAndSolver(ParFiniteElementSpace& coarse_fespace);
|
||||
|
||||
void ConstructOperatorAndSmoother(ParFiniteElementSpace& fespace, int level);
|
||||
|
||||
void ConstructBilinearForm(ParFiniteElementSpace& fespace);
|
||||
|
||||
};
|
||||
|
||||
void AssembleElementLpqJacobiDiag(ParBilinearForm& form, real_t p, real_t q,
|
||||
Vector& diag);
|
||||
|
||||
real_t diffusion_solution(const Vector &x);
|
||||
real_t diffusion_source(const Vector &x);
|
||||
|
||||
void maxwell_solution(const Vector &x, Vector &u);
|
||||
void maxwell_source(const Vector &x, Vector &f);
|
||||
|
||||
} // namespace ds_common
|
||||
|
||||
#endif // MFEM_DS_COMMON_HPP
|
||||
@@ -0,0 +1,96 @@
|
||||
# Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
# at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
# LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
#
|
||||
# This file is part of the MFEM library. For more information and source code
|
||||
# availability visit https://mfem.org.
|
||||
#
|
||||
# MFEM is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
# Use the MFEM build directory
|
||||
MFEM_DIR ?= ../..
|
||||
MFEM_BUILD_DIR ?= ../..
|
||||
|
||||
SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/diag-smoothers/,)
|
||||
CONFIG_MK = $(MFEM_BUILD_DIR)/config/config.mk
|
||||
DEFAULTS_MK = $(MFEM_DIR)/config/defaults.mk
|
||||
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
|
||||
-include $(DEFAULTS_MK)
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
DS_COMMON_SRC = ds-common.cpp
|
||||
DS_COMMON_OBJ = $(DS_COMMON_SRC:.cpp=.o)
|
||||
|
||||
PAR_MINIAPPS = abs-l1-jacobi mg-abs-l1-jacobi
|
||||
|
||||
MINIAPPS = $(if $(MFEM_USE_MPI:NO=),$(PAR_MINIAPPS),)
|
||||
|
||||
COMMON_LIB = -L$(MFEM_BUILD_DIR)/miniapps/common -lmfem-common
|
||||
COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\
|
||||
$(if $(MFEM_USE_CUDA:YES=),$(CXX_XLINKER),$(CUDA_XLINKER))-rpath,$(abspath\
|
||||
$(MFEM_BUILD_DIR)/miniapps/common))
|
||||
|
||||
APP_DEPS = $(DS_COMMON_OBJ) $(MFEM_LIB_FILE) $(CONFIG_MK)
|
||||
APP_LIBS = $(COMMON_LIB) $(MFEM_LIBS)
|
||||
|
||||
# Phony targets
|
||||
.PHONY: all lib-common clean clean-build clean-exec test
|
||||
|
||||
# Remove built-in rule
|
||||
%: %.cpp
|
||||
%.o: %.cpp
|
||||
|
||||
# Main targets
|
||||
all: $(MINIAPPS)
|
||||
|
||||
# Build rules
|
||||
%.o: $(SRC)%.cpp $(wildcard $(SRC)%.hpp) $(MFEM_LIB_FILE) $(CONFIG_MK)
|
||||
$(MFEM_CXX) $(MFEM_FLAGS) -c $< -o $@
|
||||
|
||||
$(MINIAPPS): %: %.o $(APP_DEPS) | lib-common
|
||||
$(MFEM_CXX) $(MFEM_LINK_FLAGS) $< $(DS_COMMON_OBJ) -o $@ $(APP_LIBS)
|
||||
|
||||
# Library and mesh targets
|
||||
lib-common:
|
||||
$(MAKE) -C $(MFEM_BUILD_DIR)/miniapps/common
|
||||
|
||||
# Test targets
|
||||
MFEM_TESTS = MINIAPPS
|
||||
include $(MFEM_TEST_MK)
|
||||
|
||||
RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP)
|
||||
|
||||
abs-l1-jacobi-test-par: abs-l1-jacobi
|
||||
@$(call mfem-test,$<, $(RUN_MPI), Abs-value-L1-Jqcobi miniapp, \
|
||||
-m ../../data/ref-cube.mesh \
|
||||
-rs 2 -rp 2 \
|
||||
-s 1 -i 1 -a 3 \
|
||||
-pc 1 \
|
||||
-no-mon)
|
||||
|
||||
mg-abs-l1-jacobi-test-par: mg-abs-l1-jacobi
|
||||
@$(call mfem-test,$<, $(RUN_MPI), MG Abs-value-L1-Jqcobi miniapp, \
|
||||
-m ../../data/ref-cube.mesh \
|
||||
-rs 2 -rp 1\
|
||||
-ol 1 -gl 1 \
|
||||
-s 1 -i 1 -a 3 \
|
||||
-no-mon)
|
||||
|
||||
# Clean targets
|
||||
clean: clean-build clean-exec
|
||||
$(MAKE) -C $(MFEM_BUILD_DIR)/miniapps/common clean
|
||||
|
||||
clean-build:
|
||||
rm -f *.o *~ $(MINIAPPS)
|
||||
rm -rf *.dSYM *.TVD.*breakpoints
|
||||
|
||||
clean-exec:
|
||||
@true
|
||||
|
||||
# Error handling
|
||||
$(MFEM_LIB_FILE):
|
||||
$(error The MFEM library is not built)
|
||||
@@ -0,0 +1,414 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
//
|
||||
// --------------------------------------
|
||||
// MG Abs L(1)-Jacobi smoothers miniapp
|
||||
// --------------------------------------
|
||||
//
|
||||
// (See abs-l1-jacobi.cpp first)
|
||||
//
|
||||
// This miniapp illustrates the use of an absolute value L(1)-Jacobi smoother.
|
||||
// We use a multigrid approach (cf. ex26(p)). The global solver and the coarse
|
||||
// level solver are user-selected. The current options are SLI and PCG. The
|
||||
// intermediate levels are directy smoothed with the absolute value L(1)-Jacobi
|
||||
// preconditioner. The systems to solve correspond to a mass matrix, and a
|
||||
// difussion system.
|
||||
//
|
||||
// The preconditioner can be defined at run-time. Similarly, the mesh can be
|
||||
// modified by a Kershaw transformation at run-time. Relative tolerance and
|
||||
// maximum number of iterations can be modified as well.
|
||||
//
|
||||
// Compile with: make mg-abs-l1-jacobi
|
||||
//
|
||||
// Sample runs:
|
||||
// mpirun -np 4 mg-abs-l1-jacobi
|
||||
// mpirun -np 4 mg-abs-l1-jacobi -s 0 -i 0
|
||||
// mpirun -np 4 mg-abs-l1-jacobi -m ../meshing/icf.mesh -f 0.5
|
||||
// mpirun -np 4 mg-abs-l1-jacobi -rs 2 -rp 1
|
||||
// mpirun -np 4 mg-abs-l1-jacobi -t 1e-5 -ni 100
|
||||
// mpirun -np 4 mg-abs-l1-jacobi -m ../../data/beam-quad.mesh -a 3 -Ky 0.5 -Kz 0.5
|
||||
// mpirun -np 4 mg-abs-l1-jacobi --device cuda
|
||||
|
||||
#include "ds-common.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
using namespace ds_common;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// 1. Initialize MPI and HYPRE.
|
||||
Mpi::Init(argc, argv);
|
||||
Hypre::Init();
|
||||
|
||||
// 2. Parse command line options.
|
||||
string mesh_file = "../../data/ref-cube.mesh";
|
||||
// System properties
|
||||
int order = 1;
|
||||
SolverType solver_type = cg;
|
||||
IntegratorType integrator_type = diffusion;
|
||||
int assembly_type_int = 3; // Default is PARTIAL
|
||||
AssemblyLevel assembly_type;
|
||||
// Number of refinements
|
||||
int refine_serial = 3;
|
||||
int refine_parallel = 0;
|
||||
// Number of geometric and order levels
|
||||
int geometric_levels = 1;
|
||||
int order_levels = 1;
|
||||
// Solver parameters
|
||||
real_t rel_tol = 1e-10;
|
||||
real_t max_iter = 3000;
|
||||
// Kershaw Transformation
|
||||
real_t eps_y = 0.0;
|
||||
real_t eps_z = 0.0;
|
||||
// Other options
|
||||
string device_config = "cpu";
|
||||
bool use_monitor = false;
|
||||
bool visualization = true;
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&mesh_file, "-m", "--mesh",
|
||||
"Mesh file to use.");
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
"Finite element order (polynomial degree)");
|
||||
args.AddOption(&geometric_levels, "-gl", "--geometric-levels",
|
||||
"Number of geometric refinements (levels) done prior to order"
|
||||
" refinements.");
|
||||
args.AddOption(&order_levels, "-ol", "--order-levels",
|
||||
"Number of order refinements (levels). "
|
||||
"Finest level in the hierarchy has order 2^{or}.");
|
||||
args.AddOption((int*)&solver_type, "-s", "--solver",
|
||||
"Solvers to be considered:"
|
||||
"\n\t0: Stationary Linear Iteration"
|
||||
"\n\t1: Preconditioned Conjugate Gradient");
|
||||
args.AddOption((int*)&integrator_type, "-i", "--integrator",
|
||||
"Integrators to be considered:"
|
||||
"\n\t0: MassIntegrator"
|
||||
"\n\t1: DiffusionIntegrator");
|
||||
args.AddOption(&assembly_type_int, "-a", "--assembly",
|
||||
"Assembly level to be considered:"
|
||||
"\n\t0: LEGACY"
|
||||
"\n\t1: FULL"
|
||||
"\n\t2: ELEMENT"
|
||||
"\n\t3: PARTIAL"
|
||||
"\n\t4: NONE");
|
||||
args.AddOption(&refine_serial, "-rs", "--refine-serial",
|
||||
"Number of serial refinements");
|
||||
args.AddOption(&refine_parallel, "-rp", "--refine-parallel",
|
||||
"Number of parallel refinements");
|
||||
args.AddOption(&rel_tol, "-t", "--tolerance",
|
||||
"Relative tolerance for the iterative solver");
|
||||
args.AddOption(&max_iter, "-ni", "--iterations",
|
||||
"Maximum number of iterations");
|
||||
args.AddOption(&eps_y, "-Ky", "--Kershaw-y",
|
||||
"Kershaw transform factor, eps_y in (0,1]");
|
||||
args.AddOption(&eps_z, "-Kz", "--Kershaw-z",
|
||||
"Kershaw transform factor, eps_z in (0,1]");
|
||||
args.AddOption(&freq, "-f", "--frequency", "Set the frequency for the exact"
|
||||
" solution.");
|
||||
args.AddOption(&device_config, "-d", "--device",
|
||||
"Device configuration string, see Device::Configure().");
|
||||
args.AddOption(&use_monitor, "-mon", "--monitor", "-no-mon",
|
||||
"--no-monitor",
|
||||
"Enable or disable Data Monitor.");
|
||||
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.ParseCheck();
|
||||
|
||||
MFEM_VERIFY((0 <= solver_type) && (solver_type < num_solvers),
|
||||
"invalid solver type: " << solver_type);
|
||||
MFEM_VERIFY((0 <= integrator_type) && (integrator_type < num_integrators),
|
||||
"invalid integrator type: " << integrator_type);
|
||||
MFEM_VERIFY((0 <= assembly_type_int) && (assembly_type_int < 6),
|
||||
"invalid assembly type: " << assembly_type_int);
|
||||
MFEM_VERIFY(geometric_levels >= 0,
|
||||
"geometric_levels needs to be non-negative");
|
||||
MFEM_VERIFY(order_levels >= 0, "order_levels needs to be non-negative");
|
||||
MFEM_VERIFY((0.0 <= eps_y) && (eps_y <= 1.0), "eps_y must be in [0,1]");
|
||||
MFEM_VERIFY((0.0 <= eps_z) && (eps_z <= 1.0), "eps_z must be in [0,1]");
|
||||
|
||||
kappa = freq * M_PI;
|
||||
|
||||
ostringstream file_name;
|
||||
if (use_monitor)
|
||||
{
|
||||
file_name << "MGABS-"
|
||||
<< "G" << geometric_levels
|
||||
<< "O" << order_levels
|
||||
<< "O" << order
|
||||
<< "I" << (int) integrator_type
|
||||
<< "S" << (int) solver_type
|
||||
<< "A" << assembly_type_int
|
||||
<< ".csv";
|
||||
}
|
||||
|
||||
string assembly_description;
|
||||
switch (assembly_type_int)
|
||||
{
|
||||
case 0:
|
||||
assembly_type = AssemblyLevel::LEGACY;
|
||||
assembly_description = "Using Legacy type of assembly level...";
|
||||
break;
|
||||
case 1:
|
||||
assembly_type = AssemblyLevel::FULL;
|
||||
assembly_description = "Using Full type of assembly level...";
|
||||
break;
|
||||
case 2:
|
||||
assembly_type = AssemblyLevel::ELEMENT;
|
||||
assembly_description = "Using Element type of assembly level...";
|
||||
break;
|
||||
case 3:
|
||||
assembly_type = AssemblyLevel::PARTIAL;
|
||||
assembly_description = "Using Partial type of assembly level...";
|
||||
break;
|
||||
case 4:
|
||||
assembly_type = AssemblyLevel::NONE;
|
||||
assembly_description = "Using matrix-free type of assembly level...";
|
||||
break;
|
||||
default:
|
||||
MFEM_ABORT("Unsupported option!");
|
||||
}
|
||||
|
||||
Device device(device_config);
|
||||
if (Mpi::Root()) { device.Print(); }
|
||||
|
||||
// 3. Read the serial mesh from the given mesh file. The number of serial and
|
||||
// parallel refinements can be set by the user on the command line.
|
||||
Mesh *serial_mesh = new Mesh(mesh_file);
|
||||
for (int ls = 0; ls < refine_serial; ls++)
|
||||
{
|
||||
serial_mesh->UniformRefinement();
|
||||
}
|
||||
|
||||
// 4. Define a parallel mesh by a partitioning of the serial mesh. The number
|
||||
// of parallel refinements can be set by the user. If defined, apply
|
||||
// Kershaw transformation.
|
||||
ParMesh *mesh = new ParMesh(MPI_COMM_WORLD, *serial_mesh);
|
||||
delete serial_mesh;
|
||||
for (int lp = 0; lp < refine_parallel; lp++)
|
||||
{
|
||||
mesh->UniformRefinement();
|
||||
}
|
||||
|
||||
dim = mesh->Dimension();
|
||||
space_dim = mesh->SpaceDimension();
|
||||
|
||||
bool cond_z = (dim < 3) ? true : (eps_z != 0.0); // lazy check
|
||||
if (eps_y != 0.0 && cond_z)
|
||||
{
|
||||
if (dim < 3) { eps_z = 0.0; }
|
||||
common::KershawTransformation kershawT(dim, eps_y, eps_z);
|
||||
mesh->Transform(kershawT);
|
||||
}
|
||||
|
||||
// 5. Define a finite element space on the mesh. We use different spaces and
|
||||
// collections for different systems.
|
||||
// - H1-conforming Lagrange elements for the H1-mass matrix and the
|
||||
// diffusion problem.
|
||||
FiniteElementCollection *fec;
|
||||
ParFiniteElementSpace *coarse_fes;
|
||||
switch (integrator_type)
|
||||
{
|
||||
case mass:
|
||||
case diffusion:
|
||||
fec = new H1_FECollection(order, dim);
|
||||
coarse_fes = new ParFiniteElementSpace(mesh, fec);
|
||||
break;
|
||||
case maxwell:
|
||||
mfem_error("Maxwell integrator not supported in this miniapp!");
|
||||
default:
|
||||
mfem_error("Invalid integrator type! Check FiniteElementCollection");
|
||||
}
|
||||
|
||||
if (order > 1)
|
||||
{
|
||||
if (Mpi::Root())
|
||||
{
|
||||
mfem::out << "Warning! Polynomial order provided. "
|
||||
<< "Ignoring order level..." << endl;
|
||||
}
|
||||
order_levels = 0;
|
||||
}
|
||||
|
||||
// 6. Define a finite element space hierarchy for the multigrid solver.
|
||||
// Define a FEC array for the order-refinement levels. Add the refinements
|
||||
// to the hierarchy.
|
||||
Array<FiniteElementCollection*> fec_array;
|
||||
fec_array.Append(fec);
|
||||
// Transfer ownership of mesh and coarse_fes to fes_hierarchy
|
||||
ParFiniteElementSpaceHierarchy* fes_hierarchy = new
|
||||
ParFiniteElementSpaceHierarchy(mesh, coarse_fes, true, true);
|
||||
|
||||
for (int lg = 0; lg < geometric_levels; ++lg)
|
||||
{
|
||||
fes_hierarchy->AddUniformlyRefinedLevel();
|
||||
}
|
||||
for (int lo = 0; lo < order_levels; ++lo)
|
||||
{
|
||||
switch (integrator_type)
|
||||
{
|
||||
case mass:
|
||||
case diffusion:
|
||||
fec_array.Append(new H1_FECollection(std::pow(2, lo + 1), dim));
|
||||
break;
|
||||
default:
|
||||
mfem_error("Invalid integrator type! Check "
|
||||
"FiniteElementCollection for order refinements...");
|
||||
}
|
||||
fes_hierarchy->AddOrderRefinedLevel(fec_array.Last());
|
||||
}
|
||||
|
||||
HYPRE_BigInt sys_size = fes_hierarchy->GetFinestFESpace().GlobalTrueVSize();
|
||||
if (Mpi::Root())
|
||||
{
|
||||
mfem::out << "Number of unknowns: " << sys_size << endl;
|
||||
mfem::out << assembly_description << endl;
|
||||
}
|
||||
|
||||
// 7. Extract the list of the essential boundary DoFs. We mark all boundary
|
||||
// attibutes as essential. AbsL1GeometricMultigrid will determine the
|
||||
// DoFs per level.
|
||||
Array<int> ess_bdr(mesh->bdr_attributes.Max());
|
||||
if (mesh->bdr_attributes.Size()) { ess_bdr = 1; }
|
||||
|
||||
// 8. Define the linear system. Set up the linear form b(.) which has the
|
||||
// standard form (f,v).
|
||||
ParLinearForm *b = new ParLinearForm(&fes_hierarchy->GetFinestFESpace());
|
||||
LinearFormIntegrator *lfi = nullptr;
|
||||
|
||||
// These pointers are not owned by the integrators
|
||||
FunctionCoefficient *scalar_u = nullptr;
|
||||
FunctionCoefficient *scalar_f = nullptr;
|
||||
|
||||
ConstantCoefficient one(1.0);
|
||||
|
||||
// These variables will define the linear system
|
||||
ParGridFunction x(&fes_hierarchy->GetFinestFESpace());
|
||||
OperatorPtr A;
|
||||
Vector B, X;
|
||||
|
||||
x = 0.0;
|
||||
|
||||
switch (integrator_type)
|
||||
{
|
||||
case mass:
|
||||
scalar_u = new FunctionCoefficient(diffusion_solution);
|
||||
lfi = new DomainLFIntegrator(*scalar_u);
|
||||
x.ProjectBdrCoefficient(*scalar_u, ess_bdr);
|
||||
break;
|
||||
case diffusion:
|
||||
scalar_u = new FunctionCoefficient(diffusion_solution);
|
||||
scalar_f = new FunctionCoefficient(diffusion_source);
|
||||
lfi = new DomainLFIntegrator(*scalar_f);
|
||||
x.ProjectBdrCoefficient(*scalar_u, ess_bdr);
|
||||
break;
|
||||
default:
|
||||
mfem_error("Invalid integrator type! Check ParLinearForm");
|
||||
}
|
||||
b->AddDomainIntegrator(lfi);
|
||||
b->Assemble();
|
||||
|
||||
// 9. Define a geometric multigrid solver. The bilinear form a(.,.) is
|
||||
// assembled internally. Set up the type of cycles and form the linear
|
||||
// system.
|
||||
auto mg = new AbsL1GeometricMultigrid(*fes_hierarchy,
|
||||
ess_bdr,
|
||||
integrator_type,
|
||||
solver_type,
|
||||
assembly_type);
|
||||
mg->SetCycleType(Multigrid::CycleType::VCYCLE, 1, 1);
|
||||
mg->FormFineLinearSystem(x, *b, A, X, B);
|
||||
|
||||
A.SetOperatorOwner(mg->GetOwnershipLevelOperators());
|
||||
|
||||
Solver *solver = nullptr;
|
||||
DataMonitor *monitor = nullptr;
|
||||
|
||||
switch (solver_type)
|
||||
{
|
||||
case sli:
|
||||
solver = new SLISolver(MPI_COMM_WORLD);
|
||||
break;
|
||||
case cg:
|
||||
solver = new CGSolver(MPI_COMM_WORLD);
|
||||
break;
|
||||
default:
|
||||
mfem_error("Invalid solver type!");
|
||||
}
|
||||
solver->SetOperator(*A.Ptr());
|
||||
|
||||
IterativeSolver *it_solver = dynamic_cast<IterativeSolver*>(solver);
|
||||
if (it_solver)
|
||||
{
|
||||
it_solver->SetRelTol(rel_tol);
|
||||
it_solver->SetMaxIter(max_iter);
|
||||
it_solver->SetPrintLevel(1);
|
||||
it_solver->SetPreconditioner(*mg);
|
||||
if (use_monitor)
|
||||
{
|
||||
monitor = new DataMonitor(file_name.str(), MONITOR_DIGITS);
|
||||
it_solver->SetMonitor(*monitor);
|
||||
}
|
||||
}
|
||||
|
||||
solver->Mult(B, X);
|
||||
|
||||
// 10. Recover the solution x as a grid function. Send the data by socket to
|
||||
// a GLVis server.
|
||||
mg->RecoverFineFEMSolution(X, *b, x);
|
||||
|
||||
if (visualization)
|
||||
{
|
||||
char vishost[] = "localhost";
|
||||
int visport = 19916;
|
||||
socketstream sol_sock(vishost, visport);
|
||||
sol_sock << "parallel " << Mpi::WorldSize() << " " << Mpi::WorldRank()
|
||||
<< "\n";
|
||||
sol_sock.precision(8);
|
||||
sol_sock << "solution\n"
|
||||
<< *fes_hierarchy->GetFinestFESpace().GetParMesh()
|
||||
<< x << flush;
|
||||
}
|
||||
|
||||
// 11. Compute and print the L^2 norm of the error.
|
||||
{
|
||||
real_t error = 0.0;
|
||||
switch (integrator_type)
|
||||
{
|
||||
case mass:
|
||||
case diffusion:
|
||||
error = x.ComputeL2Error(*scalar_u);
|
||||
break;
|
||||
default:
|
||||
mfem_error("Invalid integrator type! Check ComputeL2Error");
|
||||
}
|
||||
if (Mpi::Root())
|
||||
{
|
||||
mfem::out << "\n|| u_h - u ||_{L^2} = " << error << "\n" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
// 12. Free the memory used.
|
||||
delete mg;
|
||||
delete solver;
|
||||
delete b;
|
||||
if (monitor) { delete monitor; }
|
||||
if (scalar_u) { delete scalar_u; }
|
||||
if (scalar_f) { delete scalar_f; }
|
||||
for (int level = 0; level < fec_array.Size(); ++level)
|
||||
{
|
||||
delete fec_array[level];
|
||||
}
|
||||
delete fes_hierarchy;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -121,6 +121,11 @@ endif()
|
||||
|
||||
# Parallel apps.
|
||||
if (MFEM_USE_MPI)
|
||||
add_mfem_miniapp(mesh-bounding-boxes
|
||||
MAIN mesh-bounding-boxes.cpp
|
||||
${MFEM_MINIAPPS_COMMON_HEADERS}
|
||||
LIBRARIES mfem-common)
|
||||
|
||||
add_mfem_miniapp(pmesh-optimizer
|
||||
MAIN pmesh-optimizer.cpp
|
||||
${MFEM_MINIAPPS_COMMON_HEADERS}
|
||||
@@ -150,6 +155,7 @@ if (MFEM_USE_MPI)
|
||||
# Add parallel tests.
|
||||
if (MFEM_ENABLE_TESTING)
|
||||
set(PARALLEL_TESTS
|
||||
mesh-bounding-boxes
|
||||
pmesh-optimizer
|
||||
pmesh-fitting
|
||||
fit-node-position
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
//
|
||||
// Sample runs: hpref -dim 2 -n 1000
|
||||
// hpref -dim 3 -n 500
|
||||
// hpref -m ../../data/star-mixed.mesh -pref -n 100
|
||||
// hpref -m ../../data/fichera-mixed.mesh -pref -n 30
|
||||
//
|
||||
// Description: This example demonstrates h- and p-refinement in a serial
|
||||
// finite element discretization of the Poisson problem (cf. ex1)
|
||||
@@ -38,6 +40,7 @@ void f_exact(const Vector &x, Vector &f);
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// 1. Parse command-line options.
|
||||
const char *mesh_file = "";
|
||||
int order = 1;
|
||||
const char *device_config = "cpu";
|
||||
bool visualization = true;
|
||||
@@ -45,8 +48,11 @@ int main(int argc, char *argv[])
|
||||
int dim = 2;
|
||||
bool deterministic = true;
|
||||
bool projectSolution = false;
|
||||
bool onlyPref = false;
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&mesh_file, "-m", "--mesh",
|
||||
"Mesh file to use.");
|
||||
args.AddOption(&order, "-o", "--order",
|
||||
"Finite element order (polynomial degree) or -1 for"
|
||||
" isoparametric space.");
|
||||
@@ -59,10 +65,13 @@ int main(int argc, char *argv[])
|
||||
args.AddOption(&dim, "-dim", "--dim", "Mesh dimension (2 or 3)");
|
||||
args.AddOption(&deterministic, "-det", "--deterministic", "-not-det",
|
||||
"--not-deterministic",
|
||||
"Whether to use deterministic random refinements");
|
||||
"Use deterministic random refinements");
|
||||
args.AddOption(&projectSolution, "-proj", "--project-solution", "-no-proj",
|
||||
"--no-project",
|
||||
"Whether to project a coefficient to solution");
|
||||
"Project a coefficient to solution");
|
||||
args.AddOption(&onlyPref, "-pref", "--only-p-refinement", "-no-pref",
|
||||
"--hp-refinement",
|
||||
"Use only p-refinement");
|
||||
args.Parse();
|
||||
if (!args.Good())
|
||||
{
|
||||
@@ -76,9 +85,15 @@ int main(int argc, char *argv[])
|
||||
Device device(device_config);
|
||||
device.Print();
|
||||
|
||||
// 3. Construct a uniform coarse mesh on all processors.
|
||||
// 3. Construct or load a coarse mesh.
|
||||
std::string mesh_filename(mesh_file);
|
||||
Mesh mesh;
|
||||
if (dim == 3)
|
||||
if (!mesh_filename.empty())
|
||||
{
|
||||
mesh = Mesh::LoadFromFile(mesh_filename, 1, 1);
|
||||
dim = mesh.Dimension();
|
||||
}
|
||||
else if (dim == 3)
|
||||
{
|
||||
mesh = Mesh::MakeCartesian3D(2, 2, 2, Element::HEXAHEDRON);
|
||||
}
|
||||
@@ -126,7 +141,7 @@ int main(int argc, char *argv[])
|
||||
const int r1 = deterministic ? DetRand(seed) : rand();
|
||||
const int r2 = deterministic ? DetRand(seed) : rand();
|
||||
const int elem = r1 % mesh.GetNE();
|
||||
const int hp = r2 % 2;
|
||||
const int hp = onlyPref ? 1 : r2 % 2;
|
||||
|
||||
cout << "hp-refinement iteration " << iter << ": "
|
||||
<< hp_char[hp] << "-refinement" << endl;
|
||||
|
||||
@@ -27,7 +27,8 @@ MFEM_LIB_FILE = mfem_is_not_built
|
||||
SEQ_MINIAPPS = mobius-strip klein-bottle toroid trimmer twist mesh-explorer\
|
||||
shaper extruder mesh-optimizer minimal-surface polar-nc reflector\
|
||||
ref321 mesh-quality hpref
|
||||
PAR_MINIAPPS = pmesh-optimizer pminimal-surface pmesh-fitting fit-node-position phpref
|
||||
PAR_MINIAPPS = pmesh-optimizer pminimal-surface pmesh-fitting fit-node-position\
|
||||
phpref mesh-bounding-boxes
|
||||
ifeq ($(MFEM_USE_MPI),NO)
|
||||
MINIAPPS = $(SEQ_MINIAPPS)
|
||||
else
|
||||
@@ -103,6 +104,8 @@ hpref-test-seq: hpref
|
||||
@$(call mfem-test,$<,, Serial hp-refinement)
|
||||
phpref-test-par: phpref
|
||||
@$(call mfem-test,$<, $(RUN_MPI), Parallel hp-refinement)
|
||||
mesh-bounding-boxes-test-par: mesh-bounding-boxes
|
||||
@$(call mfem-test,$<, $(RUN_MPI), Parallel bounding boxes)
|
||||
ref321-test-seq: ref321
|
||||
@$(call mfem-test-file,$<,, Meshing miniapp,ref321.mesh)
|
||||
|
||||
@@ -121,10 +124,7 @@ $(MFEM_LIB_FILE):
|
||||
clean: clean-build clean-exec
|
||||
|
||||
clean-build:
|
||||
rm -f *.o *~ mobius-strip klein-bottle toroid twist
|
||||
rm -f mesh-explorer shaper extruder trimmer reflector ref321
|
||||
rm -f mesh-optimizer pmesh-optimizer pmesh-fitting polar-nc hpref phpref
|
||||
rm -f minimal-surface pminimal-surface mesh-quality fit-node-position
|
||||
rm -f *.o *~ $(SEQ_MINIAPPS) $(PAR_MINIAPPS)
|
||||
rm -rf *.dSYM *.TVD.*breakpoints
|
||||
|
||||
clean-exec:
|
||||
@@ -134,3 +134,4 @@ clean-exec:
|
||||
@rm -f optimized* perturbed* polar-nc.mesh
|
||||
@rm -rf mesh-explorer-{visit,paraview}*
|
||||
@rm -f mesh.* order.* sol.* refined.mesh
|
||||
@rm -rf jacobian-determinant-bounds* bounding-box*
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
//
|
||||
// ---------------------------------------------------------------------
|
||||
// Bounding Boxes Miniapp: Construct Bounding Boxes of Quad/Hex Meshes
|
||||
// ---------------------------------------------------------------------
|
||||
//
|
||||
// This miniapp computes bounding boxes for each element in a given mesh, and
|
||||
// also computes the bounds on the determinant of the Jacobian of the
|
||||
// transformation for each element. The bounding approach is based on the
|
||||
// method described in:
|
||||
//
|
||||
// (1) Section 3 of Mittal et al., "General Field Evaluation in High-Order
|
||||
// Meshes on GPUs"
|
||||
// and
|
||||
// (2) Dzanic et al., "A method for bounding high-order finite element
|
||||
// functions: Applications to mesh validity and bounds-preserving limiters".
|
||||
//
|
||||
//
|
||||
// Compile with: make mesh-bounding-boxes
|
||||
//
|
||||
// Sample runs:
|
||||
// mpirun -np 4 mesh-bounding-boxes -m ../../data/klein-bottle.mesh
|
||||
// mpirun -np 4 mesh-bounding-boxes -m ../gslib/triple-pt-1.mesh
|
||||
// mpirun -np 4 mesh-bounding-boxes -m ../../data/star-surf.mesh
|
||||
// mpirun -np 4 mesh-bounding-boxes -m ../../data/fichera-q2.mesh
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
using namespace mfem;
|
||||
using namespace std;
|
||||
|
||||
Mesh MakeBoundingBoxMesh(Mesh &mesh, GridFunction &nodal_bb_gf);
|
||||
void GetDeterminantJacobianGF(ParMesh *mesh, ParGridFunction *detgf);
|
||||
void VisualizeBB(Mesh &mesh, char *title, int pos_x, int pos_y);
|
||||
void VisualizeField(ParMesh &pmesh, ParGridFunction &input,
|
||||
char *title, int pos_x, int pos_y);
|
||||
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
// 0. Initialize MPI and HYPRE.
|
||||
Mpi::Init(argc, argv);
|
||||
Hypre::Init();
|
||||
|
||||
// Set the method's default parameters.
|
||||
const char *mesh_file = "../../data/klein-bottle.mesh";
|
||||
int mesh_poly_deg = 2;
|
||||
bool visualization = true;
|
||||
bool visit = false;
|
||||
bool jacobian = true;
|
||||
|
||||
// Parse command-line options.
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&mesh_file, "-m", "--mesh",
|
||||
"Mesh file to use.");
|
||||
args.AddOption(&mesh_poly_deg, "-o", "--order",
|
||||
"Polynomial degree of mesh finite element space.");
|
||||
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.AddOption(&visit, "-visit", "--visit", "-no-visit",
|
||||
"--no-visit",
|
||||
"Enable or disable VisIt output.");
|
||||
args.AddOption(&jacobian, "-jac", "--jacobian", "-no-jac",
|
||||
"--no-jacobian",
|
||||
"Compute bounds on determinant of mesh Jacobian");
|
||||
args.ParseCheck();
|
||||
|
||||
// Initialize and refine the starting mesh.
|
||||
Mesh mesh(mesh_file, 1, 1, false);
|
||||
const int rdim = mesh.Dimension();
|
||||
const int sdim = mesh.SpaceDimension();
|
||||
|
||||
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
||||
if (pmesh.GetNodes() == NULL) { pmesh.SetCurvature(mesh_poly_deg); }
|
||||
else { mesh_poly_deg = pmesh.GetNodes()->FESpace()->GetMaxElementOrder(); }
|
||||
mesh.Clear();
|
||||
|
||||
// Setup finite element space and gridfunction to store bounding box
|
||||
// x/y/z min & max for each element.
|
||||
L2_FECollection fec_pc(0, rdim);
|
||||
ParFiniteElementSpace fes_l2_bb(&pmesh, &fec_pc, sdim*2, Ordering::byVDIM);
|
||||
ParGridFunction nodal_bb(&fes_l2_bb);
|
||||
Array<int> vdofs;
|
||||
|
||||
GridFunction *nodes = pmesh.GetNodes();
|
||||
int nelem = pmesh.GetNE();
|
||||
|
||||
// Compute bounds on nodal positions and save in nodal_bb gridfunction.
|
||||
Vector lower, upper;
|
||||
nodes->GetElementBounds(lower, upper, 2, -1);
|
||||
for (int e = 0; e < nelem; e++)
|
||||
{
|
||||
fes_l2_bb.GetElementVDofs(e, vdofs);
|
||||
Vector lower_upper(vdofs.Size());
|
||||
for (int d = 0; d < sdim; d++)
|
||||
{
|
||||
lower_upper(d) = lower(e + d*nelem);
|
||||
lower_upper(d+sdim) = upper(e + d*nelem);
|
||||
}
|
||||
nodal_bb.SetSubVector(vdofs, lower_upper);
|
||||
}
|
||||
|
||||
// Make a mesh of bounding boxes to output.
|
||||
Mesh pmesh_ser = pmesh.GetSerialMesh(0);
|
||||
GridFunction nodal_bb_ser = nodal_bb.GetSerialGridFunction(0, pmesh_ser);
|
||||
Mesh meshbb = MakeBoundingBoxMesh(pmesh_ser, nodal_bb_ser);
|
||||
|
||||
// Output in GLVis and VisIt
|
||||
if (visualization && Mpi::Root())
|
||||
{
|
||||
char title1[] = "Input mesh";
|
||||
VisualizeBB(pmesh_ser, title1, 0, 0);
|
||||
char title2[] = "Bounding box mesh";
|
||||
VisualizeBB(meshbb, title2, 400, 0);
|
||||
}
|
||||
if (visit && Mpi::Root())
|
||||
{
|
||||
VisItDataCollection visit_dc("bounding-box-input", &pmesh_ser);
|
||||
visit_dc.SetFormat(DataCollection::SERIAL_FORMAT);
|
||||
visit_dc.Save();
|
||||
|
||||
VisItDataCollection visit_dc_bb("bounding-box", &meshbb);
|
||||
visit_dc_bb.SetFormat(DataCollection::SERIAL_FORMAT);
|
||||
visit_dc_bb.Save();
|
||||
}
|
||||
|
||||
// Print min and max bound of nodal gridfunction
|
||||
int ref_factor = 4;
|
||||
nodes->GetBounds(lower, upper, ref_factor);
|
||||
if (Mpi::Root())
|
||||
{
|
||||
out << "Nodal position minimum bounds:" << endl;
|
||||
lower.Print();
|
||||
out << "Nodal position maximum bounds:" << endl;
|
||||
upper.Print();
|
||||
}
|
||||
|
||||
if (!jacobian) { return 0; }
|
||||
|
||||
// Setup gridfunction for the determinant of the Jacobian.
|
||||
// Note: determinant order = rdim*mesh_order - 1 for quads/hexes
|
||||
int det_order = rdim*mesh_poly_deg-1;
|
||||
L2_FECollection fec_det(det_order, rdim, BasisType::GaussLobatto);
|
||||
ParFiniteElementSpace fespace_det(&pmesh, &fec_det);
|
||||
ParGridFunction detgf(&fespace_det);
|
||||
GetDeterminantJacobianGF(&pmesh, &detgf);
|
||||
|
||||
// Setup piecewise constant gridfunction to save bounds on the determinant
|
||||
// of the Jacobian
|
||||
L2_FECollection fec_det_pc(0, rdim);
|
||||
ParFiniteElementSpace fes_det_pc(&pmesh, &fec_det_pc);
|
||||
ParGridFunction bounds_detgf_lower(&fes_det_pc);
|
||||
ParGridFunction bounds_detgf_upper(&fes_det_pc);
|
||||
|
||||
// Compute bounds
|
||||
detgf.GetElementBounds(bounds_detgf_lower, bounds_detgf_upper, ref_factor);
|
||||
|
||||
// GLVis Visualization
|
||||
if (visualization)
|
||||
{
|
||||
char title1[] = "Determinant of Jacobian (det J)";
|
||||
VisualizeField(pmesh, detgf, title1, 0, 465);
|
||||
char title2[] = "Element-wise lower bound on det J";
|
||||
VisualizeField(pmesh, bounds_detgf_lower, title2, 400, 465);
|
||||
char title3[] = "Element-wise upper bound on det J";
|
||||
VisualizeField(pmesh, bounds_detgf_upper, title3, 800, 465);
|
||||
}
|
||||
// Visit Visualization
|
||||
if (visit)
|
||||
{
|
||||
VisItDataCollection visit_dc("jacobian-determinant-bounds", &pmesh);
|
||||
visit_dc.SetFormat(DataCollection::PARALLEL_FORMAT);
|
||||
visit_dc.RegisterField("determinant", &detgf);
|
||||
visit_dc.RegisterField("det-lower-bound", &bounds_detgf_lower);
|
||||
visit_dc.RegisterField("det-upper-bound", &bounds_detgf_upper);
|
||||
visit_dc.Save();
|
||||
}
|
||||
|
||||
// Print min and max bound of determinant gridfunction
|
||||
detgf.GetBounds(lower, upper, ref_factor);
|
||||
if (Mpi::Root())
|
||||
{
|
||||
out << "Jacobian determinant minimum bound: " << lower(0) << endl;
|
||||
out << "Jacobian determinant maximum bound: " << upper(0) << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Mesh MakeBoundingBoxMesh(Mesh &mesh, GridFunction &nodal_bb_gf)
|
||||
{
|
||||
int nelem = mesh.GetNE();
|
||||
int sdim = mesh.SpaceDimension();
|
||||
int nverts = pow(2,sdim)*nelem;
|
||||
Mesh meshbb(sdim, nverts, nelem, 0, sdim);
|
||||
int eidx = 0;
|
||||
int vidx = 0;
|
||||
for (int e = 0; e < nelem; e++)
|
||||
{
|
||||
Vector xyzminmax_el;
|
||||
nodal_bb_gf.GetElementDofValues(e, xyzminmax_el);
|
||||
if (sdim == 2)
|
||||
{
|
||||
Vector xyz(2);
|
||||
xyz(0) = xyzminmax_el(0);
|
||||
xyz(1) = xyzminmax_el(1);
|
||||
meshbb.AddVertex(xyz);
|
||||
|
||||
xyz(0) = xyzminmax_el(2);
|
||||
xyz(1) = xyzminmax_el(1);
|
||||
meshbb.AddVertex(xyz);
|
||||
|
||||
xyz(0) = xyzminmax_el(2);
|
||||
xyz(1) = xyzminmax_el(3);
|
||||
meshbb.AddVertex(xyz);
|
||||
|
||||
xyz(0) = xyzminmax_el(0);
|
||||
xyz(1) = xyzminmax_el(3);
|
||||
meshbb.AddVertex(xyz);
|
||||
|
||||
const int inds[4] = {vidx++, vidx++, vidx++, vidx++};
|
||||
int attr = eidx+1;
|
||||
meshbb.AddQuad(inds, attr);
|
||||
eidx++;
|
||||
}
|
||||
else if (sdim == 3)
|
||||
{
|
||||
Vector xyz(3);
|
||||
xyz(0) = xyzminmax_el(0);
|
||||
xyz(1) = xyzminmax_el(1);
|
||||
xyz(2) = xyzminmax_el(2);
|
||||
meshbb.AddVertex(xyz);
|
||||
|
||||
xyz(0) = xyzminmax_el(3);
|
||||
xyz(1) = xyzminmax_el(1);
|
||||
xyz(2) = xyzminmax_el(2);
|
||||
meshbb.AddVertex(xyz);
|
||||
|
||||
xyz(0) = xyzminmax_el(3);
|
||||
xyz(1) = xyzminmax_el(4);
|
||||
xyz(2) = xyzminmax_el(2);
|
||||
meshbb.AddVertex(xyz);
|
||||
|
||||
xyz(0) = xyzminmax_el(0);
|
||||
xyz(1) = xyzminmax_el(4);
|
||||
xyz(2) = xyzminmax_el(2);
|
||||
meshbb.AddVertex(xyz);
|
||||
|
||||
xyz(0) = xyzminmax_el(0);
|
||||
xyz(1) = xyzminmax_el(1);
|
||||
xyz(2) = xyzminmax_el(5);
|
||||
meshbb.AddVertex(xyz);
|
||||
|
||||
xyz(0) = xyzminmax_el(3);
|
||||
xyz(1) = xyzminmax_el(1);
|
||||
xyz(2) = xyzminmax_el(5);
|
||||
meshbb.AddVertex(xyz);
|
||||
|
||||
xyz(0) = xyzminmax_el(3);
|
||||
xyz(1) = xyzminmax_el(4);
|
||||
xyz(2) = xyzminmax_el(5);
|
||||
meshbb.AddVertex(xyz);
|
||||
|
||||
xyz(0) = xyzminmax_el(0);
|
||||
xyz(1) = xyzminmax_el(4);
|
||||
xyz(2) = xyzminmax_el(5);
|
||||
meshbb.AddVertex(xyz);
|
||||
|
||||
const int inds[8] = {vidx++, vidx++, vidx++, vidx++,
|
||||
vidx++, vidx++, vidx++, vidx++
|
||||
};
|
||||
meshbb.AddHex(inds, (eidx++)+1);
|
||||
}
|
||||
}
|
||||
if (sdim == 2)
|
||||
{
|
||||
meshbb.FinalizeQuadMesh(1, 1, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
meshbb.FinalizeHexMesh(1, 1, true);
|
||||
}
|
||||
return meshbb;
|
||||
}
|
||||
|
||||
IntegrationRule PermuteIR(const IntegrationRule &irule,
|
||||
const Array<int> ordering)
|
||||
{
|
||||
const int np = irule.GetNPoints();
|
||||
MFEM_VERIFY(np == ordering.Size(), "Invalid permutation size");
|
||||
IntegrationRule ir(np);
|
||||
ir.SetOrder(irule.GetOrder());
|
||||
|
||||
for (int i = 0; i < np; i++)
|
||||
{
|
||||
IntegrationPoint &ip_new = ir.IntPoint(i);
|
||||
const IntegrationPoint &ip_old = irule.IntPoint(ordering[i]);
|
||||
ip_new.Set(ip_old.x, ip_old.y, ip_old.z, ip_old.weight);
|
||||
}
|
||||
|
||||
return ir;
|
||||
}
|
||||
|
||||
void GetDeterminantJacobianGF(ParMesh *mesh, ParGridFunction *detgf)
|
||||
{
|
||||
int dim = mesh->Dimension();
|
||||
FiniteElementSpace *fespace = detgf->FESpace();
|
||||
Array<int> dofs;
|
||||
|
||||
for (int e = 0; e < mesh->GetNE(); e++)
|
||||
{
|
||||
const FiniteElement *fe = fespace->GetFE(e);
|
||||
const IntegrationRule ir = fe->GetNodes();
|
||||
ElementTransformation *transf = mesh->GetElementTransformation(e);
|
||||
DenseMatrix Jac(fe->GetDim());
|
||||
const NodalFiniteElement *nfe = dynamic_cast<const NodalFiniteElement*>
|
||||
(fe);
|
||||
const Array<int> &irordering = nfe->GetLexicographicOrdering();
|
||||
IntegrationRule ir2 = irordering.Size() ?
|
||||
PermuteIR(ir, irordering) :
|
||||
ir;
|
||||
|
||||
Vector detvals(ir2.GetNPoints());
|
||||
Vector loc(dim);
|
||||
for (int q = 0; q < ir2.GetNPoints(); q++)
|
||||
{
|
||||
IntegrationPoint ip = ir2.IntPoint(q);
|
||||
transf->SetIntPoint(&ip);
|
||||
transf->Transform(ip, loc);
|
||||
Jac = transf->Jacobian();
|
||||
detvals(q) = Jac.Weight();
|
||||
}
|
||||
|
||||
fespace->GetElementDofs(e, dofs);
|
||||
if (irordering.Size())
|
||||
{
|
||||
for (int i = 0; i < dofs.Size(); i++)
|
||||
{
|
||||
(*detgf)(dofs[i]) = detvals(irordering[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
detgf->SetSubVector(dofs, detvals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VisualizeBB(Mesh &mesh, char *title, int pos_x, int pos_y)
|
||||
{
|
||||
socketstream sock;
|
||||
sock.open("localhost", 19916);
|
||||
sock << "mesh\n";
|
||||
mesh.Print(sock);
|
||||
std::string keystrokes = mesh.SpaceDimension() == 2 ? "keys em" : "keys )";
|
||||
sock << "window_title '"<< title << "'\n"
|
||||
<< "window_geometry "
|
||||
<< pos_x << " " << pos_y << " " << 400 << " " << 400 << "\n"
|
||||
// << "keys jRmclA//]]]]]]]]" << endl;
|
||||
<< keystrokes << endl;
|
||||
}
|
||||
|
||||
void VisualizeField(ParMesh &pmesh, ParGridFunction &input,
|
||||
char *title, int pos_x, int pos_y)
|
||||
{
|
||||
socketstream sock;
|
||||
if (pmesh.GetMyRank() == 0)
|
||||
{
|
||||
sock.open("localhost", 19916);
|
||||
sock << "solution\n";
|
||||
}
|
||||
pmesh.PrintAsOne(sock);
|
||||
input.SaveAsOne(sock);
|
||||
if (pmesh.GetMyRank() == 0)
|
||||
{
|
||||
sock << "window_title '"<< title << "'\n"
|
||||
<< "window_geometry "
|
||||
<< pos_x << " " << pos_y << " " << 400 << " " << 400 << "\n"
|
||||
<< "keys jRmclApppppppppppp//]]]]]]]]" << endl;
|
||||
}
|
||||
}
|
||||
@@ -371,6 +371,7 @@ int main (int argc, char *argv[])
|
||||
cout << "What would you like to do?\n"
|
||||
"r) Refine\n"
|
||||
"c) Change curvature\n"
|
||||
"i) Increase space dimension\n"
|
||||
"s) Scale\n"
|
||||
"t) Transform\n"
|
||||
"j) Jitter\n"
|
||||
@@ -534,6 +535,37 @@ int main (int argc, char *argv[])
|
||||
print_char = 1;
|
||||
}
|
||||
|
||||
if (mk == 'i')
|
||||
{
|
||||
int curr_sdim = mesh->SpaceDimension();
|
||||
cout << "Current space dimension is " << curr_sdim << "\n";
|
||||
cout << "Enter new space dimension --> " << flush;
|
||||
int new_sdim;
|
||||
cin >> new_sdim;
|
||||
if (new_sdim > curr_sdim && new_sdim <= 3)
|
||||
{
|
||||
if (mesh->GetNodes() == NULL)
|
||||
{
|
||||
mesh->SetCurvature(1, false, new_sdim); // Set Space Dimension
|
||||
mesh->SetCurvature(-1); // Remove Nodes GridFunction created
|
||||
// // by the previous line
|
||||
}
|
||||
else
|
||||
{
|
||||
const FiniteElementSpace *fes = mesh->GetNodalFESpace();
|
||||
const int order = fes->GetMaxElementOrder();
|
||||
const FiniteElementCollection *fec = fes->FEColl();
|
||||
const bool discont = dynamic_cast<const L2_FECollection*>(fec);
|
||||
mesh->SetCurvature(order, discont, new_sdim);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "New space dimension must be greater than current space "
|
||||
<< "dimension and less than 4." << endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (mk == 'c')
|
||||
{
|
||||
int p;
|
||||
@@ -576,6 +608,7 @@ int main (int argc, char *argv[])
|
||||
char type;
|
||||
cout << "Choose a transformation:\n"
|
||||
"u) User-defined transform through mesh-explorer::transformation()\n"
|
||||
"a) Affine transform\n"
|
||||
"k) Kershaw transform\n"
|
||||
"s) Spiral transform\n"<< "---> " << flush;
|
||||
cin >> type;
|
||||
@@ -583,6 +616,102 @@ int main (int argc, char *argv[])
|
||||
{
|
||||
mesh->Transform(transformation);
|
||||
}
|
||||
else if (type == 'a')
|
||||
{
|
||||
DenseMatrix A(sdim);
|
||||
Vector b(sdim);
|
||||
|
||||
char tmtype;
|
||||
cout << "Type of transformation matrix:\n"
|
||||
"i) Identity\n"
|
||||
"r) Rotation\n"
|
||||
"s) Scale\n"
|
||||
"g) General\n" << " ---> " << flush;
|
||||
cin >> tmtype;
|
||||
|
||||
if (tmtype == 'i')
|
||||
{
|
||||
A = 0.0;
|
||||
A(0,0) = 1.0;
|
||||
if (sdim > 1) { A(1,1) = 1.0; }
|
||||
if (sdim > 2) { A(2,2) = 1.0; }
|
||||
}
|
||||
if (tmtype == 'r')
|
||||
{
|
||||
if (sdim == 2)
|
||||
{
|
||||
real_t angle_deg;
|
||||
cout << "Rotation angle (degrees) --> " << flush;
|
||||
cin >> angle_deg;
|
||||
const real_t angle = angle_deg * M_PI / 180.0;
|
||||
A(0,0) = cos(angle);
|
||||
A(1,0) = sin(angle);
|
||||
A(0,1) = -A(1,0);
|
||||
A(1,1) = A(0,0);
|
||||
}
|
||||
else
|
||||
{
|
||||
real_t a_deg, b_deg, c_deg;
|
||||
cout << "Euler angles z-x-z (degrees) --> " << flush;
|
||||
cin >> a_deg >> b_deg >> c_deg;
|
||||
|
||||
const real_t alpha = a_deg * M_PI / 180.0;
|
||||
const real_t beta = b_deg * M_PI / 180.0;
|
||||
const real_t gamma = c_deg * M_PI / 180.0;
|
||||
|
||||
const real_t ca = cos(alpha), sa = sin(alpha);
|
||||
const real_t cb = cos(beta ), sb = sin(beta );
|
||||
const real_t cc = cos(gamma), sc = sin(gamma);
|
||||
|
||||
A(0,0) = ca * cc - cb * sa * sc;
|
||||
A(0,1) = -ca * sc - cb * cc * sa;
|
||||
A(0,2) = sa * sb;
|
||||
|
||||
A(1,0) = cc * sa + ca * cb * sc;
|
||||
A(1,1) = ca * cb * cc - sa * sc;
|
||||
A(1,2) = -ca * sb;
|
||||
|
||||
A(2,0) = sb * sc;
|
||||
A(2,1) = cc * sb;
|
||||
A(2,2) = cb;
|
||||
}
|
||||
}
|
||||
if (tmtype == 's')
|
||||
{
|
||||
A = 0.0;
|
||||
cout << "Scale factors for each cartesian direction --> "
|
||||
<< flush;
|
||||
cin >> A(0,0);
|
||||
if (sdim > 1) { cin >> A(1,1); }
|
||||
if (sdim > 2) { cin >> A(2,2); }
|
||||
}
|
||||
if (tmtype == 'g')
|
||||
{
|
||||
cout << "General matrix entries in column major order --> "
|
||||
<< flush;
|
||||
for (int j=0; j<sdim; j++)
|
||||
for (int i=0; i<sdim; i++)
|
||||
{
|
||||
cin >> A(i,j);
|
||||
}
|
||||
|
||||
const real_t detA = A.Det();
|
||||
if (detA <= 0.0)
|
||||
{
|
||||
cout << "Warning - transformation matrix has non-positive "
|
||||
<< "determinant. Elements may be flattened or "
|
||||
<< "inverted.\n";
|
||||
}
|
||||
}
|
||||
|
||||
cout << "Translation vector components --> " << flush;
|
||||
cin >> b(0);
|
||||
if (sdim > 1) { cin >> b(1); }
|
||||
if (sdim > 2) { cin >> b(2); }
|
||||
|
||||
common::AffineTransformation affineT(sdim, A, b);
|
||||
mesh->Transform(affineT);
|
||||
}
|
||||
else if (type == 'k')
|
||||
{
|
||||
cout << "Note: For Kershaw transformation, the input must be "
|
||||
|
||||
@@ -65,10 +65,10 @@ int main(int argc, char *argv[])
|
||||
args.AddOption(&dim, "-dim", "--dim", "Mesh dimension (2 or 3)");
|
||||
args.AddOption(&deterministic, "-det", "--deterministic", "-not-det",
|
||||
"--not-deterministic",
|
||||
"Whether to use deterministic random refinements");
|
||||
"Use deterministic random refinements");
|
||||
args.AddOption(&projectSolution, "-proj", "--project-solution", "-no-proj",
|
||||
"--no-project",
|
||||
"Whether to project a coefficient to solution");
|
||||
"Project a coefficient to solution");
|
||||
|
||||
args.Parse();
|
||||
if (!args.Good())
|
||||
|
||||
@@ -38,13 +38,13 @@ if (MFEM_USE_MPI)
|
||||
${MFEM_MINIAPPS_COMMON_HEADERS}
|
||||
LIBRARIES mfem mfem-common)
|
||||
add_dependencies(multidomain_nd copy_miniapps_multidomain_data)
|
||||
|
||||
|
||||
add_mfem_miniapp(multidomain_rt
|
||||
MAIN multidomain_rt.cpp
|
||||
${MFEM_MINIAPPS_COMMON_HEADERS}
|
||||
LIBRARIES mfem mfem-common)
|
||||
add_dependencies(multidomain_rt copy_miniapps_multidomain_data)
|
||||
|
||||
|
||||
# Add parallel tests.
|
||||
if (MFEM_ENABLE_TESTING)
|
||||
add_test(NAME multidomain_np${MFEM_MPI_NP}
|
||||
|
||||
@@ -11,9 +11,14 @@
|
||||
|
||||
if (MFEM_USE_MPI)
|
||||
list(APPEND NAVIER_COMMON_SOURCES
|
||||
navier_solver.cpp)
|
||||
navier_solver.cpp
|
||||
incompressible_navier_solver.cpp
|
||||
stokes_solver.cpp)
|
||||
|
||||
list(APPEND NAVIER_COMMON_HEADERS
|
||||
navier_solver.hpp)
|
||||
navier_solver.hpp
|
||||
incompressible_navier_solver.hpp
|
||||
stokes_solver.hpp)
|
||||
|
||||
convert_filenames_to_full_paths(NAVIER_COMMON_SOURCES)
|
||||
convert_filenames_to_full_paths(NAVIER_COMMON_HEADERS)
|
||||
@@ -52,6 +57,11 @@ if (MFEM_USE_MPI)
|
||||
${NAVIER_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
add_mfem_miniapp(incompNS_2Dtest
|
||||
MAIN incompNS_2Dtest.cpp
|
||||
${NAVIER_COMMON_FILES}
|
||||
LIBRARIES mfem)
|
||||
|
||||
add_mfem_miniapp(navier_turbchan
|
||||
MAIN navier_turbchan.cpp
|
||||
${NAVIER_COMMON_FILES}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2010-2024, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
// 3D flow over a cylinder benchmark example
|
||||
|
||||
#include "incompressible_navier_solver.hpp"
|
||||
#include <fstream>
|
||||
|
||||
using namespace mfem;
|
||||
using namespace incompressible_navier;
|
||||
|
||||
void vel(const Vector &x, real_t t, Vector &u)
|
||||
{
|
||||
real_t xi = x(0);
|
||||
real_t yi = x(1);
|
||||
|
||||
u = 0.0;
|
||||
}
|
||||
|
||||
void vel_inlet(const Vector &x, real_t t, Vector &u)
|
||||
{
|
||||
u = 0.0;
|
||||
if (x(0) < 0.001) {
|
||||
|
||||
u(0) = -0.001 * (std::pow(x(1) - 0.5, 2.0) - 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Mpi::Init(argc, argv);
|
||||
Hypre::Init();
|
||||
|
||||
int serial_refinements = 1;
|
||||
int vOrder = 2;
|
||||
int pOrder = 1;
|
||||
int tOrder = 1;
|
||||
real_t kin_vis = 20.0;
|
||||
real_t dt = 1e-2;
|
||||
real_t t = 0.0;
|
||||
real_t t_final = 1.0;
|
||||
bool last_step = false;
|
||||
|
||||
//Mesh *mesh = new Mesh("box-cylinder.mesh");
|
||||
Mesh mesh = Mesh::MakeCartesian2D(90, 30, mfem::Element::QUADRILATERAL, true, 3, 1);
|
||||
|
||||
for (int i = 0; i < serial_refinements; ++i)
|
||||
{
|
||||
mesh.UniformRefinement();
|
||||
}
|
||||
|
||||
if (Mpi::Root())
|
||||
{
|
||||
std::cout << "Number of elements: " << mesh.GetNE() << std::endl;
|
||||
}
|
||||
|
||||
auto *pmesh = new ParMesh(MPI_COMM_WORLD, mesh);
|
||||
|
||||
// Create the flow solver.
|
||||
IncompressibleNavierSolver flowsolver(pmesh, vOrder, pOrder, tOrder, kin_vis);
|
||||
flowsolver.EnablePA(false);
|
||||
|
||||
// // Set the initial condition.
|
||||
// ParGridFunction *u_ic = flowsolver.GetCurrentVelocity();
|
||||
// VectorFunctionCoefficient u_excoeff(pmesh->Dimension(), vel);
|
||||
// u_ic->ProjectCoefficient(u_excoeff);
|
||||
|
||||
// Add Dirichlet boundary conditions to velocity space restricted to
|
||||
// selected attributes on the mesh.
|
||||
Array<int> attr(pmesh->bdr_attributes.Max()); attr = 0;
|
||||
Array<int> attr_inlet(pmesh->bdr_attributes.Max()); attr_inlet = 0;
|
||||
// Inlet is attribute 1.
|
||||
attr[0] = 1;
|
||||
// Walls is attribute 3.
|
||||
attr[2] = 1;
|
||||
flowsolver.AddVelDirichletBC(vel, attr);
|
||||
|
||||
attr_inlet[3] = 1;
|
||||
flowsolver.AddVelDirichletBC(vel_inlet, attr_inlet);
|
||||
|
||||
flowsolver.Setup(dt);
|
||||
|
||||
ParGridFunction *u_gf = flowsolver.GetCurrentVelocity();
|
||||
ParGridFunction *p_gf = flowsolver.GetCurrentPressure();
|
||||
ParGridFunction *psi_gf = flowsolver.GetCurrentPsi();
|
||||
|
||||
ParaViewDataCollection pvdc("3dfoc", pmesh);
|
||||
pvdc.SetDataFormat(VTKFormat::BINARY32);
|
||||
//pvdc.SetHighOrderOutput(true);
|
||||
pvdc.SetCycle(0);
|
||||
pvdc.SetTime(t);
|
||||
pvdc.RegisterField("velocity", u_gf);
|
||||
pvdc.RegisterField("pressure", p_gf);
|
||||
pvdc.RegisterField("psi", psi_gf);
|
||||
pvdc.Save();
|
||||
|
||||
for (int step = 0; !last_step; ++step)
|
||||
{
|
||||
if (t + dt >= t_final - dt / 2)
|
||||
{
|
||||
last_step = true;
|
||||
}
|
||||
|
||||
flowsolver.Step(t, dt, step);
|
||||
|
||||
if (step % 1 == 0)
|
||||
{
|
||||
pvdc.SetCycle(step);
|
||||
pvdc.SetTime(t);
|
||||
pvdc.Save();
|
||||
}
|
||||
|
||||
if (Mpi::Root())
|
||||
{
|
||||
printf("%11s %11s\n", "Time", "dt");
|
||||
printf("%.5E %.5E\n", t, dt);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
// flowsolver.PrintTimingData();
|
||||
|
||||
delete pmesh;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
// Copyright (c) 2010-2024, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#include "incompressible_navier_solver.hpp"
|
||||
#include "../../general/forall.hpp"
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
|
||||
using namespace mfem;
|
||||
using namespace incompressible_navier;
|
||||
|
||||
IncompressibleNavierSolver::IncompressibleNavierSolver(ParMesh *mesh, int velorder, int porder, int torder_, real_t kin_vis)
|
||||
: pmesh(mesh), velorder(velorder), porder(porder), torder(torder_), kin_vis(kin_vis),
|
||||
gll_rules(0, Quadrature1D::GaussLobatto), velGF(torder_+1,nullptr), pGF(torder_+1,nullptr)
|
||||
{
|
||||
vfec = new H1_FECollection(velorder, pmesh->Dimension());
|
||||
psifec = new H1_FECollection(porder);
|
||||
pfec = new H1_FECollection(porder);
|
||||
vfes = new ParFiniteElementSpace(pmesh, vfec, pmesh->Dimension());
|
||||
psifes = new ParFiniteElementSpace(pmesh, pfec);
|
||||
pfes = new ParFiniteElementSpace(pmesh, pfec);
|
||||
|
||||
// Check if fully periodic mesh
|
||||
if (!(pmesh->bdr_attributes.Size() == 0))
|
||||
{
|
||||
vel_ess_attr.SetSize(pmesh->bdr_attributes.Max());
|
||||
vel_ess_attr = 0;
|
||||
|
||||
pres_ess_attr.SetSize(pmesh->bdr_attributes.Max());
|
||||
pres_ess_attr = 0;
|
||||
}
|
||||
|
||||
int vfes_truevsize = vfes->GetTrueVSize();
|
||||
int pfes_truevsize = pfes->GetTrueVSize();
|
||||
|
||||
for( int i = 0; i<torder+1; i++)
|
||||
{
|
||||
velGF[i] = new ParGridFunction(vfes); *velGF[i] = 0.0;
|
||||
pGF[i] = new ParGridFunction(pfes); *pGF[i] = 0.0;
|
||||
}
|
||||
|
||||
psiGF.SetSpace(psifes);
|
||||
DvGF.SetSpace(vfes);
|
||||
divVelGF.SetSpace(pfes);
|
||||
pRHS.SetSpace(pfes);
|
||||
}
|
||||
|
||||
void IncompressibleNavierSolver::Setup(real_t dt)
|
||||
{
|
||||
if (verbose && pmesh->GetMyRank() == 0)
|
||||
{
|
||||
mfem::out << "Setup" << std::endl;
|
||||
if (partial_assembly)
|
||||
{
|
||||
mfem::out << "Using Partial Assembly" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
mfem::out << "Using Full Assembly" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
this->Setup_velocity( dt );
|
||||
|
||||
this->Setup_auxiliary( dt );
|
||||
|
||||
this->Setup_pressure( dt );
|
||||
}
|
||||
|
||||
void IncompressibleNavierSolver::Setup_velocity(real_t dt)
|
||||
{
|
||||
// GLL integration rule (Numerical Integration)
|
||||
const IntegrationRule &ir_ni = gll_rules.Get(vfes->GetFE(0)->GetGeomType(),
|
||||
2 * velorder - 1);
|
||||
|
||||
vfes->GetEssentialTrueDofs(vel_ess_attr, vel_ess_tdof);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
//Setup of coefficient for mass term of Eq(13)
|
||||
dtCoeff = new ConstantCoefficient(1.0/dt);
|
||||
auto *vmass_blfi = new VectorMassIntegrator(*dtCoeff);
|
||||
|
||||
//Setup of coefficient for stiffness term of Eq(13)
|
||||
kinvisCoeff = new ConstantCoefficient(kin_vis);
|
||||
auto *vdiff_blfi = new VectorDiffusionIntegrator(*kinvisCoeff);
|
||||
|
||||
// setup of Bilinear form of Eq(13)
|
||||
velBForm = new ParBilinearForm(vfes);
|
||||
if (numerical_integ)
|
||||
{
|
||||
vmass_blfi->SetIntRule(&ir_ni);
|
||||
vdiff_blfi->SetIntRule(&ir_ni);
|
||||
}
|
||||
velBForm->AddDomainIntegrator(vmass_blfi);
|
||||
velBForm->AddDomainIntegrator(vdiff_blfi);
|
||||
if (partial_assembly)
|
||||
{
|
||||
velBForm->SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
||||
}
|
||||
|
||||
velBForm->Assemble();
|
||||
velBForm->FormSystemMatrix(vel_ess_tdof, vOp);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
//Setup of coefficient for Eq(18)
|
||||
pUnitVectorCoeff = new UnitVectorGridFunctionCoeff(pmesh->Dimension());
|
||||
auto *pvel_lfi = new VectorDomainLFGradIntegrator(*pUnitVectorCoeff);
|
||||
|
||||
//Setup of coefficient for Eq(20)
|
||||
nonlinTermCoeff = new NonLinTermVectorGridFunctionCoeff(pmesh->Dimension());
|
||||
auto *p_nonlintermlfi = new VectorDomainLFIntegrator(*nonlinTermCoeff);
|
||||
|
||||
//Setup of coefficient for Eq(21)
|
||||
prevVelLoadCoeff = new PrevVelVectorGridFunctionCoeff(pmesh->Dimension());
|
||||
auto *prevVelLoadLFi = new VectorDomainLFIntegrator(*prevVelLoadCoeff);
|
||||
|
||||
//Setup of linear form of Eq(13)
|
||||
velLForm = new ParLinearForm(vfes);
|
||||
if (numerical_integ)
|
||||
{
|
||||
prevVelLoadLFi->SetIntRule(&ir_ni);
|
||||
pvel_lfi->SetIntRule(&ir_ni);
|
||||
p_nonlintermlfi->SetIntRule(&ir_ni);
|
||||
}
|
||||
velLForm->AddDomainIntegrator(prevVelLoadLFi);
|
||||
velLForm->AddDomainIntegrator(pvel_lfi);
|
||||
velLForm->AddDomainIntegrator(p_nonlintermlfi);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
if (partial_assembly)
|
||||
{
|
||||
Vector diag_pa(vfes->GetTrueVSize());
|
||||
velBForm->AssembleDiagonal(diag_pa);
|
||||
velInvPC = new OperatorJacobiSmoother(diag_pa, vel_ess_tdof);
|
||||
}
|
||||
else
|
||||
{
|
||||
velInvPC = new HypreSmoother(*vOp.As<HypreParMatrix>());
|
||||
dynamic_cast<HypreSmoother *>(velInvPC)->SetType(HypreSmoother::Jacobi, 1);
|
||||
}
|
||||
|
||||
velInv = new CGSolver(vfes->GetComm());
|
||||
velInv->iterative_mode = true;
|
||||
velInv->SetOperator(*vOp);
|
||||
velInv->SetPreconditioner(*velInvPC);
|
||||
velInv->SetPrintLevel(pl_velsolve);
|
||||
velInv->SetRelTol(rtol_velsolve);
|
||||
velInv->SetMaxIter(1200);
|
||||
}
|
||||
|
||||
void IncompressibleNavierSolver::Setup_auxiliary(real_t dt)
|
||||
{
|
||||
// GLL integration rule (Numerical Integration)
|
||||
const IntegrationRule &ir_ni = gll_rules.Get(vfes->GetFE(0)->GetGeomType(),
|
||||
2 * velorder - 1);
|
||||
Array<int> empty;
|
||||
|
||||
// setup of Bilinear form of Eq(14)
|
||||
psiBForm = new ParBilinearForm(psifes);
|
||||
auto *psidiff_blfi = new DiffusionIntegrator;
|
||||
|
||||
if (numerical_integ)
|
||||
{
|
||||
psidiff_blfi->SetIntRule(&ir_ni);
|
||||
}
|
||||
psiBForm->AddDomainIntegrator(psidiff_blfi);
|
||||
if (partial_assembly)
|
||||
{
|
||||
psiBForm->SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
||||
}
|
||||
|
||||
psiBForm->Assemble();
|
||||
psiBForm->FormSystemMatrix(empty, psiOp);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
//Setup of coefficient for linear form in Eq(14)
|
||||
DvelCoeff = new VectorGridFunctionCoefficient;
|
||||
auto *Dvel_lfi = new DomainLFGradIntegrator(*DvelCoeff);
|
||||
|
||||
//Setup of linear form of Eq(14)
|
||||
psiLForm = new ParLinearForm(psifes);
|
||||
|
||||
if (numerical_integ)
|
||||
{
|
||||
Dvel_lfi->SetIntRule(&ir_ni);
|
||||
}
|
||||
psiLForm->AddDomainIntegrator(Dvel_lfi);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
if (partial_assembly)
|
||||
{
|
||||
int psifes_truevsize = psifes->GetTrueVSize();
|
||||
mfem::Vector psin(psifes_truevsize); psin = 0.0;
|
||||
mfem::Vector respsi(psifes_truevsize); respsi = 0.0;
|
||||
|
||||
lor = new ParLORDiscretization(*psiBForm, empty);
|
||||
psiInvPC = new HypreBoomerAMG(lor->GetAssembledMatrix());
|
||||
psiInvPC->SetPrintLevel(0);
|
||||
psiInvPC->Mult(respsi, psin);
|
||||
SpInvOrthoPC = new OrthoSolver(psifes->GetComm());
|
||||
SpInvOrthoPC->SetSolver(*psiInvPC);
|
||||
}
|
||||
else
|
||||
{
|
||||
psiInvPC = new HypreBoomerAMG(*psiOp.As<HypreParMatrix>());
|
||||
psiInvPC->SetPrintLevel(0);
|
||||
SpInvOrthoPC = new OrthoSolver(psifes->GetComm());
|
||||
SpInvOrthoPC->SetSolver(*psiInvPC);
|
||||
}
|
||||
|
||||
psiInv = new CGSolver(psifes->GetComm());
|
||||
psiInv->iterative_mode = true;
|
||||
psiInv->SetOperator(*psiOp);
|
||||
psiInv->SetPreconditioner(*SpInvOrthoPC);
|
||||
psiInv->SetPrintLevel(pl_psisolve);
|
||||
psiInv->SetRelTol(rtol_psisolve);
|
||||
psiInv->SetMaxIter(1000);
|
||||
}
|
||||
|
||||
void IncompressibleNavierSolver::Setup_pressure(real_t dt)
|
||||
{
|
||||
// GLL integration rule (Numerical Integration)
|
||||
const IntegrationRule &ir_ni = gll_rules.Get(vfes->GetFE(0)->GetGeomType(),
|
||||
2 * velorder - 1);
|
||||
Array<int> empty;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// setup of Bilinear form of Eq(15)
|
||||
pBForm = new ParBilinearForm(pfes);
|
||||
auto *pmass_blfi = new MassIntegrator;
|
||||
|
||||
if (numerical_integ)
|
||||
{
|
||||
pmass_blfi->SetIntRule(&ir_ni);
|
||||
}
|
||||
pBForm->AddDomainIntegrator(pmass_blfi);
|
||||
if (partial_assembly)
|
||||
{
|
||||
pBForm->SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
||||
}
|
||||
|
||||
pBForm->Assemble();
|
||||
pBForm->FormSystemMatrix(empty, pOp);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
//Setup of divergence of velocity coefficient for linear form in Eq(15)
|
||||
divVelCoeff = new DivergenceGridFunctionCoefficient(velGF[0]);
|
||||
|
||||
//Setup of coefficient for linear form in Eq(14)
|
||||
pRHSCoeff = new GridFunctionCoefficient(&pRHS);
|
||||
auto *p_lfi = new DomainLFIntegrator(*pRHSCoeff);
|
||||
|
||||
//Setup of linear form of Eq(15)
|
||||
pLForm = new ParLinearForm(pfes);
|
||||
if (numerical_integ)
|
||||
{
|
||||
p_lfi->SetIntRule(&ir_ni);
|
||||
}
|
||||
pLForm->AddDomainIntegrator(p_lfi);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
if (partial_assembly)
|
||||
{
|
||||
Vector diag_pa(pfes->GetTrueVSize());
|
||||
pBForm->AssembleDiagonal(diag_pa);
|
||||
pInvPC = new OperatorJacobiSmoother(diag_pa, empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
pInvPC = new HypreSmoother(*pOp.As<HypreParMatrix>());
|
||||
dynamic_cast<HypreSmoother *>(pInvPC)->SetType(HypreSmoother::Jacobi, 1);
|
||||
}
|
||||
|
||||
pInv = new CGSolver(pfes->GetComm());
|
||||
pInv->iterative_mode = true;
|
||||
pInv->SetOperator(*pOp);
|
||||
pInv->SetPreconditioner(*pInvPC);
|
||||
pInv->SetPrintLevel(pl_psolve);
|
||||
pInv->SetRelTol(rtol_psolve);
|
||||
pInv->SetMaxIter(1000);
|
||||
}
|
||||
|
||||
void IncompressibleNavierSolver::UpdateTimestepHistory(real_t dt)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IncompressibleNavierSolver::Step(real_t &time, real_t dt, int current_step)
|
||||
{
|
||||
this->Step_velocity(time, dt, current_step);
|
||||
|
||||
this->Step_auxiliary(time, dt, current_step);
|
||||
|
||||
this->Step_pressure(time, dt, current_step);
|
||||
|
||||
*velGF[1] = *velGF[0];
|
||||
*pGF[1] = *pGF[0];
|
||||
|
||||
mfem::out << "It: " << iter << " | Iter_U: " << iter_vsolve << " | Iter_Psi: " << iter_psisolve << " | Iter_P: " << iter_psolve << "\n";
|
||||
mfem::out << "It: " << iter << " | Resid_U: " << res_vsolve << " | Resid_Psi: " << res_psisolve << " | Resid_P: " << res_psisolve << "\n";
|
||||
|
||||
time += dt;
|
||||
iter ++;
|
||||
}
|
||||
|
||||
void IncompressibleNavierSolver::Step_velocity(real_t &time, real_t dt, int current_step)
|
||||
{
|
||||
for (auto &vel_dbc : vel_dbcs)
|
||||
{
|
||||
velGF[0]->ProjectBdrCoefficient(*vel_dbc.coeff, vel_dbc.attr);
|
||||
velGF[1]->ProjectBdrCoefficient(*vel_dbc.coeff, vel_dbc.attr);
|
||||
}
|
||||
|
||||
//Update state in coefficient for Eq(18)
|
||||
pUnitVectorCoeff->SetGridFunction( pGF[1] );
|
||||
|
||||
//Update state in coefficient for Eq(20)
|
||||
nonlinTermCoeff->SetGridFunction( velGF[1] );
|
||||
|
||||
//Update state in coefficient for Eq(21)
|
||||
prevVelLoadCoeff ->SetGridFunction( velGF[1], dt );
|
||||
|
||||
velLForm->Assemble();
|
||||
velLForm->ParallelAssemble(velLF);
|
||||
|
||||
Vector X1, B1;
|
||||
|
||||
if (partial_assembly)
|
||||
{
|
||||
auto *vpC = vOp.As<ConstrainedOperator>();
|
||||
EliminateRHS(*velBForm, *vpC, vel_ess_tdof, *velGF[0], velLF, X1, B1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
velBForm->FormLinearSystem(vel_ess_tdof, *velGF[0], velLF, vOp , X1, B1, 1);
|
||||
}
|
||||
|
||||
velInv->Mult(B1, X1);
|
||||
iter_vsolve = velInv->GetNumIterations();
|
||||
res_vsolve = velInv->GetFinalNorm();
|
||||
velBForm->RecoverFEMSolution(X1, velLF, *velGF[0]);
|
||||
}
|
||||
|
||||
void IncompressibleNavierSolver::Step_auxiliary(real_t &time, real_t dt, int current_step)
|
||||
{
|
||||
// Compute new increment GF for LF of Eq(14) and update state in coefficient
|
||||
subtract(1.0/dt, *velGF[0], *velGF[1], DvGF);
|
||||
DvelCoeff->SetGridFunction( &DvGF );
|
||||
|
||||
psiLForm->Assemble();
|
||||
psiLForm->ParallelAssemble(psiLF);
|
||||
|
||||
Vector X2, B2;
|
||||
Array<int> empty;
|
||||
if (partial_assembly)
|
||||
{
|
||||
auto *psipC = psiOp.As<ConstrainedOperator>();
|
||||
EliminateRHS(*psiBForm, *psipC, empty, psiGF, psiLF, X2, B2, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
psiBForm->FormLinearSystem(empty, psiGF, psiLF, psiOp, X2, B2, 1);
|
||||
}
|
||||
|
||||
psiInv->Mult(B2, X2);
|
||||
iter_psisolve = psiInv->GetNumIterations();
|
||||
res_psisolve = psiInv->GetFinalNorm();
|
||||
psiBForm->RecoverFEMSolution(X2, psiLF, psiGF);
|
||||
}
|
||||
|
||||
void IncompressibleNavierSolver::Step_pressure(real_t &time, real_t dt, int current_step)
|
||||
{
|
||||
Array<int> empty;
|
||||
|
||||
// Compute new GF for LF of Eq(15) and update state in coefficient
|
||||
divVelCoeff->SetGridFunction( velGF[0]);
|
||||
divVelGF.ProjectCoefficient( *divVelCoeff );
|
||||
|
||||
add( *pGF[1], psiGF, pRHS);
|
||||
add( pRHS, -1.0*kin_vis, divVelGF, pRHS);
|
||||
pRHSCoeff->SetGridFunction( &pRHS );
|
||||
|
||||
pLForm->Assemble();
|
||||
pLForm->ParallelAssemble(pLF);
|
||||
|
||||
Vector X3, B3;
|
||||
|
||||
if (partial_assembly)
|
||||
{
|
||||
auto *ppC = pOp.As<ConstrainedOperator>();
|
||||
EliminateRHS(*pBForm, *ppC, empty, *pGF[0], pLF, X3, B3, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
pBForm->FormLinearSystem(empty, *pGF[0] , pLF , pOp , X3, B3, 1);
|
||||
}
|
||||
|
||||
pInv->Mult(B3, X3);
|
||||
iter_psolve = pInv->GetNumIterations();
|
||||
res_psisolve = pInv->GetFinalNorm();
|
||||
pBForm->RecoverFEMSolution(X3, pLF, *pGF[0]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void IncompressibleNavierSolver::EliminateRHS(Operator &A,
|
||||
ConstrainedOperator &constrainedA,
|
||||
const Array<int> &ess_tdof_list,
|
||||
Vector &x,
|
||||
Vector &b,
|
||||
Vector &X,
|
||||
Vector &B,
|
||||
int copy_interior)
|
||||
{
|
||||
const Operator *Po = A.GetOutputProlongation();
|
||||
const Operator *Pi = A.GetProlongation();
|
||||
const Operator *Ri = A.GetRestriction();
|
||||
A.InitTVectors(Po, Ri, Pi, x, b, X, B);
|
||||
if (!copy_interior)
|
||||
{
|
||||
X.SetSubVectorComplement(ess_tdof_list, 0.0);
|
||||
}
|
||||
constrainedA.EliminateRHS(X, B);
|
||||
}
|
||||
|
||||
real_t IncompressibleNavierSolver::ComputeCFL(ParGridFunction &u, real_t dt)
|
||||
{
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IncompressibleNavierSolver::AddVelDirichletBC(VectorCoefficient *coeff, Array<int> &attr)
|
||||
{
|
||||
vel_dbcs.emplace_back(attr, coeff);
|
||||
|
||||
if (verbose && pmesh->GetMyRank() == 0)
|
||||
{
|
||||
mfem::out << "Adding Velocity Dirichlet BC to attributes ";
|
||||
for (int i = 0; i < attr.Size(); ++i)
|
||||
{
|
||||
if (attr[i] == 1)
|
||||
{
|
||||
mfem::out << i << " ";
|
||||
}
|
||||
}
|
||||
mfem::out << std::endl;
|
||||
}
|
||||
|
||||
for (int i = 0; i < attr.Size(); ++i)
|
||||
{
|
||||
MFEM_ASSERT((vel_ess_attr[i] && attr[i]) == 0,
|
||||
"Duplicate boundary definition deteceted.");
|
||||
if (attr[i] == 1)
|
||||
{
|
||||
vel_ess_attr[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void IncompressibleNavierSolver::AddVelDirichletBC(VecFuncT *f, Array<int> &attr)
|
||||
{
|
||||
AddVelDirichletBC(new VectorFunctionCoefficient(pmesh->Dimension(), f), attr);
|
||||
}
|
||||
|
||||
IncompressibleNavierSolver::~IncompressibleNavierSolver()
|
||||
{
|
||||
delete velBForm;
|
||||
delete psiBForm;
|
||||
delete pBForm;
|
||||
|
||||
delete kinvisCoeff;
|
||||
delete dtCoeff;
|
||||
|
||||
for( int i = 0; i<torder+1; i++)
|
||||
{
|
||||
delete velGF[i];
|
||||
delete pGF[i];
|
||||
}
|
||||
|
||||
delete DvelCoeff;
|
||||
delete divVelCoeff;
|
||||
delete pRHSCoeff;
|
||||
delete pUnitVectorCoeff;
|
||||
|
||||
delete velInv;
|
||||
delete velInvPC;
|
||||
delete psiInv;
|
||||
delete SpInvOrthoPC;
|
||||
delete psiInvPC;
|
||||
delete lor;
|
||||
delete pInv;
|
||||
delete pInvPC;
|
||||
|
||||
delete vfec;
|
||||
delete psifec;
|
||||
delete pfec;
|
||||
delete vfes;
|
||||
delete psifes;
|
||||
delete pfes;
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
// Copyright (c) 2010-2024, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#ifndef MFEM_INCOMP_NAVIER_SOLVER_HPP
|
||||
#define MFEM_INCOMP_NAVIER_SOLVER_HPP
|
||||
|
||||
#define INCOMP_NAVIER_VERSION 0.1
|
||||
|
||||
#include "mfem.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
namespace incompressible_navier
|
||||
{
|
||||
using VecFuncT = void(const Vector &x, real_t t, Vector &u);
|
||||
using ScalarFuncT = real_t(const Vector &x, real_t t);
|
||||
|
||||
//Coefficient which computed contribution of Eq(18)
|
||||
class UnitVectorGridFunctionCoeff : public VectorCoefficient
|
||||
{
|
||||
public:
|
||||
UnitVectorGridFunctionCoeff( int dim)
|
||||
: VectorCoefficient(dim*dim)
|
||||
{ }
|
||||
|
||||
void Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip)
|
||||
{
|
||||
real_t coeffVal = gridfunc_->GetValue(T, ip);
|
||||
|
||||
V.SetSize(vdim); V = 0.0; // FIXME
|
||||
V[0] = coeffVal;
|
||||
V[3] = coeffVal;
|
||||
}
|
||||
|
||||
void SetGridFunction( GridFunction * gridfunc )
|
||||
{
|
||||
gridfunc_ = gridfunc;
|
||||
}
|
||||
|
||||
GridFunction *gridfunc_ = nullptr;
|
||||
};
|
||||
|
||||
//Coefficient which computed contribution of Eq(21)
|
||||
class PrevVelVectorGridFunctionCoeff : public VectorCoefficient
|
||||
{
|
||||
public:
|
||||
PrevVelVectorGridFunctionCoeff( int dim)
|
||||
: VectorCoefficient(dim)
|
||||
{ }
|
||||
|
||||
void Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip)
|
||||
{
|
||||
V.SetSize(vdim);
|
||||
gridFuncCoeff->Eval(V, T, ip);
|
||||
|
||||
V *= 1.0/dt_;
|
||||
}
|
||||
|
||||
void SetGridFunction( GridFunction * gridfunc, real_t dt )
|
||||
{
|
||||
gridfunc_ = gridfunc;
|
||||
dt_ = dt;
|
||||
delete gridFuncCoeff;
|
||||
gridFuncCoeff = new VectorGridFunctionCoefficient( gridfunc );
|
||||
}
|
||||
|
||||
GridFunction *gridfunc_ = nullptr;
|
||||
VectorGridFunctionCoefficient *gridFuncCoeff = nullptr;
|
||||
real_t dt_;
|
||||
};
|
||||
|
||||
//Coefficient which computed contribution of Eq(20)
|
||||
class NonLinTermVectorGridFunctionCoeff : public VectorCoefficient
|
||||
{
|
||||
public:
|
||||
NonLinTermVectorGridFunctionCoeff( int dim)
|
||||
: VectorCoefficient(dim)
|
||||
{ }
|
||||
|
||||
void Eval(Vector &V, ElementTransformation &T, const IntegrationPoint &ip)
|
||||
{
|
||||
Vector val(vdim);
|
||||
Vector resultVal(vdim);
|
||||
DenseMatrix vecGrad;
|
||||
V.SetSize(vdim);
|
||||
gridFuncCoeff->Eval(val, T, ip);
|
||||
|
||||
gridfunc_->GetVectorGradient(T, vecGrad);
|
||||
|
||||
vecGrad.MultTranspose( val, V );
|
||||
|
||||
V *= -1.0;
|
||||
}
|
||||
|
||||
void SetGridFunction( ParGridFunction * gridfunc )
|
||||
{
|
||||
delete gridFuncCoeff;
|
||||
gridfunc_ = gridfunc;
|
||||
gridFuncCoeff = new VectorGridFunctionCoefficient( gridfunc );
|
||||
}
|
||||
|
||||
VectorGridFunctionCoefficient *gridFuncCoeff = nullptr;
|
||||
ParGridFunction *gridfunc_ = nullptr;
|
||||
};
|
||||
|
||||
/// Container for a Dirichlet boundary condition of the velocity field.
|
||||
class VelDirichletBC_T
|
||||
{
|
||||
public:
|
||||
VelDirichletBC_T(Array<int> attr, VectorCoefficient *coeff)
|
||||
: attr(attr), coeff(coeff)
|
||||
{}
|
||||
|
||||
VelDirichletBC_T(VelDirichletBC_T &&obj)
|
||||
{
|
||||
// Deep copy the attribute array
|
||||
this->attr = obj.attr;
|
||||
|
||||
// Move the coefficient pointer
|
||||
this->coeff = obj.coeff;
|
||||
obj.coeff = nullptr;
|
||||
}
|
||||
|
||||
~VelDirichletBC_T() { delete coeff; }
|
||||
|
||||
Array<int> attr;
|
||||
VectorCoefficient *coeff;
|
||||
};
|
||||
|
||||
/// Transient incompressible Navier Stokes solver in a split scheme formulation.
|
||||
/**
|
||||
* This implementation of a transient incompressible Navier Stokes solver uses
|
||||
* the non-dimensionalized formulation. The coupled momentum and
|
||||
* incompressibility equations are decoupled using the split scheme described in
|
||||
* [1]. This leads to three solving steps.
|
||||
*
|
||||
*/
|
||||
class IncompressibleNavierSolver
|
||||
{
|
||||
public:
|
||||
/// Initialize data structures, set FE space order and kinematic viscosity.
|
||||
/**
|
||||
* The ParMesh @a mesh can be a linear or curved parallel mesh. The @a order
|
||||
* of the finite element spaces is
|
||||
*/
|
||||
IncompressibleNavierSolver(ParMesh *mesh, int velorder, int porder, int tOrder, real_t kin_vis);
|
||||
|
||||
/// Initialize forms, solvers and preconditioners.
|
||||
void Setup(real_t dt);
|
||||
|
||||
void Setup_velocity(real_t dt);
|
||||
|
||||
void Setup_auxiliary(real_t dt);
|
||||
|
||||
void Setup_pressure(real_t dt);
|
||||
|
||||
/// Compute solution at the next time step t+dt.
|
||||
/**
|
||||
* This method can
|
||||
*/
|
||||
void Step(real_t &time, real_t dt, int cur_step);
|
||||
|
||||
void Step_velocity(real_t &time, real_t dt, int cur_step);
|
||||
|
||||
void Step_auxiliary(real_t &time, real_t dt, int cur_step);
|
||||
|
||||
void Step_pressure(real_t &time, real_t dt, int cur_step);
|
||||
|
||||
/// Return a pointer to the provisional velocity ParGridFunction.
|
||||
ParGridFunction *GetProvisionalVelocity() { return velGF[1]; }
|
||||
|
||||
/// Return a pointer to the current velocity ParGridFunction.
|
||||
ParGridFunction *GetCurrentVelocity() { return velGF[0]; }
|
||||
|
||||
/// Return a pointer to the current pressure ParGridFunction.
|
||||
ParGridFunction *GetCurrentPressure() { return pGF[0]; }
|
||||
|
||||
/// Return a pointer to the current pressure ParGridFunction.
|
||||
ParGridFunction *GetCurrentPsi() { return &psiGF ; }
|
||||
|
||||
|
||||
/// Add a Dirichlet boundary condition to the velocity field.
|
||||
void AddVelDirichletBC(VectorCoefficient *coeff, Array<int> &attr);
|
||||
|
||||
void AddVelDirichletBC(VecFuncT *f, Array<int> &attr);
|
||||
|
||||
/// Add a Dirichlet boundary condition to the pressure field.
|
||||
// void AddPresDirichletBC(Coefficient *coeff, Array<int> &attr);
|
||||
|
||||
// void AddPresDirichletBC(ScalarFuncT *f, Array<int> &attr);
|
||||
|
||||
/// Enable partial assembly for every operator.
|
||||
void EnablePA(bool pa) { partial_assembly = pa; }
|
||||
|
||||
/// Enable numerical integration rules. This means collocated quadrature at
|
||||
/// the nodal points.
|
||||
void EnableNI(bool ni) { numerical_integ = ni; }
|
||||
|
||||
/// Print timing summary of the solving routine.
|
||||
/**
|
||||
* The summary shows the timing in seconds in the first row of
|
||||
*
|
||||
*/
|
||||
void PrintTimingData();
|
||||
|
||||
~IncompressibleNavierSolver();
|
||||
|
||||
/// Rotate entries in the time step and solution history arrays.
|
||||
void UpdateTimestepHistory(real_t dt);
|
||||
|
||||
|
||||
/// Compute CFL
|
||||
real_t ComputeCFL(ParGridFunction &u, real_t dt);
|
||||
|
||||
protected:
|
||||
|
||||
/// Eliminate essential BCs in an Operator and apply to RHS.
|
||||
void EliminateRHS(Operator &A,
|
||||
ConstrainedOperator &constrainedA,
|
||||
const Array<int> &ess_tdof_list,
|
||||
Vector &x,
|
||||
Vector &b,
|
||||
Vector &X,
|
||||
Vector &B,
|
||||
int copy_interior = 0);
|
||||
|
||||
/// Enable/disable debug output.
|
||||
bool debug = false;
|
||||
|
||||
/// Enable/disable verbose output.
|
||||
bool verbose = true;
|
||||
|
||||
/// Enable/disable partial assembly of forms.
|
||||
bool partial_assembly = false;
|
||||
|
||||
/// Enable/disable numerical integration rules of forms.
|
||||
bool numerical_integ = false;
|
||||
|
||||
/// The parallel mesh.
|
||||
ParMesh *pmesh = nullptr;
|
||||
|
||||
/// The order of the velocity and pressure space.
|
||||
int velorder;
|
||||
int porder;
|
||||
int torder;
|
||||
|
||||
/// Kinematic viscosity (dimensionless).
|
||||
real_t kin_vis;
|
||||
Coefficient * kinvisCoeff = nullptr;
|
||||
|
||||
Coefficient *dtCoeff = nullptr;
|
||||
|
||||
IntegrationRules gll_rules;
|
||||
|
||||
/// Velocity $H^1$ finite element collection.
|
||||
FiniteElementCollection *vfec = nullptr;
|
||||
|
||||
/// Psi $H^1$ finite element collection.
|
||||
FiniteElementCollection *psifec = nullptr;
|
||||
|
||||
/// Pressure $H^1$ finite element collection.
|
||||
FiniteElementCollection *pfec = nullptr;
|
||||
|
||||
/// Velocity $(H^1)^d$ finite element space.
|
||||
ParFiniteElementSpace *vfes = nullptr;
|
||||
|
||||
/// Psi $(H^1)^d$ finite element space.
|
||||
ParFiniteElementSpace *psifes = nullptr;
|
||||
|
||||
/// Pressure $H^1$ finite element space.
|
||||
ParFiniteElementSpace *pfes = nullptr;
|
||||
|
||||
ParBilinearForm *velBForm = nullptr;
|
||||
ParBilinearForm *psiBForm = nullptr;
|
||||
ParBilinearForm *pBForm = nullptr;
|
||||
|
||||
ParLinearForm *velLForm = nullptr;
|
||||
ParLinearForm *psiLForm = nullptr;
|
||||
ParLinearForm *pLForm = nullptr;
|
||||
|
||||
std::vector<ParGridFunction*> velGF;
|
||||
std::vector<ParGridFunction*> pGF;
|
||||
ParGridFunction psiGF;
|
||||
|
||||
ParGridFunction DvGF, divVelGF, pRHS;
|
||||
VectorGridFunctionCoefficient * DvelCoeff = nullptr;
|
||||
DivergenceGridFunctionCoefficient * divVelCoeff = nullptr;
|
||||
GridFunctionCoefficient * pRHSCoeff = nullptr;
|
||||
UnitVectorGridFunctionCoeff * pUnitVectorCoeff = nullptr;
|
||||
NonLinTermVectorGridFunctionCoeff * nonlinTermCoeff = nullptr;
|
||||
PrevVelVectorGridFunctionCoeff * prevVelLoadCoeff = nullptr;
|
||||
|
||||
OperatorHandle vOp;
|
||||
OperatorHandle psiOp;
|
||||
OperatorHandle pOp;
|
||||
|
||||
Solver *velInvPC = nullptr;
|
||||
CGSolver *velInv = nullptr;
|
||||
|
||||
ParLORDiscretization *lor = nullptr;
|
||||
HypreBoomerAMG *psiInvPC = nullptr;
|
||||
OrthoSolver *SpInvOrthoPC = nullptr;
|
||||
CGSolver *psiInv = nullptr;
|
||||
|
||||
Solver *pInvPC = nullptr;
|
||||
CGSolver *pInv = nullptr;
|
||||
|
||||
Vector velLF, psiLF, pLF;
|
||||
|
||||
// All essential attributes.
|
||||
Array<int> vel_ess_attr;
|
||||
Array<int> pres_ess_attr;
|
||||
|
||||
// All essential true dofs.
|
||||
Array<int> vel_ess_tdof;
|
||||
Array<int> pres_ess_tdof;
|
||||
|
||||
// Bookkeeping for velocity dirichlet bcs.
|
||||
std::vector<VelDirichletBC_T> vel_dbcs;
|
||||
|
||||
// Print levels.
|
||||
int pl_psolve = 0;
|
||||
int pl_psisolve = 0;
|
||||
int pl_velsolve = 0;
|
||||
int pl_amg = 0;
|
||||
|
||||
#if defined(MFEM_USE_DOUBLE)
|
||||
real_t rtol_psolve = 1e-10;
|
||||
real_t rtol_psisolve = 1e-10;
|
||||
real_t rtol_velsolve = 1e-12;
|
||||
#elif defined(MFEM_USE_SINGLE)
|
||||
real_t rtol_psolve = 1e-9;
|
||||
real_t rtol_psisolve = 1e-5;
|
||||
real_t rtol_velsolve = 1e-7;
|
||||
#else
|
||||
#error "Only single and double precision are supported!"
|
||||
real_t rtol_psolve = 1e-12;
|
||||
real_t rtol_psisolve = 1e-6;
|
||||
real_t rtol_velsolve = 1e-8;
|
||||
#endif
|
||||
|
||||
// Iteration counts.
|
||||
int iter = 1, iter_vsolve = 0, iter_psolve = 0, iter_psisolve = 0;
|
||||
|
||||
// Residuals.
|
||||
real_t res_vsolve = 0.0, res_psolve = 0.0, res_psisolve = 0.0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace incompressible_navier
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,119 @@
|
||||
#include "stokes_solver.hpp"
|
||||
|
||||
namespace mfem {
|
||||
|
||||
StokesOperator::StokesOperator(ParFiniteElementSpace &vel_fes,
|
||||
ParFiniteElementSpace &pres_fes):
|
||||
Operator(vel_fes.GetTrueVSize()+pres_fes.GetTrueVSize()),
|
||||
vfes(vel_fes),
|
||||
pfes(pres_fes),
|
||||
offsets({0, vel_fes.GetTrueVSize(), pres_fes.GetTrueVSize()}),
|
||||
intrules(0, Quadrature1D::GaussLobatto),
|
||||
zero_coeff(0.0)
|
||||
{
|
||||
if (vel_fes.GetParMesh()->bdr_attributes.Size() > 0)
|
||||
{
|
||||
vel_ess_bdr.SetSize(vel_fes.GetParMesh()->bdr_attributes.Max());
|
||||
vel_ess_bdr = 0.0;
|
||||
pres_ess_bdr.SetSize(vel_fes.GetParMesh()->bdr_attributes.Max());
|
||||
pres_ess_bdr = 0.0;
|
||||
}
|
||||
|
||||
vfes.GetEssentialTrueDofs(vel_ess_bdr, vel_ess_tdofs);
|
||||
pfes.GetEssentialTrueDofs(pres_ess_bdr, pres_ess_tdofs);
|
||||
|
||||
offsets.PartialSum();
|
||||
|
||||
|
||||
vel_bc_gf.reset(new ParGridFunction(&vfes));
|
||||
*vel_bc_gf = 0.0; //set the velocity grid function to zero
|
||||
|
||||
pres_bc_gf.reset(new ParGridFunction(&pfes));
|
||||
*pres_bc_gf = 0.0; //set the pressure grid function to zero
|
||||
|
||||
// The nonlinear convective integrators use over-integration (dealiasing) as
|
||||
// a stabilization mechanism.
|
||||
ir_nl = intrules.Get(vfes.GetFE(0)->GetGeomType(),
|
||||
(int)(ceil(1.5 * 2*(vel_fes.GetOrder(0)+1) - 3)));
|
||||
|
||||
ir = intrules.Get(vfes.GetFE(0)->GetGeomType(),
|
||||
(int)(2*(vel_fes.GetOrder(0)+1) - 3));
|
||||
|
||||
ir_face = intrules.Get(vfes.GetFaceElement(0)->GetGeomType(),
|
||||
(int)(2*(vel_fes.GetOrder(0)+1) - 3));
|
||||
|
||||
b11_form=nullptr;
|
||||
b22_form=nullptr;
|
||||
b12_form=nullptr;
|
||||
b21_form=nullptr;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void StokesOperator::SetVelBC(std::vector<VelDirichletBC>& vvbc)
|
||||
{
|
||||
for(auto vbc=vvbc.begin();vbc!=vvbc.end();vbc++)
|
||||
{
|
||||
for (int i = 0; i < vbc->second->Size(); i++)
|
||||
{
|
||||
if (*(vbc->second)[i] == 1)
|
||||
{
|
||||
vel_ess_bdr[i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vfes.GetEssentialTrueDofs(vel_ess_bdr, vel_ess_tdofs);
|
||||
}
|
||||
|
||||
void StokesOperator::SetPressBC(std::vector<PresDirichletBC>& vpbc)
|
||||
{
|
||||
for(auto pbc=vpbc.begin();pbc!=vpbc.end();pbc++)
|
||||
{
|
||||
for(int i=0;i<pbc->second->Size();i++){
|
||||
if (*(pbc->second)[i] == 1)
|
||||
{
|
||||
vel_ess_bdr[i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pfes.GetEssentialTrueDofs(pres_ess_bdr, pres_ess_tdofs);
|
||||
}
|
||||
|
||||
void StokesOperator::Mult(const Vector &x, Vector &y) const
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void StokesOperator::Setup()
|
||||
{
|
||||
BilinearFormIntegrator *integrator;
|
||||
|
||||
delete b11_form;
|
||||
b11_form=new ParBilinearForm(&vfes);
|
||||
integrator=new ElasticityIntegrator(zero_coeff,*viscosity);
|
||||
integrator->SetIntRule(&ir);
|
||||
b11_form->AddDomainIntegrator(integrator);
|
||||
|
||||
delete b12_form;
|
||||
b12_form=new ParMixedBilinearForm(&pfes,&vfes);
|
||||
integrator=new VectorDivergenceIntegrator();
|
||||
integrator->SetIntRule(&ir);
|
||||
b12_form->AddDomainIntegrator(integrator);
|
||||
|
||||
delete b21_form;
|
||||
b21_form=new ParMixedBilinearForm(&vfes,&pfes);
|
||||
integrator=new GradientIntegrator();
|
||||
integrator->SetIntRule(&ir);
|
||||
b21_form->AddDomainIntegrator(integrator);
|
||||
|
||||
if (matrix_free)
|
||||
{
|
||||
b11_form->SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
||||
b12_form->SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
||||
b21_form->SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#ifndef STOKESSOLVER_H
|
||||
#define STOKESSOLVER_H
|
||||
|
||||
#define STOKES_VERSION 0.1
|
||||
|
||||
#include "mfem.hpp"
|
||||
|
||||
|
||||
namespace mfem {
|
||||
|
||||
using VelDirichletBC = std::pair<VectorCoefficient *, Array<int> *>;
|
||||
using PresDirichletBC = std::pair<Coefficient *, Array<int> *>;
|
||||
|
||||
class StokesOperator:public Operator
|
||||
{
|
||||
public:
|
||||
StokesOperator(ParFiniteElementSpace &vel_fes,
|
||||
ParFiniteElementSpace &pres_fes);
|
||||
|
||||
void SetVelBC(std::vector<VelDirichletBC>& vvbc);
|
||||
void SetPressBC(std::vector<PresDirichletBC>& vpbc);
|
||||
|
||||
virtual
|
||||
void Mult(const Vector &x, Vector &y) const override;
|
||||
|
||||
const Array<int>& GetOffsets() const
|
||||
{
|
||||
return offsets;
|
||||
}
|
||||
|
||||
void Setup();
|
||||
void Assemble();
|
||||
|
||||
|
||||
private:
|
||||
ParFiniteElementSpace &vfes;
|
||||
ParFiniteElementSpace &pfes;
|
||||
|
||||
// ParGridFunction &kinematic_viscosity;
|
||||
|
||||
std::unique_ptr<ParGridFunction> vel_bc_gf;
|
||||
std::unique_ptr<ParGridFunction> pres_bc_gf;
|
||||
|
||||
Array<int> vel_ess_bdr;
|
||||
Array<int> pres_ess_bdr;
|
||||
|
||||
Array<int> vel_ess_tdofs;
|
||||
Array<int> pres_ess_tdofs;
|
||||
|
||||
bool matrix_free;
|
||||
|
||||
Array<int> offsets;
|
||||
|
||||
|
||||
IntegrationRules intrules;
|
||||
IntegrationRule ir; //general integraion rule
|
||||
IntegrationRule ir_nl; //non-linear integration rule
|
||||
IntegrationRule ir_face; //face integration rule
|
||||
|
||||
ConstantCoefficient zero_coeff;
|
||||
|
||||
std::unique_ptr<Coefficient> viscosity;
|
||||
|
||||
ParBilinearForm *b11_form; //velocity
|
||||
ParBilinearForm *b22_form; //pressure
|
||||
ParMixedBilinearForm *b12_form; //mixed (velocity,pressure)
|
||||
ParMixedBilinearForm *b21_form; //mized (pressure,velocity)
|
||||
|
||||
BlockOperator* A;
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // STOKESSOLVER_H
|
||||
@@ -9,41 +9,76 @@
|
||||
# terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
set(MESH_FILES
|
||||
cube-nurbs.mesh
|
||||
ijk-hex-nurbs.mesh
|
||||
plus-nurbs-permuted.mesh
|
||||
plus-nurbs.mesh
|
||||
square-nurbs.mesh
|
||||
two-cubes-nurbs-autoedge.mesh
|
||||
two-cubes-nurbs-rot.mesh
|
||||
two-cubes-nurbs.mesh
|
||||
two-squares-nurbs-autoedge.mesh
|
||||
two-squares-nurbs-rot.mesh
|
||||
two-squares-nurbs.mesh
|
||||
)
|
||||
# Add a target to copy the mesh files from the source directory; used by sample
|
||||
# runs.
|
||||
set(SRC_MESH_FILES)
|
||||
foreach(MESH_FILE ${MESH_FILES})
|
||||
list(APPEND SRC_MESH_FILES ${CMAKE_CURRENT_SOURCE_DIR}/meshes/${MESH_FILE})
|
||||
endforeach()
|
||||
add_custom_command(OUTPUT data_is_copied
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory meshes
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SRC_MESH_FILES} meshes/
|
||||
COMMAND ${CMAKE_COMMAND} -E touch data_is_copied
|
||||
COMMENT "Copying nurbs miniapps data files ...")
|
||||
add_custom_target(copy_miniapps_nurbs_data DEPENDS data_is_copied)
|
||||
|
||||
add_mfem_miniapp(nurbs_ex1
|
||||
MAIN nurbs_ex1.cpp
|
||||
LIBRARIES mfem)
|
||||
add_dependencies(nurbs_ex1 copy_miniapps_nurbs_data)
|
||||
|
||||
add_mfem_miniapp(nurbs_ex3
|
||||
MAIN nurbs_ex3.cpp
|
||||
LIBRARIES mfem)
|
||||
add_dependencies(nurbs_ex3 copy_miniapps_nurbs_data)
|
||||
|
||||
add_mfem_miniapp(nurbs_ex5
|
||||
MAIN nurbs_ex5.cpp
|
||||
LIBRARIES mfem)
|
||||
add_dependencies(nurbs_ex5 copy_miniapps_nurbs_data)
|
||||
|
||||
add_mfem_miniapp(nurbs_ex24
|
||||
MAIN nurbs_ex24.cpp
|
||||
LIBRARIES mfem)
|
||||
add_dependencies(nurbs_ex24 copy_miniapps_nurbs_data)
|
||||
|
||||
add_mfem_miniapp(nurbs_curveint
|
||||
MAIN nurbs_curveint.cpp
|
||||
LIBRARIES mfem)
|
||||
add_dependencies(nurbs_curveint copy_miniapps_nurbs_data)
|
||||
|
||||
add_mfem_miniapp(nurbs_naca_cmesh
|
||||
MAIN nurbs_naca_cmesh.cpp
|
||||
LIBRARIES mfem)
|
||||
add_dependencies(nurbs_naca_cmesh copy_miniapps_nurbs_data)
|
||||
|
||||
add_mfem_miniapp(nurbs_printfunc
|
||||
MAIN nurbs_printfunc.cpp
|
||||
LIBRARIES mfem)
|
||||
add_dependencies(nurbs_printfunc copy_miniapps_nurbs_data)
|
||||
|
||||
add_mfem_miniapp(nurbs_patch_ex1
|
||||
MAIN nurbs_patch_ex1.cpp
|
||||
LIBRARIES mfem)
|
||||
add_dependencies(nurbs_patch_ex1 copy_miniapps_nurbs_data)
|
||||
|
||||
add_mfem_miniapp(nurbs_solenoidal
|
||||
MAIN nurbs_solenoidal.cpp
|
||||
LIBRARIES mfem)
|
||||
add_dependencies(nurbs_solenoidal copy_miniapps_nurbs_data)
|
||||
|
||||
if (MFEM_ENABLE_TESTING)
|
||||
add_test(NAME nurbs_ex1_1d_r1_o2_ser
|
||||
@@ -90,25 +125,30 @@ if (MFEM_ENABLE_TESTING)
|
||||
|
||||
add_test(NAME nurbs_ex1_weak_patch_format_ser
|
||||
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
|
||||
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 0)
|
||||
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh
|
||||
-o 2 --weak-bc -r 0)
|
||||
|
||||
add_test(NAME nurbs_ex1_weak_patch_format_r1_ser
|
||||
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
|
||||
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 1)
|
||||
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh
|
||||
-o 2 --weak-bc -r 1)
|
||||
|
||||
add_test(NAME nurbs_printfunc
|
||||
COMMAND $<TARGET_FILE:nurbs_printfunc>)
|
||||
|
||||
if (MFEM_USE_LAPACK)
|
||||
add_test(NAME nurbs_patch_ex1_o4_r2_iro8_patcha_ser
|
||||
COMMAND $<TARGET_FILE:nurbs_patch_ex1> -no-vis -incdeg 3 -ref 2 -iro 8 -patcha)
|
||||
COMMAND $<TARGET_FILE:nurbs_patch_ex1> -no-vis -incdeg 3 -ref 2 -iro 8
|
||||
-patcha)
|
||||
endif()
|
||||
|
||||
add_test(NAME nurbs_patch_ex1_o4_r2_iro8_patcha_pa_ser
|
||||
COMMAND $<TARGET_FILE:nurbs_patch_ex1> -no-vis -incdeg 3 -ref 2 -iro 8 -patcha -pa)
|
||||
COMMAND $<TARGET_FILE:nurbs_patch_ex1> -no-vis -incdeg 3 -ref 2 -iro 8
|
||||
-patcha -pa)
|
||||
|
||||
add_test(NAME nurbs_patch_ex1_o4_r2_iro8_patcha_fint_ser
|
||||
COMMAND $<TARGET_FILE:nurbs_patch_ex1> -no-vis -incdeg 3 -ref 2 -iro 8 -patcha -fint)
|
||||
COMMAND $<TARGET_FILE:nurbs_patch_ex1> -no-vis -incdeg 3 -ref 2 -iro 8
|
||||
-patcha -fint)
|
||||
|
||||
add_test(NAME nurbs_curveint_unit_weight_ser
|
||||
COMMAND $<TARGET_FILE:nurbs_curveint> -no-vis -no-visit
|
||||
@@ -119,35 +159,43 @@ if (MFEM_ENABLE_TESTING)
|
||||
-nw -n 9)
|
||||
|
||||
add_test(NAME nurbs_naca_cmesh_ser
|
||||
COMMAND $<TARGET_FILE:nurbs_naca_cmesh> -no-vis -no-visit -ntail 80 -nbnd 80 -ntip 20 -nwake 40 -sw 2.0 -sbnd 2.5 -stip 1.1 -aoa 3)
|
||||
COMMAND $<TARGET_FILE:nurbs_naca_cmesh> -no-vis -no-visit -ntail 80 -nbnd 80
|
||||
-ntip 20 -nwake 40 -sw 2.0 -sbnd 2.5 -stip 1.1 -aoa 3)
|
||||
|
||||
add_test(NAME nurbs_ex1_two_squares_knot_insert
|
||||
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
|
||||
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares-nurbs.mesh -o 1 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares.ref)
|
||||
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares-nurbs.mesh -o 1
|
||||
-rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares.ref)
|
||||
|
||||
add_test(NAME nurbs_ex1_two_squares_rot_knot_insert
|
||||
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
|
||||
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares-nurbs-rot.mesh -o 1 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares.ref)
|
||||
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares-nurbs-rot.mesh
|
||||
-o 1 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares.ref)
|
||||
|
||||
add_test(NAME nurbs_ex1_two_squares_autoedge_knot_insert
|
||||
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
|
||||
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares-nurbs-autoedge.mesh -o 1 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares.ref)
|
||||
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares-nurbs-autoedge.mesh
|
||||
-o 1 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-squares.ref)
|
||||
|
||||
add_test(NAME nurbs_ex1_two_cubes_knot_insert
|
||||
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
|
||||
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes-nurbs.mesh -o 1 -r 3 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes.ref)
|
||||
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes-nurbs.mesh -o 1
|
||||
-r 3 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes.ref)
|
||||
|
||||
add_test(NAME nurbs_ex1_two_cubes_rot_knot_insert
|
||||
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
|
||||
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes-nurbs-rot.mesh -o 1 -r 3 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes.ref)
|
||||
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes-nurbs-rot.mesh -o 1
|
||||
-r 3 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes.ref)
|
||||
|
||||
add_test(NAME nurbs_ex1_two_cubes_autoedge_knot_insert
|
||||
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
|
||||
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes-nurbs-autoedge.mesh -o 1 -r 3 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes.ref)
|
||||
-m ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes-nurbs-autoedge.mesh
|
||||
-o 1 -r 3 -rf ${PROJECT_SOURCE_DIR}/miniapps/nurbs/meshes/two-cubes.ref)
|
||||
|
||||
add_test(NAME nurbs_ex1_periodic_2d
|
||||
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
|
||||
-m ${PROJECT_SOURCE_DIR}/data/pipe-nurbs-2d.mesh -o 2 -r 2 --master "3" --slave "4")
|
||||
-m ${PROJECT_SOURCE_DIR}/data/pipe-nurbs-2d.mesh -o 2 -r 2 --master "3"
|
||||
--slave "4")
|
||||
|
||||
add_test(NAME nurbs_ex1_periodic_3d
|
||||
COMMAND $<TARGET_FILE:nurbs_ex1> -no-vis
|
||||
@@ -205,10 +253,12 @@ if (MFEM_USE_MPI)
|
||||
add_mfem_miniapp(nurbs_ex1p
|
||||
MAIN nurbs_ex1p.cpp
|
||||
LIBRARIES mfem)
|
||||
add_dependencies(nurbs_ex1p copy_miniapps_nurbs_data)
|
||||
|
||||
add_mfem_miniapp(nurbs_ex11p
|
||||
MAIN nurbs_ex11p.cpp
|
||||
LIBRARIES mfem)
|
||||
add_dependencies(nurbs_ex11p copy_miniapps_nurbs_data)
|
||||
|
||||
if (MFEM_ENABLE_TESTING)
|
||||
add_test(NAME nurbs_ex1p_np=4
|
||||
@@ -238,12 +288,14 @@ if (MFEM_USE_MPI)
|
||||
add_test(NAME nurbs_ex1_weak_patch_format_np=4
|
||||
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
|
||||
${MPIEXEC_PREFLAGS} $<TARGET_FILE:nurbs_ex1p> -no-vis
|
||||
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 0)
|
||||
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh -o 2
|
||||
--weak-bc -r 0)
|
||||
|
||||
add_test(NAME nurbs_ex1_weak_patch_format_r1_np=4
|
||||
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
|
||||
${MPIEXEC_PREFLAGS} $<TARGET_FILE:nurbs_ex1p> -no-vis
|
||||
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 1)
|
||||
-m ${PROJECT_SOURCE_DIR}/data/square-disc-nurbs-patch.mesh -o 2
|
||||
--weak-bc -r 1)
|
||||
|
||||
add_test(NAME nurbs_ex11p_np=4
|
||||
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
|
||||
|
||||
+26
-13
@@ -20,7 +20,8 @@ CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
|
||||
MFEM_LIB_FILE = mfem_is_not_built
|
||||
-include $(CONFIG_MK)
|
||||
|
||||
SEQ_MINIAPPS = nurbs_ex1 nurbs_patch_ex1 nurbs_ex3 nurbs_ex5 nurbs_ex24 nurbs_curveint nurbs_printfunc nurbs_solenoidal nurbs_naca_cmesh
|
||||
SEQ_MINIAPPS = nurbs_ex1 nurbs_patch_ex1 nurbs_ex3 nurbs_ex5 nurbs_ex24 \
|
||||
nurbs_curveint nurbs_printfunc nurbs_solenoidal nurbs_naca_cmesh
|
||||
PAR_MINIAPPS = nurbs_ex1p nurbs_ex11p
|
||||
ifeq ($(MFEM_USE_MPI),NO)
|
||||
MINIAPPS = $(SEQ_MINIAPPS)
|
||||
@@ -42,6 +43,17 @@ endif
|
||||
|
||||
all: $(MINIAPPS)
|
||||
|
||||
# For out-of-source builds, link the meshes directory from the source tree to
|
||||
# the build tree. This is needed for running the tests and the sample runs when
|
||||
# building out-of-source:
|
||||
ifneq ($(SRC),)
|
||||
meshes: $(SRC)meshes
|
||||
ln -sf $(<) .
|
||||
$(MINIAPPS): | meshes
|
||||
.PHONY: copy-data
|
||||
copy-data: | meshes
|
||||
endif
|
||||
|
||||
MFEM_TESTS = MINIAPPS
|
||||
include $(MFEM_TEST_MK)
|
||||
|
||||
@@ -63,13 +75,13 @@ EX1_ARGS_8 := -m ../../data/pipe-nurbs-2d.mesh -o 2 --weak-bc -r 2
|
||||
EX1_ARGS_9 := -m ../../data/ball-nurbs.mesh -o 2 --weak-bc -r 0
|
||||
EX1_ARGS_10 := -m ../../data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 0
|
||||
EX1_ARGS_11 := -m ../../data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 1
|
||||
EX1_ARGS_12 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/two-squares-nurbs.mesh -o 1 -rf $(MFEM_DIR)/miniapps/nurbs/meshes/two-squares.ref
|
||||
EX1_ARGS_13 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/two-squares-nurbs-rot.mesh -o 1 -rf $(MFEM_DIR)/miniapps/nurbs/meshes/two-squares.ref
|
||||
EX1_ARGS_14 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/two-squares-nurbs-autoedge.mesh -o 1 -rf $(MFEM_DIR)/miniapps/nurbs/meshes/two-squares.ref
|
||||
EX1_ARGS_15 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/two-cubes-nurbs.mesh -o 1 -r 3 -rf $(MFEM_DIR)/miniapps/nurbs/meshes/two-cubes.ref
|
||||
EX1_ARGS_16 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/two-cubes-nurbs-rot.mesh -o 1 -r 3 -rf $(MFEM_DIR)/miniapps/nurbs/meshes/two-cubes.ref
|
||||
EX1_ARGS_17 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/two-cubes-nurbs-autoedge.mesh -o 1 -r 3 -rf $(MFEM_DIR)/miniapps/nurbs/meshes/two-cubes.ref
|
||||
EX1_ARGS_18 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/cube-nurbs.mesh -pm "1" -ps "2" -rf $(MFEM_DIR)/miniapps/nurbs/meshes/cube.ref
|
||||
EX1_ARGS_12 := -m meshes/two-squares-nurbs.mesh -o 1 -rf meshes/two-squares.ref
|
||||
EX1_ARGS_13 := -m meshes/two-squares-nurbs-rot.mesh -o 1 -rf meshes/two-squares.ref
|
||||
EX1_ARGS_14 := -m meshes/two-squares-nurbs-autoedge.mesh -o 1 -rf meshes/two-squares.ref
|
||||
EX1_ARGS_15 := -m meshes/two-cubes-nurbs.mesh -o 1 -r 3 -rf meshes/two-cubes.ref
|
||||
EX1_ARGS_16 := -m meshes/two-cubes-nurbs-rot.mesh -o 1 -r 3 -rf meshes/two-cubes.ref
|
||||
EX1_ARGS_17 := -m meshes/two-cubes-nurbs-autoedge.mesh -o 1 -r 3 -rf meshes/two-cubes.ref
|
||||
EX1_ARGS_18 := -m meshes/cube-nurbs.mesh -pm "1" -ps "2" -rf meshes/cube.ref
|
||||
|
||||
nurbs_ex1-test-seq: nurbs_ex1
|
||||
@$(call mfem-test,$<,, NURBS miniapp)
|
||||
@@ -126,10 +138,10 @@ nurbs_ex24-test-seq: nurbs_ex24
|
||||
@$(call mfem-test,$<,, NURBS miniapp,$(EX24_ARGS_5))
|
||||
|
||||
SOL_ARGS_1 := -m $(MFEM_DIR)/data/pipe-nurbs-2d.mesh -r 1 -o 2
|
||||
SOL_ARGS_1 := -m $(MFEM_DIR)/data/cube-nurbs.mesh -r 1 -o 2
|
||||
nurbs_sol-test-seq: nurbs_solenoidal
|
||||
SOL_ARGS_2 := -m $(MFEM_DIR)/data/cube-nurbs.mesh -r 1 -o 2
|
||||
nurbs_solenoidal-test-seq: nurbs_solenoidal
|
||||
@$(call mfem-test,$<,, NURBS miniapp,$(SOL_ARGS_1))
|
||||
@$(call mfem-test,$<,, NURBS miniapp,$(SOl_ARGS_2))
|
||||
@$(call mfem-test,$<,, NURBS miniapp,$(SOL_ARGS_2))
|
||||
|
||||
CI_ARGS_1 := -uw -n 9 -no-visit
|
||||
CI_ARGS_2 := -nw -n 9 -no-visit
|
||||
@@ -151,7 +163,7 @@ EX1P_ARGS_2 := -m ../../data/pipe-nurbs-2d.mesh -o 2 -no-ibp
|
||||
EX1P_ARGS_3 := -m ../../data/ball-nurbs.mesh -o 2 --weak-bc -r 0
|
||||
EX1P_ARGS_4 := -m ../../data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 0
|
||||
EX1P_ARGS_5 := -m ../../data/square-disc-nurbs-patch.mesh -o 2 --weak-bc -r 1
|
||||
EX1P_ARGS_6 := -m $(MFEM_DIR)/miniapps/nurbs/meshes/square-nurbs.mesh -r 4 -pm "1" -ps "2"
|
||||
EX1P_ARGS_6 := -m meshes/square-nurbs.mesh -r 4 -pm "1" -ps "2"
|
||||
|
||||
nurbs_ex1p-test-par: nurbs_ex1p
|
||||
@$(call mfem-test,$<, $(RUN_MPI), NURBS miniapp,$(EX1P_ARGS_1))
|
||||
@@ -179,6 +191,7 @@ clean-build:
|
||||
rm -rf *.dSYM *.TVD.*breakpoints
|
||||
|
||||
clean-exec:
|
||||
@rm -f refined.mesh sin-fit.mesh ex5.mesh exsol.mesh mesh.* sol.* mode_* naca-cmesh.mesh sol_?.gf
|
||||
@rm -f refined.mesh sin-fit.mesh ex5.mesh exsol.mesh mesh.* sol.* mode_*
|
||||
@rm -f naca-cmesh.mesh sol_?.gf
|
||||
@rm -rf Example1* Example3* Example5* Solenoidal_* ParaView
|
||||
@rm -rf CurveInt Naca_cmesh glvis_naca-cmesh.mesh solution.dat
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
MFEM NURBS mesh v1.0
|
||||
|
||||
# 3D mesh with 8 (2x2x2) patches. Each patch
|
||||
# has I*J*K elements where e.g. I is the patch
|
||||
# index (starting from 1) in the x-dimension.
|
||||
|
||||
dimension
|
||||
3
|
||||
|
||||
elements
|
||||
8
|
||||
1 5 0 1 4 3 9 10 13 12
|
||||
1 5 1 2 5 4 10 11 14 13
|
||||
1 5 3 4 7 6 12 13 16 15
|
||||
1 5 4 5 8 7 13 14 17 16
|
||||
1 5 9 10 13 12 18 19 22 21
|
||||
1 5 10 11 14 13 19 20 23 22
|
||||
1 5 12 13 16 15 21 22 25 24
|
||||
1 5 13 14 17 16 22 23 26 25
|
||||
|
||||
boundary
|
||||
24
|
||||
1 3 0 3 4 1
|
||||
1 3 1 4 5 2
|
||||
1 3 3 6 7 4
|
||||
1 3 4 7 8 5
|
||||
6 3 18 19 22 21
|
||||
6 3 19 20 23 22
|
||||
6 3 21 22 25 24
|
||||
6 3 22 23 26 25
|
||||
5 3 0 9 12 3
|
||||
5 3 3 12 15 6
|
||||
5 3 9 18 21 12
|
||||
5 3 12 21 24 15
|
||||
3 3 2 5 14 11
|
||||
3 3 5 8 17 14
|
||||
3 3 11 14 23 20
|
||||
3 3 14 17 26 23
|
||||
2 3 0 1 10 9
|
||||
2 3 9 10 19 18
|
||||
2 3 1 2 11 10
|
||||
2 3 10 11 20 19
|
||||
4 3 6 15 16 7
|
||||
4 3 15 24 25 16
|
||||
4 3 7 16 17 8
|
||||
4 3 16 25 26 17
|
||||
|
||||
edges
|
||||
54
|
||||
0 0 1
|
||||
1 1 4
|
||||
0 3 4
|
||||
1 0 3
|
||||
0 9 10
|
||||
1 10 13
|
||||
0 12 13
|
||||
1 9 12
|
||||
2 0 9
|
||||
2 1 10
|
||||
2 4 13
|
||||
2 3 12
|
||||
3 1 2
|
||||
1 2 5
|
||||
3 4 5
|
||||
3 10 11
|
||||
1 11 14
|
||||
3 13 14
|
||||
2 2 11
|
||||
2 5 14
|
||||
4 4 7
|
||||
0 6 7
|
||||
4 3 6
|
||||
4 13 16
|
||||
0 15 16
|
||||
4 12 15
|
||||
2 7 16
|
||||
2 6 15
|
||||
4 5 8
|
||||
3 7 8
|
||||
4 14 17
|
||||
3 16 17
|
||||
2 8 17
|
||||
0 18 19
|
||||
1 19 22
|
||||
0 21 22
|
||||
1 18 21
|
||||
5 9 18
|
||||
5 10 19
|
||||
5 13 22
|
||||
5 12 21
|
||||
3 19 20
|
||||
1 20 23
|
||||
3 22 23
|
||||
5 11 20
|
||||
5 14 23
|
||||
4 22 25
|
||||
0 24 25
|
||||
4 21 24
|
||||
5 16 25
|
||||
5 15 24
|
||||
4 23 26
|
||||
3 25 26
|
||||
5 17 26
|
||||
|
||||
vertices
|
||||
27
|
||||
|
||||
knotvectors
|
||||
6
|
||||
1 2 0 0 1 1
|
||||
1 2 0 0 1 1
|
||||
1 2 0 0 1 1
|
||||
1 3 0 0 0.5 1 1
|
||||
1 3 0 0 0.5 1 1
|
||||
1 3 0 0 0.5 1 1
|
||||
|
||||
weights
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
|
||||
FiniteElementSpace
|
||||
FiniteElementCollection: NURBS1
|
||||
VDim: 3
|
||||
Ordering: 1
|
||||
|
||||
0 0 0
|
||||
1 0 0
|
||||
2 0 0
|
||||
0 1 0
|
||||
1 1 0
|
||||
2 1 0
|
||||
0 2 0
|
||||
1 2 0
|
||||
2 2 0
|
||||
0 0 1
|
||||
1 0 1
|
||||
2 0 1
|
||||
0 1 1
|
||||
1 1 1
|
||||
2 1 1
|
||||
0 2 1
|
||||
1 2 1
|
||||
2 2 1
|
||||
0 0 2
|
||||
1 0 2
|
||||
2 0 2
|
||||
0 1 2
|
||||
1 1 2
|
||||
2 1 2
|
||||
0 2 2
|
||||
1 2 2
|
||||
2 2 2
|
||||
1.5 0 0
|
||||
1.5 1 0
|
||||
1.5 0 1
|
||||
1.5 1 1
|
||||
1 1.5 0
|
||||
0 1.5 0
|
||||
1 1.5 1
|
||||
0 1.5 1
|
||||
2 1.5 0
|
||||
1.5 2 0
|
||||
2 1.5 1
|
||||
1.5 2 1
|
||||
0 0 1.5
|
||||
1 0 1.5
|
||||
1 1 1.5
|
||||
0 1 1.5
|
||||
1.5 0 2
|
||||
1.5 1 2
|
||||
2 0 1.5
|
||||
2 1 1.5
|
||||
1 1.5 2
|
||||
0 1.5 2
|
||||
1 2 1.5
|
||||
0 2 1.5
|
||||
2 1.5 2
|
||||
1.5 2 2
|
||||
2 2 1.5
|
||||
1.5 1.5 0
|
||||
1.5 1.5 1
|
||||
1.5 0 1.5
|
||||
1.5 1 1.5
|
||||
1 1.5 1.5
|
||||
0 1.5 1.5
|
||||
2 1.5 1.5
|
||||
1.5 2 1.5
|
||||
1.5 1.5 2
|
||||
1.5 1.5 1.5
|
||||
@@ -0,0 +1,109 @@
|
||||
MFEM NURBS mesh v1.0
|
||||
|
||||
# 5 2D patches arranged in a "+" sign configuration.
|
||||
# The geometry is the same as plus-nurbs.mesh, but
|
||||
# the patches are ordered differently:
|
||||
#
|
||||
# +-----+
|
||||
# | 2 |
|
||||
# +-----+-----+-----+
|
||||
# | 1 | 4 | 3 |
|
||||
# +-----+-----+-----+
|
||||
# | 0 |
|
||||
# +-----+
|
||||
#
|
||||
# This mesh can be used to test the generation of
|
||||
# patch topology to knotvector index maps.
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
elements
|
||||
5
|
||||
1 3 0 1 4 3
|
||||
1 3 2 3 7 6
|
||||
1 3 7 8 11 10
|
||||
1 3 4 5 9 8
|
||||
1 3 3 4 8 7
|
||||
|
||||
boundary
|
||||
0
|
||||
|
||||
edges
|
||||
0
|
||||
|
||||
vertices
|
||||
12
|
||||
|
||||
patches
|
||||
|
||||
knotvectors
|
||||
2
|
||||
1 2 0 0 1 1
|
||||
1 2 0 0 1 1
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
controlpoints
|
||||
0 0 1
|
||||
1 0 1
|
||||
0 1 1
|
||||
1 1 1
|
||||
|
||||
knotvectors
|
||||
2
|
||||
1 2 0 0 1 1
|
||||
1 2 0 0 1 1
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
controlpoints
|
||||
-1 1 1
|
||||
0 1 1
|
||||
-1 2 1
|
||||
0 2 1
|
||||
|
||||
knotvectors
|
||||
2
|
||||
1 2 0 0 1 1
|
||||
1 2 0 0 1 1
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
controlpoints
|
||||
0 2 1
|
||||
1 2 1
|
||||
0 3 1
|
||||
1 3 1
|
||||
|
||||
knotvectors
|
||||
2
|
||||
1 2 0 0 1 1
|
||||
1 2 0 0 1 1
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
controlpoints
|
||||
1 1 1
|
||||
2 1 1
|
||||
1 2 1
|
||||
2 2 1
|
||||
|
||||
knotvectors
|
||||
2
|
||||
1 2 0 0 1 1
|
||||
1 2 0 0 1 1
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
controlpoints
|
||||
0 1 1
|
||||
1 1 1
|
||||
0 2 1
|
||||
1 2 1
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
MFEM NURBS mesh v1.0
|
||||
|
||||
# 5 2D patches arranged in a "+" sign configuration.
|
||||
# Patches are ordered using cartesian indexing:
|
||||
#
|
||||
# +-----+
|
||||
# | 4 |
|
||||
# +-----+-----+-----+
|
||||
# | 1 | 2 | 3 |
|
||||
# +-----+-----+-----+
|
||||
# | 0 |
|
||||
# +-----+
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
elements
|
||||
5
|
||||
1 3 0 1 4 3
|
||||
1 3 2 3 7 6
|
||||
1 3 3 4 8 7
|
||||
1 3 4 5 9 8
|
||||
1 3 7 8 11 10
|
||||
|
||||
boundary
|
||||
0
|
||||
|
||||
edges
|
||||
0
|
||||
|
||||
vertices
|
||||
12
|
||||
|
||||
patches
|
||||
|
||||
knotvectors
|
||||
2
|
||||
1 2 0 0 1 1
|
||||
1 2 0 0 1 1
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
controlpoints
|
||||
0 0 1
|
||||
1 0 1
|
||||
0 1 1
|
||||
1 1 1
|
||||
|
||||
knotvectors
|
||||
2
|
||||
1 2 0 0 1 1
|
||||
1 2 0 0 1 1
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
controlpoints
|
||||
-1 1 1
|
||||
0 1 1
|
||||
-1 2 1
|
||||
0 2 1
|
||||
|
||||
knotvectors
|
||||
2
|
||||
1 2 0 0 1 1
|
||||
1 2 0 0 1 1
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
controlpoints
|
||||
0 1 1
|
||||
1 1 1
|
||||
0 2 1
|
||||
1 2 1
|
||||
|
||||
knotvectors
|
||||
2
|
||||
1 2 0 0 1 1
|
||||
1 2 0 0 1 1
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
controlpoints
|
||||
1 1 1
|
||||
2 1 1
|
||||
1 2 1
|
||||
2 2 1
|
||||
|
||||
knotvectors
|
||||
2
|
||||
1 2 0 0 1 1
|
||||
1 2 0 0 1 1
|
||||
|
||||
dimension
|
||||
2
|
||||
|
||||
controlpoints
|
||||
0 2 1
|
||||
1 2 1
|
||||
0 3 1
|
||||
1 3 1
|
||||
|
||||
@@ -388,7 +388,7 @@ int main(int argc, char *argv[])
|
||||
delete hdiv_coll;
|
||||
delete mesh;
|
||||
|
||||
if (err_div > 1e4*std::numeric_limits<real_t>::epsilon() )
|
||||
if (err_div > 2e4*std::numeric_limits<real_t>::epsilon() )
|
||||
{
|
||||
mfem::out << "std::numeric_limits<real_t>::epsilon() = "
|
||||
<< std::numeric_limits<real_t>::epsilon() << "\n";
|
||||
|
||||
@@ -9,6 +9,23 @@
|
||||
# terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
set(MESH_GF_FILES
|
||||
../gslib/triple-pt-1.mesh
|
||||
../gslib/triple-pt-1.gf
|
||||
)
|
||||
|
||||
# Add a target to copy the mesh files from the source directory; used by sample
|
||||
# runs.
|
||||
set(SRC_MESH_GF_FILES)
|
||||
foreach(MESH_FILE ${MESH_GF_FILES})
|
||||
list(APPEND SRC_MESH_GF_FILES ${CMAKE_CURRENT_SOURCE_DIR}/${MESH_FILE})
|
||||
endforeach()
|
||||
add_custom_command(OUTPUT data_is_copied
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SRC_MESH_GF_FILES} ../gslib/.
|
||||
COMMAND ${CMAKE_COMMAND} -E touch data_is_copied
|
||||
COMMENT "Copying tools miniapps data files ...")
|
||||
add_custom_target(copy_miniapps_tools_data DEPENDS data_is_copied)
|
||||
|
||||
add_mfem_miniapp(display-basis
|
||||
MAIN display-basis.cpp
|
||||
${MFEM_MINIAPPS_COMMON_HEADERS}
|
||||
@@ -28,11 +45,6 @@ add_mfem_miniapp(convert-dc
|
||||
add_mfem_miniapp(lor-transfer
|
||||
MAIN lor-transfer.cpp LIBRARIES mfem)
|
||||
|
||||
if (MFEM_USE_MPI)
|
||||
add_mfem_miniapp(plor-transfer
|
||||
MAIN plor-transfer.cpp LIBRARIES mfem)
|
||||
endif()
|
||||
|
||||
add_mfem_miniapp(tmop-check-metric
|
||||
MAIN tmop-check-metric.cpp LIBRARIES mfem)
|
||||
|
||||
@@ -42,6 +54,15 @@ add_mfem_miniapp(tmop-metric-magnitude
|
||||
LIBRARIES mfem-common)
|
||||
|
||||
if (MFEM_USE_MPI)
|
||||
add_mfem_miniapp(gridfunction-bounds
|
||||
MAIN gridfunction-bounds.cpp
|
||||
${MFEM_MINIAPPS_COMMON_HEADERS}
|
||||
LIBRARIES mfem-common)
|
||||
add_dependencies(gridfunction-bounds copy_miniapps_tools_data)
|
||||
|
||||
add_mfem_miniapp(plor-transfer
|
||||
MAIN plor-transfer.cpp LIBRARIES mfem)
|
||||
|
||||
add_mfem_miniapp(nodal-transfer
|
||||
MAIN nodal-transfer.cpp LIBRARIES mfem)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
//
|
||||
// ---------------------------------------------------------------------
|
||||
// Compute bounds of the given grid-function
|
||||
// ---------------------------------------------------------------------
|
||||
//
|
||||
// This miniapp computes piecewise linear bounds on a given gridfunction, and
|
||||
// visualizes the lower and upper bound for each element. The bounding approach
|
||||
// is based on the method described in:
|
||||
//
|
||||
// (1) Section 3 of Mittal et al., "General Field Evaluation in High-Order
|
||||
// Meshes on GPUs"
|
||||
// and
|
||||
// (2) Dzanic et al., "A method for bounding high-order finite element
|
||||
// functions: Applications to mesh validity and bounds-preserving limiters".
|
||||
//
|
||||
//
|
||||
// Compile with: make gridfunction-bounds
|
||||
//
|
||||
// Sample runs:
|
||||
// mpirun -np 4 gridfunction-bounds
|
||||
// mpirun -np 4 gridfunction-bounds -nb 100 -ref 5 -bt 2 -l2
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
using namespace mfem;
|
||||
using namespace std;
|
||||
|
||||
void VisualizeField(ParMesh &pmesh, ParGridFunction &input,
|
||||
char *title, int pos_x, int pos_y);
|
||||
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
// 0. Initialize MPI and HYPRE.
|
||||
Mpi::Init(argc, argv);
|
||||
Hypre::Init();
|
||||
|
||||
// Set the method's default parameters.
|
||||
const char *mesh_file = "../gslib/triple-pt-1.mesh";
|
||||
const char *sltn_file = "../gslib/triple-pt-1.gf";
|
||||
int ref = 2;
|
||||
bool visualization = true;
|
||||
bool visit = false;
|
||||
int b_type = -1;
|
||||
bool continuous = true;
|
||||
int nbrute = 0;
|
||||
|
||||
// Parse command-line options.
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&mesh_file, "-m", "--mesh",
|
||||
"Mesh file to use.");
|
||||
args.AddOption(&sltn_file, "-s", "--sltn",
|
||||
"Solution file to use.");
|
||||
args.AddOption(&ref, "-ref", "--piecewise-linear-ref-factor",
|
||||
"Scaling factor for resolution of piecewise linear bounds."
|
||||
" If less than 2, the resolution is picked automatically");
|
||||
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.AddOption(&visit, "-visit", "--visit", "-no-visit",
|
||||
"--no-visit",
|
||||
"Enable or disable VisIt output.");
|
||||
args.AddOption(&b_type, "-bt", "--basis-type",
|
||||
"Project input function to a different bases. "
|
||||
"-1 = don't project (default)."
|
||||
"0 = Gauss-Legendre nodes. "
|
||||
"1 = Gauss-Lobatto nodes. "
|
||||
"2 = uniformly spaced nodes. ");
|
||||
args.AddOption(&continuous, "-h1", "--h1", "-l2", "--l2",
|
||||
"Use continuous or discontinuous space.");
|
||||
args.AddOption(&nbrute, "-nb", "--nbrute",
|
||||
"Brute force search for minimum in an array of nxnxn points "
|
||||
"in each element.");
|
||||
args.ParseCheck();
|
||||
|
||||
Mesh mesh(mesh_file, 1, 1, false);
|
||||
const int dim = mesh.Dimension();
|
||||
if (continuous && b_type != -1)
|
||||
{
|
||||
MFEM_VERIFY(b_type > 0, "Continuous space do not support GL nodes. "
|
||||
"Please use basis type: 1 for Lagrange interpolants on GLL "
|
||||
" nodes 2 for positive bases on uniformly spaced nodes.");
|
||||
}
|
||||
|
||||
std::unique_ptr<int[]> partition(
|
||||
mesh.GeneratePartitioning(Mpi::WorldSize())
|
||||
);
|
||||
|
||||
ifstream mat_stream_1(sltn_file);
|
||||
std::unique_ptr<GridFunction> func(new GridFunction(&mesh, mat_stream_1));
|
||||
|
||||
ParMesh pmesh(MPI_COMM_WORLD, mesh, partition.get());
|
||||
ParGridFunction pfunc(&pmesh, func.get(), partition.get());
|
||||
int func_order = func->FESpace()->GetMaxElementOrder();
|
||||
int vdim = pfunc.FESpace()->GetVDim();
|
||||
int nel = pmesh.GetNE();
|
||||
|
||||
func.reset();
|
||||
mesh.Clear();
|
||||
partition.reset();
|
||||
|
||||
// Project input function based on user input
|
||||
ParGridFunction *pfunc_proj = NULL;
|
||||
if (b_type >= 0)
|
||||
{
|
||||
FiniteElementCollection *fec = NULL;
|
||||
if (continuous)
|
||||
{
|
||||
fec = new H1_FECollection(func_order, dim, b_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
fec = new L2_FECollection(func_order, dim, b_type);
|
||||
}
|
||||
int ordering = pfunc.FESpace()->GetOrdering();
|
||||
ParFiniteElementSpace *fes = new ParFiniteElementSpace(&pmesh, fec,
|
||||
vdim, ordering);
|
||||
pfunc_proj = new ParGridFunction(fes);
|
||||
pfunc_proj->MakeOwner(fec);
|
||||
pfunc_proj->ProjectGridFunction(pfunc);
|
||||
if (Mpi::Root())
|
||||
{
|
||||
cout << "fec name orig: " << pfunc.FESpace()->FEColl()->Name() <<
|
||||
endl;
|
||||
cout << "fec name: " << fec->Name() << endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pfunc_proj = &pfunc;
|
||||
if (Mpi::Root())
|
||||
{
|
||||
cout << "fec name: " << pfunc.FESpace()->FEColl()->Name() << endl;
|
||||
}
|
||||
}
|
||||
|
||||
L2_FECollection fec_pc(0, dim);
|
||||
ParFiniteElementSpace fes_pc(&pmesh, &fec_pc, vdim, Ordering::byNODES);
|
||||
ParGridFunction lowerb(&fes_pc), upperb(&fes_pc);
|
||||
|
||||
// Compute bounds
|
||||
pfunc_proj->GetElementBounds(lowerb, upperb, ref);
|
||||
|
||||
Vector bound_min(vdim), bound_max(vdim);
|
||||
for (int d = 0; d < vdim; d++)
|
||||
{
|
||||
Vector lowerT(lowerb.GetData() + d*nel, nel);
|
||||
Vector upperT(upperb.GetData() + d*nel, nel);
|
||||
bound_min(d) = lowerT.Min();
|
||||
bound_max(d) = upperT.Max();
|
||||
}
|
||||
|
||||
MPI_Allreduce(MPI_IN_PLACE, bound_min.GetData(), vdim,
|
||||
MPITypeMap<real_t>::mpi_type, MPI_MIN, pmesh.GetComm());
|
||||
MPI_Allreduce(MPI_IN_PLACE, bound_max.GetData(), vdim,
|
||||
MPITypeMap<real_t>::mpi_type, MPI_MAX, pmesh.GetComm());
|
||||
|
||||
// GLVis Visualization
|
||||
if (visualization)
|
||||
{
|
||||
char title1[] = "Input gridfunction";
|
||||
VisualizeField(pmesh, pfunc, title1, 0, 0);
|
||||
if (b_type >= 0)
|
||||
{
|
||||
char title1p[] = "Projected gridfunction";
|
||||
VisualizeField(pmesh, *pfunc_proj, title1p, 0, 400);
|
||||
}
|
||||
char title2[] = "Element-wise lower bound";
|
||||
VisualizeField(pmesh, lowerb, title2, 400, 0);
|
||||
char title3[] = "Element-wise upper bound";
|
||||
VisualizeField(pmesh, upperb, title3, 800, 0);
|
||||
}
|
||||
|
||||
// Visit Visualization
|
||||
if (visit)
|
||||
{
|
||||
VisItDataCollection visit_dc("jacobian-determinant-bounds", &pmesh);
|
||||
visit_dc.SetFormat(DataCollection::PARALLEL_FORMAT);
|
||||
visit_dc.RegisterField("input-function", &pfunc);
|
||||
if (b_type >= 0)
|
||||
{
|
||||
visit_dc.RegisterField("projected-function", pfunc_proj);
|
||||
}
|
||||
visit_dc.RegisterField("lower-bound", &lowerb);
|
||||
visit_dc.RegisterField("upper-bound", &upperb);
|
||||
visit_dc.Save();
|
||||
}
|
||||
|
||||
if (nbrute > 0)
|
||||
{
|
||||
Vector global_min(vdim), global_max(vdim);
|
||||
global_min = numeric_limits<real_t>::max();
|
||||
global_max = numeric_limits<real_t>::min();
|
||||
// search for the minimum value of pfunc_proj in each element at
|
||||
// an array of integration points
|
||||
for (int e = 0; e < pmesh.GetNE(); e++)
|
||||
{
|
||||
IntegrationPoint ip;
|
||||
for (int k = 0; k < (dim > 2 ? nbrute : 1); k++)
|
||||
{
|
||||
ip.z = k/(nbrute-1.0);
|
||||
for (int j = 0; j < (dim > 1 ? nbrute : 1); j++)
|
||||
{
|
||||
ip.y = j/(nbrute-1.0);
|
||||
for (int i = 0; i < nbrute; i++)
|
||||
{
|
||||
ip.x = i/(nbrute-1.0);
|
||||
for (int d = 0; d < vdim; d++)
|
||||
{
|
||||
real_t val = pfunc_proj->GetValue(e, ip, d+1);
|
||||
global_min(d) = min(global_min(d), val);
|
||||
global_max(d) = max(global_max(d), val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MPI_Allreduce(MPI_IN_PLACE, global_min.GetData(), vdim,
|
||||
MPITypeMap<real_t>::mpi_type, MPI_MIN, pmesh.GetComm());
|
||||
MPI_Allreduce(MPI_IN_PLACE, global_max.GetData(), vdim,
|
||||
MPITypeMap<real_t>::mpi_type, MPI_MAX, pmesh.GetComm());
|
||||
if (Mpi::Root())
|
||||
{
|
||||
for (int d = 0; d < vdim; d++)
|
||||
{
|
||||
cout << "Brute force and bounding comparison for component " <<
|
||||
d << endl;
|
||||
cout << "Brute force minimum and minimum bound: " << global_min(d)
|
||||
<< " " << bound_min(d) << endl;
|
||||
|
||||
cout << "Brute force maximum and maximum bound: " << global_max(d)
|
||||
<< " " << bound_max(d) << endl;
|
||||
|
||||
cout << "The difference in bounds is: " <<
|
||||
global_min(d)-bound_min(d) << " " <<
|
||||
bound_max(d)-global_max(d) << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nbrute == 0 && Mpi::Root())
|
||||
{
|
||||
for (int d = 0; d < vdim; d++)
|
||||
{
|
||||
cout << "Minimum bound for component " << d << " is " <<
|
||||
bound_min(d) << endl;
|
||||
cout << "Maximum bound for component " << d << " is " <<
|
||||
bound_max(d) << endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (b_type >= 0)
|
||||
{
|
||||
delete pfunc_proj;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void VisualizeField(ParMesh &pmesh, ParGridFunction &input,
|
||||
char *title, int pos_x, int pos_y)
|
||||
{
|
||||
socketstream sock;
|
||||
if (pmesh.GetMyRank() == 0)
|
||||
{
|
||||
sock.open("localhost", 19916);
|
||||
sock << "solution\n";
|
||||
}
|
||||
pmesh.PrintAsOne(sock);
|
||||
input.SaveAsOne(sock);
|
||||
if (pmesh.GetMyRank() == 0)
|
||||
{
|
||||
sock << "window_title '"<< title << "'\n"
|
||||
<< "window_geometry "
|
||||
<< pos_x << " " << pos_y << " " << 400 << " " << 400 << "\n"
|
||||
<< "keys jRmclApppppppppppp//]]]]]]]]" << endl;
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ MFEM_LIB_FILE = mfem_is_not_built
|
||||
SEQ_MINIAPPS = display-basis load-dc convert-dc get-values lor-transfer \
|
||||
tmop-check-metric tmop-metric-magnitude
|
||||
|
||||
PAR_MINIAPPS = nodal-transfer plor-transfer
|
||||
PAR_MINIAPPS = nodal-transfer plor-transfer gridfunction-bounds
|
||||
|
||||
ifeq ($(MFEM_USE_MPI),NO)
|
||||
MINIAPPS = $(SEQ_MINIAPPS)
|
||||
@@ -83,7 +83,7 @@ RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP)
|
||||
# Testing: Specific execution options
|
||||
# Do not test: display-basis, load-dc, convert-dc, get-values, lor-transfer, plor-transfer
|
||||
NO_TEST_APPS = display-basis load-dc convert-dc get-values lor-transfer \
|
||||
plor-transfer tmop-check-metric tmop-metric-magnitude
|
||||
plor-transfer tmop-check-metric tmop-metric-magnitude gridfunction-bounds
|
||||
$(foreach app,$(NO_TEST_APPS),$(app)-test-seq $(app)-test-par):
|
||||
@true
|
||||
|
||||
@@ -100,5 +100,5 @@ clean-build:
|
||||
rm -rf *.dSYM *.TVD.*breakpoints
|
||||
|
||||
clean-exec:
|
||||
@rm -rf mesh_* gridfunc_*
|
||||
@rm -rf mesh_* gridfunc_* jacobian-determinant-bounds*
|
||||
@true
|
||||
|
||||
@@ -161,6 +161,8 @@ add_dependencies(${MFEM_ALL_TESTS_TARGET_NAME} unit_tests)
|
||||
add_dependencies(unit_tests copy_data)
|
||||
# ParSubMesh tests need meshes in ../../miniapps/multidomain
|
||||
add_dependencies(unit_tests copy_miniapps_multidomain_data)
|
||||
# NURBS tests need meshes in ../../miniapps/nurbs
|
||||
add_dependencies(unit_tests copy_miniapps_nurbs_data)
|
||||
|
||||
# Copy data to the build directory.
|
||||
add_custom_command(TARGET unit_tests POST_BUILD
|
||||
|
||||
@@ -9,16 +9,15 @@
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#include "mfem.hpp"
|
||||
|
||||
// must be included after mfem.hpp
|
||||
#include "general/forall.hpp"
|
||||
|
||||
#include "unit_tests.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "unit_tests.hpp"
|
||||
|
||||
// must be included after mfem.hpp
|
||||
#include "general/reducers.hpp"
|
||||
|
||||
using namespace mfem;
|
||||
|
||||
TEST_CASE("Reduce Sum", "[Reduction],[CUDA]")
|
||||
|
||||
+6
-1
@@ -181,11 +181,16 @@ $(eval $(call psedov_tests,cuda_uvm,CUDA_UVM,cuda:uvm))
|
||||
|
||||
# For out-of-source builds, copy the meshes in ../../miniapps/multidomain from
|
||||
# the source location; these are used by 'punit_tests'.
|
||||
# Also, link the directory 'meshes' in ../../miniapps/nurbs from the source
|
||||
# location; this is needed by 'unit_tests'.
|
||||
ifneq ($(SRC),)
|
||||
.PHONY: copy-miniapps-multidomain-data
|
||||
.PHONY: copy-miniapps-multidomain-data copy-miniapps-nurbs-meshes
|
||||
copy-miniapps-multidomain-data:
|
||||
$(MAKE) -C ../../miniapps/multidomain copy-data
|
||||
punit_tests: | copy-miniapps-multidomain-data
|
||||
copy-miniapps-nurbs-meshes:
|
||||
$(MAKE) -C ../../miniapps/nurbs copy-data
|
||||
unit_tests: | copy-miniapps-nurbs-meshes
|
||||
endif
|
||||
|
||||
# For out-of-source builds, copy the meshes in ../../miniapps/meshing from the
|
||||
|
||||
@@ -84,3 +84,50 @@ TEST_CASE("NURBS refinement and coarsening by spacing formulas", "[NURBS]")
|
||||
const real_t error = d.Norml2();
|
||||
REQUIRE(error == MFEM_Approx(0.0));
|
||||
}
|
||||
|
||||
TEST_CASE("NURBS mesh reconstruction", "[NURBS]")
|
||||
{
|
||||
auto mesh_fname =
|
||||
GENERATE("../../data/segment-nurbs.mesh",
|
||||
"../../data/square-nurbs.mesh",
|
||||
"../../data/beam-quad-nurbs.mesh",
|
||||
"../../data/pipe-nurbs.mesh",
|
||||
"../../miniapps/nurbs/meshes/two-squares-nurbs.mesh",
|
||||
"../../miniapps/nurbs/meshes/two-squares-nurbs-rot.mesh",
|
||||
"../../miniapps/nurbs/meshes/two-squares-nurbs-autoedge.mesh",
|
||||
"../../miniapps/nurbs/meshes/plus-nurbs.mesh",
|
||||
"../../miniapps/nurbs/meshes/plus-nurbs-permuted.mesh",
|
||||
"../../miniapps/nurbs/meshes/ijk-hex-nurbs.mesh");
|
||||
|
||||
Mesh mesh1(mesh_fname, 1, 1);
|
||||
|
||||
// Reconstruct mesh using patches + topology
|
||||
Array<NURBSPatch*> patches;
|
||||
mesh1.GetNURBSPatches(patches);
|
||||
const Mesh patchtopo = mesh1.NURBSext->GetPatchTopology();
|
||||
|
||||
NURBSExtension ne(&patchtopo, patches);
|
||||
Mesh mesh2(ne);
|
||||
|
||||
// Meshes should be identical
|
||||
REQUIRE(mesh1.GetNodes()->Size() > 0);
|
||||
REQUIRE(mesh1.GetNodes()->Size() == mesh2.GetNodes()->Size());
|
||||
|
||||
Vector diff(*mesh1.GetNodes());
|
||||
diff -= *mesh2.GetNodes();
|
||||
const real_t error = diff.Norml2();
|
||||
REQUIRE(error == MFEM_Approx(0.0));
|
||||
|
||||
// Compare weights (these are stored separately from nodes)
|
||||
REQUIRE(mesh1.NURBSext->GetWeights().Size() > 0);
|
||||
REQUIRE(mesh1.NURBSext->GetWeights().Size() ==
|
||||
mesh2.NURBSext->GetWeights().Size());
|
||||
|
||||
Vector wdiff = mesh1.NURBSext->GetWeights();
|
||||
wdiff -= mesh2.NURBSext->GetWeights();
|
||||
const real_t werror = wdiff.Norml2();
|
||||
REQUIRE(werror == MFEM_Approx(0.0));
|
||||
|
||||
// Cleanup
|
||||
for (auto *p : patches) { delete p; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user