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}
23
where \f$ \boldsymbol{\nabla} \boldsymbol{\cdot} \bigr( \bigr) \f$ denotes the divergence-operator
24
and \f$ \boldsymbol{\nabla} \f$ denotes the gradient-operator, i.e.
25
\f$ \boldsymbol{\nabla} \phi \f$ denotes the gradient of \f$ \phi \f$. The
26
boundary conditions here state that \f$ \phi=0 \f$ on the boundary.
27
28
29
30
31
\subsection CodeTut3Sec1_1 1.1 Our specific problem
32
For our specific problem we will choose \f$ q(\mathbf{x})=1 \f$ and \f$ \mathcal{D} \f$ a cartesian domain,
33
either 1D, 2D or 3D, with each dimension always between \f$ -1,+1 \f$. We can generate the mesh for this
34
problem using an input file
35
\code
36
--############################################### Setup mesh
37
chiMeshHandlerCreate()
38
39
mesh={}
40
N=20
41
L=2
42
xmin = -L/2
43
dx = L/N
44
for i=1,(N+1) do
45
k=i-1
46
mesh[i] = xmin + k*dx
47
end
48
49
--chiMeshCreateUnpartitioned3DOrthoMesh(mesh,mesh,mesh)
50
chiMeshCreateUnpartitioned2DOrthoMesh(mesh,mesh)
51
--chiMeshCreateUnpartitioned1DOrthoMesh(mesh)
52
chiVolumeMesherExecute();
53
54
--############################################### Set Material IDs
55
chiVolumeMesherSetMatIDToAll(0)
56
\endcode
57
This 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
64
When using the finite element method we develop the so-called weak-form by
65
weighting the Poisson equation with a trial space function, \f$ t_i(\mathbf{x}) \f$, where \f$ i \f$
66
is a unique node in the problem, and then integrate over the volume of the
67
domain
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
74
Next we apply integration by parts to the left term, for which,
75
\f[
76
t_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]
82
which 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
90
We 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
98
This is essentially the weak-form.
99
100
101
\subsection CodeTut3Sec1_3 1.3 The discretized equation with the Continuous Galerkin approach
102
To fully discretize this equation we next approximate the continuous nature of
103
\f$ \phi(\mathbf{x}) \f$ using essentially a very fancy interpolation scheme.
104
With 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]
109
where 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
111
will be utilizing the Piecewise Linear Continuous (PWLC) shape functions which
112
are generally specially in the fact that they can support arbitrary polygons and
113
polyhedra.
114
115
We 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
118
With this approximation defined, our weak form, after neglecting the surface integral
119
since 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
130
Now we are faced with the dilemma of how to compute the integral terms in this
131
equation. 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
133
by defining
134
\f{align*}{
135
\int_V f(\mathbf{x}) dV &= \sum_c \int_{V_c} f_c(\mathbf{x}) dV \\
136
\f}
137
where \f$ V_c \f$ is the volume of cell \f$ c \f$. The function \f$ f_c \f$ is a
138
function uniquely defined within cell \f$ c \f$'s volume.
139
140
These integrals are often difficult to perform in real-world coordinates and are
141
therefore carried out on a reference element in a different coordinate system. This
142
necessitates 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}
149
where \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$.
154
Now, the big advantage of doing this is that we can use quadrature rules for
155
these 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}
161
where \f$ \tilde{\mathbf{x}}_n \f$ are the abscissas of the quadrature rule and
162
\f$ w_n \f$ are the quadrature weights.
163
164
With 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
178
For this tutorial we basically follow the flow of \ref CodeTut1. Make sure you
179
make sensible changes to the `CMakeLists.txt` file and name your source file
180
appropriately, which for me is `code_tut3.cc`.
181
182
The first portion of the tutorial is the same. We create a `mesh.lua` file in
183
the `chi_build` directory. We get the grid
184
\code
185
int 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
205
To gain access to the `chi_math::SpatialDiscretization_PWLC` class we need to
206
include the header
207
\code
208
#include "math/SpatialDiscretization/FiniteElement/PiecewiseLinear/pwlc.h"
209
\endcode
210
211
Next we add the following code
212
\code
213
//============================================= Make SDM
214
typedef std::shared_ptr<chi_math::SpatialDiscretization> SDMPtr;
215
SDMPtr sdm_ptr = chi_math::SpatialDiscretization_PWLC::New(grid_ptr);
216
const auto& sdm = *sdm_ptr;
217
218
const auto& OneDofPerNode = sdm.UNITARY_UNKNOWN_MANAGER;
219
220
const size_t num_local_dofs = sdm.GetNumLocalDOFs(OneDofPerNode);
221
const size_t num_globl_dofs = sdm.GetNumGlobalDOFs(OneDofPerNode);
222
223
chi::log.Log() << "Num local DOFs: " << num_local_dofs;
224
chi::log.Log() << "Num globl DOFs: " << num_globl_dofs;
225
\endcode
226
227
The initialization of the matrices and vectors remain exactly the same as in
228
the Finite Volume tutorial:
229
\code
230
//============================================= Initializes Mats and Vecs
231
const auto n = static_cast<int64_t>(num_local_dofs);
232
const auto N = static_cast<int64_t>(num_globl_dofs);
233
Mat A;
234
Vec x,b;
235
236
A = chi_math::PETScUtils::CreateSquareMatrix(n,N);
237
x = chi_math::PETScUtils::CreateVector(n,N);
238
b = chi_math::PETScUtils::CreateVector(n,N);
239
240
std::vector<int64_t> nodal_nnz_in_diag;
241
std::vector<int64_t> nodal_nnz_off_diag;
242
sdm.BuildSparsityPattern(nodal_nnz_in_diag,nodal_nnz_off_diag, OneDofPerNode);
243
244
chi_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
253
With the finite element method the conventional way to assemble the matrix is
254
to do so by element (even though continuous FEM is only concerned about nodes).
255
Therefore we start with a loop over elements then assemble our discretized
256
equation
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}
267
For which the first portion of the code is
268
\code
269
for (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
298
The first thing we do here, as with any SD, is to grab the relevant cell mapping.
299
After that we immediately create a
300
`chi_math::finite_element::InternalQuadraturePointData` with the line of code
301
\code
302
const auto qp_data = cell_mapping.MakeVolumeQuadraturePointData();
303
\endcode
304
This data structure contains the shape function values, together with other data,
305
pre-stored per quadrature point.
306
307
Next we create a local cell-matrix and local right-hand-side (RHS) which serve as
308
lightweight proxies before we assemble relevant values into the global matrix and
309
global RHS.
310
\code
311
const size_t num_nodes = cell_mapping.NumNodes();
312
MatDbl Acell(num_nodes, VecDbl(num_nodes, 0.0));
313
VecDbl cell_rhs(num_nodes, 0.0);
314
\endcode
315
316
We then loop over all the \f$ (i,j) \f$ node combinations of our equation above
317
but with scope only on the local element. Notice that we never actually pass
318
around the actual quadrature points but rather use the quadrature point indices
319
via `qp_data.QuadraturePointIndices()`.
320
\code
321
for (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
338
Notice 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}
347
for 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
352
Dirichlet requires some special considerations. The equation for node \f$ i \f$
353
essentially becomes \f$ \phi_i = 0 \f$ for our case of zero Dirichlet boundary
354
conditions. This essentially places just a 1 on the diagonal with no entries
355
off-diagonal for the entire row \f$ i \f$. Therefore, we need to modify the
356
local cell-matrix to reflect this situation.
357
358
We are unfortunately not done yet. Any row in the matrix that has an off-diaganol
359
entry that corresponds to column \f$ i \f$ will need to be moved to the RHS. This
360
is because it could destroy the symmetry of an otherwise symmetric matrix.
361
362
We handle both these modifications in the following way. We build a flag-list
363
for each cell node
364
\code
365
std::vector<bool> node_boundary_flag(num_nodes, false);
366
const size_t num_faces = cell.faces.size();
367
for (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
380
Notice the use of `face.has_neighbor` here. If the face does not have a neighbor
381
it means the face is on a boundary and therefore a Dirichlet node. Once this
382
condition applies we loop over the face nodes, map the face-node to a cell-node
383
then flag that node as being on a boundary.
384
385
Finally, we now assemble the local cell-matrix and local RHS into the global
386
system.
387
\code
388
//======================= Assembly into system
389
for (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
407
We essentially loop over each node, after which we have two conditions: if the
408
node is a boundary node then we just add an entry on the diagonal, if the node
409
is not a boundary node we then loop over the row entries. We perform a check
410
again to see if the j-entries are boundary nodes. If they are we put them on the
411
RHS, if they are not then they are allowed to be matrix entries.
412
413
After this process we assemble the matrix and the RHS globally like we did
414
before:
415
\code
416
chi::log.Log() << "Global assembly";
417
418
MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY);
419
MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY);
420
VecAssemblyBegin(b);
421
VecAssemblyEnd(b);
422
423
chi::log.Log() << "Done global assembly";
424
\endcode
425
426
427
428
429
\section CodeTut3Sec6 6 Solving and visualizing
430
Solving the system and visualizing it is the same as was done for the Finite
431
Volume tutorial. The matrix is still Symmetric Positive Definite (SPD) so we
432
use the same solver/precondtioner setup.
433
\code
434
//============================================= Create Krylov Solver
435
chi::log.Log() << "Solving: ";
436
auto 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
446
KSPSolve(petsc_solver.ksp,b,x);
447
448
chi::log.Log() << "Done solving";
449
450
//============================================= Extract PETSc vector
451
std::vector<double> field;
452
sdm.LocalizePETScVector(x,field,OneDofPerNode);
453
454
//============================================= Clean up
455
KSPDestroy(&petsc_solver.ksp);
456
457
VecDestroy(&x);
458
VecDestroy(&b);
459
MatDestroy(&A);
460
461
chi::log.Log() << "Done cleanup";
462
463
//============================================= Create Field Function
464
auto 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
470
ff->UpdateFieldVector(field);
471
472
ff->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
493
int 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
*/
doc
PAGES
ProgrammersManual
CodingTutorials
CodeTut3.h
Generated by
1.9.3