Chi-Tech
CodeTut3.h
Go to the documentation of this file.
1/**\page CodeTut3 Coding Tutorial 3 - Poisson's problem with PWLC
2
3## Table of contents
4- \ref CodeTut3Sec1
5 - \ref CodeTut3Sec1_1
6 - \ref CodeTut3Sec1_2
7 - \ref CodeTut3Sec1_3
8- \ref CodeTut3Sec2
9- \ref CodeTut3Sec3
10- \ref CodeTut3Sec4
11- \ref CodeTut3Sec5
12- \ref CodeTut3Sec6
13- \ref CodeTut3Sec7
14
15
16\section CodeTut3Sec1 1 Poisson's equation
17<a href="https://en.wikipedia.org/wiki/Poisson%27s_equation">The Poisson's equation</a> states the following, for
18\f$ \phi, q \in \mathcal{R} \f$,
19\f{eqnarray*}{
20-\boldsymbol{\nabla} \boldsymbol{\cdot} \bigr(\boldsymbol{\nabla} \phi(\mathbf{x})\bigr) = q(\mathbf{x}), \quad \quad \quad (\mathbf{x})\in\mathcal{D} \\
21\phi(\mathbf{x}) = 0, \quad \quad \quad \mathbf{x}\in \partial \mathcal{D}
22\f}
23where \f$ \boldsymbol{\nabla} \boldsymbol{\cdot} \bigr( \bigr) \f$ denotes the divergence-operator
24and \f$ \boldsymbol{\nabla} \f$ denotes the gradient-operator, i.e.
25\f$ \boldsymbol{\nabla} \phi \f$ denotes the gradient of \f$ \phi \f$. The
26boundary conditions here state that \f$ \phi=0 \f$ on the boundary.
27
28
29
30
31\subsection CodeTut3Sec1_1 1.1 Our specific problem
32For our specific problem we will choose \f$ q(\mathbf{x})=1 \f$ and \f$ \mathcal{D} \f$ a cartesian domain,
33either 1D, 2D or 3D, with each dimension always between \f$ -1,+1 \f$. We can generate the mesh for this
34problem using an input file
35\code
36--############################################### Setup mesh
37chiMeshHandlerCreate()
38
39mesh={}
40N=20
41L=2
42xmin = -L/2
43dx = L/N
44for i=1,(N+1) do
45 k=i-1
46 mesh[i] = xmin + k*dx
47end
48
49--chiMeshCreateUnpartitioned3DOrthoMesh(mesh,mesh,mesh)
50chiMeshCreateUnpartitioned2DOrthoMesh(mesh,mesh)
51--chiMeshCreateUnpartitioned1DOrthoMesh(mesh)
52chiVolumeMesherExecute();
53
54--############################################### Set Material IDs
55chiVolumeMesherSetMatIDToAll(0)
56\endcode
57This code can be used to generate any of the following meshes,
58\image html CodingTutorials/OrthoMesh_1D_2D_3D.png "[From left to right] 1D, 2D, 3D orthogonal mesh" width=1200px
59
60
61
62
63\subsection CodeTut3Sec1_2 1.2 The Weak-form of Poisson's equation
64When using the finite element method we develop the so-called weak-form by
65weighting the Poisson equation with a trial space function, \f$ t_i(\mathbf{x}) \f$, where \f$ i \f$
66is a unique node in the problem, and then integrate over the volume of the
67domain
68\f[
69-\int_V t_i \boldsymbol{\nabla} \boldsymbol{\cdot} \bigr(\boldsymbol{\nabla} \phi(\mathbf{x})\bigr) dV
70 =
71\int_V t_i q(\mathbf{x}) dV.
72\f]
73
74Next we apply integration by parts to the left term, for which,
75\f[
76t_i \boldsymbol{\nabla} \boldsymbol{\cdot} \bigr(\boldsymbol{\nabla} \phi(\mathbf{x})\bigr)
77 =
78\boldsymbol{\nabla} \cdot (t_i \boldsymbol{\nabla} \phi)
79-
80(\boldsymbol{\nabla} t_i ) \boldsymbol{\cdot} (\boldsymbol{\nabla} \phi)
81\f]
82which gives
83\f[
84\int_V (\boldsymbol{\nabla} t_i ) \boldsymbol{\cdot} (\boldsymbol{\nabla} \phi) dV
85-\int_V \boldsymbol{\nabla} \boldsymbol{\cdot} (t_i \boldsymbol{\nabla} \phi) dV
86 =
87\int_V t_i q(\mathbf{x}) dV.
88\f]
89
90We then apply Gauss's Divergence Theorem to the second term from the left, to get
91\f[
92\int_V (\boldsymbol{\nabla} t_i ) \boldsymbol{\cdot} (\boldsymbol{\nabla} \phi) dV
93-\int_S \mathbf{n} \boldsymbol{\cdot} (t_i \boldsymbol{\nabla} \phi) dA
94 =
95\int_V t_i q(\mathbf{x}) dV.
96\f]
97
98This is essentially the weak-form.
99
100
101\subsection CodeTut3Sec1_3 1.3 The discretized equation with the Continuous Galerkin approach
102To fully discretize this equation we next approximate the continuous nature of
103\f$ \phi(\mathbf{x}) \f$ using essentially a very fancy interpolation scheme.
104With this scheme we approximate \f$ \phi(\mathbf{x}) \f$ as
105\f$ \phi_h(\mathbf{x}) \f$ where
106\f[
107\phi(\mathbf{x}) \approx \phi_h(\mathbf{x}) = \sum_j \phi_j b_j(\mathbf{x})
108\f]
109where the coefficients \f$ \phi_j \f$ do not dependent on space and
110\f$ b_j(\mathbf{x}) \f$ are called shape functions. For this tutorial we
111will be utilizing the Piecewise Linear Continuous (PWLC) shape functions which
112are generally specially in the fact that they can support arbitrary polygons and
113polyhedra.
114
115We will also follow a Galerkin approach, resulting in the trial-functions
116\f$ t_i \f$ being the same as the shape functions, i.e., \f$ t_n(\mathbf{x}) = b_n(\mathbf{x}) \f$.
117
118With this approximation defined, our weak form, after neglecting the surface integral
119since we have Dirichlet boundary conditions, becomes
120\f{align*}{
121\int_V (\boldsymbol{\nabla} b_i ) \boldsymbol{\cdot} (\boldsymbol{\nabla} \phi) dV
122 &=
123\int_V b_i q(\mathbf{x}) dV
124\\
125\sum_j \phi_j \int_V (\boldsymbol{\nabla} b_i ) \boldsymbol{\cdot} (\boldsymbol{\nabla} b_j) dV
126 &=
127\int_V b_i q(\mathbf{x}) dV
128\f}
129
130Now we are faced with the dilemma of how to compute the integral terms in this
131equation. The first thing we do here is to observe that the shape functions
132\f$ b_n \f$ are defined cell-by-cell, therefore we can drill a little deeper
133by defining
134\f{align*}{
135\int_V f(\mathbf{x}) dV &= \sum_c \int_{V_c} f_c(\mathbf{x}) dV \\
136\f}
137where \f$ V_c \f$ is the volume of cell \f$ c \f$. The function \f$ f_c \f$ is a
138function uniquely defined within cell \f$ c \f$'s volume.
139
140These integrals are often difficult to perform in real-world coordinates and are
141therefore carried out on a reference element in a different coordinate system. This
142necessitates the use of a transformation using the magnitude of the Jacobian that
143 transforms the above integrals into the form
144\f{align*}{
145\int_{V_c} f_c(\mathbf{x}) dV
146&=
147\int_{V_e} f_e(\tilde{\mathbf{x}}) |J(\tilde{\mathbf{x}})| dV
148\f}
149where \f$ \tilde{\mathbf{x}} \f$ is position in the reference coordinate frame,
150\f$|J(\tilde{\mathbf{x}})| \f$ is the magnitude of the Jacobian,
151\f$ V_e \f$ is the volume of the reference element,
152 and
153\f$ f_e \f$ is the reference element equivalent of \f$ f_c \f$.
154Now, the big advantage of doing this is that we can use quadrature rules for
155these integrals
156\f{align*}{
157\int_{V_e} f_e(\tilde{\mathbf{x}}) |J(\tilde{\mathbf{x}})| dV
158&=
159\sum_n w_n f_e(\tilde{\mathbf{x}}_n) |J(\tilde{\mathbf{x}}_n)|
160\f}
161where \f$ \tilde{\mathbf{x}}_n \f$ are the abscissas of the quadrature rule and
162\f$ w_n \f$ are the quadrature weights.
163
164With these mathematical formulations defined we can write
165\f{align*}{
166\sum_j \phi_j \sum_c \sum_n^{N_V}
167\biggr[ w_n
168 (\boldsymbol{\nabla} b_i(\tilde{\mathbf{x}}_n) )
169 \boldsymbol{\cdot} (\boldsymbol{\nabla} b_j(\tilde{\mathbf{x}}_n))
170 |J(\tilde{\mathbf{x}}_n)|
171\biggr]
172 &=
173\sum_c \sum_n^{N_V} w_n b_i(\tilde{\mathbf{x}}_n) q(\tilde{\mathbf{x}}_n \to \mathbf{x}) |J(\tilde{\mathbf{x}}_n)|
174\f}
175
176
177\section CodeTut3Sec2 2 Setting up the problem
178For this tutorial we basically follow the flow of \ref CodeTut1. Make sure you
179make sensible changes to the `CMakeLists.txt` file and name your source file
180appropriately, which for me is `code_tut3.cc`.
181
182The first portion of the tutorial is the same. We create a `mesh.lua` file in
183the `chi_build` directory. We get the grid
184\code
185int main(int argc, char* argv[])
186{
187 chi::Initialize(argc,argv);
188 chi::RunBatch(argc, argv);
189
190 chi::log.Log() << "Coding Tutorial 3";
191
192 //============================================= Get grid
193 auto grid_ptr = chi_mesh::GetCurrentHandler().GetGrid();
194 const auto& grid = *grid_ptr;
195
196 chi::log.Log() << "Global num cells: " << grid.GetGlobalNumberOfCells();
197\endcode
198
199
200
201
202
203
204\section CodeTut3Sec3 3 Initializing the PWLC Spatial Discretization
205To gain access to the `chi_math::SpatialDiscretization_PWLC` class we need to
206include the header
207\code
208#include "math/SpatialDiscretization/FiniteElement/PiecewiseLinear/pwlc.h"
209\endcode
210
211Next we add the following code
212\code
213//============================================= Make SDM
214typedef std::shared_ptr<chi_math::SpatialDiscretization> SDMPtr;
215SDMPtr sdm_ptr = chi_math::SpatialDiscretization_PWLC::New(grid_ptr);
216const auto& sdm = *sdm_ptr;
217
218const auto& OneDofPerNode = sdm.UNITARY_UNKNOWN_MANAGER;
219
220const size_t num_local_dofs = sdm.GetNumLocalDOFs(OneDofPerNode);
221const size_t num_globl_dofs = sdm.GetNumGlobalDOFs(OneDofPerNode);
222
223chi::log.Log() << "Num local DOFs: " << num_local_dofs;
224chi::log.Log() << "Num globl DOFs: " << num_globl_dofs;
225\endcode
226
227The initialization of the matrices and vectors remain exactly the same as in
228the Finite Volume tutorial:
229\code
230//============================================= Initializes Mats and Vecs
231const auto n = static_cast<int64_t>(num_local_dofs);
232const auto N = static_cast<int64_t>(num_globl_dofs);
233Mat A;
234Vec x,b;
235
236A = chi_math::PETScUtils::CreateSquareMatrix(n,N);
237x = chi_math::PETScUtils::CreateVector(n,N);
238b = chi_math::PETScUtils::CreateVector(n,N);
239
240std::vector<int64_t> nodal_nnz_in_diag;
241std::vector<int64_t> nodal_nnz_off_diag;
242sdm.BuildSparsityPattern(nodal_nnz_in_diag,nodal_nnz_off_diag, OneDofPerNode);
243
244chi_math::PETScUtils::InitMatrixSparsity(A,
245 nodal_nnz_in_diag,
246 nodal_nnz_off_diag);
247\endcode
248
249
250
251
252\section CodeTut3Sec4 4 Using quadrature data to assemble the system
253With the finite element method the conventional way to assemble the matrix is
254to do so by element (even though continuous FEM is only concerned about nodes).
255Therefore we start with a loop over elements then assemble our discretized
256equation
257\f{align*}{
258\sum_j \phi_j \sum_c \sum_n^{N_V}
259\biggr[ w_n
260 (\boldsymbol{\nabla} b_i(\tilde{\mathbf{x}}_n) )
261 \boldsymbol{\cdot} (\boldsymbol{\nabla} b_j(\tilde{\mathbf{x}}_n))
262 |J(\tilde{\mathbf{x}}_n)|
263\biggr]
264 &=
265\sum_c \sum_n^{N_V} w_n b_i(\tilde{\mathbf{x}}_n) q(\tilde{\mathbf{x}}_n \to \mathbf{x}) |J(\tilde{\mathbf{x}}_n)|
266\f}
267For which the first portion of the code is
268\code
269for (const auto& cell : grid.local_cells)
270{
271 const auto& cell_mapping = sdm.GetCellMapping(cell);
272 const auto qp_data = cell_mapping.MakeVolumeQuadraturePointData();
273
274 const size_t num_nodes = cell_mapping.NumNodes();
275 MatDbl Acell(num_nodes, VecDbl(num_nodes, 0.0));
276 VecDbl cell_rhs(num_nodes, 0.0);
277
278 for (size_t i=0; i<num_nodes; ++i)
279 {
280 for (size_t j=0; j<num_nodes; ++j)
281 {
282 double entry_aij = 0.0;
283 for (size_t qp : qp_data.QuadraturePointIndices())
284 {
285 entry_aij +=
286 qp_data.ShapeGrad(i, qp).Dot(qp_data.ShapeGrad(j, qp)) *
287 qp_data.JxW(qp);
288 }//for qp
289 Acell[i][j] = entry_aij;
290 }//for j
291 for (size_t qp : qp_data.QuadraturePointIndices())
292 cell_rhs[i] += 1.0 * qp_data.ShapeValue(i, qp) * qp_data.JxW(qp);
293 }//for i
294 //
295 // More code not shown
296 //
297\endcode
298The first thing we do here, as with any SD, is to grab the relevant cell mapping.
299After that we immediately create a
300`chi_math::finite_element::InternalQuadraturePointData` with the line of code
301\code
302const auto qp_data = cell_mapping.MakeVolumeQuadraturePointData();
303\endcode
304This data structure contains the shape function values, together with other data,
305pre-stored per quadrature point.
306
307Next we create a local cell-matrix and local right-hand-side (RHS) which serve as
308lightweight proxies before we assemble relevant values into the global matrix and
309global RHS.
310\code
311const size_t num_nodes = cell_mapping.NumNodes();
312MatDbl Acell(num_nodes, VecDbl(num_nodes, 0.0));
313VecDbl cell_rhs(num_nodes, 0.0);
314\endcode
315
316We then loop over all the \f$ (i,j) \f$ node combinations of our equation above
317but with scope only on the local element. Notice that we never actually pass
318around the actual quadrature points but rather use the quadrature point indices
319via `qp_data.QuadraturePointIndices()`.
320\code
321for (size_t i=0; i<num_nodes; ++i)
322{
323 for (size_t j=0; j<num_nodes; ++j)
324 {
325 double entry_aij = 0.0;
326 for (size_t qp : qp_data.QuadraturePointIndices())
327 {
328 entry_aij +=
329 qp_data.ShapeGrad(i, qp).Dot(qp_data.ShapeGrad(j, qp)) *
330 qp_data.JxW(qp);
331 }//for qp
332 Acell[i][j] = entry_aij;
333 }//for j
334 for (size_t qp : qp_data.QuadraturePointIndices())
335 cell_rhs[i] += 1.0 * qp_data.ShapeValue(i, qp) * qp_data.JxW(qp);
336}//for i
337\endcode
338Notice that we are actually doing
339\f{align*}{
340\sum_j \sum_n^{N_V}
341\biggr[
342 (\boldsymbol{\nabla} b_i(\tilde{\mathbf{x}}_n) )
343 \boldsymbol{\cdot} (\boldsymbol{\nabla} b_j(\tilde{\mathbf{x}}_n))
344 w_n |J(\tilde{\mathbf{x}}_n)|
345\biggr]
346\f}
347for cell \f$ c \f$, where \f$ w_n |J(\tilde{\mathbf{x}}_n)| \f$ is combined into
348`qp_data.JxW(qp)`.
349
350
351\section CodeTut3Sec5 5 Compensating for Dirichlet boundaries
352Dirichlet requires some special considerations. The equation for node \f$ i \f$
353essentially becomes \f$ \phi_i = 0 \f$ for our case of zero Dirichlet boundary
354conditions. This essentially places just a 1 on the diagonal with no entries
355off-diagonal for the entire row \f$ i \f$. Therefore, we need to modify the
356local cell-matrix to reflect this situation.
357
358We are unfortunately not done yet. Any row in the matrix that has an off-diaganol
359entry that corresponds to column \f$ i \f$ will need to be moved to the RHS. This
360is because it could destroy the symmetry of an otherwise symmetric matrix.
361
362We handle both these modifications in the following way. We build a flag-list
363for each cell node
364\code
365std::vector<bool> node_boundary_flag(num_nodes, false);
366const size_t num_faces = cell.faces.size();
367for (size_t f=0; f<num_faces; ++f)
368{
369 const auto& face = cell.faces[f];
370 if (face.has_neighbor) continue;
371
372 const size_t num_face_nodes = face.vertex_ids.size();
373 for (size_t fi=0; fi<num_face_nodes; ++fi)
374 {
375 const uint i = cell_mapping.MapFaceNode(f,fi);
376 node_boundary_flag[i] = true;
377 }//for fi
378}//for face f
379\endcode
380Notice the use of `face.has_neighbor` here. If the face does not have a neighbor
381it means the face is on a boundary and therefore a Dirichlet node. Once this
382condition applies we loop over the face nodes, map the face-node to a cell-node
383then flag that node as being on a boundary.
384
385Finally, we now assemble the local cell-matrix and local RHS into the global
386system.
387\code
388//======================= Assembly into system
389for (size_t i=0; i<num_nodes; ++i)
390{
391 if (node_boundary_flag[i]) //if dirichlet node
392 {
393 MatSetValue(A, imap[i], imap[i], 1.0, ADD_VALUES);
394 VecSetValue(b, imap[i], 0.0, ADD_VALUES);
395 }
396 else
397 {
398 for (size_t j=0; j<num_nodes; ++j)
399 {
400 if (not node_boundary_flag[j])
401 MatSetValue(A, imap[i], imap[j], Acell[i][j], ADD_VALUES);
402 }//for j
403 VecSetValue(b, imap[i], cell_rhs[i], ADD_VALUES);
404 }
405}//for i
406\endcode
407We essentially loop over each node, after which we have two conditions: if the
408node is a boundary node then we just add an entry on the diagonal, if the node
409is not a boundary node we then loop over the row entries. We perform a check
410again to see if the j-entries are boundary nodes. If they are we put them on the
411RHS, if they are not then they are allowed to be matrix entries.
412
413After this process we assemble the matrix and the RHS globally like we did
414before:
415\code
416chi::log.Log() << "Global assembly";
417
418MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY);
419MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY);
420VecAssemblyBegin(b);
421VecAssemblyEnd(b);
422
423chi::log.Log() << "Done global assembly";
424\endcode
425
426
427
428
429\section CodeTut3Sec6 6 Solving and visualizing
430Solving the system and visualizing it is the same as was done for the Finite
431Volume tutorial. The matrix is still Symmetric Positive Definite (SPD) so we
432use the same solver/precondtioner setup.
433\code
434//============================================= Create Krylov Solver
435chi::log.Log() << "Solving: ";
436auto petsc_solver =
437 chi_math::PETScUtils::CreateCommonKrylovSolverSetup(
438 A, //Matrix
439 "PWLCDiffSolver", //Solver name
440 KSPCG, //Solver type
441 PCGAMG, //Preconditioner type
442 1.0e-6, //Relative residual tolerance
443 1000); //Max iterations
444
445//============================================= Solve
446KSPSolve(petsc_solver.ksp,b,x);
447
448chi::log.Log() << "Done solving";
449
450//============================================= Extract PETSc vector
451std::vector<double> field;
452sdm.LocalizePETScVector(x,field,OneDofPerNode);
453
454//============================================= Clean up
455KSPDestroy(&petsc_solver.ksp);
456
457VecDestroy(&x);
458VecDestroy(&b);
459MatDestroy(&A);
460
461chi::log.Log() << "Done cleanup";
462
463//============================================= Create Field Function
464auto ff = std::make_shared<chi_physics::FieldFunction>(
465 "Phi", //Text name
466 sdm_ptr, //Spatial Discr.
467 chi_math::Unknown(chi_math::UnknownType::SCALAR) //Unknown
468);
469
470ff->UpdateFieldVector(field);
471
472ff->ExportToVTK("CodeTut3_PWLC");
473\endcode
474
475\image html CodingTutorials/Tut3_Solution.png "Solution for a 2D mesh with a flat and warped view" width=900px
476
477
478
479
480
481\section CodeTut3Sec7 The complete program
482\code
483#include "chi_runtime.h"
484#include "chi_log.h"
485
486#include "mesh/MeshHandler/chi_meshhandler.h"
487
488#include "math/SpatialDiscretization/FiniteElement/PiecewiseLinear/pwlc.h"
489#include "math/PETScUtils/petsc_utils.h"
490
491#include "physics/FieldFunction/fieldfunction2.h"
492
493int main(int argc, char* argv[])
494{
495 chi::Initialize(argc,argv);
496 chi::RunBatch(argc, argv);
497
498 chi::log.Log() << "Coding Tutorial 3";
499
500 //============================================= Get grid
501 auto grid_ptr = chi_mesh::GetCurrentHandler().GetGrid();
502 const auto& grid = *grid_ptr;
503
504 chi::log.Log() << "Global num cells: " << grid.GetGlobalNumberOfCells();
505
506 //============================================= Make SDM
507 typedef std::shared_ptr<chi_math::SpatialDiscretization> SDMPtr;
508 SDMPtr sdm_ptr = chi_math::SpatialDiscretization_PWLC::New(grid_ptr);
509 const auto& sdm = *sdm_ptr;
510
511 const auto& OneDofPerNode = sdm.UNITARY_UNKNOWN_MANAGER;
512
513 const size_t num_local_dofs = sdm.GetNumLocalDOFs(OneDofPerNode);
514 const size_t num_globl_dofs = sdm.GetNumGlobalDOFs(OneDofPerNode);
515
516 chi::log.Log() << "Num local DOFs: " << num_local_dofs;
517 chi::log.Log() << "Num globl DOFs: " << num_globl_dofs;
518
519 //============================================= Initializes Mats and Vecs
520 const auto n = static_cast<int64_t>(num_local_dofs);
521 const auto N = static_cast<int64_t>(num_globl_dofs);
522 Mat A;
523 Vec x,b;
524
525 A = chi_math::PETScUtils::CreateSquareMatrix(n,N);
526 x = chi_math::PETScUtils::CreateVector(n,N);
527 b = chi_math::PETScUtils::CreateVector(n,N);
528
529 std::vector<int64_t> nodal_nnz_in_diag;
530 std::vector<int64_t> nodal_nnz_off_diag;
531 sdm.BuildSparsityPattern(nodal_nnz_in_diag,nodal_nnz_off_diag, OneDofPerNode);
532
533 chi_math::PETScUtils::InitMatrixSparsity(A,
534 nodal_nnz_in_diag,
535 nodal_nnz_off_diag);
536
537 //============================================= Assemble the system
538 chi::log.Log() << "Assembling system: ";
539 for (const auto& cell : grid.local_cells)
540 {
541 const auto& cell_mapping = sdm.GetCellMapping(cell);
542 const auto qp_data = cell_mapping.MakeVolumeQuadraturePointData();
543
544 const size_t num_nodes = cell_mapping.NumNodes();
545 MatDbl Acell(num_nodes, VecDbl(num_nodes, 0.0));
546 VecDbl cell_rhs(num_nodes, 0.0);
547
548 for (size_t i=0; i<num_nodes; ++i)
549 {
550 for (size_t j=0; j<num_nodes; ++j)
551 {
552 double entry_aij = 0.0;
553 for (size_t qp : qp_data.QuadraturePointIndices())
554 {
555 entry_aij +=
556 qp_data.ShapeGrad(i, qp).Dot(qp_data.ShapeGrad(j, qp)) *
557 qp_data.JxW(qp);
558 }//for qp
559 Acell[i][j] = entry_aij;
560 }//for j
561 for (size_t qp : qp_data.QuadraturePointIndices())
562 cell_rhs[i] += 1.0 * qp_data.ShapeValue(i, qp) * qp_data.JxW(qp);
563 }//for i
564
565 //======================= Flag nodes for being on dirichlet boundary
566 std::vector<bool> node_boundary_flag(num_nodes, false);
567 const size_t num_faces = cell.faces.size();
568 for (size_t f=0; f<num_faces; ++f)
569 {
570 const auto& face = cell.faces[f];
571 if (face.has_neighbor) continue;
572
573 const size_t num_face_nodes = face.vertex_ids.size();
574 for (size_t fi=0; fi<num_face_nodes; ++fi)
575 {
576 const uint i = cell_mapping.MapFaceNode(f,fi);
577 node_boundary_flag[i] = true;
578 }//for fi
579 }//for face f
580
581 //======================= Develop node mapping
582 std::vector<int64_t> imap(num_nodes, 0); //node-mapping
583 for (size_t i=0; i<num_nodes; ++i)
584 imap[i] = sdm.MapDOF(cell, i);
585
586 //======================= Assembly into system
587 for (size_t i=0; i<num_nodes; ++i)
588 {
589 if (node_boundary_flag[i]) //if dirichlet node
590 {
591 MatSetValue(A, imap[i], imap[i], 1.0, ADD_VALUES);
592 VecSetValue(b, imap[i], 0.0, ADD_VALUES);
593 }
594 else
595 {
596 for (size_t j=0; j<num_nodes; ++j)
597 {
598 if (not node_boundary_flag[j])
599 MatSetValue(A, imap[i], imap[j], Acell[i][j], ADD_VALUES);
600 }//for j
601 VecSetValue(b, imap[i], cell_rhs[i], ADD_VALUES);
602 }
603 }//for i
604 }//for cell
605
606 chi::log.Log() << "Global assembly";
607
608 MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY);
609 MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY);
610 VecAssemblyBegin(b);
611 VecAssemblyEnd(b);
612
613 chi::log.Log() << "Done global assembly";
614
615 //============================================= Create Krylov Solver
616 chi::log.Log() << "Solving: ";
617 auto petsc_solver =
618 chi_math::PETScUtils::CreateCommonKrylovSolverSetup(
619 A, //Matrix
620 "PWLCDiffSolver", //Solver name
621 KSPCG, //Solver type
622 PCGAMG, //Preconditioner type
623 1.0e-6, //Relative residual tolerance
624 1000); //Max iterations
625
626 //============================================= Solve
627 KSPSolve(petsc_solver.ksp,b,x);
628
629 chi::log.Log() << "Done solving";
630
631 //============================================= Extract PETSc vector
632 std::vector<double> field;
633 sdm.LocalizePETScVector(x,field,OneDofPerNode);
634
635 //============================================= Clean up
636 KSPDestroy(&petsc_solver.ksp);
637
638 VecDestroy(&x);
639 VecDestroy(&b);
640 MatDestroy(&A);
641
642 chi::log.Log() << "Done cleanup";
643
644 //============================================= Create Field Function
645 auto ff = std::make_shared<chi_physics::FieldFunction>(
646 "Phi", //Text name
647 sdm_ptr, //Spatial Discr.
648 chi_math::Unknown(chi_math::UnknownType::SCALAR) //Unknown
649 );
650
651 ff->UpdateFieldVector(field);
652
653 ff->ExportToVTK("CodeTut3_PWLC");
654
655 chi::Finalize();
656 return 0;
657}
658\endcode
659
660*/