1 // Copyright (c) 2010-2021, Lawrence Livermore National Security, LLC. Produced
2 // at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3 // LICENSE and NOTICE for details. LLNL-CODE-806117.
4 //
5 // This file is part of the MFEM library. For more information and source code
6 // availability visit https://mfem.org.
7 //
8 // MFEM is free software; you can redistribute it and/or modify it under the
9 // terms of the BSD-3 license. We welcome feedback and contributions, see file
10 // CONTRIBUTING.md for details.
11
12 #include "unit_tests.hpp"
13 #include "mfem.hpp"
14 #include <fstream>
15 #include <iostream>
16
17 using namespace mfem;
18
19 namespace pa_kernels
20 {
21
zero_field(const Vector & x)22 double zero_field(const Vector &x)
23 {
24 return 0.0;
25 }
26
solenoidal_field2d(const Vector & x,Vector & u)27 void solenoidal_field2d(const Vector &x, Vector &u)
28 {
29 u(0) = x(1);
30 u(1) = -x(0);
31 }
32
non_solenoidal_field2d(const Vector & x,Vector & u)33 void non_solenoidal_field2d(const Vector &x, Vector &u)
34 {
35 u(0) = x(0) * x(1);
36 u(1) = -x(0) + x(1);
37 }
38
div_non_solenoidal_field2d(const Vector & x)39 double div_non_solenoidal_field2d(const Vector &x)
40 {
41 return 1.0 + x(1);
42 }
43
solenoidal_field3d(const Vector & x,Vector & u)44 void solenoidal_field3d(const Vector &x, Vector &u)
45 {
46 u(0) = -x(0)*x(0);
47 u(1) = x(0)*x(1);
48 u(2) = x(0)*x(2);
49 }
50
non_solenoidal_field3d(const Vector & x,Vector & u)51 void non_solenoidal_field3d(const Vector &x, Vector &u)
52 {
53 u(0) = x(0)*x(0);
54 u(1) = x(1)*x(1);
55 u(2) = x(2)*x(2);
56 }
57
div_non_solenoidal_field3d(const Vector & x)58 double div_non_solenoidal_field3d(const Vector &x)
59 {
60 return 2*(x(0) + x(1) + x(2));
61 }
62
pa_divergence_testnd(int dim,void (* f1)(const Vector &,Vector &),double (* divf1)(const Vector &))63 double pa_divergence_testnd(int dim,
64 void (*f1)(const Vector &, Vector &),
65 double (*divf1)(const Vector &))
66 {
67 Mesh mesh;
68 if (dim == 2)
69 {
70 mesh = Mesh::MakeCartesian2D(2, 2, Element::QUADRILATERAL, 0, 1.0, 1.0);
71 }
72 if (dim == 3)
73 {
74 mesh = Mesh::MakeCartesian3D(2, 2, 2, Element::HEXAHEDRON, 1.0, 1.0, 1.0);
75 }
76
77 int order = 4;
78
79 // Vector valued
80 H1_FECollection fec1(order, dim);
81 FiniteElementSpace fes1(&mesh, &fec1, dim);
82
83 // Scalar
84 H1_FECollection fec2(order, dim);
85 FiniteElementSpace fes2(&mesh, &fec2);
86
87 GridFunction field(&fes1), field2(&fes2);
88
89 MixedBilinearForm dform(&fes1, &fes2);
90 dform.SetAssemblyLevel(AssemblyLevel::PARTIAL);
91 dform.AddDomainIntegrator(new VectorDivergenceIntegrator);
92 dform.Assemble();
93
94 // Project u = f1
95 VectorFunctionCoefficient fcoeff1(dim, f1);
96 field.ProjectCoefficient(fcoeff1);
97
98 // Check if div(u) = divf1
99 dform.Mult(field, field2);
100 FunctionCoefficient fcoeff2(divf1);
101 LinearForm lf(&fes2);
102 lf.AddDomainIntegrator(new DomainLFIntegrator(fcoeff2));
103 lf.Assemble();
104 field2 -= lf;
105
106 return field2.Norml2();
107 }
108
109 TEST_CASE("PA VectorDivergence", "[PartialAssembly]")
110 {
111 SECTION("2D")
112 {
113 // Check if div([y, -x]) == 0
114 REQUIRE(pa_divergence_testnd(2, solenoidal_field2d, zero_field)
115 == MFEM_Approx(0.0));
116
117 // Check if div([x*y, -x+y]) == 1 + y
118 REQUIRE(pa_divergence_testnd(2,
119 non_solenoidal_field2d,
120 div_non_solenoidal_field2d)
121 == MFEM_Approx(0.0));
122 }
123
124 SECTION("3D")
125 {
126 // Check if
127 // div([-x^2, xy, xz]) == 0
128 REQUIRE(pa_divergence_testnd(3, solenoidal_field3d, zero_field)
129 == MFEM_Approx(0.0));
130
131 // Check if
132 // div([x^2, y^2, z^2]) == 2(x + y + z)
133 REQUIRE(pa_divergence_testnd(3,
134 non_solenoidal_field3d,
135 div_non_solenoidal_field3d)
136 == MFEM_Approx(0.0));
137 }
138 }
139
f1(const Vector & x)140 double f1(const Vector &x)
141 {
142 double r = pow(x(0),2);
143 if (x.Size() >= 2) { r += pow(x(1), 3); }
144 if (x.Size() >= 3) { r += pow(x(2), 4); }
145 return r;
146 }
147
gradf1(const Vector & x,Vector & u)148 void gradf1(const Vector &x, Vector &u)
149 {
150 u(0) = 2*x(0);
151 if (x.Size() >= 2) { u(1) = 3*pow(x(1), 2); }
152 if (x.Size() >= 3) { u(2) = 4*pow(x(2), 3); }
153 }
154
pa_gradient_testnd(int dim,double (* f1)(const Vector &),void (* gradf1)(const Vector &,Vector &))155 double pa_gradient_testnd(int dim,
156 double (*f1)(const Vector &),
157 void (*gradf1)(const Vector &, Vector &))
158 {
159 Mesh mesh;
160 if (dim == 2)
161 {
162 mesh = Mesh::MakeCartesian2D(2, 2, Element::QUADRILATERAL, 0, 1.0, 1.0);
163 }
164 if (dim == 3)
165 {
166 mesh = Mesh::MakeCartesian3D(2, 2, 2, Element::HEXAHEDRON, 1.0, 1.0, 1.0);
167 }
168
169 int order = 4;
170
171 // Scalar
172 H1_FECollection fec1(order, dim);
173 FiniteElementSpace fes1(&mesh, &fec1);
174
175 // Vector valued
176 H1_FECollection fec2(order, dim);
177 FiniteElementSpace fes2(&mesh, &fec2, dim);
178
179 GridFunction field(&fes1), field2(&fes2);
180
181 MixedBilinearForm gform(&fes1, &fes2);
182 gform.SetAssemblyLevel(AssemblyLevel::PARTIAL);
183 gform.AddDomainIntegrator(new GradientIntegrator);
184 gform.Assemble();
185
186 // Project u = f1
187 FunctionCoefficient fcoeff1(f1);
188 field.ProjectCoefficient(fcoeff1);
189
190 // Check if grad(u) = gradf1
191 gform.Mult(field, field2);
192 VectorFunctionCoefficient fcoeff2(dim, gradf1);
193 LinearForm lf(&fes2);
194 lf.AddDomainIntegrator(new VectorDomainLFIntegrator(fcoeff2));
195 lf.Assemble();
196 field2 -= lf;
197
198 return field2.Norml2();
199 }
200
201 TEST_CASE("PA Gradient", "[PartialAssembly]")
202 {
203 SECTION("2D")
204 {
205 // Check if grad(x^2 + y^3) == [2x, 3y^2]
206 REQUIRE(pa_gradient_testnd(2, f1, gradf1) == MFEM_Approx(0.0));
207 }
208
209 SECTION("3D")
210 {
211 // Check if grad(x^2 + y^3 + z^4) == [2x, 3y^2, 4z^3]
212 REQUIRE(pa_gradient_testnd(3, f1, gradf1) == MFEM_Approx(0.0));
213 }
214 }
215
test_nl_convection_nd(int dim)216 double test_nl_convection_nd(int dim)
217 {
218 Mesh mesh;
219
220 if (dim == 2)
221 {
222 mesh = Mesh::MakeCartesian2D(2, 2, Element::QUADRILATERAL, 0, 1.0, 1.0);
223 }
224 if (dim == 3)
225 {
226 mesh = Mesh::MakeCartesian3D(2, 2, 2, Element::HEXAHEDRON, 1.0, 1.0, 1.0);
227 }
228
229 int order = 2;
230 H1_FECollection fec(order, dim);
231 FiniteElementSpace fes(&mesh, &fec, dim);
232
233 GridFunction x(&fes), y_fa(&fes), y_pa(&fes);
234 x.Randomize(3);
235
236 NonlinearForm nlf_fa(&fes);
237 nlf_fa.AddDomainIntegrator(new VectorConvectionNLFIntegrator);
238 nlf_fa.Mult(x, y_fa);
239
240 NonlinearForm nlf_pa(&fes);
241 nlf_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
242 nlf_pa.AddDomainIntegrator(new VectorConvectionNLFIntegrator);
243 nlf_pa.Setup();
244 nlf_pa.Mult(x, y_pa);
245
246 y_fa -= y_pa;
247 double difference = y_fa.Norml2();
248
249
250 return difference;
251 }
252
253 TEST_CASE("Nonlinear Convection", "[PartialAssembly], [NonlinearPA]")
254 {
255 SECTION("2D")
256 {
257 REQUIRE(test_nl_convection_nd(2) == MFEM_Approx(0.0));
258 }
259
260 SECTION("3D")
261 {
262 REQUIRE(test_nl_convection_nd(3) == MFEM_Approx(0.0));
263 }
264 }
265
266 template <typename INTEGRATOR>
test_vector_pa_integrator(int dim)267 double test_vector_pa_integrator(int dim)
268 {
269 Mesh mesh =
270 (dim == 2) ?
271 Mesh::MakeCartesian2D(2, 2, Element::QUADRILATERAL, 0, 1.0, 1.0):
272 Mesh::MakeCartesian3D(2, 2, 2, Element::HEXAHEDRON, 1.0, 1.0, 1.0);
273
274 int order = 2;
275 H1_FECollection fec(order, dim);
276 FiniteElementSpace fes(&mesh, &fec, dim);
277
278 GridFunction x(&fes), y_fa(&fes), y_pa(&fes);
279 x.Randomize(1);
280
281 BilinearForm blf_fa(&fes);
282 blf_fa.AddDomainIntegrator(new INTEGRATOR);
283 blf_fa.Assemble();
284 blf_fa.Finalize();
285 blf_fa.Mult(x, y_fa);
286
287 BilinearForm blf_pa(&fes);
288 blf_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
289 blf_pa.AddDomainIntegrator(new INTEGRATOR);
290 blf_pa.Assemble();
291 blf_pa.Mult(x, y_pa);
292
293 y_fa -= y_pa;
294 double difference = y_fa.Norml2();
295
296 return difference;
297 }
298
299 TEST_CASE("PA Vector Mass", "[PartialAssembly], [VectorPA]")
300 {
301 SECTION("2D")
302 {
303 REQUIRE(test_vector_pa_integrator<VectorMassIntegrator>(2) == MFEM_Approx(0.0));
304 }
305
306 SECTION("3D")
307 {
308 REQUIRE(test_vector_pa_integrator<VectorMassIntegrator>(3) == MFEM_Approx(0.0));
309 }
310 }
311
312 TEST_CASE("PA Vector Diffusion", "[PartialAssembly], [VectorPA]")
313 {
314 SECTION("2D")
315 {
316 REQUIRE(test_vector_pa_integrator<VectorDiffusionIntegrator>(2)
317 == MFEM_Approx(0.0));
318 }
319
320 SECTION("3D")
321 {
322 REQUIRE(test_vector_pa_integrator<VectorDiffusionIntegrator>(3)
323 == MFEM_Approx(0.0));
324 }
325 }
326
velocity_function(const Vector & x,Vector & v)327 void velocity_function(const Vector &x, Vector &v)
328 {
329 int dim = x.Size();
330 switch (dim)
331 {
332 case 1: v(0) = 1.0; break;
333 case 2: v(0) = x(1); v(1) = -x(0); break;
334 case 3: v(0) = x(1); v(1) = -x(0); v(2) = x(0); break;
335 }
336 }
337
AddConvectionIntegrators(BilinearForm & k,Coefficient & rho,VectorCoefficient & velocity,bool dg)338 void AddConvectionIntegrators(BilinearForm &k, Coefficient &rho,
339 VectorCoefficient &velocity, bool dg)
340 {
341 k.AddDomainIntegrator(new ConvectionIntegrator(velocity, -1.0));
342
343 if (dg)
344 {
345 k.AddInteriorFaceIntegrator(
346 new TransposeIntegrator(new DGTraceIntegrator(rho, velocity, 1.0, -0.5)));
347 k.AddBdrFaceIntegrator(
348 new TransposeIntegrator(new DGTraceIntegrator(rho, velocity, 1.0, -0.5)));
349 }
350 }
351
test_pa_convection(const char * meshname,int order,int prob)352 void test_pa_convection(const char *meshname, int order, int prob)
353 {
354 INFO("mesh=" << meshname << ", order=" << order << ", prob=" << prob);
355 Mesh mesh(meshname, 1, 1);
356 mesh.EnsureNodes();
357 mesh.SetCurvature(mesh.GetNodalFESpace()->GetElementOrder(0));
358 int dim = mesh.Dimension();
359
360 FiniteElementCollection *fec;
361 if (prob)
362 {
363 fec = new L2_FECollection(order, dim, BasisType::GaussLobatto);
364 }
365 else
366 {
367 fec = new H1_FECollection(order, dim);
368 }
369 FiniteElementSpace fespace(&mesh, fec);
370
371 L2_FECollection vel_fec(order, dim, BasisType::GaussLobatto);
372 FiniteElementSpace vel_fespace(&mesh, &vel_fec, dim);
373 GridFunction vel_gf(&vel_fespace);
374 GridFunction rho_gf(&fespace);
375
376 BilinearForm k_pa(&fespace);
377 BilinearForm k_fa(&fespace);
378
379 VectorCoefficient *vel_coeff;
380 Coefficient *rho;
381
382 // prob: 0: CG, 1: DG continuous coeff, 2: DG discontinuous coeff
383 if (prob == 2)
384 {
385 vel_gf.Randomize(1);
386 vel_coeff = new VectorGridFunctionCoefficient(&vel_gf);
387 rho_gf.Randomize(1);
388 rho = new GridFunctionCoefficient(&rho_gf);
389 }
390 else
391 {
392 vel_coeff = new VectorFunctionCoefficient(dim, velocity_function);
393 rho = new ConstantCoefficient(1.0);
394 }
395
396
397 AddConvectionIntegrators(k_fa, *rho, *vel_coeff, prob > 0);
398 AddConvectionIntegrators(k_pa, *rho, *vel_coeff, prob > 0);
399
400 k_fa.Assemble();
401 k_fa.Finalize();
402
403 k_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
404 k_pa.Assemble();
405
406 GridFunction x(&fespace), y_fa(&fespace), y_pa(&fespace);
407
408 x.Randomize(1);
409
410 k_fa.Mult(x,y_fa);
411 k_pa.Mult(x,y_pa);
412
413 y_pa -= y_fa;
414
415 REQUIRE(y_pa.Norml2() < 1.e-12);
416
417 delete vel_coeff;
418 delete rho;
419 delete fec;
420 }
421
422 // Basic unit test for convection
423 TEST_CASE("PA Convection", "[PartialAssembly]")
424 {
425 // prob: 0: CG, 1: DG continuous coeff, 2: DG discontinuous coeff
426 auto prob = GENERATE(0, 1, 2);
427 auto order_2d = GENERATE(2, 3, 4);
428 auto order_3d = GENERATE(2);
429
430 SECTION("2D")
431 {
432 test_pa_convection("../../data/periodic-square.mesh", order_2d, prob);
433 test_pa_convection("../../data/periodic-hexagon.mesh", order_2d, prob);
434 test_pa_convection("../../data/star-q3.mesh", order_2d, prob);
435 }
436
437 SECTION("3D")
438 {
439 test_pa_convection("../../data/periodic-cube.mesh", order_3d, prob);
440 test_pa_convection("../../data/fichera-q3.mesh", order_3d, prob);
441 }
442
443 // Test AMR cases (DG not implemented)
444 SECTION("AMR 2D")
445 {
446 test_pa_convection("../../data/amr-quad.mesh", order_2d, 0);
447 }
448
449 SECTION("AMR 3D")
450 {
451 test_pa_convection("../../data/fichera-amr.mesh", order_3d, 0);
452 }
453
454 } // test case
455
456 } // namespace pa_kernels
457