Chi-Tech
CodeTut4.h
Go to the documentation of this file.
1/**\page CodeTut4 Coding Tutorial 4 - MMS for Poisson's problem with PWLC
2
3## Table of contents
4- \ref CodeTut4Sec1
5- \ref CodeTut4Sec2
6- \ref CodeTut4Sec3
7- \ref CodeTut4Sec4
8- \ref CodeTut4SecX
9
10\section CodeTut4Sec1 1 Defining a wrapper to call a lua function
11To implement a manufactured solution in our PWLC solver from \ref CodeTut3
12we are required to call a lua-function
13with spatial coordinates x,y,z. In order to use the lua-console, specifically
14the lua state, we have to include the headers
15\code
16#include "console/chi_console.h"
17#include "chi_lua.h"
18\endcode
19
20When then have to grab the console state from the runtime environment. To do this
21we add the following code, in the beginning of the program,
22just below `chi::RunBatch`
23\code
24auto L = chi::console.consoleState;
25\endcode
26
27Next we have to tell the c++ code exactly how to call a lua-function. To this
28end we define a wrapper using a c++ lambda as shown below. You can define this
29function where ever you like as long as it can capture the lua state, `L`.
30For this tutorial we defined it just before the matrix assembly.
31\code
32auto CallLuaXYZFunction = [&L](const std::string& lua_func_name,
33 const chi_mesh::Vector3& xyz)
34{
35 //============= Load lua function
36 lua_getglobal(L, lua_func_name.c_str());
37
38 //============= Error check lua function
39 if (not lua_isfunction(L, -1))
40 throw std::logic_error("CallLuaXYZFunction attempted to access lua-function, " +
41 lua_func_name + ", but it seems the function"
42 " could not be retrieved.");
43
44 //============= Push arguments
45 lua_pushnumber(L, xyz.x);
46 lua_pushnumber(L, xyz.y);
47 lua_pushnumber(L, xyz.z);
48
49 //============= Call lua function
50 //3 arguments, 1 result (double), 0=original error object
51 double lua_return = 0.0;
52 if (lua_pcall(L,3,1,0) == 0)
53 {
54 LuaCheckNumberValue("CallLuaXYZFunction", L, -1);
55 lua_return = lua_tonumber(L,-1);
56 }
57 else
58 throw std::logic_error("CallLuaXYZFunction attempted to call lua-function, " +
59 lua_func_name + ", but the call failed." +
60 xyz.PrintStr());
61
62 lua_pop(L,1); //pop the double, or error code
63
64 return lua_return;
65};
66\endcode
67Notice this function takes 2 arguments: a string value representing the lua
68function's name, and a `chi_mesh::Vector3` indicating the real-word XYZ
69coordinates at which to evaluate the function.
70
71In order to implement a manufactures solution we need a right-hand-side (RHS)
72function and an actual manufactured solution (MS) function. The RHS function will essentially
73steer the entire solution towards the manufactured solution. The evaluation of
74the actual manufactured solution is required in two places, i.e., in the
75implementation of Dirichlet boundary conditions, and when we compute the L2-norm
76of the error between our FEM-solution and the manufactured one.
77
78\section CodeTut4Sec2 2 Lua functions
79The RHS function and the MS function both need to exist within the lua state.
80We therefore ad the following to the `mesh.lua` input file.
81\code
82function MMS_phi(x,y,z)
83 return math.cos(math.pi*x) + math.cos(math.pi*y)
84end
85function MMS_q(x,y,z)
86 return math.pi*math.pi * (math.cos(math.pi*x)+math.cos(math.pi*y))
87end
88\endcode
89The function `MMS_phi` is the MS function and the function `MMS_q` is the RHS
90function. These functions can be defined anywhere in the input file as long as
91they are available in the state when solver needs them.
92
93\section CodeTut4Sec3 3 Getting guadrature-point real world XYZ
94Quadrautre-point real-world positions are stored when the `qp_data` structure
95is created. They are accessed with the call
96`chi_math::finite_element::InternalQuadraturePointData::QPointXYZ`, taking the
97quadrature point index as a parameter.
98\code
99for (size_t qp : qp_data.QuadraturePointIndices())
100 cell_rhs[i] += CallLuaXYZFunction(qp_data.QPointXYZ(qp),"MMS_q", L) *
101 qp_data.ShapeValue(i, qp) * qp_data.JxW(qp);
102\endcode
103
104\section CodeTut4Sec4 4 Convergence study
105The Method of Manufactured Solution (MMS) is often used to verify the rate of
106convergence of computational methods. In order to determine magnitude of the
107error of the FEM solution, \f$ \phi_{FEM} \f$, compared to the MS,
108\f$ \phi_{MMS} \f$, we need to evaluate the following norm
109\f[
110||e||_2 = \sqrt{\int_V \biggr( \phi_{MMS}(\mathbf{x}) - \phi_{FEM}(\mathbf{x})\biggr)^2 dV}
111\f]
112We again here use the quadrature rules to obtain this integral as
113\f[
114\int_V \biggr( \phi_{MMS}(\mathbf{x}) - \phi_{FEM}(\mathbf{x})\biggr)^2 dV = \sum_c \sum_n^{N_V}
115w_n \biggr( \phi_{MMS}(\mathbf{x}_n) - \phi_{FEM}(\tilde{\mathbf{x}}_n)\biggr)^2 \ | J(\tilde{\mathbf{x}}_n |
116\f]
117for which we use the code
118\code
119//============================================= Compute error
120//First get ghosted values
121const auto field_wg = ff->GetGhostedFieldVector();
122
123double local_error = 0.0;
124for (const auto& cell : grid.local_cells)
125{
126 const auto& cell_mapping = sdm.GetCellMapping(cell);
127 const size_t num_nodes = cell_mapping.NumNodes();
128 const auto qp_data = cell_mapping.MakeVolumeQuadraturePointData();
129
130 //======================= Grab nodal phi values
131 std::vector<double> nodal_phi(num_nodes,0.0);
132 for (size_t j=0; j < num_nodes; ++j)
133 {
134 const int64_t imap = sdm.MapDOFLocal(cell, j);
135 nodal_phi[j] = field_wg[imap];
136 }//for j
137
138 //======================= Quadrature loop
139 for (size_t qp : qp_data.QuadraturePointIndices())
140 {
141 double phi_fem = 0.0;
142 for (size_t j=0; j < num_nodes; ++j)
143 phi_fem += nodal_phi[j] * qp_data.ShapeValue(j, qp);
144
145 double phi_true = CallLuaXYZFunction("MMS_phi",qp_data.QPointXYZ(qp));
146
147 local_error += std::pow(phi_true - phi_fem,2.0) * qp_data.JxW(qp);
148 }
149}//for cell
150
151double global_error = 0.0;
152MPI_Allreduce(&local_error, //sendbuf
153 &global_error, //recvbuf
154 1, MPI_DOUBLE, //count+datatype
155 MPI_SUM, //operation
156 Chi::mpi.comm); //communicator
157
158global_error = std::sqrt(global_error);
159
160chi::log.Log() << "Error: " << std::scientific << global_error
161 << " Num-cells: " << grid.GetGlobalNumberOfCells();
162\endcode
163Notice that we grab the nodal values of \f$ \phi_{FEM} \f$ before we loop over
164quadrature points. We then use these nodal values within the quadrature-point
165loop to construct \f$ \phi_{FEM}(\tilde{\mathbf{x}}_n) \f$ as
166\f[
167\phi_{FEM}(\tilde{\mathbf{x}}_n) = \sum_j \phi_j b_j(\tilde{\mathbf{x}}_n)
168\f]
169with the code
170\code
171for (size_t j=0; j < num_nodes; ++j)
172 phi_fem += nodal_phi[j] * qp_data.ShapeValue(j, qp);
173\endcode
174
175Notice also that we first have to compute a local error, from the cells that are
176on the local partition. This is then followed by an `MPI_Allreduce`, with the
177`MPI_SUM` operation, to gather all the local error values into a single global
178error value. This is done via the code
179\code
180double global_error = 0.0;
181MPI_Allreduce(&local_error, //sendbuf
182 &global_error, //recvbuf
183 1, MPI_DOUBLE, //count+datatype
184 MPI_SUM, //operation
185 Chi::mpi.comm); //communicator
186
187global_error = std::sqrt(global_error);
188\endcode
189
190With this code in-place we can run the program several times with a different
191amount of cells, which provides us with the data below:
192
193\image html CodingTutorials/Tut4_OrderOfConv.png width=700px
194
195\section CodeTut4SecX The complete program
196\code
197#include "chi_runtime.h"
198#include "chi_log.h"
199
200#include "mesh/MeshHandler/chi_meshhandler.h"
201
202#include "math/SpatialDiscretization/FiniteElement/PiecewiseLinear/pwlc.h"
203#include "math/PETScUtils/petsc_utils.h"
204
205#include "physics/FieldFunction/fieldfunction2.h"
206
207#include "console/chi_console.h"
208#include "chi_lua.h"
209
210int main(int argc, char* argv[])
211{
212 chi::Initialize(argc,argv);
213 chi::RunBatch(argc, argv);
214
215 chi::log.Log() << "Coding Tutorial 4";
216
217 //============================================= Get grid
218 auto grid_ptr = chi_mesh::GetCurrentHandler().GetGrid();
219 const auto& grid = *grid_ptr;
220
221 chi::log.Log() << "Global num cells: " << grid.GetGlobalNumberOfCells();
222
223 //============================================= Make SDM
224 typedef std::shared_ptr<chi_math::SpatialDiscretization> SDMPtr;
225 SDMPtr sdm_ptr = chi_math::SpatialDiscretization_PWLC::New(grid_ptr);
226 const auto& sdm = *sdm_ptr;
227
228 const auto& OneDofPerNode = sdm.UNITARY_UNKNOWN_MANAGER;
229
230 const size_t num_local_dofs = sdm.GetNumLocalDOFs(OneDofPerNode);
231 const size_t num_globl_dofs = sdm.GetNumGlobalDOFs(OneDofPerNode);
232
233 chi::log.Log() << "Num local DOFs: " << num_local_dofs;
234 chi::log.Log() << "Num globl DOFs: " << num_globl_dofs;
235
236 //============================================= Initializes Mats and Vecs
237 const auto n = static_cast<int64_t>(num_local_dofs);
238 const auto N = static_cast<int64_t>(num_globl_dofs);
239 Mat A;
240 Vec x,b;
241
242 A = chi_math::PETScUtils::CreateSquareMatrix(n,N);
243 x = chi_math::PETScUtils::CreateVector(n,N);
244 b = chi_math::PETScUtils::CreateVector(n,N);
245
246 std::vector<int64_t> nodal_nnz_in_diag;
247 std::vector<int64_t> nodal_nnz_off_diag;
248 sdm.BuildSparsityPattern(nodal_nnz_in_diag,nodal_nnz_off_diag, OneDofPerNode);
249
250 chi_math::PETScUtils::InitMatrixSparsity(A,
251 nodal_nnz_in_diag,
252 nodal_nnz_off_diag);
253
254 //============================================= Source lambda
255 auto CallLuaXYZFunction = [&L](const std::string& lua_func_name,
256 const chi_mesh::Vector3& xyz)
257 {
258 //============= Load lua function
259 lua_getglobal(L, lua_func_name.c_str());
260
261 //============= Error check lua function
262 if (not lua_isfunction(L, -1))
263 throw std::logic_error("CallLuaXYZFunction attempted to access lua-function, " +
264 lua_func_name + ", but it seems the function"
265 " could not be retrieved.");
266
267 //============= Push arguments
268 lua_pushnumber(L, xyz.x);
269 lua_pushnumber(L, xyz.y);
270 lua_pushnumber(L, xyz.z);
271
272 //============= Call lua function
273 //3 arguments, 1 result (double), 0=original error object
274 double lua_return = 0.0;
275 if (lua_pcall(L,3,1,0) == 0)
276 {
277 LuaCheckNumberValue("CallLuaXYZFunction", L, -1);
278 lua_return = lua_tonumber(L,-1);
279 }
280 else
281 throw std::logic_error("CallLuaXYZFunction attempted to call lua-function, " +
282 lua_func_name + ", but the call failed." +
283 xyz.PrintStr());
284
285 lua_pop(L,1); //pop the double, or error code
286
287 return lua_return;
288 };
289
290 //============================================= Assemble the system
291 chi::log.Log() << "Assembling system: ";
292 for (const auto& cell : grid.local_cells)
293 {
294 const auto& cell_mapping = sdm.GetCellMapping(cell);
295 const auto qp_data = cell_mapping.MakeVolumeQuadraturePointData();
296 const auto cell_node_xyzs = cell_mapping.GetNodeLocations();
297
298 const size_t num_nodes = cell_mapping.NumNodes();
299 MatDbl Acell(num_nodes, VecDbl(num_nodes, 0.0));
300 VecDbl cell_rhs(num_nodes, 0.0);
301
302 for (size_t i=0; i<num_nodes; ++i)
303 {
304 for (size_t j=0; j<num_nodes; ++j)
305 {
306 double entry_aij = 0.0;
307 for (size_t qp : qp_data.QuadraturePointIndices())
308 {
309 entry_aij +=
310 qp_data.ShapeGrad(i, qp).Dot(qp_data.ShapeGrad(j, qp)) *
311 qp_data.JxW(qp);
312 }//for qp
313 Acell[i][j] = entry_aij;
314 }//for j
315 for (size_t qp : qp_data.QuadraturePointIndices())
316 cell_rhs[i] += CallLuaXYZFunction("MMS_q",qp_data.QPointXYZ(qp)) *
317 qp_data.ShapeValue(i, qp) * qp_data.JxW(qp);
318 }//for i
319
320 //======================= Flag nodes for being on dirichlet boundary
321 std::vector<bool> node_boundary_flag(num_nodes, false);
322 const size_t num_faces = cell.faces.size();
323 for (size_t f=0; f<num_faces; ++f)
324 {
325 const auto& face = cell.faces[f];
326 if (face.has_neighbor) continue;
327
328 const size_t num_face_nodes = face.vertex_ids.size();
329 for (size_t fi=0; fi<num_face_nodes; ++fi)
330 {
331 const uint i = cell_mapping.MapFaceNode(f,fi);
332 node_boundary_flag[i] = true;
333 }//for fi
334 }//for face f
335
336 //======================= Develop node mapping
337 std::vector<int64_t> imap(num_nodes, 0); //node-mapping
338 for (size_t i=0; i<num_nodes; ++i)
339 imap[i] = sdm.MapDOF(cell, i);
340
341 //======================= Assembly into system
342 for (size_t i=0; i<num_nodes; ++i)
343 {
344 if (node_boundary_flag[i]) //if dirichlet node
345 {
346 MatSetValue(A, imap[i], imap[i], 1.0, ADD_VALUES);
347 double bval = CallLuaXYZFunction("MMS_phi",cell_node_xyzs[i]);
348 VecSetValue(b, imap[i], bval, ADD_VALUES);
349 }
350 else
351 {
352 for (size_t j=0; j<num_nodes; ++j)
353 {
354 if (not node_boundary_flag[j])
355 MatSetValue(A, imap[i], imap[j], Acell[i][j], ADD_VALUES);
356 else
357 {
358 double bval = CallLuaXYZFunction("MMS_phi",cell_node_xyzs[j]);
359 VecSetValue(b, imap[i], -Acell[i][j]*bval, ADD_VALUES);
360 }
361 }//for j
362 VecSetValue(b, imap[i], cell_rhs[i], ADD_VALUES);
363 }
364 }//for i
365 }//for cell
366
367 chi::log.Log() << "Global assembly";
368
369 MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY);
370 MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY);
371 VecAssemblyBegin(b);
372 VecAssemblyEnd(b);
373
374 chi::log.Log() << "Done global assembly";
375
376 //============================================= Create Krylov Solver
377 chi::log.Log() << "Solving: ";
378 auto petsc_solver =
379 chi_math::PETScUtils::CreateCommonKrylovSolverSetup(
380 A, //Matrix
381 "PWLCDiffSolver", //Solver name
382 KSPCG, //Solver type
383 PCGAMG, //Preconditioner type
384 1.0e-6, //Relative residual tolerance
385 1000); //Max iterations
386
387 //============================================= Solve
388 KSPSolve(petsc_solver.ksp,b,x);
389
390 chi::log.Log() << "Done solving";
391
392 //============================================= Extract PETSc vector
393 std::vector<double> field;
394 sdm.LocalizePETScVector(x,field,OneDofPerNode);
395
396 //============================================= Clean up
397 KSPDestroy(&petsc_solver.ksp);
398
399 VecDestroy(&x);
400 VecDestroy(&b);
401 MatDestroy(&A);
402
403 chi::log.Log() << "Done cleanup";
404
405 //============================================= Create Field Function
406 auto ff = std::make_shared<chi_physics::FieldFunction>(
407 "Phi", //Text name
408 sdm_ptr, //Spatial Discr.
409 chi_math::Unknown(chi_math::UnknownType::SCALAR) //Unknown
410 );
411
412 ff->UpdateFieldVector(field);
413
414 ff->ExportToVTK("CodeTut4_PWLC");
415
416 //============================================= Compute error
417 //First get ghosted values
418 const auto field_wg = ff->GetGhostedFieldVector();
419
420 double local_error = 0.0;
421 for (const auto& cell : grid.local_cells)
422 {
423 const auto& cell_mapping = sdm.GetCellMapping(cell);
424 const size_t num_nodes = cell_mapping.NumNodes();
425 const auto qp_data = cell_mapping.MakeVolumeQuadraturePointData();
426
427 //======================= Grab nodal phi values
428 std::vector<double> nodal_phi(num_nodes,0.0);
429 for (size_t j=0; j < num_nodes; ++j)
430 {
431 const int64_t jmap = sdm.MapDOFLocal(cell, j);
432 nodal_phi[j] = field_wg[jmap];
433 }//for j
434
435 //======================= Quadrature loop
436 for (size_t qp : qp_data.QuadraturePointIndices())
437 {
438 double phi_fem = 0.0;
439 for (size_t j=0; j < num_nodes; ++j)
440 phi_fem += nodal_phi[j] * qp_data.ShapeValue(j, qp);
441
442 double phi_true = CallLuaXYZFunction("MMS_phi",qp_data.QPointXYZ(qp));
443
444 local_error += std::pow(phi_true - phi_fem,2.0) * qp_data.JxW(qp);
445 }
446 }//for cell
447
448 double global_error = 0.0;
449 MPI_Allreduce(&local_error, //sendbuf
450 &global_error, //recvbuf
451 1, MPI_DOUBLE, //count+datatype
452 MPI_SUM, //operation
453 Chi::mpi.comm); //communicator
454
455 global_error = std::sqrt(global_error);
456
457 chi::log.Log() << "Error: " << std::scientific << global_error
458 << " Num-cells: " << grid.GetGlobalNumberOfCells();
459
460 chi::Finalize();
461 return 0;
462}
463\endcode
464
465*/