]> https://gitweb.dealii.org/ - dealii.git/commitdiff
Changelog, prm fixes, initial docs, add new parameters
authortcclevenger <tcleven@clemson.edu>
Fri, 10 May 2019 19:19:57 +0000 (13:19 -0600)
committerMartin Kronbichler <kronbichler@lnm.mw.tum.de>
Wed, 15 May 2019 12:09:38 +0000 (14:09 +0200)
doc/news/changes/major/20190510Clevenger [new file with mode: 0644]
examples/step-63/block_jacobi.prm
examples/step-63/block_sor.prm
examples/step-63/doc/kind
examples/step-63/doc/results.dox
examples/step-63/jacobi.prm
examples/step-63/sor.prm
examples/step-63/step-63.cc

diff --git a/doc/news/changes/major/20190510Clevenger b/doc/news/changes/major/20190510Clevenger
new file mode 100644 (file)
index 0000000..a4f820f
--- /dev/null
@@ -0,0 +1,3 @@
+New: The Step-63 tutorial has been added which discusses block smoothers vs. point smoothers inside a geometric multigrid v-cycle. This tutorial also implements a GMG solver for a non-symmetric PDE, and demonstrates the effect of DoF/cell renumbering for multiplicative smoothers with advection dominated problems. 
+<br>
+(Conrad Clevenger, 2019/05/10)
index 9e6f98326eaa1f7ccaa80fcaaaacfecdd96ef99e..941a0df9ed020bad19249e8105568b39672b6ff6 100644 (file)
@@ -1,9 +1,15 @@
+#Epsilon
+set Epsilon = 0.005
+
 # Finite Element degree
 set Fe degree = 1 
 
 # Smoother Type: SOR|Jacobi|block SOR|block Jacobi
 set Smoother type = block Jacobi
 
+# Number of smoothing steps
+set Smoothing steps = 3
+
 # DoF renumbering: no|downstream|upstream|random
 set DoF renumbering = downstream
 
@@ -12,3 +18,4 @@ set With streamline diffusion = true
 
 # Output: true|false
 set Output = false
+
index 375e7e63cb3207e34d2f7a11c3630fbf84907453..3b20e403a296373b9b98f0137ba223c3a7a6d1ed 100644 (file)
@@ -1,9 +1,15 @@
+#Epsilon
+set Epsilon = 0.005
+
 # Finite Element degree
 set Fe degree = 1 
 
 # Smoother Type: SOR|Jacobi|block SOR|block Jacobi
 set Smoother type = block SOR
 
+# Number of smoothing steps
+set Smoothing steps = 1 
+
 # DoF renumbering: no|downstream|upstream|random
 set DoF renumbering = downstream 
 
@@ -11,4 +17,4 @@ set DoF renumbering = downstream
 set With streamline diffusion = true
 
 # Output: true|false
-set Output = false
\ No newline at end of file
+set Output = false 
index 6816e9090f56f5d42e20186513be4546872a8661..387b9f817cc16ab76d473408f7d1e22e1e8f6cd8 100644 (file)
@@ -1 +1,2 @@
 unfinished
+
index b5eaba9377b60dbcba45ee1cbac9ff5dcef3138d..d2fe3c9640a9b9e02e92621d69bca02a50ea5c9f 100644 (file)
@@ -1,2 +1,472 @@
 <h1>Results</h1>
 
+<h3> GMRES Iteration Counts </h3>
+
+The major advantage for GMG is that it is an $\mathcal{O}(n)$ method, that is, the complexity of the problem increases linearly with the problem size. To show then that the linear solver presented in this tutorial is also $\mathcal{O}(n)$, all one needs to do is show that the iteration counts for the GMRES solve stay roughly constant as we refine the mesh.
+
+Each of the following tables gives the GMRES iteration counts to reduce the initial residual by 1e-8.
+
+<h5> DoF/Cell Renumbering: </h5>
+
+Staring with the additive smoothers, we see that renumbering the DoFs/cells has no effect on convergence speed. This is because these smoothers compute operations on each DoF (point-smoother) or cell (block-smoother) independently and add up the results. Since we can define these smoothers as an application of a sum of matrices, and matrix addition is communicative, the order at which we sum the different components will not affect the end result.
+
+<table align="center" border="1">
+<tr>
+  <th></th>
+  <th></th>
+  <th colspan="1">$Q_1$</th>
+  <th colspan="7">Smoother (smoothing steps)</th>
+</tr>
+<tr>
+  <th></th>
+  <th></th>
+  <th></th>
+  <th colspan="3">Jacobi (6)</th>
+  <th></th>
+  <th colspan="3">Block Jacobi (3)</th>
+</tr>
+
+<tr>
+  <th></th>
+  <th></th>
+  <th></th>
+  <th colspan="3">Renumbering Strategy</th>
+  <th></th>
+  <th colspan="3">Renumbering Strategy</th>
+</tr>
+
+<tr>
+  <th>Cells</th>
+  <th></th>
+  <th>DoFs</th>
+  <th>Downstream</th>
+  <th>Random</th>
+  <th>Upstream</th>
+  <th></th>
+  <th>Downstream</th>
+  <th>Random</th>
+  <th>Upstream</th>
+</tr>
+
+<tr>
+  <th>32</th>
+  <th></th>
+  <th>48</th>
+  <td>3</th>
+  <td>3</th>
+  <td>3</th> 
+  <th></th>
+  <td>3</th>
+  <td>3</th>
+  <td>3</th> 
+</tr>
+
+<tr>
+  <th>128</th>
+  <th></th>
+  <th>160</th>
+  <td>6</th>
+  <td>6</th>
+  <td>6</th>
+  <th></th> 
+  <td>6</th>
+  <td>6</th>
+  <td>6</th> 
+</tr>
+
+<tr>
+  <th>512</th>
+  <th></th>
+  <th>576</th>
+  <td>11</th>
+  <td>11</th>
+  <td>11</th>
+  <th></th>
+  <td>9</th>
+  <td>9</th>
+  <td>9</th>  
+</tr>
+
+<tr>
+  <th>2048</th>
+  <th></th>
+  <th>2176</th>
+  <td>15</th>
+  <td>15</th>
+  <td>15</th> 
+  <th></th>
+  <td>13</th>
+  <td>13</th>
+  <td>13</th> 
+</tr>
+
+<tr>
+  <th>8192</th>
+  <th></th>
+  <th>8448</th>
+  <td>18</th>
+  <td>18</th>
+  <td>18</th> 
+  <th></th>
+  <td>15</th>
+  <td>15</th>
+  <td>15</th> 
+</tr>
+
+<tr>
+  <th>32768</th>
+  <th></th>
+  <th>33280</th>
+  <td>20</th>
+  <td>20</th>
+  <td>20</th> 
+  <th></th>
+  <td>16</th>
+  <td>16</th>
+  <td>16</th> 
+</tr>
+
+<tr>
+  <th>131072</th>
+  <th></th>
+  <th>132096</th>
+  <td>20</th>
+  <td>20</th>
+  <td>20</th> 
+  <th></th>
+  <td>16</th>
+  <td>16</th>
+  <td>16</th> 
+</tr>
+
+</table>
+
+On the other hand, for multiplicative smoothers, we can speed up convergence by renumbering the DoFs/cells in the advection direction, and similarly, we can slow down convergence if we do the renumbering in the opposite direction. This is because the multiplicative smoothers transfer information from DoF to DoF, and the solutions at any one DoF of an advection-dominate problem is highly dependent on the solution at DoFs upstream of the advection direction. 
+
+As we increase $\varepsilon$, however, the problem becomes more diffusion-dominated and the effectiveness of DoF/cell renumbering drops. On the other hand, in the extreme case of $\varepsilon=0$ (advection-only), we have a 1st-order PDE and multiplicative methods become effective solvers (Note: special care must be taken for the boundary conditions in this case).
+
+<table align="center" border="1">
+<tr>
+  <th></th>
+  <th></th>
+  <th colspan="1">$Q_1$</th>
+  <th colspan="7">Smoother (smoothing steps)</th>
+</tr>
+<tr>
+  <th></th>
+  <th></th>
+  <th></th>
+  <th colspan="3">SOR (3)</th>
+  <th></th>
+  <th colspan="3">Block SOR (1)</th>
+</tr>
+
+<tr>
+  <th></th>
+  <th></th>
+  <th></th>
+  <th colspan="3">Renumbering Strategy</th>
+  <th></th>
+  <th colspan="3">Renumbering Strategy</th>
+</tr>
+
+<tr>
+  <th>Cells</th>
+  <th></th>
+  <th>DoFs</th>
+  <th>Downstream</th>
+  <th>Random</th>
+  <th>Upstream</th>
+  <th></th>
+  <th>Downstream</th>
+  <th>Random</th>
+  <th>Upstream</th>
+</tr>
+
+<tr>
+  <th>32</th>
+  <th></th>
+  <th>48</th>
+  <td>2</th>
+  <td>2</th>
+  <td>3</th> 
+  <th></th>
+  <td>2</th>
+  <td>2</th>
+  <td>3</th> 
+</tr>
+
+<tr>
+  <th>128</th>
+  <th></th>
+  <th>160</th>
+  <td>5</th>
+  <td>5</th>
+  <td>7</th>
+  <th></th> 
+  <td>5</th>
+  <td>5</th>
+  <td>7</th> 
+</tr>
+
+<tr>
+  <th>512</th>
+  <th></th>
+  <th>576</th>
+  <td>7</th>
+  <td>9</th>
+  <td>11</th>
+  <th></th>
+  <td>7</th>
+  <td>7</th>
+  <td>12</th>  
+</tr>
+
+<tr>
+  <th>2048</th>
+  <th></th>
+  <th>2176</th>
+  <td>10</th>
+  <td>12</th>
+  <td>15</th>
+  <th></th> 
+  <td>8</th>
+  <td>10</th>
+  <td>17</th> 
+</tr>
+
+<tr>
+  <th>8192</th>
+  <th></th>
+  <th>8448</th>
+  <td>11</th>
+  <td>15</th>
+  <td>19</th> 
+  <th></th>
+  <td>10</th>
+  <td>11</th>
+  <td>20</th> 
+</tr>
+
+<tr>
+  <th>32768</th>
+  <th></th>
+  <th>33280</th>
+  <td>12</th>
+  <td>16</th>
+  <td>20</th> 
+  <th></th>
+  <td>10</th>
+  <td>12</th>
+  <td>21</th> 
+</tr>
+
+<tr>
+  <th>131072</th>
+  <th></th>
+  <th>132096</th>
+  <td>12</th>
+  <td>16</th>
+  <td>19</th> 
+  <th></th>
+  <td>11</th>
+  <td>12</th>
+  <td>21</th> 
+</tr>
+
+</table>
+
+
+<h5> Point vs. Block Smoothers </h5>
+
+We will limit the results to runs using the downstream renumbering. Here is a cross comparison of all four smoothers for both $Q_1$ and $Q_3$ elements:
+
+<table align="center" border="1">
+<tr>
+  <th></th>
+  <td></th>
+  <th colspan="1">$Q_1$</th>
+  <th colspan="4">Smoother (smoothing steps)</th>
+  <th></th>
+  <th colspan="1">$Q_3$</th>
+  <th colspan="4">Smoother (smoothing steps)</th>
+</tr>
+
+<tr>
+  <th colspan="1">Cells</th>
+  <td></th>
+  <th colspan="1">DoFs</th>
+  <th colspan="1">Jacobi (6)</th>
+  <th colspan="1">Block Jacobi (3)</th>
+  <th colspan="1">SOR (3)</th>
+  <th colspan="1">Block SOR (1)</th>
+  <th></th>
+  <th colspan="1">DoFs</th>
+  <th colspan="1">Jacobi (6)</th>
+  <th colspan="1">Block Jacobi (3)</th>
+  <th colspan="1">SOR (3)</th>
+  <th colspan="1">Block SOR (1)</th>
+</tr>
+
+<tr>
+  <th>32</th>
+  <td></th>
+  <th>48</th>
+  <td>3</th>
+  <td>3</th>
+  <td>2</th>
+  <td>2</th>
+  <td></th>
+  <th>336</th>
+  <td>15</th>
+  <td>14</th>
+  <td>15</th>
+  <td>6</th>
+</tr>
+
+<tr>
+  <th>128</th>
+  <td></th>
+  <th>160</th>
+  <td>6</th>
+  <td>6</th>
+  <td>5</th>
+  <td>5</th>
+  <td></th>
+  <th>1248</th>
+  <td>23</th>
+  <td>18</th>
+  <td>21</th>
+  <td>9</th>
+</tr>
+
+<tr>
+  <th>512</th>
+  <td></th>
+  <th>576</th>
+  <td>11</th>
+  <td>9</th>
+  <td>7</th>
+  <td>7</th>
+  <td></th>
+  <th>4800</th>
+  <td>29</th>
+  <td>21</th>
+  <td>28</th>
+  <td>9</th>
+</tr>
+
+<tr>
+  <th>2048</th>
+  <td></th>
+  <th>2176</th>
+  <td>15</th>
+  <td>13</th>
+  <td>10</th>
+  <td>8</th>
+  <td></th>
+  <th>18816</th>
+  <td>33</th>
+  <td>22</th>
+  <td>32</th>
+  <td>9</th>
+</tr>
+
+<tr>
+  <th>8192</th>
+  <td></th>
+  <th>8448</th>
+  <td>18</th>
+  <td>15</th>
+  <td>11</th>
+  <td>10</th>
+  <td></th>
+  <th>74496</th>
+  <td>35</th>
+  <td>22</th>
+  <td>34</th>
+  <td>10</th>
+</tr>
+
+<tr>
+  <th>32768</th>
+  <td></th>
+  <th>33280</th>
+  <td>20</th>
+  <td>16</th>
+  <td>12</th>
+  <td>10</th>
+  <td></th>
+</tr>
+
+<tr>
+  <th>131072</th>
+  <td></th>
+  <th>132096</th>
+  <td>20</th>
+  <td>16</th>
+  <td>12</th>
+  <td>11</th>
+  <td></th>
+</tr>
+
+</table>
+
+We see that for $Q_1$, both multiplicative smoothers require a smaller combination of smoothing steps and iteration counts than either additive smoother. However, when we in increase the degree to a $Q_3$ element, there is a clear advantage for the block smoothers in terms of the increase in smoothing steps and iterations required to solve. Specifically, the block SOR smoother gives constant iterations over degree, and the block Jacobi only sees about a 38% increase in iterations compared to 75% and 183% for Jacobi and SOR respectively.
+
+<h3> Cost </h3>
+
+Iteration counts do not tell the full story in the optimality of a one smoother over another. Obviously we must examine the cost of an iteration. Block smoothers here are at a disadvantage as they are having to construct and invert a cell matrix for each cell. Here is a comparison of solve times for a $Q_3$ element with 74,496 DoFs:
+<table align="center" border="1">
+<tr>
+  <th colspan="1">$Q_3$</th>
+  <th colspan="4">Smoother (smoothing steps)</th>
+</tr>
+
+<tr>
+  <th colspan="1">DoFs</th>
+  <th colspan="1">Jacobi (6)</th>
+  <th colspan="1">Block Jacobi (3)</th>
+  <th colspan="1">SOR (3)</th>
+  <th colspan="1">Block SOR (1)</th>
+</tr>
+
+<tr>
+  <th>74496</th>
+  <td>0.689 s</th>
+  <td>5.826 s</th>
+  <td>1.189 s</th>
+  <td>1.021 s</th>
+</tr>
+
+</table>
+
+The smoother that requires the most iterations (Jacobi) actually take the shortest time (roughly 2/3 the time of the next fastest method). This is because all that is requires to apply a Jacobi smoothing step is multiplication by a diagonal matrix which is very cheap. On the other hand, while SOR requires over 3x more iterations (each with 3x more smoothing steps) than block SOR, the times are roughly equivalent, implying that a smoothing step of block SOR is roughly 9x slower than a smoothing step of SOR. Lastly, block Jacobi is almost 6x more expensive than block SOR, which intuitively makes sense from the fact that 1 step of each method has the same cost (inverting the cell matrices and either adding or multiply them together), and block Jacobi has 3 times the smoothing step per iteration with 2 times the iterations.
+
+
+<h3> Additional Points </h3>
+
+There are a few more important points to mention:
+
+1. For a mesh distributed in parallel, multiplicative methods cannot be executed over the entire domain. One can use a hybrid method where a multiplicative smoother is applied on each subdomain, but as you increase the number of subdomains, the method converges to that of an additive method. This is a major disadvantage to these methods.
+
+2. Current research into block smoothers suggest that soon we will be able to compute the inverse of the cell matrices much cheaper than what is currently being done inside deal.II. It seems that one should be able to take advantage of matrix free implementations and also the fact that, in the interior of the domain, cell matrices tend to look very similar. 
+
+Combining 1. and 2. gives a good reason for expecting that a method like block Jacobi could become very powerful in the future, even though currently for these examples it is quite slow.
+
+
+
+<h3> Possible Extensions </h3>
+
+<h5> Constant iterations for $Q_5$ </h5>
+
+Change the number of smoothing steps and the smoother relaxation parameter (set in <code>Smoother::AdditionalData()</code> inside <code>create_smoother()</code>, only necessary for point smoothers) so that we maintain a constant number of iterations for a $Q_5$ element. 
+
+<h5> Effectiveness of renumbering for changing $\varepsilon$ </h5>
+
+Increase/decrease the parameter ``Epsilon" in the .prm files of the multiplicative methods and observe the relationship of downstream vs. upstream reordering.
+
+
+
+
index 4c7365e06a7dc196e6f5292d269c9b704f9a761e..75314cd087ac8756bf11c6cc73344eb5bc422ade 100644 (file)
@@ -1,14 +1,20 @@
+#Epsilon
+set Epsilon = 0.005
+
 # Finite Element degree
 set Fe degree = 1
 
 # Smoother Type: SOR|Jacobi|block SOR|block Jacobi
 set Smoother type = Jacobi
 
+# Number of smoothing steps
+set Smoothing steps = 6
+
 # DoF renumbering: no|downstream|upstream|random
-set DoF renumbering = downstream
+set DoF renumbering = downstream 
 
 # With streamline diffusion: true|false
 set With streamline diffusion = true
 
 # Output: true|false
-set Output = false
\ No newline at end of file
+set Output = false
index dbdc74c1981008fef230aee40a045ade0d13b080..51d83bf3bb75b3a6ce9e76bb867f14c8299e245e 100644 (file)
@@ -1,14 +1,20 @@
+#Epsilon
+set Epsilon = 0.005
+
 # Finite Element degree
-set Fe degree = 1
+set Fe degree = 1 
 
 # Smoother Type: SOR|Jacobi|block SOR|block Jacobi
 set Smoother type = SOR
 
+# Number of smoothing steps
+set Smoothing steps = 3  
+
 # DoF renumbering: no|downstream|upstream|random
-set DoF renumbering = downstream  
+set DoF renumbering = downstream
 
 # With streamline diffusion: true|false
 set With streamline diffusion = true
 
 # Output: true|false
-set Output = false 
\ No newline at end of file
+set Output = false
index ba6230ca31327b9269f0371ee56ae30b45fd8da4..65d2fae72e583e4834dc5493fa5acddc1f9666fb 100644 (file)
  *          Timo Heister, University of Utah
  */
 
-// @note: This is work in progress and will be an example for block smoothers
-// in geometric multigrid.
+// @sect3{Include files}
+
+// Typical files needed for standard deal.II:
 #include <deal.II/base/tensor_function.h>
-#include <deal.II/base/work_stream.h>
-#include <deal.II/base/std_cxx14/memory.h>
 #include <deal.II/base/quadrature_lib.h>
 #include <deal.II/base/function.h>
-#include <deal.II/base/logstream.h>
 #include <deal.II/base/timer.h>
 #include <deal.II/base/parameter_handler.h>
-#include <deal.II/base/path_search.h>
+
+#include <deal.II/lac/affine_constraints.h>
 #include <deal.II/lac/vector.h>
 #include <deal.II/lac/full_matrix.h>
 #include <deal.II/lac/sparse_matrix.h>
 #include <deal.II/lac/dynamic_sparsity_pattern.h>
 #include <deal.II/lac/solver_gmres.h>
 #include <deal.II/lac/precondition.h>
-#include <deal.II/lac/affine_constraints.h>
 #include <deal.II/lac/relaxation_block.h>
 
 #include <deal.II/grid/tria.h>
 #include <deal.II/grid/tria_accessor.h>
 #include <deal.II/grid/tria_iterator.h>
 #include <deal.II/grid/manifold_lib.h>
+#include <deal.II/grid/grid_out.h>
+
 #include <deal.II/dofs/dof_handler.h>
 #include <deal.II/dofs/dof_accessor.h>
 #include <deal.II/dofs/dof_renumbering.h>
 #include <deal.II/dofs/dof_tools.h>
+
+#include <deal.II/fe/fe_q.h>
+#include <deal.II/fe/mapping_q.h>
 #include <deal.II/fe/fe_values.h>
+
 #include <deal.II/numerics/vector_tools.h>
 #include <deal.II/numerics/matrix_tools.h>
 #include <deal.II/numerics/data_out.h>
-#include <deal.II/numerics/error_estimator.h>
-#include <deal.II/meshworker/mesh_loop.h>
-#include <deal.II/fe/fe_q.h>
-#include <deal.II/fe/mapping_q.h>
-#include <deal.II/grid/grid_out.h>
 
+// Include all relevant multilevel files:
 #include <deal.II/multigrid/mg_constrained_dofs.h>
 #include <deal.II/multigrid/multigrid.h>
 #include <deal.II/multigrid/mg_transfer.h>
-#include <deal.II/multigrid/mg_transfer_matrix_free.h>
 #include <deal.II/multigrid/mg_tools.h>
 #include <deal.II/multigrid/mg_coarse.h>
 #include <deal.II/multigrid/mg_smoother.h>
 #include <deal.II/multigrid/mg_matrix.h>
 
+// C++:
 #include <fstream>
 #include <iostream>
 #include <random>
 
+// We will be using Meshworker::mesh_loop functionality for assembling matrices:
+#include <deal.II/meshworker/mesh_loop.h>
+
 
 namespace Step63
 {
   using namespace dealii;
 
+  // @sect3{MeshWorker Data}
+
+  // The following are structure needed by assemble_cell()
+  // function used by Meshworker::mesh_loop(). ScratchData
+  // contains a FeValues object with is needed for assembling
+  // a cells local contribution, while CopyData contains the
+  // output from a cells local contribution and necessary information
+  // to copy that to the global system.
 
   template <int dim>
   struct ScratchData
@@ -112,6 +123,13 @@ namespace Step63
   };
 
 
+
+  // @sect3{Problem parameters}
+
+  // We will use ParameterHandler to pass in parameters at runtime. The
+  // structure Settings parses and stores these parameters to be queried
+  // throughout the program.
+
   struct Settings
   {
     enum DoFRenumberingStrategy
@@ -124,8 +142,10 @@ namespace Step63
 
     void get_parameters(const std::string &prm_filename);
 
+    double                 epsilon;
     unsigned int           fe_degree;
     std::string            smoother_type;
+    unsigned int           smoothing_steps;
     DoFRenumberingStrategy dof_renumbering;
     bool                   with_streamline_diffusion;
     bool                   output;
@@ -135,14 +155,20 @@ namespace Step63
   {
     ParameterHandler prm;
 
+    prm.declare_entry("Epsilon", "0.005", Patterns::Double(0), "Epsilon");
+
     prm.declare_entry("Fe degree",
                       "1",
                       Patterns::Integer(0),
                       "Finite Element degree");
     prm.declare_entry("Smoother type",
                       "block SOR",
-                      Patterns::Selection("sor|Jacobi|block SOR|block Jacobi"),
+                      Patterns::Selection("SOR|Jacobi|block SOR|block Jacobi"),
                       "Smoother Type: SOR|Jacobi|block SOR|block Jacobi");
+    prm.declare_entry("Smoothing steps",
+                      "2",
+                      Patterns::Integer(1),
+                      "Number of smoothing steps");
     prm.declare_entry("DoF renumbering",
                       "downstream",
                       Patterns::Selection("none|downstream|upstream|random"),
@@ -165,8 +191,10 @@ namespace Step63
 
     prm.parse_input(prm_filename);
 
-    fe_degree     = prm.get_integer("Fe degree");
-    smoother_type = prm.get("Smoother type");
+    epsilon         = prm.get_double("Epsilon");
+    fe_degree       = prm.get_integer("Fe degree");
+    smoother_type   = prm.get("Smoother type");
+    smoothing_steps = prm.get_integer("Smoothing steps");
 
     std::string renumbering = prm.get("DoF renumbering");
     if (renumbering == "none")
@@ -183,8 +211,18 @@ namespace Step63
   }
 
 
-  // Functions for creating permutation of cells for output and Block
-  // smoothers
+  // @sect1{Cell permutations}
+  //
+  // The ordering in which cells and degrees of freedom are traversed
+  // will play a roll in the speed of convergence for multiplicative
+  // methods. Here we define functions which return a specific ordering
+  // of cells to be used by the block smoothers.
+
+  // For each type of cell ordering, we define a function for the active
+  // mesh and one for a level mesh. While the only reordering necessary
+  // for solving the system will be on the level meshes, we include the
+  // active reordering for visualization purposes in output_results().
+
   template <int dim>
   std::vector<unsigned int>
   create_downstream_cell_ordering(const DoFHandler<dim> &dof_handler,
@@ -236,8 +274,6 @@ namespace Step63
     return ordered_indices;
   }
 
-
-
   template <int dim>
   std::vector<unsigned int>
   create_random_cell_ordering(const DoFHandler<dim> &dof_handler,
@@ -251,15 +287,15 @@ namespace Step63
     for (const auto &cell : dof_handler.cell_iterators_on_level(level))
       ordered_cells.push_back(cell->index());
 
-    // shuffle the elements
+    // Shuffle the elements:
     std::mt19937 random_number_generator;
     for (unsigned int i = 1; i < n_cells; ++i)
       {
-        // get a random number between 0 and i (inclusive)
+        // Get a random number between 0 and i (inclusive):
         const unsigned int j =
           std::uniform_int_distribution<>(0, i)(random_number_generator);
 
-        // if possible, swap the elements
+        // If possible, swap the elements:
         if (i != j)
           std::swap(ordered_cells[i], ordered_cells[j]);
       }
@@ -280,15 +316,15 @@ namespace Step63
     for (const auto &cell : dof_handler.active_cell_iterators())
       ordered_cells.push_back(cell->index());
 
-    // shuffle the elements
+    // shuffle the elements:
     std::mt19937 random_number_generator;
     for (unsigned int i = 1; i < n_cells; ++i)
       {
-        // get a random number between 0 and i (inclusive)
+        // get a random number between 0 and i (inclusive):
         const unsigned int j =
           std::uniform_int_distribution<>(0, i)(random_number_generator);
 
-        // if possible, swap the elements
+        // if possible, swap the elements:
         if (i != j)
           std::swap(ordered_cells[i], ordered_cells[j]);
       }
@@ -297,7 +333,13 @@ namespace Step63
   }
 
 
+  // @sect3{Right-hand Side and Boundary Values}
+
+  // The problem solved in this tutorial is an adaptation of Ex. ___
+  // found in ______ (how to cite?), namely, we add a hole in the middle
+  // of our domain.
 
+  // We have a zero right-hand side.
   template <int dim>
   class RightHandSide : public Function<dim>
   {
@@ -339,7 +381,9 @@ namespace Step63
   }
 
 
-
+  // We have Dirichlet boundary conditions. On a connected portion of the
+  // outer, square boundary we set the value to 1, and we set the value to 0
+  // everywhere else (including the inner, circular boundary).
   template <int dim>
   class BoundaryValues : public Function<dim>
   {
@@ -364,9 +408,9 @@ namespace Step63
     Assert(component == 0, ExcIndexRange(component, 0, 1));
     (void)component;
 
-    if (std::fabs(p[0] - 1) < 1e-8                     // x == 1
-        || (std::fabs(p[1] + 1) < 1e-8 && p[0] >= 0.5) // y == -1, x > 0.5
-    )
+    // Set boundary to 1 if $x=1$, or if $x>0.5$ and $y=-1$.
+    if (std::fabs(p[0] - 1) < 1e-8 ||
+        (std::fabs(p[1] + 1) < 1e-8 && p[0] >= 0.5))
       {
         return 1.0;
       }
@@ -390,14 +434,19 @@ namespace Step63
       values[i] = BoundaryValues<dim>::value(points[i], component);
   }
 
+
+
+  // @sect3{Streamline Diffusion}
+
+  // Streamline diffusion stabilization term. Value defined in
+  // 'On discontinuity–capturing methods for convection–diffusion
+  // equations' (cite?)
   template <int dim>
   double compute_stabilization_delta(const double         hk,
                                      const double         eps,
                                      const Tensor<1, dim> dir,
                                      const double         pk)
   {
-    // Value defined in 'On discontinuity–capturing methods for
-    // convection–diffusion equations'
     const double Peclet = dir.norm() * hk / (2.0 * eps * pk);
     const double coth =
       (1.0 + std::exp(-2.0 * Peclet)) / (1.0 - std::exp(-2.0 * Peclet));
@@ -406,6 +455,17 @@ namespace Step63
   }
 
 
+  // @sect3{<code>AdvectionProlem</code> class}
+
+  // This main class of the program, and should look very similar to step-16.
+  // The major difference is that, since we are defining our multigrid smoother
+  // at runtime, we choose to define a function create_smoother() and a class
+  // object mg_smoother which is a std::unique_ptr to a smoother that is derived
+  // from MGSmoother. Note that for smoother derived from RelaxationBlock, we
+  // must include a smoother_data object for each level. This will contain
+  // information about the cell ordering and the method of inverting cell
+  // matrices.
+
   template <int dim>
   class AdvectionProblem
   {
@@ -453,16 +513,15 @@ namespace Step63
     mg::Matrix<Vector<double>> mg_interface_matrix_in;
     mg::Matrix<Vector<double>> mg_interface_matrix_out;
 
+    std::unique_ptr<MGSmoother<Vector<double>>> mg_smoother;
+
     using SmootherType =
       RelaxationBlock<SparseMatrix<double>, double, Vector<double>>;
     using SmootherAdditionalDataType = SmootherType::AdditionalData;
-    std::unique_ptr<MGSmoother<Vector<double>>> mg_smoother;
-
     MGLevelObject<SmootherAdditionalDataType> smoother_data;
 
     MGConstrainedDoFs mg_constrained_dofs;
 
-    const double   epsilon;
     Tensor<1, dim> advection_direction;
 
     const Settings settings;
@@ -476,29 +535,33 @@ namespace Step63
     , dof_handler(triangulation)
     , fe(settings.fe_degree)
     , mapping(settings.fe_degree)
-    , epsilon(0.005)
     , settings(settings)
   {
     advection_direction[0] = -std::sin(numbers::PI / 6.0);
     if (dim > 1)
       advection_direction[1] = std::cos(numbers::PI / 6.0);
     if (dim > 2)
-      advection_direction[2] = std::sin(numbers::PI / 6.0);
+      AssertThrow(false, ExcNotImplemented());
   }
 
 
+  // @sect4{<code>AdvectionProblem::setup_system</code>}
+
+  // Here we setup the DoFHandler, ConstraintMatrix, and sparsity patterns for
+  // both active and multigrid level meshes.
 
   template <int dim>
   void AdvectionProblem<dim>::setup_system()
   {
     const unsigned int n_levels = triangulation.n_levels();
 
+    // Setup active DoFs:
     dof_handler.distribute_dofs(fe);
 
-    // We could renumber the active dofs with DoFRenumbering::downstream()
+    // We could renumber the active DoFs with the DoFRenumbering class
     // here, but the smoothers only act on multigrid levels and as such, this
-    // wouldn't matter. Instead, we will renumber the DoFs on each multigrid
-    // level below.
+    // wouldn't matter for the computations. Instead, we will renumber the
+    // DoFs on each multigrid level below.
 
     solution.reinit(dof_handler.n_dofs());
     system_rhs.reinit(dof_handler.n_dofs());
@@ -523,7 +586,7 @@ namespace Step63
     system_matrix.reinit(sparsity_pattern);
 
 
-    // Setup GMG DoFs
+    // Setup GMG DoFs:
     dof_handler.distribute_mg_dofs();
 
     // Renumber DoFs on each level in downstream or upstream direction if
@@ -591,8 +654,8 @@ namespace Step63
                                                    level);
           mg_interface_sparsity_patterns[level].copy_from(dsp);
 
-          // We need both interface in and out matrices since our problem is not
-          // symmetric
+          // Unlike the other GMG tutorials, we need both interface in and out
+          // matrices since our problem is non-symmetric.
           mg_interface_in[level].reinit(mg_interface_sparsity_patterns[level]);
           mg_interface_out[level].reinit(mg_interface_sparsity_patterns[level]);
         }
@@ -600,6 +663,13 @@ namespace Step63
   }
 
 
+  // @sect4{<code>AdvectionProblem::assemble_cell</code>}
+
+  // Here we define the assembly of the linear system on each cell to be used by
+  // the mesh_loop() function below. This one function assembles the cell matrix
+  // for both and active and a level cell, and only assembles a right-hand side
+  // if called for an active cell.
+
   template <int dim>
   template <class IteratorType>
   void AdvectionProblem<dim>::assemble_cell(const IteratorType &cell,
@@ -630,9 +700,14 @@ namespace Step63
     right_hand_side.value_list(scratch_data.fe_values.get_quadrature_points(),
                                rhs_values);
 
+    // If we are using streamline diffusion we must add its contribution
+    // to both the cell matrix and the cell right-handside. If we are not
+    // using streamline diffusion, setting $\delta=0$ negates this contribution
+    // below and we are left with the standard, Galerkin finite element
+    // assembly.
     const double delta = settings.with_streamline_diffusion ?
                            compute_stabilization_delta(cell->diameter(),
-                                                       epsilon,
+                                                       settings.epsilon,
                                                        advection_direction,
                                                        settings.fe_degree) :
                            0.0;
@@ -643,36 +718,36 @@ namespace Step63
           for (unsigned int j = 0; j < dofs_per_cell; ++j)
             {
               copy_data.cell_matrix(i, j) +=
-                (epsilon * scratch_data.fe_values.shape_grad(j, q_point) *
+                // Galerkin contribution:
+                (settings.epsilon *
+                 scratch_data.fe_values.shape_grad(j, q_point) *
                  scratch_data.fe_values.shape_grad(i, q_point) *
                  scratch_data.fe_values.JxW(q_point)) +
                 ((advection_direction *
                   scratch_data.fe_values.shape_grad(j, q_point)) *
                  scratch_data.fe_values.shape_value(i, q_point)) *
+                  scratch_data.fe_values.JxW(q_point) +
+                // Streamline diffusion contribution:
+                delta *
+                  (advection_direction *
+                   scratch_data.fe_values.shape_grad(j, q_point)) *
+                  (advection_direction *
+                   scratch_data.fe_values.shape_grad(i, q_point)) *
+                  scratch_data.fe_values.JxW(q_point) -
+                delta * settings.epsilon *
+                  trace(scratch_data.fe_values.shape_hessian(j, q_point)) *
+                  (advection_direction *
+                   scratch_data.fe_values.shape_grad(i, q_point)) *
                   scratch_data.fe_values.JxW(q_point);
-
-              if (settings.with_streamline_diffusion)
-                copy_data.cell_matrix(i, j) +=
-                  delta *
-                    (advection_direction *
-                     scratch_data.fe_values.shape_grad(j, q_point)) *
-                    (advection_direction *
-                     scratch_data.fe_values.shape_grad(i, q_point)) *
-                    scratch_data.fe_values.JxW(q_point) -
-                  delta * epsilon *
-                    trace(scratch_data.fe_values.shape_hessian(j, q_point)) *
-                    (advection_direction *
-                     scratch_data.fe_values.shape_grad(i, q_point)) *
-                    scratch_data.fe_values.JxW(q_point);
             }
           if (cell->is_level_cell() == false)
             {
               copy_data.cell_rhs(i) +=
+                // Galerkin contribution:
                 scratch_data.fe_values.shape_value(i, q_point) *
-                rhs_values[q_point] * scratch_data.fe_values.JxW(q_point);
-              if (settings.with_streamline_diffusion)
-                copy_data.cell_rhs(i) +=
-                  delta * rhs_values[q_point] * advection_direction *
+                  rhs_values[q_point] * scratch_data.fe_values.JxW(q_point) +
+                // Streamline diffusion contribution:
+                delta * rhs_values[q_point] * advection_direction *
                   scratch_data.fe_values.shape_grad(i, q_point) *
                   scratch_data.fe_values.JxW(q_point);
             }
@@ -680,6 +755,11 @@ namespace Step63
   }
 
 
+  // @sect4{<code>AdvectionProblem::assemble_system_and_multigrid</code>}
+
+  // Here we employ Meshworker::mesh_loop() to go over cells and assemble the
+  // system_matrix, system_rhs, and all mg_matrices for us.
+
   template <int dim>
   void AdvectionProblem<dim>::assemble_system_and_multigrid()
   {
@@ -709,6 +789,9 @@ namespace Step63
                           CopyData(),
                           MeshWorker::assemble_own_cells);
 
+    // Unlike the constraints for the active level, we choose to create
+    // local constraint matrices for each multigrid level since they are
+    // never needed elsewhere in the program.
     std::vector<AffineConstraints<double>> boundary_constraints(
       triangulation.n_global_levels());
     for (unsigned int level = 0; level < triangulation.n_global_levels();
@@ -740,7 +823,10 @@ namespace Step63
       // If (i,j) is an interface_out dof pair, then (j,i) is an interface_in
       // dof pair. Note: for interface_in, we load the transpose of the
       // interface entries, i.e., the entry for dof pair (j,i) is stored in
-      // interface_in(i,j).
+      // interface_in(i,j). This is an optimization for the symmetric case
+      // which allows only one matrix to be used when setting the edge_matrices
+      // in solve(). Here, however, since our problem is non-symmetric, we must
+      // store both interface_in and interface_out matrices.
       for (unsigned int i = 0; i < copy_data.dofs_per_cell; ++i)
         for (unsigned int j = 0; j < copy_data.dofs_per_cell; ++j)
           if (mg_constrained_dofs.is_interface_matrix_entry(
@@ -769,6 +855,35 @@ namespace Step63
   }
 
 
+  // @sect4{<code>AdvectionProblem::setup_smoother</code>}
+
+  // Here we setup the smoother based on the settings in the .prm. The two
+  // options that are of significance is the number of pre- and post-smoothing
+  // steps on each level of the multigrid v-cycle and the relaxation parameter.
+
+  // Since multiplicative methods tend to be more powerful than additive method,
+  // fewer smoothing steps are required to see convergence indepedent of mesh
+  // size. The same hold for block smoothers over point smoothers. This is
+  // reflected in the choice for the number of smoothing steps for each type of
+  // smoother below.
+
+  // The relaxation parameter for point smoothers is chosen based on trial and
+  // error, and they reflect values necessary to keep the iteration counts in
+  // the GMRES solve constant (or as close as possible) as we refine the mesh.
+  // The two values given for both ``Jacobi" and ``SOR" are for degree 1 and
+  // degree 3 finite elements. If the user wants to change to another degree,
+  // they may need to adjust these numbers. For block smoothers, this parameter
+  // has a more straightforward interpretation, namely that for additive methods
+  // in 2D, a DoF can have a repeated contribution from up to 4 cells,
+  // therefore we must relax these methods by 0.25 to compensate. This is not an
+  // issue for multiplicative methods as each cell inverse application carries
+  // new information to all its DoFs.
+
+  // Finally, as mention above, the point smoothers only operate on DoFs, and
+  // the block smoothers on cells, so only the block smoothers need to be given
+  // information regarding cell orderings. DoF ordering for point smoothers has
+  // already been taken care of in setup_system().
+
   template <int dim>
   void AdvectionProblem<dim>::setup_smoother()
   {
@@ -783,7 +898,7 @@ namespace Step63
         smoother->initialize(mg_matrices,
                              Smoother::AdditionalData(fe.degree == 1 ? 1.0 :
                                                                        0.62));
-        smoother->set_steps(2);
+        smoother->set_steps(settings.smoothing_steps);
         mg_smoother = std::move(smoother);
       }
     else if (settings.smoother_type == "Jacobi")
@@ -796,7 +911,7 @@ namespace Step63
         smoother->initialize(mg_matrices,
                              Smoother::AdditionalData(fe.degree == 1 ? 0.6667 :
                                                                        0.47));
-        smoother->set_steps(4);
+        smoother->set_steps(settings.smoothing_steps);
         mg_smoother = std::move(smoother);
       }
     else if (settings.smoother_type == "block SOR" ||
@@ -817,6 +932,8 @@ namespace Step63
             std::vector<unsigned int> ordered_indices;
             switch (settings.dof_renumbering)
               {
+                // Order the cells downstream with respect
+                // to the advection direction.
                 case Settings::DoFRenumberingStrategy::downstream:
                   ordered_indices =
                     create_downstream_cell_ordering(dof_handler,
@@ -824,6 +941,9 @@ namespace Step63
                                                     level);
                   break;
 
+                // Order the cells upstream with respect to the advection
+                // direction, i.e., downstream with respect to the negative
+                // of the advection direction.
                 case Settings::DoFRenumberingStrategy::upstream:
                   ordered_indices =
                     create_downstream_cell_ordering(dof_handler,
@@ -831,12 +951,14 @@ namespace Step63
                                                     level);
                   break;
 
+                // Order the cells randomly.
                 case Settings::DoFRenumberingStrategy::random:
                   ordered_indices =
                     create_random_cell_ordering(dof_handler, level);
                   break;
 
-                case Settings::DoFRenumberingStrategy::none: // Do nothing
+                // Keep the default cell ordering (z-order, see Glossary).
+                case Settings::DoFRenumberingStrategy::none:
                   break;
 
                 default:
@@ -855,7 +977,7 @@ namespace Step63
               RelaxationBlockSOR<SparseMatrix<double>, double, Vector<double>>,
               Vector<double>>>();
             smoother->initialize(mg_matrices, smoother_data);
-            smoother->set_steps(1);
+            smoother->set_steps(settings.smoothing_steps);
             mg_smoother = std::move(smoother);
           }
         else if (settings.smoother_type == "block Jacobi")
@@ -867,7 +989,7 @@ namespace Step63
                                                            Vector<double>>,
                                      Vector<double>>>();
             smoother->initialize(mg_matrices, smoother_data);
-            smoother->set_steps(2);
+            smoother->set_steps(settings.smoothing_steps);
             mg_smoother = std::move(smoother);
           }
       }
@@ -876,6 +998,29 @@ namespace Step63
   }
 
 
+  // @sect4{<code>AdvectionProblem::solve</code>}
+
+  // Before we can solve the system, we must first set up the multigrid
+  // preconditioner. This is requires the setup of the transfer between levels,
+  // the coarse matrix solver, and the smoother. This setup follows almost
+  // identically to Step-16, the main difference being the various smoothers
+  // defined above and the fact that we need different interface edge matrices
+  // for in and out since our problem is non-symetric. (In reality, for this
+  // tutorial these interface matrices are empty since we are only using global
+  // refinement, and thus have no refinement edges. However, we have still
+  // included both here since if one made the simple switch to an adaptively
+  // refined method, the program would still run correctly.)
+
+  // The last thing to note is that since our problem is non-symetric, we must
+  // use an appropriate Krylov subspace method. We choose here to
+  // use GMRES since it offers the guarentee of residual reduction in each
+  // iteration. The major disatvantage to GMRES is that, for each iteration, we
+  // must store an additional temporary vector as well as compute an additional
+  // scalar product. However, the goal of this tutorial is to have very low
+  // iteration counts by using a powerful GMG preconditioner, so this should not
+  // be a factor. If the user is interested, another sutaible method offered in
+  // deal.II would be BiCGStab.
+
   template <int dim>
   void AdvectionProblem<dim>::solve()
   {
@@ -909,7 +1054,8 @@ namespace Step63
 
     std::cout << "     Solving with GMRES to tol " << solve_tolerance << "..."
               << std::endl;
-    SolverGMRES<> solver(solver_control);
+    SolverGMRES<> solver(solver_control,
+                         SolverGMRES<>::AdditionalData(50, true));
 
     Timer time;
     time.start();
@@ -926,11 +1072,14 @@ namespace Step63
   }
 
 
+  // @sect4{<code>AdvectionProblem::output_results</code>}
+
+  // Here we output the solution and cell ordering in a .vtu format.
 
   template <int dim>
   void AdvectionProblem<dim>::output_results(const unsigned int cycle) const
   {
-    // Here we generate an index for each cell to visualize the ordering used
+    // We generate an index for each cell to visualize the ordering used
     // by the smoothers. Note that we do this only for the active cells
     // instead of the levels, where the smoothers are actually used. For the
     // point smoothers we renumber DoFs instead of cells, so this is only an
@@ -988,6 +1137,12 @@ namespace Step63
   }
 
 
+  // @sect4{<code>AdvectionProblem::run</code>}
+
+  // As in most tutorials, this function creates/refines the mesh and calls
+  // the various functions defined above to setup, assemble, solve, and output
+  // the results.
+
   template <int dim>
   void AdvectionProblem<dim>::run()
   {
@@ -998,8 +1153,13 @@ namespace Step63
 
         if (cycle == 0)
           {
-            GridGenerator::hyper_cube_with_cylindrical_hole(
-              triangulation, 0.3, 1.0, 0.5, 1, false);
+            // We are solving on the square <code>[-1,1]^dim</code> with a hole
+            // of radius 3/10 units centered at the origin.
+            GridGenerator::hyper_cube_with_cylindrical_hole(triangulation,
+                                                            0.3,
+                                                            1.0);
+
+            // Set manifold for the inner (curved) boundary.
             static const SphericalManifold<dim> manifold_description(
               Point<dim>(0, 0));
             triangulation.set_manifold(1, manifold_description);
@@ -1028,6 +1188,14 @@ namespace Step63
 } // namespace Step63
 
 
+// @sect4{The <code>main</code> function}
+
+// Here the main function is like most tutorials. The only interesting bit
+// is that we require the user to pass a .prm file as a sole command line
+// argument (see Step-19 for a complete discussion of parameter files). If no
+// parameter file is given, the program will output the contents of a sample
+// parameter file with all default values to the screen that the user can then
+// copy and paste into their own .prm file.
 
 int main(int argc, char *argv[])
 {

In the beginning the Universe was created. This has made a lot of people very angry and has been widely regarded as a bad move.

Douglas Adams


Typeset in Trocchi and Trocchi Bold Sans Serif.