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