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