geometric_multigrid.h
Go to the documentation of this file.
1// LIC// ====================================================================
2// LIC// This file forms part of oomph-lib, the object-oriented,
3// LIC// multi-physics finite-element library, available
4// LIC// at http://www.oomph-lib.org.
5// LIC//
6// LIC// Copyright (C) 2006-2025 Matthias Heil and Andrew Hazel
7// LIC//
8// LIC// This library is free software; you can redistribute it and/or
9// LIC// modify it under the terms of the GNU Lesser General Public
10// LIC// License as published by the Free Software Foundation; either
11// LIC// version 2.1 of the License, or (at your option) any later version.
12// LIC//
13// LIC// This library is distributed in the hope that it will be useful,
14// LIC// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// LIC// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16// LIC// Lesser General Public License for more details.
17// LIC//
18// LIC// You should have received a copy of the GNU Lesser General Public
19// LIC// License along with this library; if not, write to the Free Software
20// LIC// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21// LIC// 02110-1301 USA.
22// LIC//
23// LIC// The authors may be contacted at oomph-lib@maths.man.ac.uk.
24// LIC//
25// LIC//====================================================================
26// Include guards
27#ifndef OOMPH_GEOMETRIC_MULTIGRID_HEADER
28#define OOMPH_GEOMETRIC_MULTIGRID_HEADER
29
30// Config header
31#ifdef HAVE_CONFIG_H
32#include <oomph-lib-config.h>
33#endif
34
35// For the Problem class
36#include "problem.h"
37
38// For the TreeBasedRefineableMeshBase and MeshAsGeomObject class
39#include "refineable_mesh.h"
41
42// For the RefineableQElement class
43#include "Qelements.h"
44
45// Other obvious stuff
46#include "matrices.h"
48#include "preconditioner.h"
49
50// Namespace extension
51namespace oomph
52{
53 //======================================================================
54 /// MGProblem class; subclass of Problem
55 //======================================================================
56 class MGProblem : public virtual Problem
57 {
58 public:
59 /// Constructor. Initialise pointers to coarser and finer levels
61
62 /// Destructor (empty)
63 virtual ~MGProblem() {}
64
65 /// This function needs to be implemented in the derived problem:
66 /// Returns a pointer to a new object of the same type as the derived
67 /// problem
69
70 /// Function to get a pointer to the mesh we will be working
71 /// with. If there are flux elements present in the mesh this will
72 /// be overloaded to return a pointer to the bulk mesh otherwise
73 /// it can be overloaded to point to the global mesh but it must
74 /// be of type RefineableMeshBase
76
77 }; // End of MGProblem class
78
79 //////////////////////////////////////////////////////////
80 //////////////////////////////////////////////////////////
81 //////////////////////////////////////////////////////////
82
83 //======================================================================
84 // MG solver class
85 //======================================================================
86 template<unsigned DIM>
88 {
89 public:
90 /// typedef for a function that returns a pointer to an object
91 /// of the class Smoother to be used as the pre-smoother
92 typedef Smoother* (*PreSmootherFactoryFctPt)();
93
94 /// typedef for a function that returns a pointer to an object
95 /// of the class Smoother to be used as the post-smoother
96 typedef Smoother* (*PostSmootherFactoryFctPt)();
97
98 /// Access function to set the pre-smoother creation function.
105
106 /// Access function to set the post-smoother creation function.
113
114 /// Constructor: Set up default values for number of V-cycles
115 /// and pre- and post-smoothing steps.
117 : Nvcycle(200),
121 Stream_pt(0),
124 Npre_smooth(2),
125 Npost_smooth(2),
129 {
130 // Set the tolerance in the base class
131 this->Tolerance = 1.0e-09;
132
133 // Set the pointer to the finest level as the first
134 // entry in Mg_hierarchy
135 Mg_hierarchy.push_back(Mg_problem_pt);
136 } // End of MGSolver
137
138 /// Delete any dynamically allocated data
140 {
141 // Run the function written to clean up the memory
143 } // End of ~MGSolver
144
145 /// Clean up anything that needs to be cleaned up
147 {
148 // We only need to destroy data if the solver has been set up and
149 // the data hasn't already been cleared
150 if (Has_been_setup)
151 {
152 // Loop over all of the levels in the hierarchy
153 for (unsigned i = 0; i < Nlevel - 1; i++)
154 {
155 // Delete the pre-smoother associated with this level
157
158 // Make it a null pointer
160
161 // Delete the post-smoother associated with this level
163
164 // Make it a null pointer
166
167 // Delete the system matrix associated with the i-th level
169
170 // Make it a null pointer
172 }
173
174 // Loop over all but the coarsest of the levels in the hierarchy
175 for (unsigned i = 0; i < Nlevel - 1; i++)
176 {
177 // Delete the interpolation matrix associated with the i-th level
179
180 // Make it a null pointer
182
183 // Delete the restriction matrix associated with the i-th level
185
186 // Make it a null pointer
188 }
189
190 // If this solver has been set up then a hierarchy of problems
191 // will have been set up. If the user chose to document everything
192 // then the coarse-grid multigrid problems will have been kept alive
193 // which means we now have to loop over the coarse-grid levels and
194 // destroy them
195 if (Doc_everything)
196 {
197 // Loop over the levels
198 for (unsigned i = 1; i < Nlevel; i++)
199 {
200 // Delete the i-th level problem
201 delete Mg_hierarchy[i];
202
203 // Make the associated pointer a null pointer
204 Mg_hierarchy[i] = 0;
205 }
206 } // if (Doc_everything)
207
208 // Everything has been deleted now so we need to indicate that the
209 // solver is not set up
210 Has_been_setup = false;
211 } // if (Has_been_setup)
212 } // End of clean_up_memory
213
214 /// Makes a vector which will be used in the self-test. Is currently
215 /// set to make the entries of the vector represent a plane wave propagating
216 /// at an angle of 45 degrees
218
219 /// Makes a vector, restricts it down the levels of the hierarchy
220 /// and documents it at each level. After this is done the vector is
221 /// interpolated up the levels of the hierarchy with the output
222 /// being documented at each level
223 void self_test();
224
225 /// Make a self-test to make sure that the interpolation matrices
226 /// are doing the same thing to restrict the vectors down through the
227 /// heirachy.
229
230 /// Make a self-test to make sure that the interpolation matrices
231 /// are doing the same thing to interpolate the vectors up.
233
234 /// Given a level in the hierarchy, an input vector and a filename
235 /// this function will document the given vector according to the structure
236 /// of the mesh on the given level
237 void plot(const unsigned& hierarchy_level,
239 const std::string& filename);
240
241 /// Disable all output from mg_solve apart from the number of
242 /// V-cycles used to solve the problem
244 {
245 // Set the value of Doc_time (inherited from LinearSolver) to false
246 Doc_time = false;
247
248 // Enable the suppression of output from the V-cycle
250 } // End of disable_v_cycle_output
251
252 /// Suppress anything that can be suppressed, i.e. any timings.
253 /// Things like mesh adaptation can not however be silenced using this
255 {
256 // Set the value of Doc_time (inherited from LinearSolver) to false
257 Doc_time = false;
258
259 // Enable the suppression of output from the V-cycle
261
262 // Enable the suppression of everything
263 Suppress_all_output = true;
264
265 // Store the output stream pointer
267
268 // Now set the oomph_info stream pointer to the null stream to
269 // disable all possible output
271 } // End of disable_output
272
273 /// Enable the output of the V-cycle timings and other output
275 {
276 // Enable time documentation
277 Doc_time = true;
278
279 // Enable output from the MG solver
281 } // End of enable_v_cycle_output
282
283 /// Enable the output from anything that could have been suppressed
285 {
286 // Enable the documentation of everything (if this is set to TRUE then
287 // the function self_test() will be run which outputs a solution
288 // represented on each level of the hierarchy
289 Doc_everything = true;
290 } // End of enable_doc_everything
291
292 /// Enable the output from anything that could have been suppressed
294 {
295 // Enable time documentation
296 Doc_time = true;
297
298 // Enable output from everything during the full setup of the solver
299 Suppress_all_output = false;
300
301 // Enable output from the MG solver
303 } // End of enable_output
304
305 /// Suppress the output of both smoothers and SuperLU
307 {
308 // Loop over all levels of the hierarchy
309 for (unsigned i = 0; i < Nlevel - 1; i++)
310 {
311 // Disable time documentation on each level (for each pre-smoother)
312 Pre_smoothers_storage_pt[i]->disable_doc_time();
313
314 // Disable time documentation on each level (for each post-smoother)
315 Post_smoothers_storage_pt[i]->disable_doc_time();
316 }
317
318 // We only do a direct solve on the coarsest level so this is the only
319 // place we need to silence SuperLU
321 ->linear_solver_pt()
322 ->disable_doc_time();
323 } // End of disable_smoother_and_superlu_doc_time
324
325 /// Return the number of post-smoothing iterations (lvalue)
326 unsigned& npost_smooth()
327 {
328 // Return the number of post-smoothing iterations to be done on each
329 // level of the hierarchy
330 return Npost_smooth;
331 } // End of npost_smooth
332
333 /// Return the number of pre-smoothing iterations (lvalue)
334 unsigned& npre_smooth()
335 {
336 // Return the number of pre-smoothing iterations to be done on each
337 // level of the hierarchy
338 return Npre_smooth;
339 } // End of npre_smooth
340
341 /// Pre-smoother: Perform 'max_iter' smoothing steps on the
342 /// linear system Ax=b with current RHS vector, b, starting with
343 /// current solution vector, x. Return the residual vector r=b-Ax.
344 /// Uses the default smoother (set in the MGProblem constructor)
345 /// which can be overloaded for a specific problem.
346 void pre_smooth(const unsigned& level)
347 {
348 // Run pre-smoother 'max_iter' times
349 Pre_smoothers_storage_pt[level]->smoother_solve(
351
352 // Calculate the residual r=b-Ax and assign it
353 Mg_matrices_storage_pt[level]->residual(
357 } // End of pre_smooth
358
359 /// Post-smoother: Perform max_iter smoothing steps on the
360 /// linear system Ax=b with current RHS vector, b, starting with
361 /// current solution vector, x. Uses the default smoother (set in
362 /// the MGProblem constructor) which can be overloaded for specific
363 /// problem.
364 void post_smooth(const unsigned& level)
365 {
366 // Run post-smoother 'max_iter' times
367 Post_smoothers_storage_pt[level]->smoother_solve(
369 } // End of post_smooth
370
371 /// Return norm of residual r=b-Ax and the residual vector itself
372 /// on the level-th level
373 double residual_norm(const unsigned& level)
374 {
375 // And zero the entries of residual
376 Residual_mg_vectors_storage[level].initialise(0.0);
377
378 // Get the residual
379 Mg_matrices_storage_pt[level]->residual(
383
384 // Return the norm of the residual
385 return Residual_mg_vectors_storage[level].norm();
386 } // End of residual_norm
387
388 /// Call the direct solver (SuperLU) to solve the problem exactly.
389 // The result is placed in X_mg
391 {
392 // Get solution by direct solve:
393 Mg_matrices_storage_pt[Nlevel - 1]->solve(
395 } // End of direct_solve
396
397 /// Builds a CRDoubleMatrix that is used to interpolate the
398 /// residual between levels. The transpose can be used as the full
399 /// weighting restriction.
400 void interpolation_matrix_set(const unsigned& level,
401 double* value,
402 int* col_index,
403 int* row_st,
404 unsigned& ncol,
405 unsigned& nnz)
406 {
407 // Dynamically allocate the interpolation matrix
409
410 // Build the matrix
411 Interpolation_matrices_storage_pt[level]->build_without_copy(
412 ncol, nnz, value, col_index, row_st);
413 } // End of interpolation_matrix_set
414
415 /// Builds a CRDoubleMatrix that is used to interpolate the
416 /// residual between levels. The transpose can be used as the full
417 /// weighting restriction.
418 void interpolation_matrix_set(const unsigned& level,
419 Vector<double>& value,
422 unsigned& ncol,
423 unsigned& nrow)
424 {
425 // Dynamically allocate the interpolation matrix
427
428 // Make the distribution pointer
430 Mg_hierarchy[level]->communicator_pt(), nrow, false);
431
432#ifdef PARANOID
433#ifdef OOMPH_HAS_MPI
434 // Set up the warning messages
435 std::string warning_message =
436 "Setup of interpolation matrix distribution ";
437 warning_message += "has not been tested with MPI.";
438
439 // If we're not running the code in serial
440 if (dist_pt->communicator_pt()->nproc() > 1)
441 {
442 // Throw a warning
445 }
446#endif
447#endif
448
449 // Build the matrix itself
451 dist_pt, ncol, value, col_index, row_st);
452
453 // Delete the newly created distribution pointer
454 delete dist_pt;
455
456 // Make it a null pointer
457 dist_pt = 0;
458 } // End of interpolation_matrix_set
459
460 /// Builds a CRDoubleMatrix on each level that is used to
461 /// restrict the residual between levels. The transpose can be used
462 /// as the interpolation matrix
464 {
465 for (unsigned i = 0; i < Nlevel - 1; i++)
466 {
467 // Dynamically allocate the restriction matrix
469
470 // Set the restriction matrix to be the transpose of the
471 // interpolation matrix
472 Interpolation_matrices_storage_pt[i]->get_matrix_transpose(
474 }
475 } // End of set_restriction_matrices_as_interpolation_transposes
476
477 /// Restrict residual (computed on level-th MG level) to the next
478 /// coarser mesh and stick it into the coarse mesh RHS vector.
479 void restrict_residual(const unsigned& level);
480
481 /// Interpolate solution at current level onto next finer mesh
482 /// and correct the solution x at that level
483 void interpolate_and_correct(const unsigned& level);
484
485 /// Given the son_type of an element and a local node number
486 /// j in that element with nnode_1d nodes per coordinate direction,
487 /// return the local coordinate s in its father element. Needed in
488 /// the setup of the interpolation matrices
489 void level_up_local_coord_of_node(const int& son_type, Vector<double>& s);
490
491 /// Setup the interpolation matrix on each level
493
494 /// Setup the interpolation matrix on each level (used for
495 /// unstructured meshes)
497
498 /// Setup the transfer matrices on each level
500
501 /// Do a full setup (assumes everything will be setup around the
502 /// MGProblem pointer given in the constructor)
503 void full_setup();
504
505 /// Virtual function in the base class that needs to be implemented
506 /// later but for now just leave it empty
507 void solve(Problem* const& problem_pt, DoubleVector& result)
508 {
509 // Dynamically cast problem_pt of type Problem to a MGProblem pointer
510 MGProblem* mg_problem_pt = dynamic_cast<MGProblem*>(problem_pt);
511
512#ifdef PARANOID
513 // PARANOID check - If the dynamic_cast produces a null pointer the
514 // input was not a MGProblem
515 if (0 == mg_problem_pt)
516 {
517 throw OomphLibError("Input problem must be of type MGProblem.",
520 }
521 // PARANOID check - If a node in the input problem has more than
522 // one value we cannot deal with it so arbitarily check the first one
523 if (problem_pt->mesh_pt()->node_pt(0)->nvalue() != 1)
524 {
525 // How many dofs are there in the first node
526 unsigned n_value = problem_pt->mesh_pt()->node_pt(0)->nvalue();
527
528 // Make the error message
529 std::ostringstream error_message_stream;
530 error_message_stream << "Cannot currently deal with more than 1 dof"
531 << " per node. This problem has " << n_value
532 << " dofs per node." << std::endl;
533
534 // Throw the error message
538 }
539#endif
540
541 // Assign the new MGProblem pointer to Mg_problem_pt
543
544 // Set up all of the required MG structures
545 full_setup();
546
547 // Run the MG method and assign the solution to result
549
550 // Only output if the V-cycle output isn't suppressed
552 {
553 // Notify user that the hierarchy of levels is complete
554 oomph_info << "\n================="
555 << "Multigrid Solve Complete"
556 << "=================\n"
557 << std::endl;
558 }
559
560 // If the user did not request all output be suppressed
562 {
563 // If the user requested all V-cycle output be suppressed
565 {
566 // Create an extra line spacing
567 oomph_info << std::endl;
568 }
569
570 // Output number of V-cycles taken to solve
571 if (Has_been_solved)
572 {
573 oomph_info << "Total number of V-cycles required for solve: "
574 << V_cycle_counter << std::endl;
575 }
576 else
577 {
578 oomph_info << "Total number of V-cycles used: " << V_cycle_counter
579 << std::endl;
580 }
581 } // if (!Suppress_all_output)
582
583 // Only enable and assign the stream pointer again if we originally
584 // suppressed everything otherwise it won't be set yet
586 {
587 // Now enable the stream pointer again
589 }
590 } // End of solve
591
592 /// Number of iterations
593 unsigned iterations() const
594 {
595 // Return the number of V-cycles which have been done
596 return V_cycle_counter;
597 } // End of iterations
598
599 /// Number of iterations
600 unsigned& max_iter()
601 {
602 // Return the number of V-cycles which have been done
603 return Nvcycle;
604 } // End of iterations
605
606 protected:
607 /// Do the actual solve -- this is called through the pure virtual
608 /// solve function in the LinearSolver base class. The function is stored
609 /// as protected to allow the MGPreconditioner derived class to use the
610 /// solver
612
613 /// Normalise the rows of the restriction matrices to avoid
614 /// amplifications when projecting to the coarser level
616
617 /// Maximum number of V-cycles (this is set as a protected variable
618 /// so
619 // that it can be changed in the MGPreconditioner class)
620 unsigned Nvcycle;
621
622 /// Pointer to the MG problem (deep copy). This is protected to
623 /// provide access to the MG preconditioner
625
626 /// Vector to store the RHS vectors (Rhs_mg). This is protected to
627 /// allow the multigrid preconditioner to assign the RHS vector during
628 /// preconditioner_solve()
630
631 /// Indicates whether or not the V-cycle output should be
632 /// suppressed. Needs to be protected member data for the multigrid
633 /// preconditioner to know whether or not to output information
634 /// with each preconditioning step
636
637 /// If this is set to true then all output from the solver is
638 /// suppressed. This is protected member data so that the multigrid
639 /// preconditioner knows whether or not to restore the stream pointer
641
642 /// Pointer to the output stream -- defaults to std::cout.
643 /// This is protected member data to allow the preconditioner to
644 /// restore normal output if everything was chosen to be
645 /// suppressed by the user
646 std::ostream* Stream_pt;
647
648 private:
649 /// Function to create pre-smoothers
651
652 /// Function to create post-smoothers
654
655 /// Function to set up the hierachy of levels. Creates a vector
656 /// of pointers to each MG level
657 void setup_mg_hierarchy();
658
659 /// Function to set up the hierachy of levels. Creates a vector
660 /// of pointers to each MG level
661 void setup_mg_structures();
662
663 /// Function to set up all of the smoothers once the system matrices
664 /// have been set up
665 void setup_smoothers();
666
667 /// The number of levels in the multigrid heirachy
668 unsigned Nlevel;
669
670 /// Vector containing pointers to problems in hierarchy
672
673 /// Vector to store the system matrices
675
676 /// Vector to store the interpolation matrices
678
679 /// Vector to store the restriction matrices
681
682 /// Vector to store the solution vectors (X_mg)
684
685 /// Vector to store the residual vectors
687
688 /// Vector to store the result of interpolation on each level (only
689 /// required if the user wishes to document the output of interpolation
690 /// and restriction on each level)
692
693 /// Vector to store the result of restriction on each level (only
694 /// required if the user wishes to document the output of interpolation
695 /// and restriction on each level)
697
698 /// Vector to store the pre-smoothers
700
701 /// Vector to store the post-smoothers
703
704 /// Number of pre-smoothing steps
705 unsigned Npre_smooth;
706
707 /// Number of post-smoothing steps
708 unsigned Npost_smooth;
709
710 /// If this is set to true we document everything. In addition
711 /// to outputting the information of the setup timings and V-cycle
712 /// data we document the refinement and unrefinement patterns given
713 /// by the transfer operators which is done by keeping the coarser
714 /// MG problem pointers alive
716
717 /// Boolean variable to indicate whether or not the solver has been setup
719
720 /// Boolean variable to indicate whether or not the problem was
721 /// successfully solved
723
724 /// Pointer to counter for V-cycles
726 };
727
728 //====================================================================
729 /// An interface to allow scalar MG to be used as a Preconditioner
730 //====================================================================
731 template<unsigned DIM>
732 class MGPreconditioner : public MGSolver<DIM>, public Preconditioner
733 {
734 public:
735 /// Constructor.
737 {
738 // Set the number of V-cycles to be 1 (as expected as a preconditioner)
739 this->Nvcycle = 2;
740 } // End of MGPreconditioner (constructor)
741
742 /// Destructor (empty)
744
745 /// Broken copy constructor.
747
748 /// Broken assignment operator.
749 void operator=(const MGPreconditioner&) = delete;
750
751 /// Function to set up a preconditioner for the linear system
752 void setup()
753 {
754#ifdef OOMPH_HAS_MPI
755 // Make sure that this is running in serial. Can't guarantee it'll
756 // work when the problem is distributed over several processors
758 {
759 // Throw a warning
760 OomphLibWarning("Can't guarantee the MG solver will work in parallel!",
763 }
764#endif
765
766 // Call the helper function that actually does all the work
767 this->full_setup();
768
769 // Only enable and assign the stream pointer again if we originally
770 // suppressed everything otherwise it won't be set yet
771 if (this->Suppress_all_output)
772 {
773 // Now enable the stream pointer again
775 }
776 } // End of setup
777
778 /// Function applies MG to the vector r for a full solve
780 {
781#ifdef PARANOID
782 if (this->Mg_problem_pt->ndof() != rhs.nrow())
783 {
784 throw OomphLibError("Matrix and RHS vector sizes incompatible.",
787 }
788#endif
789
790 // Set the right-hand side vector on the finest level to r
791 this->Rhs_mg_vectors_storage[0] = rhs;
792
793 // Run the MG method and assign the solution to z
794 this->mg_solve(z);
795
796 // Only output if the V-cycle output isn't suppressed
797 if (!(this->Suppress_v_cycle_output))
798 {
799 // Notify user that the hierarchy of levels is complete
801 << "\n==========Multigrid Preconditioner Solve Complete========="
802 << "\n"
803 << std::endl;
804 }
805
806 // Only enable and assign the stream pointer again if we originally
807 // suppressed everything otherwise it won't be set yet
808 if (this->Suppress_all_output)
809 {
810 // Now enable the stream pointer again
812 }
813 } // End of preconditioner_solve
814
815 /// Clean up memory
817 };
818
819 //===================================================================
820 /// Runs a full setup of the MG solver
821 //===================================================================
822 template<unsigned DIM>
824 {
825 // Initialise the timer start variable
826 double t_fs_start = 0.0;
827
828 // If we're allowed to output
829 if (!Suppress_all_output)
830 {
831 // Start the timer
833
834 // Notify user that the hierarchy of levels is complete
836 << "\n===============Starting Multigrid Full Setup=============="
837 << std::endl;
838
839 // Notify user that the hierarchy of levels is complete
840 oomph_info << "\nStarting the full setup of the multigrid solver."
841 << std::endl;
842 }
843
844#ifdef PARANOID
845 // PARANOID check - Make sure the dimension of the solver matches the
846 // dimension of the elements used in the problems mesh
847 if (dynamic_cast<FiniteElement*>(Mg_problem_pt->mesh_pt()->element_pt(0))
848 ->dim() != DIM)
849 {
850 std::string err_strng = "The dimension of the elements used in the mesh ";
851 err_strng += "does not match the dimension of the solver.";
852 throw OomphLibError(
854 }
855
856 // PARANOID check - The elements of the bulk mesh must all be refineable
857 // elements otherwise we cannot deal with this
858 if (Mg_problem_pt->mg_bulk_mesh_pt() != 0)
859 {
860 // Find the number of elements in the bulk mesh
861 unsigned n_elements = Mg_problem_pt->mg_bulk_mesh_pt()->nelement();
862
863 // Loop over the elements in the mesh and ensure that they are
864 // all refineable elements
865 for (unsigned el_counter = 0; el_counter < n_elements; el_counter++)
866 {
867 // Upcast global mesh element to a refineable element
868 RefineableElement* el_pt = dynamic_cast<RefineableElement*>(
869 Mg_problem_pt->mg_bulk_mesh_pt()->element_pt(el_counter));
870
871 // Check if the upcast worked or not; if el_pt is a null pointer the
872 // element is not refineable
873 if (el_pt == 0)
874 {
875 throw OomphLibError(
876 "Element in global mesh could not be upcast to a refineable "
877 "element. We cannot deal with elements that are not refineable.",
880 }
881 }
882 }
883 else
884 {
885 throw OomphLibError(
886 "The provided bulk mesh pointer is set to be a null pointer. "
887 "The multigrid solver operates on the bulk mesh thus a pointer "
888 "to the correct mesh must be given.",
891 }
892#endif
893
894 // If this is not the first Newton step then we will already have things
895 // in storage. If this is the case, delete them
896 clean_up_memory();
897
898 // Resize the Mg_hierarchy vector
899 Mg_hierarchy.resize(1, 0);
900
901 // Set the pointer to the finest level as the first entry in Mg_hierarchy
902 Mg_hierarchy[0] = Mg_problem_pt;
903
904 // Create the hierarchy of levels
905 setup_mg_hierarchy();
906
907 // Set up the interpolation and restriction matrices
908 setup_transfer_matrices();
909
910 // Set up the data structures on each level, i.e. the system matrix,
911 // LHS and RHS vectors
912 setup_mg_structures();
913
914 // Set up the smoothers on all of the levels
915 setup_smoothers();
916
917 // If we do not want to document everything we want to delete all the
918 // coarse-grid problems
919 if (!Doc_everything)
920 {
921 // Loop over all of the coarser levels
922 for (unsigned i = 1; i < Nlevel; i++)
923 {
924 // Delete the i-th coarse-grid MGProblem
925 delete Mg_hierarchy[i];
926
927 // Set it to be a null pointer
928 Mg_hierarchy[i] = 0;
929 }
930 }
931 // Otherwise, document everything!
932 else
933 {
934 // If the user wishes to document everything we run the self-test
935 self_test();
936 } // if (!Doc_everything)
937
938 // Indicate that the full setup has been completed
939 Has_been_setup = true;
940
941 // If we're allowed to output to the screen
942 if (!Suppress_all_output)
943 {
944 // Output the time taken to complete the full setup
947
948 // Output the CPU time
949 oomph_info << "\nCPU time for full setup [sec]: " << full_setup_time
950 << std::endl;
951
952 // Notify user that the hierarchy of levels is complete
954 << "\n===============Multigrid Full Setup Complete=============="
955 << std::endl;
956 } // if (!Suppress_all_output)
957 } // End of full_setup
958
959 //===================================================================
960 /// Set up the MG hierarchy
961 //===================================================================
962 // Function to set up the hierachy of levels. Creates a vector of
963 // pointers to each MG level
964 template<unsigned DIM>
966 {
967 // Initialise the timer start variable
968 double t_m_start = 0.0;
969
970 // Notify the user if it is allowed
971 if (!Suppress_all_output)
972 {
973 // Notify user of progress
975 << "\n===============Creating Multigrid Hierarchy==============="
976 << std::endl;
977
978 // Start clock
980 }
981
982 // Create a bool to indicate whether or not we could create an unrefined
983 // copy. This bool will be assigned the value FALSE when the current copy
984 // is the last level of the multigrid hierarchy
986
987 // Now keep making copies and try to make an unrefined copy of
988 // the mesh
989 unsigned level = 0;
990
991 // Set up all of the levels by making a completely unrefined copy
992 // of the problem using the function make_new_problem
994 {
995 // Make a new object of the same type as the derived problem
996 MGProblem* new_problem_pt = Mg_problem_pt->make_new_problem();
997
998 // Do anything that needs to be done before we can refine the mesh
999 new_problem_pt->actions_before_adapt();
1000
1001 // To create the next level in the hierarchy we need to create a mesh
1002 // which matches the refinement of the current problem and then unrefine
1003 // the mesh. This can alternatively be done using the function
1004 // refine_base_mesh_as_in_reference_mesh_minus_one which takes a
1005 // reference mesh to do precisely the above with
1007 new_problem_pt->mg_bulk_mesh_pt()
1008 ->refine_base_mesh_as_in_reference_mesh_minus_one(
1009 Mg_hierarchy[level]->mg_bulk_mesh_pt());
1010
1011 // If we were able to unrefine the problem on the current level
1012 // then add the unrefined problem to a vector of the levels
1014 {
1015 // Another level has been created so increment the level counter
1016 level++;
1017
1018 // If the documentation of everything has not been suppressed
1019 // then tell the user we managed to create another level
1020 if (!Suppress_all_output)
1021 {
1022 // Notify user that unrefinement was successful
1023 oomph_info << "\nSuccess! Level " << level << " has been created."
1024 << std::endl;
1025 }
1026
1027 // Do anything that needs to be done after refinement
1028 new_problem_pt->actions_after_adapt();
1029
1030 // Do the equation numbering for the new problem
1031 oomph_info << "\n - Number of equations: "
1032 << new_problem_pt->assign_eqn_numbers() << std::endl;
1033
1034 // Add the new problem pointer onto the vector of MG levels
1035 // and increment the value of level by 1
1036 Mg_hierarchy.push_back(new_problem_pt);
1037 }
1038 // If we weren't able to create an unrefined copy
1039 else
1040 {
1041 // Delete the new problem
1042 delete new_problem_pt;
1043
1044 // Make it a null pointer
1045 new_problem_pt = 0;
1046
1047 // Assign the number of levels to Nlevel
1048 Nlevel = Mg_hierarchy.size();
1049
1050 // If we're allowed to document then tell the user we've reached
1051 // the coarsest level of the hierarchy
1052 if (!Suppress_all_output)
1053 {
1054 // Notify the user
1055 oomph_info << "\n Reached the coarsest level! "
1056 << "Number of levels: " << Nlevel << std::endl;
1057 }
1058 } // if (managed_to_unrefine)
1059 } // while (managed_to_unrefine)
1060
1061 // Given that we know the number of levels in the hierarchy we can resize
1062 // the vectors which will store all the information required for our solver:
1063 // Resize the vector storing all of the system matrices
1064 Mg_matrices_storage_pt.resize(Nlevel, 0);
1065
1066 // Resize the vector storing all of the solution vectors (X_mg)
1067 X_mg_vectors_storage.resize(Nlevel);
1068
1069 // Resize the vector storing all of the RHS vectors (Rhs_mg)
1070 Rhs_mg_vectors_storage.resize(Nlevel);
1071
1072 // Resize the vector storing all of the residual vectors
1073 Residual_mg_vectors_storage.resize(Nlevel);
1074
1075 // Allocate space for the pre-smoother storage vector (remember, we do
1076 // not need a smoother on the coarsest level we use a direct solve there)
1077 Pre_smoothers_storage_pt.resize(Nlevel - 1, 0);
1078
1079 // Allocate space for the post-smoother storage vector (remember, we do
1080 // not need a smoother on the coarsest level we use a direct solve there)
1081 Post_smoothers_storage_pt.resize(Nlevel - 1, 0);
1082
1083 // Resize the vector storing all of the interpolation matrices
1084 Interpolation_matrices_storage_pt.resize(Nlevel - 1, 0);
1085
1086 // Resize the vector storing all of the restriction matrices
1087 Restriction_matrices_storage_pt.resize(Nlevel - 1, 0);
1088
1089 if (!Suppress_all_output)
1090 {
1091 // Stop clock
1092 double t_m_end = TimingHelpers::timer();
1095 << "\nCPU time for creation of hierarchy of MG problems [sec]: "
1096 << total_setup_time << std::endl;
1097
1098 // Notify user that the hierarchy of levels is complete
1100 << "\n===============Hierarchy Creation Complete================"
1101 << "\n"
1102 << std::endl;
1103 }
1104 } // End of setup_mg_hierarchy
1105
1106 //===================================================================
1107 /// Set up the transfer matrices. Both the pure injection and
1108 /// full weighting method have been implemented here but it is highly
1109 /// recommended that full weighting is used in general. In both
1110 /// methods the transpose of the transfer matrix is used to transfer
1111 /// a vector back
1112 //===================================================================
1113 template<unsigned DIM>
1115 {
1116 // Initialise the timer start variable
1117 double t_r_start = 0.0;
1118
1119 // Notify the user (if we're allowed)
1120 if (!Suppress_all_output)
1121 {
1122 // Notify user of progress
1123 oomph_info << "Creating the transfer matrices ";
1124
1125 // Start the clock!
1127 }
1128
1129 // If we're allowed to output information
1130 if (!Suppress_all_output)
1131 {
1132 // Say what method we're using
1133 oomph_info << "using full weighting (recommended).\n" << std::endl;
1134 }
1135
1136 // Using full weighting so use setup_interpolation_matrices.
1137 // Note: There are two methods to choose from here, the ideal choice is
1138 // setup_interpolation_matrices() but that requires a refineable mesh base
1139 if (dynamic_cast<TreeBasedRefineableMeshBase*>(
1140 Mg_problem_pt->mg_bulk_mesh_pt()))
1141 {
1142 setup_interpolation_matrices();
1143 }
1144 // If the mesh is unstructured we have to use the locate_zeta function
1145 // to set up the interpolation matrices
1146 else
1147 {
1148 setup_interpolation_matrices_unstructured();
1149 }
1150
1151 // Loop over all levels that will be assigned a restriction matrix
1152 set_restriction_matrices_as_interpolation_transposes();
1153
1154 // If we're allowed
1155 if (!Suppress_all_output)
1156 {
1157 // Stop the clock
1158 double t_r_end = TimingHelpers::timer();
1160 oomph_info << "CPU time for transfer matrices setup [sec]: "
1161 << total_G_setup_time << std::endl;
1162
1163 // Notify user that the hierarchy of levels is complete
1165 << "\n============Transfer Matrices Setup Complete=============="
1166 << "\n"
1167 << std::endl;
1168 }
1169 } // End of setup_transfer_matrices function
1170
1171 //===================================================================
1172 /// Set up the MG hierarchy structures
1173 //===================================================================
1174 // Function to set up the hierachy of levels. Creates a vector of
1175 // pointers to each MG level
1176 template<unsigned DIM>
1178 {
1179 // Initialise the timer start variable
1180 double t_m_start = 0.0;
1181
1182 // Start the clock (if we're allowed to time things)
1183 if (!Suppress_all_output)
1184 {
1185 // Start the clock
1187 }
1188
1189 // Allocate space for the system matrix on each level
1190 for (unsigned i = 0; i < Nlevel; i++)
1191 {
1192 // Dynamically allocate a new CRDoubleMatrix
1193 Mg_matrices_storage_pt[i] = new CRDoubleMatrix;
1194 }
1195
1196 // Loop over each level and extract the system matrix, solution vector
1197 // right-hand side vector and residual vector (to store the value of r=b-Ax)
1198 for (unsigned i = 0; i < Nlevel; i++)
1199 {
1200 // If we're allowed to output
1201 if (!Suppress_all_output)
1202 {
1203 // Output the level we're working on
1204 oomph_info << "Setting up MG structures on level: " << i << "\n"
1205 << std::endl;
1206 }
1207
1208 // Resize the solution and RHS vector
1209 unsigned n_dof = Mg_hierarchy[i]->ndof();
1211 Mg_hierarchy[i]->communicator_pt(), n_dof, false);
1212
1213#ifdef PARANOID
1214#ifdef OOMPH_HAS_MPI
1215 // Set up the warning messages
1216 std::string warning_message = "Setup of distribution has not been ";
1217 warning_message += "tested with MPI.";
1218
1219 // If we're not running the code in serial
1220 if (dist_pt->communicator_pt()->nproc() > 1)
1221 {
1222 // Throw a warning
1225 }
1226#endif
1227#endif
1228
1229 // Build the approximate solution
1230 X_mg_vectors_storage[i].clear();
1231 X_mg_vectors_storage[i].build(dist_pt);
1232
1233 // Build the point source function
1234 Rhs_mg_vectors_storage[i].clear();
1235 Rhs_mg_vectors_storage[i].build(dist_pt);
1236
1237 // Build the residual vector (r=b-Ax)
1238 Residual_mg_vectors_storage[i].clear();
1239 Residual_mg_vectors_storage[i].build(dist_pt);
1240
1241 // Delete the distribution pointer
1242 delete dist_pt;
1243
1244 // Make it a null pointer
1245 dist_pt = 0;
1246
1247 // Build the matrix distribution
1248 Mg_matrices_storage_pt[i]->clear();
1249 Mg_matrices_storage_pt[i]->distribution_pt()->build(
1250 Mg_hierarchy[i]->communicator_pt(), n_dof, false);
1251
1252 // Compute system matrix on the current level. On the finest level of the
1253 // hierarchy the system matrix and RHS vector is given by the Jacobian and
1254 // vector of residuals which define the original problem which the
1255 // Galerkin approximation to the system matrix is used on the subsequent
1256 // levels so that the correct contributions are taken from each dof on the
1257 // level above (that is to say, it should match the contribution taken
1258 // from the solution vector and RHS vector on the level above)
1259 if (i == 0)
1260 {
1261 // Initialise the timer start variable
1262 double t_jac_start = 0.0;
1263
1264 // If we're allowed to output things
1265 if (!Suppress_all_output)
1266 {
1267 // Start timer for Jacobian setup
1269 }
1270
1271 // The system matrix on the finest level is the Jacobian and the RHS
1272 // vector is given by the residual vector which accompanies the Jacobian
1273 Mg_hierarchy[0]->get_jacobian(Rhs_mg_vectors_storage[0],
1274 *Mg_matrices_storage_pt[0]);
1275
1276 if (!Suppress_all_output)
1277 {
1278 // Document the time taken
1280 double jacobian_setup_time = t_jac_end - t_jac_start;
1281 oomph_info << " - Time for setup of Jacobian [sec]: "
1282 << jacobian_setup_time << "\n"
1283 << std::endl;
1284 }
1285 }
1286 else
1287 {
1288 // Initialise the timer start variable
1289 double t_gal_start = 0.0;
1290
1291 // If we're allowed
1292 if (!Suppress_all_output)
1293 {
1294 // Start timer for Galerkin matrix calculation
1296 }
1297
1298 // The system matrix on the coarser levels must be formed using the
1299 // Galerkin approximation which we do by calculating the product
1300 // A^2h = I^2h_h * A^h * I^h_2h, i.e. the coarser version of the
1301 // finer grid system matrix is formed by multiplying by the (fine grid)
1302 // restriction matrix from the left and the (fine grid) interpolation
1303 // matrix from the left
1304
1305 // First we need to calculate A^h * I^h_2h which we store as A^2h
1306 Mg_matrices_storage_pt[i - 1]->multiply(
1307 *Interpolation_matrices_storage_pt[i - 1],
1308 *Mg_matrices_storage_pt[i]);
1309
1310 // Now calculate I^2h_h * (A^h * I^h_2h) where the quantity in brackets
1311 // was just calculated. This updates A^2h to give us the true
1312 // Galerkin approximation to the finer grid matrix
1313 Restriction_matrices_storage_pt[i - 1]->multiply(
1314 *Mg_matrices_storage_pt[i], *Mg_matrices_storage_pt[i]);
1315
1316 // If the user did not choose to suppress everything
1317 if (!Suppress_all_output)
1318 {
1319 // End timer for Galerkin matrix calculation
1321
1322 // Calculate setup time
1324
1325 // Document the time taken
1327 << " - Time for system matrix formation using the Galerkin "
1328 << "approximation [sec]: " << galerkin_matrix_calculation_time
1329 << "\n"
1330 << std::endl;
1331 }
1332 } // if (i==0) else
1333 } // for (unsigned i=0;i<Nlevel;i++)
1334
1335 // If we're allowed to output
1336 if (!Suppress_all_output)
1337 {
1338 // Stop clock
1339 double t_m_end = TimingHelpers::timer();
1341 oomph_info << "Total CPU time for setup of MG structures [sec]: "
1342 << total_setup_time << std::endl;
1343
1344 // Notify user that the hierarchy of levels is complete
1345 oomph_info << "\n============"
1346 << "Multigrid Structures Setup Complete"
1347 << "===========\n"
1348 << std::endl;
1349 }
1350 } // End of setup_mg_structures
1351
1352 //=========================================================================
1353 /// Set up the smoothers on all levels
1354 //=========================================================================
1355 template<unsigned DIM>
1357 {
1358 // Initialise the timer start variable
1359 double t_m_start = 0.0;
1360
1361 // Start the clock (if we're allowed to time things)
1362 if (!Suppress_all_output)
1363 {
1364 // Notify user
1365 oomph_info << "Starting the setup of all smoothers.\n" << std::endl;
1366
1367 // Start the clock
1369 }
1370
1371 // Loop over the levels and assign the pre- and post-smoother points
1372 for (unsigned i = 0; i < Nlevel - 1; i++)
1373 {
1374 // If the pre-smoother factory function pointer hasn't been assigned
1375 // then we simply create a new instance of the DampedJacobi smoother
1376 // which is the default pre-smoother
1377 if (0 == Pre_smoother_factory_function_pt)
1378 {
1379 Pre_smoothers_storage_pt[i] = new DampedJacobi<CRDoubleMatrix>;
1380 }
1381 // Otherwise we use the pre-smoother factory function pointer to
1382 // generate a new pre-smoother
1383 else
1384 {
1385 // Get a pointer to an object of the same type as the pre-smoother
1386 Pre_smoothers_storage_pt[i] = (*Pre_smoother_factory_function_pt)();
1387 }
1388
1389 // If the post-smoother factory function pointer hasn't been assigned
1390 // then we simply create a new instance of the DampedJacobi smoother
1391 // which is the default post-smoother
1392 if (0 == Post_smoother_factory_function_pt)
1393 {
1394 Post_smoothers_storage_pt[i] = new DampedJacobi<CRDoubleMatrix>;
1395 }
1396 // Otherwise we use the post-smoother factory function pointer to
1397 // generate a new post-smoother
1398 else
1399 {
1400 // Get a pointer to an object of the same type as the post-smoother
1401 Post_smoothers_storage_pt[i] = (*Post_smoother_factory_function_pt)();
1402 }
1403 }
1404
1405 // Set the tolerance for the pre- and post-smoothers. The norm of the
1406 // solution which is compared against the tolerance is calculated
1407 // differently to the multigrid solver. To ensure that the smoother
1408 // continues to solve for the prescribed number of iterations we
1409 // lower the tolerance
1410 for (unsigned i = 0; i < Nlevel - 1; i++)
1411 {
1412 // Set the tolerance of the i-th level pre-smoother
1413 Pre_smoothers_storage_pt[i]->tolerance() = 1.0e-16;
1414
1415 // Set the tolerance of the i-th level post-smoother
1416 Post_smoothers_storage_pt[i]->tolerance() = 1.0e-16;
1417 }
1418
1419 // Set the number of pre- and post-smoothing iterations in each
1420 // pre- and post-smoother
1421 for (unsigned i = 0; i < Nlevel - 1; i++)
1422 {
1423 // Set the number of pre-smoothing iterations as the value of Npre_smooth
1424 Pre_smoothers_storage_pt[i]->max_iter() = Npre_smooth;
1425
1426 // Set the number of pre-smoothing iterations as the value of Npost_smooth
1427 Post_smoothers_storage_pt[i]->max_iter() = Npost_smooth;
1428 }
1429
1430 // Complete the setup of all of the smoothers
1431 for (unsigned i = 0; i < Nlevel - 1; i++)
1432 {
1433 // Pass a pointer to the system matrix on the i-th level to the i-th
1434 // level pre-smoother
1435 Pre_smoothers_storage_pt[i]->smoother_setup(Mg_matrices_storage_pt[i]);
1436
1437 // Pass a pointer to the system matrix on the i-th level to the i-th
1438 // level post-smoother
1439 Post_smoothers_storage_pt[i]->smoother_setup(Mg_matrices_storage_pt[i]);
1440 }
1441
1442 // Set up the distributions of each smoother
1443 for (unsigned i = 0; i < Nlevel - 1; i++)
1444 {
1445 // Get the number of dofs on the i-th level of the hierarchy.
1446 // This will be the same as the size of the solution vector
1447 // associated with the i-th level
1448 unsigned n_dof = X_mg_vectors_storage[i].nrow();
1449
1450 // Create a LinearAlgebraDistribution which will be passed to the
1451 // linear solver
1453 Mg_hierarchy[i]->communicator_pt(), n_dof, false);
1454
1455#ifdef PARANOID
1456#ifdef OOMPH_HAS_MPI
1457 // Set up the warning messages
1458 std::string warning_message =
1459 "Setup of pre- and post-smoother distribution ";
1460 warning_message += "has not been tested with MPI.";
1461
1462 // If we're not running the code in serial
1463 if (dist.communicator_pt()->nproc() > 1)
1464 {
1465 // Throw a warning
1468 }
1469#endif
1470#endif
1471
1472 // Build the distribution of the pre-smoother
1473 Pre_smoothers_storage_pt[i]->build_distribution(dist);
1474
1475 // Build the distribution of the post-smoother
1476 Post_smoothers_storage_pt[i]->build_distribution(dist);
1477 }
1478
1479 // Disable the smoother output on this level
1480 if (!Doc_time)
1481 {
1482 disable_smoother_and_superlu_doc_time();
1483 }
1484
1485 // If we're allowed to output
1486 if (!Suppress_all_output)
1487 {
1488 // Stop clock
1489 double t_m_end = TimingHelpers::timer();
1491 oomph_info << "CPU time for setup of smoothers on all levels [sec]: "
1492 << total_setup_time << std::endl;
1493
1494 // Notify user that the extraction is complete
1496 << "\n==================Smoother Setup Complete================="
1497 << std::endl;
1498 }
1499 } // End of setup_smoothers
1500
1501 //===================================================================
1502 /// Setup the interpolation matrices
1503 //===================================================================
1504 template<unsigned DIM>
1506 {
1507 // Variable to hold the number of sons in an element
1508 unsigned n_sons;
1509
1510 // Number of son elements
1511 if (DIM == 2)
1512 {
1513 n_sons = 4;
1514 }
1515 else if (DIM == 3)
1516 {
1517 n_sons = 8;
1518 }
1519 else
1520 {
1521 std::ostringstream error_message_stream;
1522 error_message_stream << "DIM should be 2 or 3 not " << DIM << std::endl;
1526 }
1527
1528 // Vector of local coordinates in the element
1529 Vector<double> s(DIM, 0.0);
1530
1531 // Loop over each level (apart from the coarsest level; an interpolation
1532 // matrix and thus a restriction matrix is not needed here), with 0 being
1533 // the finest level and Nlevel-1 being the coarsest level
1534 for (unsigned level = 0; level < Nlevel - 1; level++)
1535 {
1536 // Assign values to a couple of variables to help out
1537 unsigned coarse_level = level + 1;
1538 unsigned fine_level = level;
1539
1540 // Make a pointer to the mesh on the finer level and dynamic_cast
1541 // it as an object of the refineable mesh class
1543 Mg_hierarchy[fine_level]->mg_bulk_mesh_pt());
1544
1545 // Make a pointer to the mesh on the coarse level and dynamic_cast
1546 // it as an object of the refineable mesh class
1548 dynamic_cast<RefineableMeshBase*>(
1549 Mg_hierarchy[coarse_level]->mg_bulk_mesh_pt());
1550
1551 // Access information about the number of elements in the fine mesh
1552 // from the pointer to the fine mesh (to loop over the rows of the
1553 // interpolation matrix)
1554 unsigned fine_n_element = ref_fine_mesh_pt->nelement();
1555
1556 // The numbers of rows and columns in the interpolation matrix. The
1557 // number of unknowns has been divided by 2 since there are 2 dofs at
1558 // each node in the mesh corresponding to the real and imaginary part
1559 // of the solution
1560 unsigned n_rows = Mg_hierarchy[fine_level]->ndof();
1561 unsigned n_cols = Mg_hierarchy[coarse_level]->ndof();
1562
1563 // Mapping relating the pointers to related elements in the coarse and
1564 // fine meshes: coarse_mesh_element_pt[fine_mesh_element_pt]
1565 // If the element in the fine mesh has been unrefined between these two
1566 // levels, this map returns the father element in the coarsened mesh.
1567 // If this element in the fine mesh has not been unrefined,
1568 // the map returns the pointer to the same-sized equivalent
1569 // element in the coarsened mesh.
1570 std::map<RefineableQElement<DIM>*, RefineableQElement<DIM>*>
1572
1573 // Counter of elements in coarse and fine meshes: Start with element
1574 // zero in both meshes.
1575 unsigned e_coarse = 0;
1576 unsigned e_fine = 0;
1577
1578 // While loop over fine elements (while because we're
1579 // incrementing the counter internally if the element was
1580 // unrefined...)
1581 while (e_fine < fine_n_element)
1582 {
1583 // Pointer to element in fine mesh
1585 dynamic_cast<RefineableQElement<DIM>*>(
1586 ref_fine_mesh_pt->finite_element_pt(e_fine));
1587
1588 // Pointer to element in coarse mesh
1590 dynamic_cast<RefineableQElement<DIM>*>(
1591 ref_coarse_mesh_pt->finite_element_pt(e_coarse));
1592
1593 // If the levels are different then the element in the fine
1594 // mesh has been unrefined between these two levels
1595 if (el_fine_pt->tree_pt()->level() != el_coarse_pt->tree_pt()->level())
1596 {
1597 // The element in the fine mesh has been unrefined between these two
1598 // levels. Hence it and its three brothers (ASSUMED to be stored
1599 // consecutively in the Mesh's vector of pointers to its constituent
1600 // elements -- we'll check this!) share the same father element in
1601 // the coarse mesh, currently pointed to by el_coarse_pt.
1602 for (unsigned i = 0; i < n_sons; i++)
1603 {
1604 // Set mapping to father element in coarse mesh
1606 [dynamic_cast<RefineableQElement<DIM>*>(
1607 ref_fine_mesh_pt->finite_element_pt(e_fine))] = el_coarse_pt;
1608
1609 // Increment counter for elements in fine mesh
1610 e_fine++;
1611 }
1612 }
1613 // The element in the fine mesh has not been unrefined between
1614 // these two levels, so the reference element is the same-sized
1615 // equivalent element in the coarse mesh
1616 else
1617 {
1618 // Set the mapping between the two elements since they are
1619 // the same element
1621
1622 // Increment counter for elements in fine mesh
1623 e_fine++;
1624 }
1625 // Increment counter for elements in coarse mesh
1626 e_coarse++;
1627 } // End of while loop for setting up element-coincidences
1628
1629 // To allow update of a row only once we use stl vectors for bools
1630 std::vector<bool> contribution_made(n_rows, false);
1631
1632 // Make storage vectors to form the interpolation matrix using a
1633 // condensed row matrix (CRDoubleMatrix). The i-th value of the vector
1634 // row_start contains entries which tells us at which entry of the
1635 // vector column_start we start on the i-th row of the actual matrix.
1636 // The entries of column_start indicate the column position of each
1637 // non-zero entry in every row. This runs through the first row
1638 // from left to right then the second row (again, left to right)
1639 // and so on until the end. The entries in value are the entries in
1640 // the interpolation matrix. The vector column_start has the same length
1641 // as the vector value.
1642 Vector<double> value;
1643 Vector<int> column_index;
1644 Vector<int> row_start(n_rows + 1);
1645
1646 // The value of index will tell us which row of the interpolation matrix
1647 // we're working on in the following for loop
1648 unsigned index = 0;
1649
1650 // New loop to go over each element in the fine mesh
1651 for (unsigned k = 0; k < fine_n_element; k++)
1652 {
1653 // Set a pointer to the element in the fine mesh
1655 dynamic_cast<RefineableQElement<DIM>*>(
1656 ref_fine_mesh_pt->finite_element_pt(k));
1657
1658 // Get the reference element (either the father element or the
1659 // same-sized element) in the coarse mesh
1662
1663 // Find out what type of son it is (set to OMEGA if no unrefinement
1664 // took place)
1665 int son_type = 0;
1666
1667 // Check if the elements are different on both levels (i.e. to check
1668 // if any unrefinement took place)
1669 if (el_fine_pt->tree_pt()->level() != el_coarse_pt->tree_pt()->level())
1670 {
1671 // If there was refinement we need to find the son type
1672 son_type = el_fine_pt->tree_pt()->son_type();
1673 }
1674 else
1675 {
1676 // If there was no refinement then the son_type is given by the
1677 // value of Tree::OMEGA
1678 son_type = Tree::OMEGA;
1679 }
1680
1681 // Find the number of nodes in the fine mesh element
1682 unsigned nnod_fine = el_fine_pt->nnode();
1683
1684 // Loop through all the nodes in an element in the fine mesh
1685 for (unsigned i = 0; i < nnod_fine; i++)
1686 {
1687 // Row number in interpolation matrix: Global equation number
1688 // of the d.o.f. stored at this node in the fine element
1689 int ii = el_fine_pt->node_pt(i)->eqn_number(0);
1690
1691 // Check whether or not the node is a proper d.o.f.
1692 if (ii >= 0)
1693 {
1694 // Only assign values to the given row of the interpolation
1695 // matrix if they haven't already been assigned
1696 if (contribution_made[ii] == false)
1697 {
1698 // The value of index was initialised when we allocated space
1699 // for the three vectors to store the CRDoubleMatrix. We use
1700 // index to go through the entries of the row_start vector.
1701 // The next row starts at the value.size() (draw out the entries
1702 // in value if this doesn't make sense noting that the storage
1703 // for the vector 'value' is dynamically allocated)
1704 row_start[index] = value.size();
1705
1706 // Calculate the local coordinates of the given node
1708
1709 // Find the local coordinates s, of the present node, in the
1710 // reference element in the coarse mesh, given the element's
1711 // son_type (e.g. SW,SE... )
1712 level_up_local_coord_of_node(son_type, s);
1713
1714 // Allocate space for shape functions in the coarse mesh
1716
1717 // Set the shape function in the reference element
1719
1720 // Auxiliary storage
1721 std::map<unsigned, double> contribution;
1723
1724 // Find the number of nodes in an element in the coarse mesh
1725 unsigned nnod_coarse = el_coarse_pt->nnode();
1726
1727 // Loop through all the nodes in the reference element
1728 for (unsigned j = 0; j < nnod_coarse; j++)
1729 {
1730 // Column number in interpolation matrix: Global equation
1731 // number of the d.o.f. stored at this node in the coarse
1732 // element
1733 int jj = el_coarse_pt->node_pt(j)->eqn_number(0);
1734
1735 // If the value stored at this node is pinned or hanging
1736 if (jj < 0)
1737 {
1738 // Hanging node: In this case we need to accumulate the
1739 // contributions from the master nodes
1741 {
1742 // Find the number of master nodes of the hanging
1743 // the node in the reference element
1746 unsigned nmaster = hang_info_pt->nmaster();
1747
1748 // Loop over the master nodes
1749 for (unsigned i_master = 0; i_master < nmaster; i_master++)
1750 {
1751 // Set up a pointer to the master node
1752 Node* master_node_pt =
1753 hang_info_pt->master_node_pt(i_master);
1754
1755 // The column number in the interpolation matrix: the
1756 // global equation number of the d.o.f. stored at this
1757 // master node for the coarse element
1758 int master_jj = master_node_pt->eqn_number(0);
1759
1760 // Is the master node a proper d.o.f.?
1761 if (master_jj >= 0)
1762 {
1763 // If the weight of the master node is non-zero
1764 if (psi(j) * hang_info_pt->master_weight(i_master) !=
1765 0.0)
1766 {
1768 psi(j) * hang_info_pt->master_weight(i_master);
1769 }
1770 } // if (master_jj>=0)
1771 } // for (unsigned i_master=0;i_master<nmaster;i_master++)
1772 } // if (el_coarse_pt->node_pt(j)->is_hanging())
1773 }
1774 // In the case that the node is not pinned or hanging
1775 else
1776 {
1777 // If we can get a nonzero contribution from the shape
1778 // function
1779 if (psi(j) != 0.0)
1780 {
1781 contribution[jj] += psi(j);
1782 }
1783 } // if (jj<0) else
1784 } // for (unsigned j=0;j<nnod_coarse;j++)
1785
1786 // Put the contributions into the value vector
1787 for (std::map<unsigned, double>::iterator it =
1788 contribution.begin();
1789 it != contribution.end();
1790 ++it)
1791 {
1792 if (it->second != 0)
1793 {
1794 column_index.push_back(it->first);
1795 value.push_back(it->second);
1796 }
1797 } // for (std::map<unsigned,double>::iterator it=...)
1798
1799 // Increment the value of index by 1 to indicate that we have
1800 // finished the row, or equivalently, covered another global
1801 // node in the fine mesh
1802 index++;
1803
1804 // Change the entry in contribution_made to true now to indicate
1805 // that the row has been filled
1806 contribution_made[ii] = true;
1807 } // if(contribution_made[ii]==false)
1808 } // if (ii>=0)
1809 } // for(unsigned i=0;i<nnod_element;i++)
1810 } // for (unsigned k=0;k<fine_n_element;k++)
1811
1812 // Set the last entry in the row_start vector
1813 row_start[n_rows] = value.size();
1814
1815 // Set the interpolation matrix to be that formed as the CRDoubleMatrix
1816 // using the vectors value, row_start, column_index and the value
1817 // of fine_n_unknowns and coarse_n_unknowns
1818 interpolation_matrix_set(
1819 level, value, column_index, row_start, n_cols, n_rows);
1820 } // for (unsigned level=0;level<Nlevel-1;level++)
1821 } // End of setup_interpolation_matrices
1822
1823 //===================================================================
1824 /// Setup the interpolation matrices
1825 //===================================================================
1826 template<unsigned DIM>
1828 {
1829 // Vector of local coordinates in the element
1830 Vector<double> s(DIM, 0.0);
1831
1832 // Loop over each level (apart from the coarsest level; an interpolation
1833 // matrix and thus a restriction matrix is not needed here), with 0 being
1834 // the finest level and Nlevel-1 being the coarsest level
1835 for (unsigned level = 0; level < Nlevel - 1; level++)
1836 {
1837 // Assign values to a couple of variables to help out
1838 unsigned coarse_level = level + 1;
1839 unsigned fine_level = level;
1840
1841 // Make a pointer to the mesh on the finer level and dynamic_cast
1842 // it as an object of the refineable mesh class
1843 Mesh* ref_fine_mesh_pt = Mg_hierarchy[fine_level]->mg_bulk_mesh_pt();
1844
1845 // To use the locate zeta functionality the coarse mesh must be of the
1846 // type MeshAsGeomObject
1848 new MeshAsGeomObject(Mg_hierarchy[coarse_level]->mg_bulk_mesh_pt());
1849
1850 // Access information about the number of degrees of freedom
1851 // from the pointers to the problem on each level
1852 unsigned coarse_n_unknowns = Mg_hierarchy[coarse_level]->ndof();
1853 unsigned fine_n_unknowns = Mg_hierarchy[fine_level]->ndof();
1854
1855 // Make storage vectors to form the interpolation matrix using a
1856 // condensed row matrix (CRDoubleMatrix). The i-th value of the vector
1857 // row_start contains entries which tells us at which entry of the
1858 // vector column_start we start on the i-th row of the actual matrix.
1859 // The entries of column_start indicate the column position of each
1860 // non-zero entry in every row. This runs through the first row
1861 // from left to right then the second row (again, left to right)
1862 // and so on until the end. The entries in value are the entries in
1863 // the interpolation matrix. The vector column_start has the same length
1864 // as the vector value.
1865 Vector<double> value;
1866 Vector<int> column_index;
1867 Vector<int> row_start(fine_n_unknowns + 1);
1868
1869 // Vector to contain the (Eulerian) spatial location of the fine node
1871
1872 // Find the number of nodes in the mesh
1874
1875 // Loop over the unknowns in the mesh
1876 for (unsigned i_fine_node = 0; i_fine_node < n_node_fine_mesh;
1877 i_fine_node++)
1878 {
1879 // Set a pointer to the i_fine_unknown-th node in the fine mesh
1881
1882 // Get the global equation number
1883 int i_fine = fine_node_pt->eqn_number(0);
1884
1885 // If the node is a proper d.o.f.
1886 if (i_fine >= 0)
1887 {
1888 // Row number in interpolation matrix: Global equation number
1889 // of the d.o.f. stored at this node in the fine element
1890 row_start[i_fine] = value.size();
1891
1892 // Get the (Eulerian) spatial location of the fine node
1894
1895 // Create a null pointer to the GeomObject class
1896 GeomObject* el_pt = 0;
1897
1898 // Get the reference element (either the father element or the
1899 // same-sized element) in the coarse mesh using locate_zeta
1901
1902 // Upcast GeomElement as a FiniteElement
1903 FiniteElement* el_coarse_pt = dynamic_cast<FiniteElement*>(el_pt);
1904
1905 // Find the number of nodes in the element
1906 unsigned n_node = el_coarse_pt->nnode();
1907
1908 // Allocate space for shape functions in the coarse mesh
1909 Shape psi(n_node);
1910
1911 // Calculate the geometric shape functions at local coordinate s
1913
1914 // Auxiliary storage
1915 std::map<unsigned, double> contribution;
1917
1918 // Loop through all the nodes in the (coarse mesh) element containing
1919 // the node pointed to by fine_node_pt (fine mesh)
1920 for (unsigned j_node = 0; j_node < n_node; j_node++)
1921 {
1922 // Get the j_coarse_unknown-th node in the coarse element
1924
1925 // Column number in interpolation matrix: Global equation number of
1926 // the d.o.f. stored at this node in the coarse element
1928
1929 // If the value stored at this node is pinned or hanging
1930 if (j_coarse < 0)
1931 {
1932 // Hanging node: In this case we need to accumulate the
1933 // contributions from the master nodes
1935 {
1936 // Find the number of master nodes of the hanging
1937 // the node in the reference element
1938 HangInfo* hang_info_pt = coarse_node_pt->hanging_pt();
1939 unsigned nmaster = hang_info_pt->nmaster();
1940
1941 // Loop over the master nodes
1942 for (unsigned i_master = 0; i_master < nmaster; i_master++)
1943 {
1944 // Set up a pointer to the master node
1945 Node* master_node_pt = hang_info_pt->master_node_pt(i_master);
1946
1947 // The column number in the interpolation matrix: the
1948 // global equation number of the d.o.f. stored at this master
1949 // node for the coarse element
1950 int master_jj = master_node_pt->eqn_number(0);
1951
1952 // Is the master node a proper d.o.f.?
1953 if (master_jj >= 0)
1954 {
1955 // If the weight of the master node is non-zero
1956 if (psi(j_node) * hang_info_pt->master_weight(i_master) !=
1957 0.0)
1958 {
1960 psi(j_node) * hang_info_pt->master_weight(i_master);
1961 }
1962 } // End of if statement (check it's not a boundary node)
1963 } // End of the loop over the master nodes
1964 } // End of the if statement for only hanging nodes
1965 } // End of the if statement for pinned or hanging nodes
1966 // In the case that the node is not pinned or hanging
1967 else
1968 {
1969 // If we can get a nonzero contribution from the shape function
1970 // at the j_node-th node in the element
1971 if (psi(j_node) != 0.0)
1972 {
1974 }
1975 } // End of the if-else statement (check if the node was
1976 // pinned/hanging)
1977 } // Finished loop over the nodes j in the reference element (coarse)
1978
1979 // Put the contributions into the value vector
1980 for (std::map<unsigned, double>::iterator it = contribution.begin();
1981 it != contribution.end();
1982 ++it)
1983 {
1984 if (it->second != 0)
1985 {
1986 value.push_back(it->second);
1987 column_index.push_back(it->first);
1988 }
1989 } // End of putting contributions into the value vector
1990 } // End check (whether or not the fine node was a d.o.f.)
1991 } // End of the for-loop over nodes in the fine mesh
1992
1993 // Set the last entry of row_start
1994 row_start[fine_n_unknowns] = value.size();
1995
1996 // Set the interpolation matrix to be that formed as the CRDoubleMatrix
1997 // using the vectors value, row_start, column_index and the value
1998 // of fine_n_unknowns and coarse_n_unknowns
1999 interpolation_matrix_set(level,
2000 value,
2001 column_index,
2002 row_start,
2005 } // End of loop over each level
2006 } // End of setup_interpolation_matrices_unstructured
2007
2008 //===================================================================
2009 /// Restrict residual (computed on current MG level) to
2010 /// next coarser mesh and stick it into the coarse mesh RHS vector
2011 /// using the restriction matrix (if restrict_flag=1) or the transpose
2012 /// of the interpolation matrix (if restrict_flag=2)
2013 //===================================================================
2014 template<unsigned DIM>
2015 void MGSolver<DIM>::restrict_residual(const unsigned& level)
2016 {
2017#ifdef PARANOID
2018 // Check to make sure we can actually restrict the vector
2019 if (!(level < Nlevel - 1))
2020 {
2021 throw OomphLibError("Input exceeds the possible parameter choice.",
2024 }
2025#endif
2026
2027 // Multiply the residual vector by the restriction matrix on the level-th
2028 // level (to restrict the vector down to the next coarser level)
2029 Restriction_matrices_storage_pt[level]->multiply(
2030 Residual_mg_vectors_storage[level], Rhs_mg_vectors_storage[level + 1]);
2031 } // End of restrict_residual
2032
2033 //===================================================================
2034 /// Interpolate solution at current level onto
2035 /// next finer mesh and correct the solution x at that level
2036 //===================================================================
2037 template<unsigned DIM>
2038 void MGSolver<DIM>::interpolate_and_correct(const unsigned& level)
2039 {
2040#ifdef PARANOID
2041 // Check to make sure we can actually restrict the vector
2042 if (!(level > 0))
2043 {
2044 throw OomphLibError("Input level exceeds the possible parameter choice.",
2047 }
2048#endif
2049
2050 // Build distribution of a temporary vector
2051 DoubleVector temp_soln(X_mg_vectors_storage[level - 1].distribution_pt());
2052
2053 // Interpolate the solution vector
2054 Interpolation_matrices_storage_pt[level - 1]->multiply(
2055 X_mg_vectors_storage[level], temp_soln);
2056
2057 // Update
2058 X_mg_vectors_storage[level - 1] += temp_soln;
2059 } // End of interpolate_and_correct
2060
2061 //===================================================================
2062 /// Modify the restriction matrices
2063 //===================================================================
2064 template<unsigned DIM>
2066 {
2067 // Create a null pointer
2069
2070 // Loop over the levels
2071 for (unsigned level = 0; level < Nlevel - 1; level++)
2072 {
2073 // Store a pointer to the (level)-th restriction matrix
2074 restriction_matrix_pt = Restriction_matrices_storage_pt[level];
2075
2076 // Get access to the row start data
2077 const int* row_start_pt = restriction_matrix_pt->row_start();
2078
2079 // Get access to the matrix entries
2080 double* value_pt = restriction_matrix_pt->value();
2081
2082 // Initialise an auxiliary variable to store the index of the start
2083 // of the i-th row
2084 unsigned start_index = 0;
2085
2086 // Initialise an auxiliary variable to store the index of the start
2087 // of the (i+1)-th row
2088 unsigned end_index = 0;
2089
2090 // Store the number of rows in the matrix
2091 unsigned n_row = restriction_matrix_pt->nrow();
2092
2093 // Loop over the rows of the matrix
2094 for (unsigned i = 0; i < n_row; i++)
2095 {
2096 // Index associated with the start of the i-th row
2098
2099 // Index associated with the start of the (i+1)-th row
2100 end_index = row_start_pt[i + 1];
2101
2102 // Variable to store the sum of the absolute values of the off-diagonal
2103 // entries in the i-th row
2104 double row_sum = 0.0;
2105
2106 // Loop over the entries in the row
2107 for (unsigned j = start_index; j < end_index; j++)
2108 {
2109 // Add the absolute value of this entry to the off-diagonal sum
2110 row_sum += value_pt[j];
2111 }
2112
2113 // Loop over the entries in the row
2114 for (unsigned j = start_index; j < end_index; j++)
2115 {
2116 // Normalise the row entry
2117 value_pt[j] /= row_sum;
2118 }
2119 } // for (unsigned i=0;i<n_row;i++)
2120 } // for (unsigned level=0;level<Nlevel-1;level++)
2121 } // End of modify_restriction_matrices
2122
2123 //===================================================================
2124 /// Makes a vector, restricts it down the levels of the hierarchy
2125 /// and documents it at each level. After this is done the vector is
2126 /// interpolated up the levels of the hierarchy with the output
2127 /// being documented at each level
2128 //===================================================================
2129 template<unsigned DIM>
2131 {
2132 // Start the timer
2134
2135 // Notify user that the hierarchy of levels is complete
2136 oomph_info << "\nStarting the multigrid solver self-test." << std::endl;
2137
2138 // Resize the vector storing all of the restricted vectors
2139 Restriction_self_test_vectors_storage.resize(Nlevel);
2140
2141 // Resize the vector storing all of the interpolated vectors
2142 Interpolation_self_test_vectors_storage.resize(Nlevel);
2143
2144 // Find the number of dofs on the finest level
2145 unsigned n_dof = X_mg_vectors_storage[0].nrow();
2146
2147 // Need to set up the distribution of the finest level vector
2149 Mg_problem_pt->communicator_pt(), n_dof, false);
2150
2151#ifdef PARANOID
2152#ifdef OOMPH_HAS_MPI
2153 // Set up the warning messages
2154 std::string warning_message = "Setup of distribution has not been ";
2155 warning_message += "tested with MPI.";
2156
2157 // If we're not running the code in serial
2158 if (dist_pt->communicator_pt()->nproc() > 1)
2159 {
2160 // Throw a warning
2163 }
2164#endif
2165#endif
2166
2167 // Build the vector using the distribution of the restriction matrix
2168 Restriction_self_test_vectors_storage[0].build(dist_pt);
2169
2170 // Delete the distribution pointer
2171 delete dist_pt;
2172
2173 // Make it a null pointer
2174 dist_pt = 0;
2175
2176 // Now assign the values to the first vector. This will be restricted down
2177 // the levels of the hierarchy then back up
2178 set_self_test_vector();
2179
2180 // Call the restriction self-test
2181 restriction_self_test();
2182
2183 // Set the coarest level vector in Restriction_self_test_vectors_storage
2184 // to be the last entry in Interpolation_self_test_vectors_storage. This
2185 // will be interpolated up to the finest level in interpolation_self_test()
2186 Interpolation_self_test_vectors_storage[Nlevel - 1] =
2187 Restriction_self_test_vectors_storage[Nlevel - 1];
2188
2189 // Call the interpolation self-test
2190 interpolation_self_test();
2191
2192 // Destroy all of the created restriction self-test vectors
2193 Restriction_self_test_vectors_storage.resize(0);
2194
2195 // Destroy all of the created interpolation self-test vectors
2196 Interpolation_self_test_vectors_storage.resize(0);
2197
2198 // Stop the clock
2199 double t_st_end = TimingHelpers::timer();
2201 oomph_info << "\nCPU time for self-test [sec]: " << total_st_time
2202 << std::endl;
2203
2204 // Notify user that the hierarchy of levels is complete
2205 oomph_info << "\n====================Self-Test Complete===================="
2206 << std::endl;
2207 } // End of self_test
2208
2209 //===================================================================
2210 /// Sets the initial vector to be used in the restriction and
2211 /// interpolation self-tests
2212 //===================================================================
2213 template<unsigned DIM>
2215 {
2216 // Set up a pointer to the refineable mesh
2218 Mg_problem_pt->mg_bulk_mesh_pt();
2219
2220 // Find the number of elements in the bulk mesh
2221 unsigned n_el = bulk_mesh_pt->nelement();
2222
2223 // Get the dimension of the problem (assumed to be the dimension of any
2224 // node in the mesh)
2225 unsigned n_dim = bulk_mesh_pt->node_pt(0)->ndim();
2226
2227 // Find the number of nodes in an element
2228 unsigned nnod = bulk_mesh_pt->finite_element_pt(0)->nnode();
2229
2230 // Loop over all elements
2231 for (unsigned e = 0; e < n_el; e++)
2232 {
2233 // Get pointer to element
2234 FiniteElement* el_pt = bulk_mesh_pt->finite_element_pt(e);
2235
2236 // Loop over nodes
2237 for (unsigned j = 0; j < nnod; j++)
2238 {
2239 // Pointer to node
2240 Node* nod_pt = el_pt->node_pt(j);
2241
2242 // Sanity check
2243 if (nod_pt->nvalue() != 1)
2244 {
2245 // Set up an output stream
2246 std::ostringstream error_stream;
2247
2248 // Output the error message
2249 error_stream << "Sorry, not sure what to do here! I can't deal with "
2250 << nod_pt->nvalue() << " values!" << std::endl;
2251
2252 // Throw an error to indicate that it was not possible to plot the
2253 // data
2254 throw OomphLibError(error_stream.str(),
2257 }
2258
2259 // Free or pinned?
2260 int eqn_num = nod_pt->eqn_number(0);
2261
2262 // If it's a free node
2263 if (eqn_num >= 0)
2264 {
2265 // Initialise the variable coordinate_sum
2266 double coordinate_sum = 0.0;
2267
2268 // Loop over the coordinates of the node
2269 for (unsigned i = 0; i < n_dim; i++)
2270 {
2271 // Increment coordinate_sum by the value of x(i)
2272 coordinate_sum += nod_pt->x(i);
2273 }
2274
2275 // Store the value of pi in cache
2277
2278 // Make the vector represent a constant function
2279 Restriction_self_test_vectors_storage[0][eqn_num] =
2280 sin(pi * (nod_pt->x(0))) * sin(pi * (nod_pt->x(1)));
2281 }
2282 } // for (unsigned j=0;j<nnod;j++)
2283 } // for (unsigned e=0;e<n_el;e++)
2284
2285 // // Get the size of the vector
2286 // unsigned n_row=Restriction_self_test_vectors_storage[0].nrow();
2287
2288 // // Loop over the entries of the vector
2289 // for (unsigned i=0;i<n_row;i++)
2290 // {
2291 // // Make the vector represent a constant function
2292 // Restriction_self_test_vectors_storage[0][i]=1.0;
2293 // }
2294 } // End of set_self_test_vector
2295
2296 //===================================================================
2297 /// Function which implements a self-test to restrict the
2298 /// residual vector down all of the coarser grids and output
2299 /// the restricted vectors to file
2300 //===================================================================
2301 template<unsigned DIM>
2303 {
2304 // Set the start of the output file name. The full names will
2305 // set after we calculate the restricted vectors
2306 std::string outputfile = "RESLT/restriction_self_test";
2307
2308 // Loop over the levels of the hierarchy
2309 for (unsigned level = 0; level < Nlevel - 1; level++)
2310 {
2311 // Restrict the vector down to the next level
2312 Restriction_matrices_storage_pt[level]->multiply(
2313 Restriction_self_test_vectors_storage[level],
2314 Restriction_self_test_vectors_storage[level + 1]);
2315 } // End of the for loop over the hierarchy levels
2316
2317 // Loop over the levels of hierarchy to plot the restricted vectors
2318 for (unsigned level = 0; level < Nlevel; level++)
2319 {
2320 // Set output file name
2321 std::string filename = outputfile;
2322 std::stringstream string;
2323 string << level;
2324 filename += string.str();
2325 filename += ".dat";
2326
2327 // Plot the RHS vector on the current level
2328 plot(level, Restriction_self_test_vectors_storage[level], filename);
2329
2330 } // End of for-loop to output the RHS vector on each level
2331 } // End of restriction_self_test
2332
2333 //=======================================================================
2334 /// Function which implements a self-test to interpolate a
2335 /// vector up all of levels of the MG hierarchy and outputs the
2336 /// restricted vectors to file
2337 //=======================================================================
2338 template<unsigned DIM>
2340 {
2341 // Set the start of the output file name. The full names will
2342 // set after we calculate the interpolated vectors
2343 std::string outputfile = "RESLT/interpolation_self_test";
2344
2345 // Loop over the levels of the hierarchy in reverse order
2346 for (unsigned level = Nlevel - 1; level > 0; level--)
2347 {
2348 // Interpolate the vector up a level
2349 Interpolation_matrices_storage_pt[level - 1]->multiply(
2350 Interpolation_self_test_vectors_storage[level],
2351 Interpolation_self_test_vectors_storage[level - 1]);
2352 } // End of the for loop over the hierarchy levels
2353
2354 for (unsigned level = 0; level < Nlevel; level++)
2355 {
2356 // Set output file name
2357 std::string filename = outputfile;
2358 std::stringstream string;
2359 string << level;
2360 filename += string.str();
2361 filename += ".dat";
2362
2363 // Plot the RHS vector on the current level
2364 plot(level, Interpolation_self_test_vectors_storage[level], filename);
2365
2366 } // End of for-loop to output the RHS vector on each level
2367 } // End of interpolation_self_test
2368
2369 //===================================================================
2370 /// Plots the input vector (assuming we're dealing with scalar
2371 /// nodal data, otherwise I don't know how to implement this...)
2372 //===================================================================
2373 template<unsigned DIM>
2376 const std::string& filename)
2377 {
2378 // Setup an output file
2379 std::ofstream some_file;
2380 some_file.open(filename.c_str());
2381
2382 // Set up a pointer to the refineable mesh
2384 Mg_hierarchy[hierarchy_level]->mg_bulk_mesh_pt();
2385
2386 // Find the number of elements in the bulk mesh
2387 unsigned n_el = bulk_mesh_pt->nelement();
2388
2389 // Get the dimension of the problem (assumed to be the dimension of any
2390 // node in the mesh)
2391 unsigned n_dim = bulk_mesh_pt->node_pt(0)->ndim();
2392
2393 // Find the number of nodes in an element
2394 unsigned nnod = bulk_mesh_pt->finite_element_pt(0)->nnode();
2395
2396 // Find the number of nodes in an element in one direction
2397 unsigned nnod_1d = bulk_mesh_pt->finite_element_pt(0)->nnode_1d();
2398
2399 // Loop over all elements
2400 for (unsigned e = 0; e < n_el; e++)
2401 {
2402 // Get pointer to element
2403 FiniteElement* el_pt = bulk_mesh_pt->finite_element_pt(e);
2404
2405 // Input string for tecplot zone header (when plotting nnode_1d
2406 // points in each coordinate direction)
2407 some_file << el_pt->tecplot_zone_string(nnod_1d) << std::endl;
2408
2409 // Loop over nodes
2410 for (unsigned j = 0; j < nnod; j++)
2411 {
2412 // Pointer to node
2413 Node* nod_pt = el_pt->node_pt(j);
2414
2415 // Sanity check
2416 if (nod_pt->nvalue() != 1)
2417 {
2418 std::ostringstream error_stream;
2419
2420 error_stream << "Sorry, not sure what to do here!" << nod_pt->nvalue()
2421 << std::endl;
2422
2423 // Output the value of dimension to screen
2424 error_stream << "The dimension is: " << n_dim << std::endl;
2425
2426 // Output the values of x_i for all i
2427 for (unsigned i = 0; i < n_dim; i++)
2428 {
2429 error_stream << nod_pt->x(i) << " ";
2430 }
2431
2432 // End line
2433 error_stream << std::endl;
2434
2435 // Throw an error to indicate that it was
2436 // not possible to plot the data
2437 throw OomphLibError(error_stream.str(),
2440 }
2441
2442 // Output the coordinates of the node
2443 for (unsigned i = 0; i < n_dim; i++)
2444 {
2445 some_file << nod_pt->x(i) << " ";
2446 }
2447
2448 // Free or pinned?
2449 int e = nod_pt->eqn_number(0);
2450 if (e >= 0)
2451 {
2452 // Output the e-th entry if the node is free
2453 some_file << input_vector[e] << std::endl;
2454 }
2455 else
2456 {
2457 // Boundary node
2458 if (nod_pt->is_on_boundary())
2459 {
2460 // On the finest level the boundary data is pinned so set to 0
2461 if (hierarchy_level == 0)
2462 {
2463 some_file << 0.0 << std::endl;
2464 }
2465 // On any other level we output this data since it corresponds
2466 // to the correction on the boundary nodes
2467 else
2468 {
2469 some_file << input_vector[e] << std::endl;
2470 }
2471 }
2472 // Hanging node
2473 else if (nod_pt->is_hanging())
2474 {
2475 // Allocate space for a double to store the weighted sum
2476 // of the surrounding master nodes of the hanging node
2477 double hang_sum = 0.0;
2478
2479 // Find the number of master nodes of the hanging
2480 // the node in the reference element
2481 HangInfo* hang_info_pt = nod_pt->hanging_pt();
2482 unsigned nmaster = hang_info_pt->nmaster();
2483
2484 // Loop over the master nodes
2485 for (unsigned i_master = 0; i_master < nmaster; i_master++)
2486 {
2487 // Set up a pointer to the master node
2488 Node* master_node_pt = hang_info_pt->master_node_pt(i_master);
2489
2490 // Get the global equation number of this node
2491 int master_jj = master_node_pt->eqn_number(0);
2492
2493 // Is the master node a proper d.o.f.?
2494 if (master_jj >= 0)
2495 {
2496 // If the weight of the master node is non-zero
2497 if (hang_info_pt->master_weight(i_master) != 0.0)
2498 {
2499 hang_sum += hang_info_pt->master_weight(i_master) *
2501 }
2502 } // if (master_jj>=0)
2503 } // for (unsigned i_master=0;i_master<nmaster;i_master++)
2504
2505 // Output the weighted sum of the surrounding master nodes
2506 some_file << hang_sum << std::endl;
2507 }
2508 } // if (e>=0)
2509 } // for (unsigned j=0;j<nnod;j++)
2510 } // for (unsigned e=0;e<n_el;e++)
2511
2512 // Close output file
2513 some_file.close();
2514 } // End of plot
2515
2516 //===================================================================
2517 /// Linear solver
2518 //===================================================================
2519 template<unsigned DIM>
2521 {
2522 // If we're allowed to time things
2523 double t_mg_start = 0.0;
2524 if (!Suppress_v_cycle_output)
2525 {
2526 // Start the clock!
2528 }
2529
2530 // Current level
2531 unsigned finest_level = 0;
2532
2533 // Initialise the V-cycle counter
2534 V_cycle_counter = 0;
2535
2536 // Calculate the norm of the residual then output
2537 double normalised_residual_norm = residual_norm(finest_level);
2538 if (!Suppress_v_cycle_output)
2539 {
2540 oomph_info << "\nResidual on finest level for V-cycle: "
2541 << normalised_residual_norm << std::endl;
2542 }
2543
2544 // Outer loop over V-cycles
2545 //-------------------------
2546 // While the tolerance is not met and the maximum number of
2547 // V-cycles has not been completed
2548 while ((normalised_residual_norm > (this->Tolerance)) &&
2549 (V_cycle_counter != Nvcycle))
2550 {
2551 if (!Suppress_v_cycle_output)
2552 {
2553 // Output the V-cycle counter
2554 oomph_info << "\nStarting V-cycle: " << V_cycle_counter << std::endl;
2555 }
2556
2557 // Loop downwards over all levels that have coarser levels beneath them
2558 //---------------------------------------------------------------------
2559 for (unsigned i = 0; i < Nlevel - 1; i++)
2560 {
2561 // Initialise x_mg and residual_mg to 0.0 except for the finest level
2562 // since x_mg contains the current approximation to the solution and
2563 // residual_mg contains the RHS vector on the finest level
2564 if (i != 0)
2565 {
2566 // Loop over the entries in x_mg and residual_mg
2567 X_mg_vectors_storage[i].initialise(0.0);
2568 Residual_mg_vectors_storage[i].initialise(0.0);
2569 }
2570
2571 // Perform a few pre-smoothing steps and return vector that contains
2572 // the residuals of the linear system at this level.
2573 pre_smooth(i);
2574
2575 // Restrict the residual to the next coarser mesh and
2576 // assign it to the RHS vector at that level
2577 restrict_residual(i);
2578 } // Moving down the V-cycle
2579
2580 // Reached the lowest level: Do a direct solve, using the RHS vector
2581 //------------------------------------------------------------------
2582 // obtained by restriction from above.
2583 //------------------------------------
2584 direct_solve();
2585
2586 // Loop upwards over all levels that have finer levels above them
2587 //---------------------------------------------------------------
2588 for (unsigned i = Nlevel - 1; i > 0; i--)
2589 {
2590 // Interpolate solution at current level onto
2591 // next finer mesh and correct the solution x at that level
2592 interpolate_and_correct(i);
2593
2594 // Perform a few post-smoothing steps (ignore
2595 // vector that contains the residuals of the linear system
2596 // at this level)
2597 post_smooth(i - 1);
2598 }
2599
2600 // Calculate the new residual norm then output (if allowed)
2601 normalised_residual_norm = residual_norm(finest_level);
2602
2603 // If required, we will document the convergence history to screen or file
2604 // (if stream open)
2605 if (Doc_convergence_history)
2606 {
2607 if (!Output_file_stream.is_open())
2608 {
2609 oomph_info << V_cycle_counter << " " << normalised_residual_norm
2610 << std::endl;
2611 }
2612 else
2613 {
2614 Output_file_stream << V_cycle_counter << " "
2615 << normalised_residual_norm << std::endl;
2616 }
2617 } // if (Doc_convergence_history)
2618
2619 // Set counter for number of cycles (for doc)
2620 V_cycle_counter++;
2621
2622 // Print the residual on the finest level
2623 if (!Suppress_v_cycle_output)
2624 {
2625 oomph_info << "Residual on finest level of V-cycle: "
2626 << normalised_residual_norm << std::endl;
2627 }
2628 } // End of the V-cycles
2629
2630 // Copy the solution into the result vector
2631 result = X_mg_vectors_storage[finest_level];
2632
2633 // Need an extra line space if V-cycle output is suppressed
2634 if (!Suppress_v_cycle_output)
2635 {
2636 oomph_info << std::endl;
2637 }
2638
2639 // If all output is to be suppressed
2640 if (!Suppress_all_output)
2641 {
2642 // Output number of V-cycles taken to solve
2643 if (normalised_residual_norm < (this->Tolerance))
2644 {
2645 // Indicate that the problem has been solved
2646 Has_been_solved = true;
2647 }
2648 } // if (!Suppress_all_output)
2649
2650 // If the V-cycle output isn't suppressed
2651 if (!Suppress_v_cycle_output)
2652 {
2653 // Stop the clock
2654 double t_mg_end = TimingHelpers::timer();
2656 oomph_info << "CPU time for MG solver [sec]: " << total_G_setup_time
2657 << std::endl;
2658 }
2659 } // End of mg_solve
2660} // End of namespace oomph
2661
2662#endif
e
Definition cfortran.h:571
static char t char * s
Definition cfortran.h:568
cstr elem_len * i
Definition cfortran.h:603
A class for compressed row matrices. This is a distributable object.
Definition matrices.h:888
unsigned nvalue() const
Return number of values stored in data object (incl pinned ones).
Definition nodes.h:483
long & eqn_number(const unsigned &i)
Return the equation number of the i-th stored variable.
Definition nodes.h:367
unsigned nrow() const
access function to the number of global rows.
A vector in the mathematical sense, initially developed for linear algebra type applications....
A general Finite Element class.
Definition elements.h:1317
void position(const Vector< double > &zeta, Vector< double > &r) const
Return the parametrised position of the FiniteElement in its incarnation as a GeomObject,...
Definition elements.h:2680
virtual void local_coordinate_of_node(const unsigned &j, Vector< double > &s) const
Get local coordinates of node j in the element; vector sets its own size (broken virtual)
Definition elements.h:1846
virtual std::string tecplot_zone_string(const unsigned &nplot) const
Return string for tecplot zone header (when plotting nplot points in each "coordinate direction")
Definition elements.h:3165
double size() const
Calculate the size of the element (length, area, volume,...) in Eulerian computational coordinates....
Definition elements.cc:4320
void locate_zeta(const Vector< double > &zeta, GeomObject *&geom_object_pt, Vector< double > &s, const bool &use_coordinate_as_initial_guess=false)
For a given value of zeta, the "global" intrinsic coordinate of a mesh of FiniteElements represented ...
Definition elements.cc:4764
virtual void shape(const Vector< double > &s, Shape &psi) const =0
Calculate the geometric shape functions at local coordinate s. This function must be overloaded for e...
unsigned dim() const
Return the spatial dimension of the element, i.e. the number of local coordinates required to paramet...
Definition elements.h:2615
unsigned nnode() const
Return the number of nodes.
Definition elements.h:2214
Node *& node_pt(const unsigned &n)
Return a pointer to the local node n.
Definition elements.h:2179
virtual unsigned nnode_1d() const
Return the number of nodes along one edge of the element Default is to return zero — must be overload...
Definition elements.h:2222
unsigned ndof() const
Return the number of equations/dofs in the element.
Definition elements.h:822
unsigned long eqn_number(const unsigned &ieqn_local) const
Return the global equation number corresponding to the ieqn_local-th local equation number.
Definition elements.h:691
A geometric object is an object that provides a parametrised description of its shape via the functio...
Class that contains data for hanging nodes.
Definition nodes.h:742
Base class for all linear iterative solvers. This merely defines standard interfaces for linear itera...
double Tolerance
Convergence tolerance.
Describes the distribution of a distributable linear algebra type object. Typically this is a contain...
bool Doc_time
Boolean flag that indicates whether the time taken.
An interface to allow scalar MG to be used as a Preconditioner.
MGPreconditioner(const MGPreconditioner &)=delete
Broken copy constructor.
void setup()
Function to set up a preconditioner for the linear system.
MGPreconditioner(MGProblem *mg_problem_pt)
Constructor.
void operator=(const MGPreconditioner &)=delete
Broken assignment operator.
void clean_up_memory()
Clean up memory.
~MGPreconditioner()
Destructor (empty)
virtual void preconditioner_solve(const DoubleVector &rhs, DoubleVector &z)
Function applies MG to the vector r for a full solve.
MGProblem class; subclass of Problem.
virtual TreeBasedRefineableMeshBase * mg_bulk_mesh_pt()=0
Function to get a pointer to the mesh we will be working with. If there are flux elements present in ...
MGProblem()
Constructor. Initialise pointers to coarser and finer levels.
virtual MGProblem * make_new_problem()=0
This function needs to be implemented in the derived problem: Returns a pointer to a new object of th...
virtual ~MGProblem()
Destructor (empty)
void set_self_test_vector()
Makes a vector which will be used in the self-test. Is currently set to make the entries of the vecto...
Smoother *(* PostSmootherFactoryFctPt)()
typedef for a function that returns a pointer to an object of the class Smoother to be used as the po...
void set_pre_smoother_factory_function(PreSmootherFactoryFctPt pre_smoother_fn)
Access function to set the pre-smoother creation function.
bool Suppress_all_output
If this is set to true then all output from the solver is suppressed. This is protected member data s...
void full_setup()
Do a full setup (assumes everything will be setup around the MGProblem pointer given in the construct...
Vector< DoubleVector > Rhs_mg_vectors_storage
Vector to store the RHS vectors (Rhs_mg). This is protected to allow the multigrid preconditioner to ...
void setup_mg_structures()
Function to set up the hierachy of levels. Creates a vector of pointers to each MG level.
void interpolation_self_test()
Make a self-test to make sure that the interpolation matrices are doing the same thing to interpolate...
void level_up_local_coord_of_node(const int &son_type, Vector< double > &s)
Given the son_type of an element and a local node number j in that element with nnode_1d nodes per co...
unsigned V_cycle_counter
Pointer to counter for V-cycles.
Vector< Smoother * > Post_smoothers_storage_pt
Vector to store the post-smoothers.
PostSmootherFactoryFctPt Post_smoother_factory_function_pt
Function to create post-smoothers.
Vector< DoubleVector > Residual_mg_vectors_storage
Vector to store the residual vectors.
bool Doc_everything
If this is set to true we document everything. In addition to outputting the information of the setup...
void setup_transfer_matrices()
Setup the transfer matrices on each level.
void enable_v_cycle_output()
Enable the output of the V-cycle timings and other output.
void setup_interpolation_matrices()
Setup the interpolation matrix on each level.
void post_smooth(const unsigned &level)
Post-smoother: Perform max_iter smoothing steps on the linear system Ax=b with current RHS vector,...
void set_post_smoother_factory_function(PostSmootherFactoryFctPt post_smoother_fn)
Access function to set the post-smoother creation function.
unsigned Nlevel
The number of levels in the multigrid heirachy.
void plot(const unsigned &hierarchy_level, const DoubleVector &input_vector, const std::string &filename)
Given a level in the hierarchy, an input vector and a filename this function will document the given ...
unsigned & npost_smooth()
Return the number of post-smoothing iterations (lvalue)
Vector< Smoother * > Pre_smoothers_storage_pt
Vector to store the pre-smoothers.
std::ostream * Stream_pt
Pointer to the output stream – defaults to std::cout. This is protected member data to allow the prec...
void set_restriction_matrices_as_interpolation_transposes()
Builds a CRDoubleMatrix on each level that is used to restrict the residual between levels....
Smoother *(* PreSmootherFactoryFctPt)()
typedef for a function that returns a pointer to an object of the class Smoother to be used as the pr...
MGSolver(MGProblem *mg_problem_pt)
Constructor: Set up default values for number of V-cycles and pre- and post-smoothing steps.
void solve(Problem *const &problem_pt, DoubleVector &result)
Virtual function in the base class that needs to be implemented later but for now just leave it empty...
void mg_solve(DoubleVector &result)
Do the actual solve – this is called through the pure virtual solve function in the LinearSolver base...
void disable_output()
Suppress anything that can be suppressed, i.e. any timings. Things like mesh adaptation can not howev...
void restrict_residual(const unsigned &level)
Restrict residual (computed on level-th MG level) to the next coarser mesh and stick it into the coar...
Vector< DoubleVector > Restriction_self_test_vectors_storage
Vector to store the result of restriction on each level (only required if the user wishes to document...
void interpolation_matrix_set(const unsigned &level, Vector< double > &value, Vector< int > &col_index, Vector< int > &row_st, unsigned &ncol, unsigned &nrow)
Builds a CRDoubleMatrix that is used to interpolate the residual between levels. The transpose can be...
void disable_smoother_and_superlu_doc_time()
Suppress the output of both smoothers and SuperLU.
void clean_up_memory()
Clean up anything that needs to be cleaned up.
void self_test()
Makes a vector, restricts it down the levels of the hierarchy and documents it at each level....
Vector< CRDoubleMatrix * > Restriction_matrices_storage_pt
Vector to store the restriction matrices.
void setup_mg_hierarchy()
Function to set up the hierachy of levels. Creates a vector of pointers to each MG level.
Vector< CRDoubleMatrix * > Mg_matrices_storage_pt
Vector to store the system matrices.
void setup_interpolation_matrices_unstructured()
Setup the interpolation matrix on each level (used for unstructured meshes)
void direct_solve()
Call the direct solver (SuperLU) to solve the problem exactly.
void setup_smoothers()
Function to set up all of the smoothers once the system matrices have been set up.
unsigned Npost_smooth
Number of post-smoothing steps.
void disable_v_cycle_output()
Disable all output from mg_solve apart from the number of V-cycles used to solve the problem.
~MGSolver()
Delete any dynamically allocated data.
unsigned Nvcycle
Maximum number of V-cycles (this is set as a protected variable so.
Vector< MGProblem * > Mg_hierarchy
Vector containing pointers to problems in hierarchy.
Vector< DoubleVector > Interpolation_self_test_vectors_storage
Vector to store the result of interpolation on each level (only required if the user wishes to docume...
Vector< CRDoubleMatrix * > Interpolation_matrices_storage_pt
Vector to store the interpolation matrices.
void enable_output()
Enable the output from anything that could have been suppressed.
void enable_doc_everything()
Enable the output from anything that could have been suppressed.
void pre_smooth(const unsigned &level)
Pre-smoother: Perform 'max_iter' smoothing steps on the linear system Ax=b with current RHS vector,...
bool Suppress_v_cycle_output
Indicates whether or not the V-cycle output should be suppressed. Needs to be protected member data f...
unsigned Npre_smooth
Number of pre-smoothing steps.
unsigned & npre_smooth()
Return the number of pre-smoothing iterations (lvalue)
unsigned iterations() const
Number of iterations.
void restriction_self_test()
Make a self-test to make sure that the interpolation matrices are doing the same thing to restrict th...
Vector< DoubleVector > X_mg_vectors_storage
Vector to store the solution vectors (X_mg)
void modify_restriction_matrices()
Normalise the rows of the restriction matrices to avoid amplifications when projecting to the coarser...
void interpolation_matrix_set(const unsigned &level, double *value, int *col_index, int *row_st, unsigned &ncol, unsigned &nnz)
Builds a CRDoubleMatrix that is used to interpolate the residual between levels. The transpose can be...
void interpolate_and_correct(const unsigned &level)
Interpolate solution at current level onto next finer mesh and correct the solution x at that level.
double residual_norm(const unsigned &level)
Return norm of residual r=b-Ax and the residual vector itself on the level-th level.
bool Has_been_setup
Boolean variable to indicate whether or not the solver has been setup.
MGProblem * Mg_problem_pt
Pointer to the MG problem (deep copy). This is protected to provide access to the MG preconditioner.
bool Has_been_solved
Boolean variable to indicate whether or not the problem was successfully solved.
PreSmootherFactoryFctPt Pre_smoother_factory_function_pt
Function to create pre-smoothers.
unsigned & max_iter()
Number of iterations.
static OomphCommunicator * communicator_pt()
access to global communicator. This is the oomph-lib equivalent of MPI_COMM_WORLD
This class provides a GeomObject representation of a given finite element mesh. The Lagrangian coordi...
A general mesh class.
Definition mesh.h:67
Node *& node_pt(const unsigned long &n)
Return pointer to global node n.
Definition mesh.h:440
Nodes are derived from Data, but, in addition, have a definite (Eulerian) position in a space of a gi...
Definition nodes.h:906
unsigned ndim() const
Return (Eulerian) spatial dimension of the node.
Definition nodes.h:1054
bool is_hanging() const
Test whether the node is geometrically hanging.
Definition nodes.h:1285
HangInfo *const & hanging_pt() const
Return pointer to hanging node data (this refers to the geometric hanging node status) (const version...
Definition nodes.h:1228
std::ostream *& stream_pt()
Access function for the stream pointer.
An OomphLibError object which should be thrown when an run-time error is encountered....
An OomphLibWarning object which should be created as a temporary object to issue a warning....
Preconditioner base class. Gives an interface to call all other preconditioners through and stores th...
////////////////////////////////////////////////////////////////// //////////////////////////////////...
Definition problem.h:154
unsigned long ndof() const
Return the number of dofs.
Definition problem.h:1704
Mesh *& mesh_pt()
Return a pointer to the global mesh.
Definition problem.h:1300
RefineableElements are FiniteElements that may be subdivided into children to provide a better local ...
Base class for refineable meshes. Provides standardised interfaces for the following standard mesh ad...
A Class for shape functions. In simple cases, the shape functions have only one index that can be tho...
Definition shape.h:76
Smoother class: The smoother class is designed for to be used in conjunction with multigrid....
TAdvectionDiffusionReactionElement<NREAGENT,DIM,NNODE_1D> elements are isoparametric triangular DIM-d...
TAdvectionDiffusionReactionElement()
Constructor: Call constructors for TElement and AdvectionDiffusionReaction equations.
Base class for tree-based refineable meshes.
static const int OMEGA
Default value for an unassigned neighbour.
Definition tree.h:262
const double Pi
50 digits from maple
double timer()
returns the time in seconds after some point in past
DRAIG: Change all instances of (SPATIAL_DIM) to (DIM-1).
Nullstream oomph_nullstream
Single (global) instantiation of the Nullstream.
OomphInfo oomph_info
Single (global) instantiation of the OomphInfo object – this is used throughout the library as a "rep...