oomph::Z2ErrorEstimator Class Reference

#include <error_estimator.h>

+ Inheritance diagram for oomph::Z2ErrorEstimator:

Public Types

typedef double(* CombinedErrorEstimateFctPt) (const Vector< double > &errors)
 Function pointer to combined error estimator function. More...
 

Public Member Functions

 Z2ErrorEstimator (const unsigned &recovery_order)
 Constructor: Set order of recovery shape functions. More...
 
 Z2ErrorEstimator ()
 
 Z2ErrorEstimator (const Z2ErrorEstimator &)=delete
 Broken copy constructor. More...
 
void operator= (const Z2ErrorEstimator &)=delete
 Broken assignment operator. More...
 
virtual ~Z2ErrorEstimator ()
 Empty virtual destructor. More...
 
void get_element_errors (Mesh *&mesh_pt, Vector< double > &elemental_error)
 
void get_element_errors (Mesh *&mesh_pt, Vector< double > &elemental_error, DocInfo &doc_info)
 
unsignedrecovery_order ()
 Access function for order of recovery polynomials. More...
 
unsigned recovery_order () const
 Access function for order of recovery polynomials (const version) More...
 
CombinedErrorEstimateFctPtcombined_error_fct_pt ()
 Access function: Pointer to combined error estimate function. More...
 
CombinedErrorEstimateFctPt combined_error_fct_pt () const
 
void setup_patches (Mesh *&mesh_pt, std::map< Node *, Vector< ElementWithZ2ErrorEstimator * > * > &adjacent_elements_pt, Vector< Node * > &vertex_node_pt)
 
doublereference_flux_norm ()
 Access function for prescribed reference flux norm. More...
 
double reference_flux_norm () const
 Access function for prescribed reference flux norm (const. version) More...
 
double get_combined_error_estimate (const Vector< double > &compound_error)
 Return a combined error estimate from all compound errors. More...
 
- Public Member Functions inherited from oomph::ErrorEstimator
 ErrorEstimator ()
 Default empty constructor. More...
 
 ErrorEstimator (const ErrorEstimator &)=delete
 Broken copy constructor. More...
 
void operator= (const ErrorEstimator &)=delete
 Broken assignment operator. More...
 
virtual ~ErrorEstimator ()
 Empty virtual destructor. More...
 
void get_element_errors (Mesh *&mesh_pt, Vector< double > &elemental_error)
 

Private Member Functions

void get_recovered_flux_in_patch (const Vector< ElementWithZ2ErrorEstimator * > &patch_el_pt, const unsigned &num_recovery_terms, const unsigned &num_flux_terms, const unsigned &dim, DenseMatrix< double > *&recovered_flux_coefficient_pt)
 
unsigned nrecovery_terms (const unsigned &dim)
 
void shape_rec (const Vector< double > &x, const unsigned &dim, Vector< double > &psi_r)
 
Integralintegral_rec (const unsigned &dim, const bool &is_q_mesh)
 
void doc_flux (Mesh *mesh_pt, const unsigned &num_flux_terms, MapMatrixMixed< Node *, int, double > &rec_flux_map, const Vector< double > &elemental_error, DocInfo &doc_info)
 Doc flux and recovered flux. More...
 

Private Attributes

unsigned Recovery_order
 Order of recovery polynomials. More...
 
bool Recovery_order_from_first_element
 
double Reference_flux_norm
 Prescribed reference flux norm. More...
 
CombinedErrorEstimateFctPt Combined_error_fct_pt
 Function pointer to combined error estimator function. More...
 

Detailed Description

Z2-error-estimator: Elements that can be used with Z2 error estimation should be derived from the base class ElementWithZ2ErrorEstimator and implement its pure virtual member functions to provide the following functionality:

  • pointer-based access to the vertex nodes in the element (this is required to facilitate setup of element patches).
  • the computation of a suitable "flux vector" which represents a quantity whose FE representation is discontinuous across element boundaries but would become continuous under infinite mesh refinement.

As an example consider the finite element solution of the Laplace problem, \( \partial^2 u/\partial x_i^2 = 0 \). If we approximate the unknown \( u \) on a finite element mesh with \( N \) nodes as

\[ u^{[FE]}(x_k) = \sum_{j=1}^{N} U_j \ \psi_j(x_k), \]

where the \( \psi_j(x_k) \) are the (global) \( C^0 \) basis functions, the finite-element representation of the flux, \( f_i = \partial u/\partial x_i \),

\[ f_i^{[FE]} = \sum_{j=1}^{N} U_j \ \frac{\partial \psi_j}{\partial x_i} \]

is discontinuous between elements but the magnitude of the jump decreases under mesh refinement. We denote the number of flux terms by \(N_{flux}\), so for a 2D (3D) Laplace problem, \(N_{flux}=2 \ (3).\)

The idea behind Z2 error estimation is to compute an approximation for the flux that is more accurate than the value \( f_i^{[FE]} \) obtained directly from the finite element solution. We refer to the improved approximation for the flux as the "recovered flux" and denote it by \( f_i^{[rec]} \). Since \( f_i^{[rec]} \) is more accurate than \( f_i^{[FE]} \), the difference between the two provides a measure of the error. In Z2 error estimation, the "recovered flux" is determined by projecting the discontinuous, FE-based flux \( f_i^{[FE]} \) onto a set of continuous basis functions, known as the "recovery shape functions". In principle, one could use the finite element shape functions \( \psi_j(x_k) \) as the recovery shape functions but then the determination of the recovered flux would involve the solution of a linear system that is as big as the original problem. To avoid this, the projection is performed over small patches of elements within which low-order polynomials (defined in terms of the global, Eulerian coordinates) are used as the recovery shape functions.

Z2 error estimation is known to be "optimal" for many self-adjoint problems. See, e.g., Zienkiewicz & Zhu's original papers "The superconvergent patch recovery and a posteriori error estimates." International Journal for Numerical Methods in Engineering 33 (1992) Part I: 1331-1364; Part II: 1365-1382. In non-self adjoint problems, the error estimator only analyses the "self-adjoint part" of the differential operator. However, in many cases, this still produces good error indicators since the error estimator detects under-resolved, sharp gradients in the solution.

Z2 error estimation works as follows:

  1. We combine the elements in the mesh into a number of "patches", which consist of all elements that share a common vertex node. Most elements will therefore be members of multiple patches.
  2. Within each patch \(p\), we expand the "recovered flux" as

    \[ f^{[rec](p)}_i(x_k) = \sum_{j=1}^{N_{rec}} F^{(p)}_{ij} \ \psi^{[rec]}_j(x_k) \mbox{ \ \ \ for $i=1,...,N_{flux}$,} \]

    where the functions \( \psi^{[rec]}_j(x_k)\) are the recovery shape functions, which are functions of the global, Eulerian coordinates. Typically, these are chosen to be low-order polynomials. For instance, in 2D, a bilinear representation of \( f^{(p)}_i(x_0,x_1) \) involves the \(N_{rec}=3\) recovery shape functions \( \psi^{[rec]}_0(x_0,x_1)=1, \ \psi^{[rec]}_1(x_0,x_1)=x_0 \) and \( \psi^{[rec]}_2(x_0,x_1)=x_1\).

    We determine the coefficients \( F^{(p)}_{ij} \) by enforcing \( f^{(p)}_i(x_k) = f^{[FE]}_i(x_k)\) in its weak form:

    \[ \int_{\mbox{Patch $p$}} \left( f^{[FE]}_i(x_k) - \sum_{j=1}^{N_{rec}} F^{(p)}_{ij} \ \psi^{[rec]}_j(x_k) \right) \psi^{[rec]}_l(x_k)\ dv = 0 \mbox{ \ \ \ \ for $l=1,...,N_{rec}$ and $i=1,...,N_{flux}$}. \]

    Once the \( F^{(p)}_{ij} \) are determined in a given patch, we determine the values of the recovered flux at all nodes that are part of the patch. We denote the value of the recovered flux at node \( k \) by \( {\cal F}^{(p)}_{ik}\).

    We repeat this procedure for every patch. For nodes that are part of multiple patches, the procedure will provide multiple, slightly different nodal values for the recovered flux. We average these values via

    \[ {\cal F}_{ij} = \frac{1}{N_p(j)} \sum_{\mbox{Node $j \in $ patch $p$}} {\cal F}^{(p)}_{ij}, \]

    where \(N_p(j)\) denotes the number of patches that node \( j\) is a member of. This allows us to obtain a globally-continuous, finite-element based representation of the recovered flux as

    \[ f_i^{[rec]} = \sum_{j=1}^{N} {\cal F}_{ij}\ \psi_j, \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (1) \]

    where the \( \psi_j \) are the (global) finite element shape functions.

  3. Since the recovered flux in (1), is based on nodal values, we can evaluate it locally within each of the \( N_e\) elements in the mesh to obtain a normalised elemental error estimate via

    \[ E_{e} = \sqrt{ \frac{ \int_{\mbox{$e$}} \left( f_i^{[rec]} - f_i^{[FE]} \right)^2 dv} {\sum_{e'=1}^{N_e} \int_{\mbox{$e'$}} \left( f_i^{[rec]} \right)^2 dv} } \mbox{\ \ \ for $e=1,...,N_e$.} \]

    In this (default) form, mesh refinement, based on this error estimate will lead to an equidistribution of the error across all elements. Usually, this is the desired behaviour. However, there are cases in which the solution evolves towards a state in which the flux tends to zero while the solution itself becomes so simple that it can be represented exactly on any finite element mesh (e.g. in spin-up problems in which the fluid motion approaches a rigid body rotation). In that case the mesh adaptation tries to equidistribute any small roundoff errors in the solution, causing strong, spatially uniform mesh refinement throughout the domain, even though the solution is already fully converged. For such cases, it is possible to replace the denominator in the above expression by a prescribed reference flux, which may be specified via the access function

    double & reference_flux_norm()
    Access function for prescribed reference flux norm.
    Definition: error_estimator.h:355

Member Typedef Documentation

◆ CombinedErrorEstimateFctPt

typedef double(* oomph::Z2ErrorEstimator::CombinedErrorEstimateFctPt) (const Vector< double > &errors)

Function pointer to combined error estimator function.

Constructor & Destructor Documentation

◆ Z2ErrorEstimator() [1/3]

oomph::Z2ErrorEstimator::Z2ErrorEstimator ( const unsigned recovery_order)
inline

Constructor: Set order of recovery shape functions.

275  Reference_flux_norm(0.0),
277  {
278  }
CombinedErrorEstimateFctPt Combined_error_fct_pt
Function pointer to combined error estimator function.
Definition: error_estimator.h:420
unsigned Recovery_order
Order of recovery polynomials.
Definition: error_estimator.h:403
double Reference_flux_norm
Prescribed reference flux norm.
Definition: error_estimator.h:417
unsigned & recovery_order()
Access function for order of recovery polynomials.
Definition: error_estimator.h:322
bool Recovery_order_from_first_element
Definition: error_estimator.h:407

◆ Z2ErrorEstimator() [2/3]

oomph::Z2ErrorEstimator::Z2ErrorEstimator ( )
inline

Constructor: Leave order of recovery shape functions open for now – they will be read out from first element of the mesh when the error estimator is applied

285  : Recovery_order(0),
287  Reference_flux_norm(0.0),
289  {
290  }

◆ Z2ErrorEstimator() [3/3]

oomph::Z2ErrorEstimator::Z2ErrorEstimator ( const Z2ErrorEstimator )
delete

Broken copy constructor.

◆ ~Z2ErrorEstimator()

virtual oomph::Z2ErrorEstimator::~Z2ErrorEstimator ( )
inlinevirtual

Empty virtual destructor.

299 {}

Member Function Documentation

◆ combined_error_fct_pt() [1/2]

CombinedErrorEstimateFctPt& oomph::Z2ErrorEstimator::combined_error_fct_pt ( )
inline

Access function: Pointer to combined error estimate function.

335  {
336  return Combined_error_fct_pt;
337  }

References Combined_error_fct_pt.

◆ combined_error_fct_pt() [2/2]

CombinedErrorEstimateFctPt oomph::Z2ErrorEstimator::combined_error_fct_pt ( ) const
inline

Access function: Pointer to combined error estimate function. Const version

342  {
343  return Combined_error_fct_pt;
344  }

References Combined_error_fct_pt.

◆ doc_flux()

void oomph::Z2ErrorEstimator::doc_flux ( Mesh mesh_pt,
const unsigned num_flux_terms,
MapMatrixMixed< Node *, int, double > &  rec_flux_map,
const Vector< double > &  elemental_error,
DocInfo doc_info 
)
private

Doc flux and recovered flux.

Doc FE and recovered flux.

1808  {
1809 #ifdef OOMPH_HAS_MPI
1810 
1811  // Get communicator from mesh
1812  OomphCommunicator* comm_pt = mesh_pt->communicator_pt();
1813 
1814 #else
1815 
1816  // Dummy communicator
1817  OomphCommunicator* comm_pt = MPI_Helpers::communicator_pt();
1818 
1819 #endif
1820 
1821  // Setup output files
1822  std::ofstream some_file, feflux_file;
1823  std::ostringstream filename;
1824  filename << doc_info.directory() << "/flux_rec" << doc_info.number()
1825  << "_on_proc_" << comm_pt->my_rank() << ".dat";
1826  some_file.open(filename.str().c_str());
1827  filename.str("");
1828  filename << doc_info.directory() << "/flux_fe" << doc_info.number()
1829  << "_on_proc_" << comm_pt->my_rank() << ".dat";
1830  feflux_file.open(filename.str().c_str());
1831 
1832  unsigned nel = mesh_pt->nelement();
1833  if (nel > 0)
1834  {
1835  // Extract first element to determine spatial dimension
1836  FiniteElement* el_pt = mesh_pt->finite_element_pt(0);
1837  unsigned dim = el_pt->dim();
1838  Vector<double> s(dim);
1839 
1840  // Decide on the number of plot points
1841  unsigned nplot = 5;
1842 
1843  // Loop over all elements
1844  for (unsigned e = 0; e < nel; e++)
1845  {
1846  ElementWithZ2ErrorEstimator* el_pt =
1847  dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(e));
1848 
1849  // Write tecplot header
1850  feflux_file << el_pt->tecplot_zone_string(nplot);
1851  some_file << el_pt->tecplot_zone_string(nplot);
1852 
1853  unsigned num_plot_points = el_pt->nplot_points(nplot);
1854  for (unsigned iplot = 0; iplot < num_plot_points; iplot++)
1855  {
1856  // Get local coordinates of plot point
1857  el_pt->get_s_plot(iplot, nplot, s);
1858 
1859  // Coordinate
1860  Vector<double> x(dim);
1861  el_pt->interpolated_x(s, x);
1862 
1863  // Number of FE nodes
1864  unsigned n_node = el_pt->nnode();
1865 
1866  // FE shape function
1867  Shape psi(n_node);
1868 
1869  // Get values of FE shape function
1870  el_pt->shape(s, psi);
1871 
1872  // Initialise recovered flux Vector
1873  Vector<double> rec_flux(num_flux_terms, 0.0);
1874 
1875  // Loop over nodes to assemble contribution
1876  for (unsigned n = 0; n < n_node; n++)
1877  {
1878  Node* nod_pt = el_pt->node_pt(n);
1879 
1880  // Loop over components
1881  for (unsigned i = 0; i < num_flux_terms; i++)
1882  {
1883  rec_flux[i] += rec_flux_map(nod_pt, i) * psi[n];
1884  }
1885  }
1886 
1887  // FE flux
1888  Vector<double> fe_flux(num_flux_terms);
1889  el_pt->get_Z2_flux(s, fe_flux);
1890 
1891  for (unsigned i = 0; i < dim; i++)
1892  {
1893  some_file << x[i] << " ";
1894  }
1895  for (unsigned i = 0; i < num_flux_terms; i++)
1896  {
1897  some_file << rec_flux[i] << " ";
1898  }
1899  some_file << elemental_error[e] << " " << std::endl;
1900 
1901 
1902  for (unsigned i = 0; i < dim; i++)
1903  {
1904  feflux_file << x[i] << " ";
1905  }
1906  for (unsigned i = 0; i < num_flux_terms; i++)
1907  {
1908  feflux_file << fe_flux[i] << " ";
1909  }
1910  feflux_file << elemental_error[e] << " " << std::endl;
1911  }
1912  }
1913 
1914 
1915  // Write tecplot footer (e.g. FE connectivity lists)
1916  // using the first element's output info.
1917  FiniteElement* first_el_pt = mesh_pt->finite_element_pt(0);
1918  first_el_pt->write_tecplot_zone_footer(some_file, nplot);
1919  first_el_pt->write_tecplot_zone_footer(feflux_file, nplot);
1920  }
1921 
1922  some_file.close();
1923  feflux_file.close();
1924  }
int i
Definition: BiCGSTAB_step_by_step.cpp:9
const unsigned n
Definition: CG3DPackingUnitTest.cpp:11
Array< double, 1, 3 > e(1./3., 0.5, 2.)
static OomphCommunicator * communicator_pt()
access to the global oomph-lib communicator
Definition: oomph_utilities.cc:1046
RealScalar s
Definition: level1_cplx_impl.h:130
string filename
Definition: MergeRestartFiles.py:39
list x
Definition: plotDoE.py:28

References oomph::MPI_Helpers::communicator_pt(), oomph::Mesh::communicator_pt(), oomph::FiniteElement::dim(), oomph::DocInfo::directory(), e(), oomph::Mesh::element_pt(), MergeRestartFiles::filename, oomph::Mesh::finite_element_pt(), oomph::FiniteElement::get_s_plot(), oomph::ElementWithZ2ErrorEstimator::get_Z2_flux(), i, oomph::FiniteElement::interpolated_x(), oomph::OomphCommunicator::my_rank(), n, oomph::Mesh::nelement(), oomph::FiniteElement::nnode(), oomph::FiniteElement::node_pt(), oomph::FiniteElement::nplot_points(), oomph::DocInfo::number(), s, oomph::FiniteElement::shape(), oomph::FiniteElement::tecplot_zone_string(), oomph::FiniteElement::write_tecplot_zone_footer(), and plotDoE::x.

Referenced by get_element_errors().

◆ get_combined_error_estimate()

double oomph::Z2ErrorEstimator::get_combined_error_estimate ( const Vector< double > &  compound_error)

Return a combined error estimate from all compound errors.

Return a combined error estimate from all compound flux errors The default is to return the maximum of the compound flux errors which will always force refinment if any field is above the single mesh error threshold and unrefinement if both are below the lower limit. Any other fancy combinations can be selected by specifying a user-defined combined estimate by setting a function pointer.

462  {
463  // If the function pointer has been set, call that function
464  if (Combined_error_fct_pt != 0)
465  {
466  return (*Combined_error_fct_pt)(compound_error);
467  }
468 
469  // Otherwise simply return the maximum of the compound errors
470  const unsigned n_compound_error = compound_error.size();
471 // If there are no errors then we have a problem
472 #ifdef PARANOID
473  if (n_compound_error == 0)
474  {
475  throw OomphLibError(
476  "No compound errors have been passed, so maximum cannot be found.",
479  }
480 #endif
481 
482 
483  // Initialise the maxmimum to the first compound error
484  double max_error = compound_error[0];
485  // Loop over the other errors to find the maximum
486  //(we have already taken absolute values, so we don't need to do so here)
487  for (unsigned i = 1; i < n_compound_error; i++)
488  {
489  if (compound_error[i] > max_error)
490  {
491  max_error = compound_error[i];
492  }
493  }
494 
495  // Return the maximum
496  return max_error;
497  }
double max_error
Definition: MortaringCantileverCompareToNonMortaring.cpp:188
#define OOMPH_EXCEPTION_LOCATION
Definition: oomph_definitions.h:61
#define OOMPH_CURRENT_FUNCTION
Definition: oomph_definitions.h:86

References Combined_error_fct_pt, i, MeshRefinement::max_error, OOMPH_CURRENT_FUNCTION, and OOMPH_EXCEPTION_LOCATION.

Referenced by get_element_errors().

◆ get_element_errors() [1/2]

void oomph::Z2ErrorEstimator::get_element_errors ( Mesh *&  mesh_pt,
Vector< double > &  elemental_error 
)
inline

Compute the elemental error measures for a given mesh and store them in a vector.

304  {
305  // Create dummy doc info object and switch off output
306  DocInfo doc_info;
307  doc_info.disable_doc();
308  // Forward call to version with doc.
309  get_element_errors(mesh_pt, elemental_error, doc_info);
310  }
void get_element_errors(Mesh *&mesh_pt, Vector< double > &elemental_error)
Definition: error_estimator.h:303

References oomph::DocInfo::disable_doc().

◆ get_element_errors() [2/2]

void oomph::Z2ErrorEstimator::get_element_errors ( Mesh *&  mesh_pt,
Vector< double > &  elemental_error,
DocInfo doc_info 
)
virtual

Compute the elemental error measures for a given mesh and store them in a vector. If doc_info.enable_doc(), doc FE and recovered fluxes in

  • flux_fe*.dat
  • flux_rec*.dat

Get Vector of Z2-based error estimates for all elements in mesh. If doc_info.is_doc_enabled()=true, doc FE and recovered fluxes in

  • flux_fe*.dat
  • flux_rec*.dat

Implements oomph::ErrorEstimator.

949  {
950 #ifdef OOMPH_HAS_MPI
951  // Storage for number of processors and current processor
952  int n_proc = 1;
953  int my_rank = 0;
954  if (mesh_pt->communicator_pt() != 0)
955  {
956  my_rank = mesh_pt->communicator_pt()->my_rank();
957  n_proc = mesh_pt->communicator_pt()->nproc();
958  }
960  {
961  my_rank = MPI_Helpers::communicator_pt()->my_rank();
962  n_proc = MPI_Helpers::communicator_pt()->nproc();
963  }
964 
965  MPI_Status status;
966 
967  // Initialise local values for all processes on mesh
968  unsigned num_flux_terms_local = 0;
969  unsigned dim_local = 0;
970  unsigned recovery_order_local = 0;
971 #endif
972 
973  // Global variables
974  unsigned num_flux_terms = 0;
975  unsigned dim = 0;
976 
977 #ifdef OOMPH_HAS_MPI
978  // It may be possible that a submesh contains no elements on a
979  // particular process after distribution. In order to instigate the
980  // error estimator calculations we need some information from the
981  // "first" element in a mesh; the following uses an MPI_Allreduce
982  // to figure out this information and communicate it to all processors
983  if (mesh_pt->nelement() > 0)
984  {
985  // Extract a few vital parameters from first element in mesh:
986  ElementWithZ2ErrorEstimator* el_pt =
987  dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(0));
988 #ifdef PARANOID
989  if (el_pt == 0)
990  {
991  throw OomphLibError(
992  "Element needs to inherit from ElementWithZ2ErrorEstimator!",
995  }
996 #endif
997 
998  // Number of 'flux'-like terms to be recovered
999  num_flux_terms_local = el_pt->num_Z2_flux_terms();
1000 
1001  // Determine spatial dimension of all elements from first element
1002  dim_local = el_pt->dim();
1003 
1004  // Do we need to determine the recovery order from first element?
1006  {
1007  recovery_order_local = el_pt->nrecovery_order();
1008  }
1009 
1010  } // end if (mesh_pt->nelement()>0)
1011 
1012  // Storage for the recovery order
1013  unsigned recovery_order = 0;
1014 
1015  // Now communicate these via an MPI_Allreduce to every process
1016  // if the mesh has been distributed
1017  if (mesh_pt->is_mesh_distributed())
1018  {
1019  // Get communicator from mesh
1020  OomphCommunicator* comm_pt = mesh_pt->communicator_pt();
1021 
1022  MPI_Allreduce(&num_flux_terms_local,
1023  &num_flux_terms,
1024  1,
1025  MPI_UNSIGNED,
1026  MPI_MAX,
1027  comm_pt->mpi_comm());
1028  MPI_Allreduce(&dim_local, &dim, 1, MPI_INT, MPI_MAX, comm_pt->mpi_comm());
1029  MPI_Allreduce(&recovery_order_local,
1030  &recovery_order,
1031  1,
1032  MPI_UNSIGNED,
1033  MPI_MAX,
1034  comm_pt->mpi_comm());
1035  }
1036  else
1037  {
1038  num_flux_terms = num_flux_terms_local;
1039  dim = dim_local;
1040  recovery_order = recovery_order_local;
1041  }
1042 
1043  // Do we need to determine the recovery order from first element?
1045  {
1047  }
1048 
1049 #else // !OOMPH_HAS_MPI
1050 
1051  // Extract a few vital parameters from first element in mesh:
1052  ElementWithZ2ErrorEstimator* el_pt =
1053  dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(0));
1054 #ifdef PARANOID
1055  if (el_pt == 0)
1056  {
1057  throw OomphLibError(
1058  "Element needs to inherit from ElementWithZ2ErrorEstimator!",
1061  }
1062 #endif
1063 
1064  // Number of 'flux'-like terms to be recovered
1065  num_flux_terms = el_pt->num_Z2_flux_terms();
1066 
1067  // Determine spatial dimension of all elements from first element
1068  dim = el_pt->dim();
1069 
1070  // Do we need to determine the recovery order from first element?
1072  {
1073  Recovery_order = el_pt->nrecovery_order();
1074  }
1075 
1076 #endif
1077 
1078  // Determine number of coefficients for expansion of recovered fluxes
1079  // Use complete polynomial of given order for recovery
1080  unsigned num_recovery_terms = nrecovery_terms(dim);
1081 
1082 
1083  // Setup patches (also returns Vector of vertex nodes)
1084  //====================================================
1085  std::map<Node*, Vector<ElementWithZ2ErrorEstimator*>*> adjacent_elements_pt;
1086  Vector<Node*> vertex_node_pt;
1087  setup_patches(mesh_pt, adjacent_elements_pt, vertex_node_pt);
1088 
1089  // Loop over all patches to get recovered flux value coefficients
1090  //===============================================================
1091 
1092  // Map to store sets of pointers to the recovered flux coefficient matrices
1093  // for each node.
1094  std::map<Node*, std::set<DenseMatrix<double>*>> flux_coeff_pt;
1095 
1096  // We store the pointers to the recovered flux coefficient matrices for
1097  // various patches in a vector so we can delete them later
1098  Vector<DenseMatrix<double>*> vector_of_recovered_flux_coefficient_pt;
1099 
1100  typedef std::map<Node*, Vector<ElementWithZ2ErrorEstimator*>*>::iterator IT;
1101 
1102  // Need to translate ElementWithZ2ErrorEstimator pointer to element number
1103  // in order to give each processor elements to work on if the problem
1104  // has not yet been distributed. In order to reduce the use of #ifdef this
1105  // is also done for the serial problem and the code is amended accordingly.
1106 
1107  std::map<ElementWithZ2ErrorEstimator*, int> elem_num;
1108  unsigned nelem = mesh_pt->nelement();
1109  for (unsigned e = 0; e < nelem; e++)
1110  {
1111  elem_num[dynamic_cast<ElementWithZ2ErrorEstimator*>(
1112  mesh_pt->element_pt(e))] = e;
1113  }
1114 
1115  // This isn't a global variable
1116  int n_patch = adjacent_elements_pt.size(); // also needed by serial version
1117 
1118  // Default values for serial AND parallel distributed problem
1119  int itbegin = 0;
1120 
1121  int itend = n_patch;
1122 
1123 #ifdef OOMPH_HAS_MPI
1124  int range = n_patch;
1125 
1126  // Work out values for parallel non-distributed problem
1127  if (!(mesh_pt->is_mesh_distributed()))
1128  {
1129  // setup the loop variables
1130  range = n_patch / n_proc; // number of patches on each proc
1131 
1132  itbegin = my_rank * range;
1133  itend = (my_rank + 1) * range;
1134 
1135  // if on the last processor, ensure the end matches
1136  if (my_rank == (n_proc - 1))
1137  {
1138  itend = n_patch;
1139  }
1140  }
1141 #endif
1142 
1143  // Set up matrices and vectors which will be sent later
1144  // - full matrix of all recovered coefficients
1145  Vector<DenseMatrix<double>*>
1146  vector_of_recovered_flux_coefficient_pt_to_send;
1147  // - vectors containing element numbers in each patch
1148  Vector<Vector<int>> vector_of_elements_in_patch_to_send;
1149 
1150  // Now we can loop over the patches on the current process
1151  for (int i = itbegin; i < itend; i++)
1152  {
1153  // Which vertex node are we at?
1154  Node* nod_pt = vertex_node_pt[i];
1155 
1156  // Pointer to vector of pointers to elements that make up
1157  // the patch.
1158  Vector<ElementWithZ2ErrorEstimator*>* el_vec_pt =
1159  adjacent_elements_pt[nod_pt];
1160 
1161  // Is the corner node that is central to the patch surrounded by
1162  // at least two elements?
1163  unsigned nelem = (*el_vec_pt).size();
1164 
1165  if (nelem >= 2)
1166  {
1167  // store the number of elements in the patch
1168  Vector<int> elements_in_this_patch;
1169  for (unsigned e = 0; e < nelem; e++)
1170  {
1171  elements_in_this_patch.push_back(elem_num[(*el_vec_pt)[e]]);
1172  }
1173 
1174  // put them into storage vector ready to send
1175  vector_of_elements_in_patch_to_send.push_back(elements_in_this_patch);
1176 
1177  // Given the vector of elements that make up the patch,
1178  // the number of recovery and flux terms, and the spatial
1179  // dimension of the problem, compute
1180  // the matrix of recovered flux coefficients and return
1181  // a pointer to it.
1182  DenseMatrix<double>* recovered_flux_coefficient_pt = 0;
1183  get_recovered_flux_in_patch(*el_vec_pt,
1184  num_recovery_terms,
1185  num_flux_terms,
1186  dim,
1187  recovered_flux_coefficient_pt);
1188 
1189  // Store pointer to recovered flux coefficients for
1190  // current patch in vector so we can send and then delete it later
1191  vector_of_recovered_flux_coefficient_pt_to_send.push_back(
1192  recovered_flux_coefficient_pt);
1193 
1194  } // end of if(nelem>=2)
1195  }
1196 
1197  // Now broadcast the result from each process to every other process
1198  // if the mesh has not yet been distributed and MPI is initialised
1199 
1200 #ifdef OOMPH_HAS_MPI
1201 
1202  if (!mesh_pt->is_mesh_distributed() &&
1204  {
1205  // Get communicator from namespace
1206  OomphCommunicator* comm_pt = MPI_Helpers::communicator_pt();
1207 
1208  // All local recovered fluxes have been calculated, so now share result
1209  for (int iproc = 0; iproc < n_proc; iproc++)
1210  {
1211  // Broadcast number of patches processed
1212  int n_patches = vector_of_recovered_flux_coefficient_pt_to_send.size();
1213  MPI_Bcast(&n_patches, 1, MPI_INT, iproc, comm_pt->mpi_comm());
1214 
1215  // Loop over these patches, broadcast recovered flux coefficients
1216  for (int ipatch = 0; ipatch < n_patches; ipatch++)
1217  {
1218  // Number of elements in this patch
1219  Vector<int> elements(0);
1220  unsigned nelements = 0;
1221 
1222  // Which processor are we on?
1223  if (my_rank == iproc)
1224  {
1225  elements = vector_of_elements_in_patch_to_send[ipatch];
1226  nelements = elements.size();
1227  }
1228 
1229  // Broadcast elements
1230  comm_pt->broadcast(iproc, elements);
1231 
1232  // Now get recovered flux coefficients
1233  DenseMatrix<double>* recovered_flux_coefficient_pt;
1234 
1235  // Which processor are we on?
1236  if (my_rank == iproc)
1237  {
1238  recovered_flux_coefficient_pt =
1239  vector_of_recovered_flux_coefficient_pt_to_send[ipatch];
1240  }
1241  else
1242  {
1243  recovered_flux_coefficient_pt = new DenseMatrix<double>;
1244  }
1245 
1246  // broadcast this matrix from the loop processor
1247  DenseMatrix<double> mattosend = *recovered_flux_coefficient_pt;
1248  comm_pt->broadcast(iproc, mattosend);
1249 
1250  // Set pointer on all processors
1251  *recovered_flux_coefficient_pt = mattosend;
1252 
1253  // End of parallel broadcasting
1254  vector_of_recovered_flux_coefficient_pt.push_back(
1255  recovered_flux_coefficient_pt);
1256 
1257  // Loop over elements in patch (work out nelements again after bcast)
1258  nelements = elements.size();
1259 
1260  for (unsigned e = 0; e < nelements; e++)
1261  {
1262  // Get pointer to element
1263  ElementWithZ2ErrorEstimator* el_pt =
1264  dynamic_cast<ElementWithZ2ErrorEstimator*>(
1265  mesh_pt->element_pt(elements[e]));
1266 
1267  // Loop over all nodes in element
1268  unsigned num_nod = el_pt->nnode();
1269  for (unsigned n = 0; n < num_nod; n++)
1270  {
1271  // Get the node
1272  Node* nod_pt = el_pt->node_pt(n);
1273  // Add the pointer to the current flux coefficient matrix
1274  // to the set for the node
1275  // Mesh not distributed here so nod_pt cannot be halo
1276  flux_coeff_pt[nod_pt].insert(recovered_flux_coefficient_pt);
1277  }
1278  }
1279 
1280  } // end loop over patches on current processor
1281 
1282  } // end loop over processors
1283  }
1284  else // is_mesh_distributed=true
1285  {
1286 #endif // end ifdef OOMPH_HAS_MPI for parallel job without mesh distribution
1287 
1288  // Do the same for a distributed mesh as for a serial job
1289  // up to the point where the elemental error is calculated
1290  // and then communicate that (see below)
1291  int n_patches = vector_of_recovered_flux_coefficient_pt_to_send.size();
1292 
1293  // Loop over these patches
1294  for (int ipatch = 0; ipatch < n_patches; ipatch++)
1295  {
1296  // Number of elements in this patch
1297  Vector<int> elements;
1298  int nelements; // Needs to be int for elem_num call later
1299  elements = vector_of_elements_in_patch_to_send[ipatch];
1300  nelements = elements.size();
1301 
1302  // Now get recovered flux coefficients
1303  DenseMatrix<double>* recovered_flux_coefficient_pt;
1304  recovered_flux_coefficient_pt =
1305  vector_of_recovered_flux_coefficient_pt_to_send[ipatch];
1306 
1307  vector_of_recovered_flux_coefficient_pt.push_back(
1308  recovered_flux_coefficient_pt);
1309 
1310  for (int e = 0; e < nelements; e++)
1311  {
1312  // Get pointer to element
1313  ElementWithZ2ErrorEstimator* el_pt =
1314  dynamic_cast<ElementWithZ2ErrorEstimator*>(
1315  mesh_pt->element_pt(elements[e]));
1316 
1317  // Loop over all nodes in element
1318  unsigned num_nod = el_pt->nnode();
1319  for (unsigned n = 0; n < num_nod; n++)
1320  {
1321  // Get the node
1322  Node* nod_pt = el_pt->node_pt(n);
1323  // Add the pointer to the current flux coefficient matrix
1324  // to the set for this node
1325  flux_coeff_pt[nod_pt].insert(recovered_flux_coefficient_pt);
1326  }
1327  }
1328 
1329  } // End loop over patches on current processor
1330 
1331 
1332 #ifdef OOMPH_HAS_MPI
1333  } // End if(is_mesh_distributed)
1334 #endif
1335 
1336  // Cleanup patch storage scheme
1337  for (IT it = adjacent_elements_pt.begin(); it != adjacent_elements_pt.end();
1338  it++)
1339  {
1340  delete it->second;
1341  }
1342  adjacent_elements_pt.clear();
1343 
1344  // Loop over all nodes, take average of recovered flux values
1345  //-----------------------------------------------------------
1346  // and evaluate recovered flux at nodes
1347  //-------------------------------------
1348 
1349  // Map of (averaged) recoverd flux values at nodes
1350  MapMatrixMixed<Node*, int, double> rec_flux_map;
1351 
1352  // Loop over all nodes
1353  unsigned n_node = mesh_pt->nnode();
1354  for (unsigned n = 0; n < n_node; n++)
1355  {
1356  Node* nod_pt = mesh_pt->node_pt(n);
1357 
1358  // How many patches is this node a member of?
1359  unsigned npatches = flux_coeff_pt[nod_pt].size();
1360 
1361  // Matrix of averaged coefficients for this node
1362  DenseMatrix<double> averaged_flux_coeff(
1363  num_recovery_terms, num_flux_terms, 0.0);
1364 
1365  // Loop over matrices for different patches and add contributions
1366  typedef std::set<DenseMatrix<double>*>::iterator IT;
1367  for (IT it = flux_coeff_pt[nod_pt].begin();
1368  it != flux_coeff_pt[nod_pt].end();
1369  it++)
1370  {
1371  for (unsigned i = 0; i < num_recovery_terms; i++)
1372  {
1373  for (unsigned j = 0; j < num_flux_terms; j++)
1374 
1375  {
1376  // ...just add it -- we'll divide by the number of patches later
1377  averaged_flux_coeff(i, j) += (*(*it))(i, j);
1378  }
1379  }
1380  }
1381 
1382  // Now evaluate the recovered flux (based on the averaged coefficients)
1383  //---------------------------------------------------------------------
1384  // at the nodal position itself.
1385  //------------------------------
1386 
1387  // Get global (Eulerian) nodal position
1388  Vector<double> x(dim);
1389  for (unsigned i = 0; i < dim; i++)
1390  {
1391  x[i] = nod_pt->x(i);
1392  }
1393 
1394  // Evaluate global recovery functions at node
1395  Vector<double> psi_r(num_recovery_terms);
1396  shape_rec(x, dim, psi_r);
1397 
1398  // Initialise nodal fluxes
1399  for (unsigned i = 0; i < num_flux_terms; i++)
1400  {
1401  rec_flux_map(nod_pt, i) = 0.0;
1402  }
1403 
1404  // Loop over coefficients for flux recovery
1405  for (unsigned i = 0; i < num_flux_terms; i++)
1406  {
1407  for (unsigned icoeff = 0; icoeff < num_recovery_terms; icoeff++)
1408  {
1409  rec_flux_map(nod_pt, i) +=
1410  averaged_flux_coeff(icoeff, i) * psi_r[icoeff];
1411  }
1412  // Now take averaging into account
1413  rec_flux_map(nod_pt, i) /= double(npatches);
1414  }
1415 
1416  } // end loop over nodes
1417 
1418  // We're done with the recovered flux coefficient matrices for
1419  // the various patches and can delete them
1420  unsigned npatch = vector_of_recovered_flux_coefficient_pt.size();
1421  for (unsigned p = 0; p < npatch; p++)
1422  {
1423  delete vector_of_recovered_flux_coefficient_pt[p];
1424  }
1425 
1426  // NOTE FOR FUTURE REFERENCE - revisit in case of adaptivity problems in
1427  // parallel jobs
1428  //
1429  // The current method for distributed problems neglects recovered flux
1430  // contributions from patches that cannot be assembled on the current
1431  // processor, but would be assembled if the problem was not distributed.
1432  // The only nodes for which this is the case are nodes which lie on the
1433  // exact boundary between processors.
1434  //
1435  // See the note at the start of setup_patches (above) for more details.
1436 
1437  // Get error estimates for all elements
1438  //======================================
1439 
1440  // Find the number of compound fluxes
1441  // Loop over all (non-halo) elements
1442  nelem = mesh_pt->nelement();
1443  // Initialise the number of compound fluxes
1444  // Must be an integer for an MPI call later on
1445  int n_compound_flux = 1;
1446  for (unsigned e = 0; e < nelem; e++)
1447  {
1448  ElementWithZ2ErrorEstimator* el_pt =
1449  dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(e));
1450 
1451 #ifdef OOMPH_HAS_MPI
1452  // Ignore halo elements
1453  if (!el_pt->is_halo())
1454  {
1455 #endif
1456  // Find the number of compound fluxes in the element
1457  const int n_compound_flux_el = el_pt->ncompound_fluxes();
1458  // If it's greater than the current (global) number of compound fluxes
1459  // bump up the global number
1460  if (n_compound_flux_el > n_compound_flux)
1461  {
1462  n_compound_flux = n_compound_flux_el;
1463  }
1464 #ifdef OOMPH_HAS_MPI
1465  } // end if (!el_pt->is_halo())
1466 #endif
1467  }
1468 
1469  // Initialise a vector of flux norms
1470  Vector<double> flux_norm(n_compound_flux, 0.0);
1471 
1472  unsigned test_count = 0;
1473 
1474  // Storage for the elemental compound flux error
1475  DenseMatrix<double> elemental_compound_flux_error(
1476  nelem, n_compound_flux, 0.0);
1477 
1478  // Loop over all (non-halo) elements again
1479  for (unsigned e = 0; e < nelem; e++)
1480  {
1481  ElementWithZ2ErrorEstimator* el_pt =
1482  dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(e));
1483 
1484 #ifdef OOMPH_HAS_MPI
1485  // Ignore halo elements
1486  if (!el_pt->is_halo())
1487  {
1488 #endif
1489 
1490  Vector<double> s(dim);
1491 
1492  // Initialise elemental error one for each compound flux in the element
1493  const unsigned n_compound_flux_el = el_pt->ncompound_fluxes();
1494  Vector<double> error(n_compound_flux_el, 0.0);
1495 
1496  Integral* integ_pt = el_pt->integral_pt();
1497 
1498  // Set the value of Nintpt
1499  const unsigned n_intpt = integ_pt->nweight();
1500 
1501  // Loop over the integration points
1502  for (unsigned ipt = 0; ipt < n_intpt; ipt++)
1503  {
1504  // Assign values of s
1505  for (unsigned i = 0; i < dim; i++)
1506  {
1507  s[i] = integ_pt->knot(ipt, i);
1508  }
1509 
1510  // Get the integral weight
1511  double w = integ_pt->weight(ipt);
1512 
1513  // Jacobian of mapping
1514  double J = el_pt->J_eulerian(s);
1515 
1516  // Get the Eulerian position
1517  Vector<double> x(dim);
1518  el_pt->interpolated_x(s, x);
1519 
1520  // Premultiply the weights and the Jacobian
1521  // and the geometric jacobian weight (used in axisymmetric
1522  // and spherical coordinate systems)
1523  double W = w * J * (el_pt->geometric_jacobian(x));
1524 
1525  // Number of FE nodes
1526  unsigned n_node = el_pt->nnode();
1527 
1528  // FE shape function
1529  Shape psi(n_node);
1530 
1531  // Get values of FE shape function
1532  el_pt->shape(s, psi);
1533 
1534  // Initialise recovered flux Vector
1535  Vector<double> rec_flux(num_flux_terms, 0.0);
1536 
1537  // Loop over all nodes (incl. halo nodes) to assemble contribution
1538  for (unsigned n = 0; n < n_node; n++)
1539  {
1540  Node* nod_pt = el_pt->node_pt(n);
1541 
1542  // Loop over components
1543  for (unsigned i = 0; i < num_flux_terms; i++)
1544  {
1545  rec_flux[i] += rec_flux_map(nod_pt, i) * psi[n];
1546  }
1547  }
1548 
1549  // FE flux
1550  Vector<double> fe_flux(num_flux_terms);
1551  el_pt->get_Z2_flux(s, fe_flux);
1552  // Get compound flux indices. Initialised to zero
1553  Vector<unsigned> flux_index(num_flux_terms, 0);
1554  el_pt->get_Z2_compound_flux_indices(flux_index);
1555 
1556  // Add to RMS errors for each compound flux:
1557  Vector<double> sum(n_compound_flux_el, 0.0);
1558  Vector<double> sum2(n_compound_flux_el, 0.0);
1559  for (unsigned i = 0; i < num_flux_terms; i++)
1560  {
1561  sum[flux_index[i]] +=
1562  (rec_flux[i] - fe_flux[i]) * (rec_flux[i] - fe_flux[i]);
1563  sum2[flux_index[i]] += rec_flux[i] * rec_flux[i];
1564  }
1565 
1566  for (unsigned i = 0; i < n_compound_flux_el; i++)
1567  {
1568  // Add the errors to the appropriate compound flux error
1569  error[i] += sum[i] * W;
1570  // Add to flux norm
1571  flux_norm[i] += sum2[i] * W;
1572  }
1573  }
1574  // Unscaled elemental RMS error:
1575  test_count++; // counting elements visited
1576 
1577  // elemental_error[e]=sqrt(error);
1578  // Take the square-root of the appropriate flux error and
1579  // store the result
1580  for (unsigned i = 0; i < n_compound_flux_el; i++)
1581  {
1582  elemental_compound_flux_error(e, i) = sqrt(error[i]);
1583  }
1584 
1585 #ifdef OOMPH_HAS_MPI
1586  } // end if (!el_pt->is_halo())
1587 #endif
1588  } // end of loop over elements
1589 
1590  // Communicate the error for haloed elements to halo elements:
1591  // - loop over processors
1592  // - if current process, receive to halo element error
1593  // - if not current process, send haloed element error
1594  // How do we know which part of elemental_error to send?
1595  // Loop over haloed elements and find element number using elem_num map;
1596  // send this - order preservation of halo/haloed elements
1597  // guarantees that they get through in the correct order
1598 #ifdef OOMPH_HAS_MPI
1599 
1600  if (mesh_pt->is_mesh_distributed())
1601  {
1602  // Get communicator from mesh
1603  OomphCommunicator* comm_pt = mesh_pt->communicator_pt();
1604 
1605  for (int iproc = 0; iproc < n_proc; iproc++)
1606  {
1607  if (iproc != my_rank) // Not current process, so send
1608  {
1609  // Get the haloed elements
1610  Vector<GeneralisedElement*> haloed_elem_pt =
1611  mesh_pt->haloed_element_pt(iproc);
1612  // Find the number of haloed elements
1613  int nelem_haloed = haloed_elem_pt.size();
1614 
1615  // If there are some haloed elements, assemble and send the
1616  // errors
1617  if (nelem_haloed != 0)
1618  {
1619  // Find the number of error entires:
1620  // number of haloed elements x number of compound fluxes
1621  int n_elem_error_haloed = nelem_haloed * n_compound_flux;
1622  // Vector for elemental errors
1623  Vector<double> haloed_elem_error(n_elem_error_haloed);
1624  // Counter for the vector index
1625  unsigned count = 0;
1626  for (int e = 0; e < nelem_haloed; e++)
1627  {
1628  // Find element number
1629  int element_num =
1630  elem_num[dynamic_cast<ElementWithZ2ErrorEstimator*>(
1631  haloed_elem_pt[e])];
1632  // Put the error in a vector to send
1633  for (int i = 0; i < n_compound_flux; i++)
1634  {
1635  haloed_elem_error[count] =
1636  elemental_compound_flux_error(element_num, i);
1637  ++count;
1638  }
1639  }
1640  // Send the errors
1641  MPI_Send(&haloed_elem_error[0],
1642  n_elem_error_haloed,
1643  MPI_DOUBLE,
1644  iproc,
1645  0,
1646  comm_pt->mpi_comm());
1647  }
1648  }
1649  else // iproc=my_rank, so receive errors from others
1650  {
1651  for (int send_rank = 0; send_rank < n_proc; send_rank++)
1652  {
1653  if (iproc != send_rank) // iproc=my_rank already!
1654  {
1655  Vector<GeneralisedElement*> halo_elem_pt =
1656  mesh_pt->halo_element_pt(send_rank);
1657  // Find number of halo elements
1658  int nelem_halo = halo_elem_pt.size();
1659  // If there are some halo elements, receive errors and
1660  // put in the appropriate places
1661  if (nelem_halo != 0)
1662  {
1663  // Find the number of error entires:
1664  // number of haloed elements x number of compound fluxes
1665  int n_elem_error_halo = nelem_halo * n_compound_flux;
1666  // Vector for elemental errors
1667  Vector<double> halo_elem_error(n_elem_error_halo);
1668 
1669 
1670  // Receive the errors from processor send_rank
1671  MPI_Recv(&halo_elem_error[0],
1672  n_elem_error_halo,
1673  MPI_DOUBLE,
1674  send_rank,
1675  0,
1676  comm_pt->mpi_comm(),
1677  &status);
1678 
1679  // Counter for the vector index
1680  unsigned count = 0;
1681  for (int e = 0; e < nelem_halo; e++)
1682  {
1683  // Find element number
1684  int element_num =
1685  elem_num[dynamic_cast<ElementWithZ2ErrorEstimator*>(
1686  halo_elem_pt[e])];
1687  // Put the error in the correct location
1688  for (int i = 0; i < n_compound_flux; i++)
1689  {
1690  elemental_compound_flux_error(element_num, i) =
1691  halo_elem_error[count];
1692  ++count;
1693  }
1694  }
1695  }
1696  }
1697  } // End of interior loop over processors
1698  }
1699  } // End of exterior loop over processors
1700 
1701  } // End of if (mesh has been distributed)
1702 
1703 #endif
1704 
1705  // NOTE FOR FUTURE REFERENCE - revisit in case of adaptivity problems in
1706  // parallel jobs
1707  //
1708  // The current method for distributed problems neglects recovered flux
1709  // contributions from patches that cannot be assembled on the current
1710  // processor, but would be assembled if the problem was not distributed.
1711  // The only nodes for which this is the case are nodes which lie on the
1712  // exact boundary between processors.
1713  //
1714  // See the note at the start of setup_patches (above) for more details.
1715 
1716  // Use computed flux norm or externally imposed reference value?
1717  if (Reference_flux_norm != 0.0)
1718  {
1719  // At the moment assume that all fluxes have the same reference norm
1720  for (int i = 0; i < n_compound_flux; i++)
1721  {
1722  flux_norm[i] = Reference_flux_norm;
1723  }
1724  }
1725  else
1726  {
1727  // In parallel, perform reduction operation to get global value
1728 #ifdef OOMPH_HAS_MPI
1729  if (mesh_pt->is_mesh_distributed())
1730  {
1731  // Get communicator from mesh
1732  OomphCommunicator* comm_pt = mesh_pt->communicator_pt();
1733 
1734  Vector<double> total_flux_norm(n_compound_flux);
1735  // every process needs to know the sum
1736  MPI_Allreduce(&flux_norm[0],
1737  &total_flux_norm[0],
1738  n_compound_flux,
1739  MPI_DOUBLE,
1740  MPI_SUM,
1741  comm_pt->mpi_comm());
1742  // take sqrt
1743  for (int i = 0; i < n_compound_flux; i++)
1744  {
1745  flux_norm[i] = sqrt(total_flux_norm[i]);
1746  }
1747  }
1748  else // mesh has not been distributed, so flux_norm already global
1749  {
1750  for (int i = 0; i < n_compound_flux; i++)
1751  {
1752  flux_norm[i] = sqrt(flux_norm[i]);
1753  }
1754  }
1755 #else // serial problem, so flux_norm already global
1756  for (int i = 0; i < n_compound_flux; i++)
1757  {
1758  flux_norm[i] = sqrt(flux_norm[i]);
1759  }
1760 #endif
1761  }
1762 
1763  // Now loop over (all!) elements again and
1764  // normalise errors by global flux norm
1765 
1766  nelem = mesh_pt->nelement();
1767  for (unsigned e = 0; e < nelem; e++)
1768  {
1769  // Get the vector of normalised compound fluxes
1770  Vector<double> normalised_compound_flux_error(n_compound_flux);
1771  for (int i = 0; i < n_compound_flux; i++)
1772  {
1773  if (flux_norm[i] != 0.0)
1774  {
1775  normalised_compound_flux_error[i] =
1776  elemental_compound_flux_error(e, i) / flux_norm[i];
1777  }
1778  else
1779  {
1780  normalised_compound_flux_error[i] =
1781  elemental_compound_flux_error(e, i);
1782  }
1783  }
1784 
1785  // calculate the combined error estimate
1786  elemental_error[e] =
1787  get_combined_error_estimate(normalised_compound_flux_error);
1788  }
1789 
1790  // Doc global fluxes?
1791  if (doc_info.is_doc_enabled())
1792  {
1793  doc_flux(
1794  mesh_pt, num_flux_terms, rec_flux_map, elemental_error, doc_info);
1795  }
1796  }
AnnoyingScalar sqrt(const AnnoyingScalar &x)
Definition: AnnoyingScalar.h:134
JacobiRotation< float > J
Definition: Jacobi_makeJacobi.cpp:3
RowVector3d w
Definition: Matrix_resize_int.cpp:3
float * p
Definition: Tutorial_Map_using.cpp:9
static bool mpi_has_been_initialised()
return true if MPI has been initialised
Definition: oomph_utilities.h:851
int my_rank() const
my rank
Definition: communicator.h:176
int nproc() const
number of processors
Definition: communicator.h:157
void get_recovered_flux_in_patch(const Vector< ElementWithZ2ErrorEstimator * > &patch_el_pt, const unsigned &num_recovery_terms, const unsigned &num_flux_terms, const unsigned &dim, DenseMatrix< double > *&recovered_flux_coefficient_pt)
Definition: error_estimator.cc:666
unsigned nrecovery_terms(const unsigned &dim)
Definition: error_estimator.cc:812
double get_combined_error_estimate(const Vector< double > &compound_error)
Return a combined error estimate from all compound errors.
Definition: error_estimator.cc:460
void setup_patches(Mesh *&mesh_pt, std::map< Node *, Vector< ElementWithZ2ErrorEstimator * > * > &adjacent_elements_pt, Vector< Node * > &vertex_node_pt)
Definition: error_estimator.cc:505
void shape_rec(const Vector< double > &x, const unsigned &dim, Vector< double > &psi_r)
Definition: error_estimator.cc:44
void doc_flux(Mesh *mesh_pt, const unsigned &num_flux_terms, MapMatrixMixed< Node *, int, double > &rec_flux_map, const Vector< double > &elemental_error, DocInfo &doc_info)
Doc flux and recovered flux.
Definition: error_estimator.cc:1802
int error
Definition: calibrate.py:297
@ W
Definition: quadtree.h:63
std::ptrdiff_t j
Definition: tut_arithmetic_redux_minmax.cpp:2

References oomph::MPI_Helpers::communicator_pt(), oomph::Mesh::communicator_pt(), oomph::FiniteElement::dim(), doc_flux(), e(), oomph::Mesh::element_pt(), calibrate::error, oomph::ElementWithZ2ErrorEstimator::geometric_jacobian(), get_combined_error_estimate(), get_recovered_flux_in_patch(), oomph::ElementWithZ2ErrorEstimator::get_Z2_compound_flux_indices(), oomph::ElementWithZ2ErrorEstimator::get_Z2_flux(), i, oomph::FiniteElement::integral_pt(), oomph::FiniteElement::interpolated_x(), oomph::DocInfo::is_doc_enabled(), oomph::Mesh::is_mesh_distributed(), J, j, oomph::FiniteElement::J_eulerian(), oomph::Integral::knot(), oomph::MPI_Helpers::mpi_has_been_initialised(), oomph::OomphCommunicator::my_rank(), n, oomph::ElementWithZ2ErrorEstimator::ncompound_fluxes(), oomph::Mesh::nelement(), oomph::FiniteElement::nnode(), oomph::Mesh::nnode(), oomph::FiniteElement::node_pt(), oomph::Mesh::node_pt(), oomph::OomphCommunicator::nproc(), oomph::ElementWithZ2ErrorEstimator::nrecovery_order(), nrecovery_terms(), oomph::ElementWithZ2ErrorEstimator::num_Z2_flux_terms(), oomph::Integral::nweight(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION, p, recovery_order(), Recovery_order, Recovery_order_from_first_element, Reference_flux_norm, s, setup_patches(), oomph::FiniteElement::shape(), shape_rec(), sqrt(), w, oomph::QuadTreeNames::W, oomph::Integral::weight(), plotDoE::x, and oomph::Node::x().

◆ get_recovered_flux_in_patch()

void oomph::Z2ErrorEstimator::get_recovered_flux_in_patch ( const Vector< ElementWithZ2ErrorEstimator * > &  patch_el_pt,
const unsigned num_recovery_terms,
const unsigned num_flux_terms,
const unsigned dim,
DenseMatrix< double > *&  recovered_flux_coefficient_pt 
)
private

Given the vector of elements that make up a patch, the number of recovery and flux terms, and the spatial dimension of the problem, compute the matrix of recovered flux coefficients and return a pointer to it.

672  {
673  // Create/initialise matrix for linear system
674  DenseDoubleMatrix recovery_mat(num_recovery_terms, num_recovery_terms, 0.0);
675 
676  // Ceate/initialise vector of RHSs
677  Vector<Vector<double>> rhs(num_flux_terms);
678  for (unsigned irhs = 0; irhs < num_flux_terms; irhs++)
679  {
680  rhs[irhs].resize(num_recovery_terms);
681  for (unsigned j = 0; j < num_recovery_terms; j++)
682  {
683  rhs[irhs][j] = 0.0;
684  }
685  }
686 
687 
688  // Create a new integration scheme based on the recovery order
689  // in the elements
690  // Need to find the type of the element, default is to assume a quad
691  bool is_q_mesh = true;
692  // If we can dynamic cast to the TElementBase, then it's a triangle/tet
693  // Note that I'm assuming that all elements are of the same geometry, but
694  // if they weren't we could adapt...
695  if (dynamic_cast<TElementBase*>(patch_el_pt[0]))
696  {
697  is_q_mesh = false;
698  }
699 
700  Integral* const integ_pt = this->integral_rec(dim, is_q_mesh);
701 
702  // Loop over all elements in patch to assemble linear system
703  unsigned nelem = patch_el_pt.size();
704  for (unsigned e = 0; e < nelem; e++)
705  {
706  // Get pointer to element
707  ElementWithZ2ErrorEstimator* const el_pt = patch_el_pt[e];
708 
709  // Create storage for the recovery shape function values
710  Vector<double> psi_r(num_recovery_terms);
711 
712  // Create vector to hold local coordinates
713  Vector<double> s(dim);
714 
715  // Loop over the integration points
716  unsigned Nintpt = integ_pt->nweight();
717 
718  for (unsigned ipt = 0; ipt < Nintpt; ipt++)
719  {
720  // Assign values of s, the local coordinate
721  for (unsigned i = 0; i < dim; i++)
722  {
723  s[i] = integ_pt->knot(ipt, i);
724  }
725 
726  // Get the integral weight
727  double w = integ_pt->weight(ipt);
728 
729  // Jaocbian of mapping
730  double J = el_pt->J_eulerian(s);
731 
732  // Interpolate the global (Eulerian) coordinate
733  Vector<double> x(dim);
734  el_pt->interpolated_x(s, x);
735 
736 
737  // Premultiply the weights and the Jacobian
738  // and the geometric jacobian weight (used in axisymmetric
739  // and spherical coordinate systems)
740  double W = w * J * (el_pt->geometric_jacobian(x));
741 
742  // Recovery shape functions at global (Eulerian) coordinate
743  shape_rec(x, dim, psi_r);
744 
745  // Get FE estimates for Z2 flux:
746  Vector<double> fe_flux(num_flux_terms);
747  el_pt->get_Z2_flux(s, fe_flux);
748 
749  // Add elemental RHSs and recovery matrix to global versions
750  //----------------------------------------------------------
751 
752  // RHS for different flux components
753  for (unsigned i = 0; i < num_flux_terms; i++)
754  {
755  // Loop over the nodes for the test functions
756  for (unsigned l = 0; l < num_recovery_terms; l++)
757  {
758  rhs[i][l] += fe_flux[i] * psi_r[l] * W;
759  }
760  }
761 
762 
763  // Loop over the nodes for the test functions
764  for (unsigned l = 0; l < num_recovery_terms; l++)
765  {
766  // Loop over the nodes for the variables
767  for (unsigned l2 = 0; l2 < num_recovery_terms; l2++)
768  {
769  // Add contribution to recovery matrix
770  recovery_mat(l, l2) += psi_r[l] * psi_r[l2] * W;
771  }
772  }
773  }
774 
775  } // End of loop over elements that make up patch.
776 
777  // Delete the integration scheme
778  delete integ_pt;
779 
780  // Linear system is now assembled: Solve recovery system
781 
782  // LU decompose the recovery matrix
783  recovery_mat.ludecompose();
784 
785  // Back-substitute (and overwrite for all rhs
786  for (unsigned irhs = 0; irhs < num_flux_terms; irhs++)
787  {
788  recovery_mat.lubksub(rhs[irhs]);
789  }
790 
791  // Now create a matrix to store the flux recovery coefficients.
792  // Pointer to this matrix will be returned.
793  recovered_flux_coefficient_pt =
794  new DenseMatrix<double>(num_recovery_terms, num_flux_terms);
795 
796  // Copy coefficients
797  for (unsigned icoeff = 0; icoeff < num_recovery_terms; icoeff++)
798  {
799  for (unsigned irhs = 0; irhs < num_flux_terms; irhs++)
800  {
801  (*recovered_flux_coefficient_pt)(icoeff, irhs) = rhs[irhs][icoeff];
802  }
803  }
804  }
Integral * integral_rec(const unsigned &dim, const bool &is_q_mesh)
Definition: error_estimator.cc:245
unsigned Nintpt
Number of integration points for new integration scheme (if used)
Definition: stefan_boltzmann.cc:112

References e(), oomph::ElementWithZ2ErrorEstimator::geometric_jacobian(), oomph::ElementWithZ2ErrorEstimator::get_Z2_flux(), i, integral_rec(), oomph::FiniteElement::interpolated_x(), J, j, oomph::FiniteElement::J_eulerian(), oomph::Integral::knot(), oomph::DenseDoubleMatrix::lubksub(), oomph::DenseDoubleMatrix::ludecompose(), GlobalParameters::Nintpt, oomph::Integral::nweight(), s, shape_rec(), w, oomph::QuadTreeNames::W, oomph::Integral::weight(), and plotDoE::x.

Referenced by get_element_errors().

◆ integral_rec()

Integral * oomph::Z2ErrorEstimator::integral_rec ( const unsigned dim,
const bool is_q_mesh 
)
private

Integation scheme associated with the recovery shape functions must be of sufficiently high order to integrate the mass matrix associated with the recovery shape functions

Integation scheme associated with the recovery shape functions must be of sufficiently high order to integrate the mass matrix associated with the recovery shape functions. The argument is the dimension of the elements. The integration is performed locally over the elements, so the integration scheme does depend on the geometry of the element. The type of element is specified by the boolean which is true if elements in the patch are QElements and false if they are TElements (will need change if we ever have other element types)

Which spatial dimension are we dealing with?

Find order of recovery shape functions

Find order of recovery shape functions

Find order of recovery shape functions

247  {
248  std::ostringstream error_stream;
249 
251  switch (dim)
252  {
253  case 1:
254 
255  // 1D:
256  //====
257 
259  switch (recovery_order())
260  {
261  case 1:
262 
263  // Complete linear polynomial in 1D
264  //(quadratic terms in mass matrix)
265  if (is_q_mesh)
266  {
267  return (new Gauss<1, 2>);
268  }
269  else
270  {
271  return (new TGauss<1, 2>);
272  }
273  break;
274 
275  case 2:
276 
277  // Complete quadratic polynomial in 1D:
278  //(quartic terms in the mass marix)
279  if (is_q_mesh)
280  {
281  return (new Gauss<1, 3>);
282  }
283  else
284  {
285  return (new TGauss<1, 3>);
286  }
287  break;
288 
289  case 3:
290 
291  // Complete cubic polynomial in 1D:
292  // (order six terms in mass matrix)
293  if (is_q_mesh)
294  {
295  return (new Gauss<1, 4>);
296  }
297  else
298  {
299  return (new TGauss<1, 4>);
300  }
301  break;
302 
303  default:
304 
305  error_stream << "Recovery shape functions for recovery order "
306  << recovery_order()
307  << " haven't yet been implemented for 1D" << std::endl;
308 
309  throw OomphLibError(error_stream.str(),
312  }
313  break;
314 
315  case 2:
316 
317  // 2D:
318  //====
319 
321  switch (recovery_order())
322  {
323  case 1:
324 
325  // Complete linear polynomial in 2D:
326  if (is_q_mesh)
327  {
328  return (new Gauss<2, 2>);
329  }
330  else
331  {
332  return (new TGauss<2, 2>);
333  }
334  break;
335 
336  case 2:
337 
338  // Complete quadratic polynomial in 2D:
339  if (is_q_mesh)
340  {
341  return (new Gauss<2, 3>);
342  }
343  else
344  {
345  return (new TGauss<2, 3>);
346  }
347  break;
348 
349  case 3:
350 
351  // Complete cubic polynomial in 2D:
352  if (is_q_mesh)
353  {
354  return (new Gauss<2, 4>);
355  }
356  else
357  {
358  return (new TGauss<2, 4>);
359  }
360  break;
361 
362  default:
363 
364  error_stream << "Recovery shape functions for recovery order "
365  << recovery_order()
366  << " haven't yet been implemented for 2D" << std::endl;
367 
368  throw OomphLibError(error_stream.str(),
371  }
372  break;
373 
374  case 3:
375 
376  // 3D:
377  //====
379  switch (recovery_order())
380  {
381  case 1:
382 
383  // Complete linear polynomial in 3D:
384  if (is_q_mesh)
385  {
386  return (new Gauss<3, 2>);
387  }
388  else
389  {
390  return (new TGauss<3, 2>);
391  }
392  break;
393 
394  case 2:
395 
396  // Complete quadratic polynomial in 3D:
397  if (is_q_mesh)
398  {
399  return (new Gauss<3, 3>);
400  }
401  else
402  {
403  return (new TGauss<3, 3>);
404  }
405  break;
406 
407  case 3:
408 
409  // Complete cubic polynomial in 3D:
410  if (is_q_mesh)
411  {
412  return (new Gauss<3, 4>);
413  }
414  else
415  {
416  return (new TGauss<3, 5>);
417  } // TGauss<3,4> not implemented
418 
419  break;
420 
421  default:
422 
423  error_stream << "Recovery shape functions for recovery order "
424  << recovery_order()
425  << " haven't yet been implemented for 3D" << std::endl;
426 
427  throw OomphLibError(error_stream.str(),
430  }
431 
432 
433  break;
434 
435  default:
436 
437  // Any other dimension?
438  //=====================
439  error_stream << "No recovery shape functions for dim " << dim
440  << std::endl;
441  throw OomphLibError(
442  error_stream.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
443  break;
444  }
445 
446  // Dummy return (never get here)
447  return 0;
448  }

References OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION, and recovery_order().

Referenced by get_recovered_flux_in_patch().

◆ nrecovery_terms()

unsigned oomph::Z2ErrorEstimator::nrecovery_terms ( const unsigned dim)
private

Return number of coefficients for expansion of recovered fluxes for given spatial dimension of elements. (We use complete polynomials of the specified given order.)

Number of coefficients for expansion of recovered fluxes for given spatial dimension of elements. Use complete polynomial of given order for recovery

813  {
814  unsigned num_recovery_terms;
815 
816 #ifdef PARANOID
817  if ((dim != 1) && (dim != 2) && (dim != 3))
818  {
819  std::string error_message = "THIS HASN'T BEEN USED/VALIDATED YET -- "
820  "CHECK NUMBER OF RECOVERY TERMS!\n";
821  error_message += "Then remove this break and continue\n";
822 
823  throw OomphLibError(
825  }
826 #endif
827 
828  switch (Recovery_order)
829  {
830  case 1:
831 
832  // Linear recovery shape functions
833  //--------------------------------
834 
835  switch (dim)
836  {
837  case 1:
838  // 1D:
839  num_recovery_terms = 2; // 1, x
840  break;
841 
842  case 2:
843  // 2D:
844  num_recovery_terms = 3; // 1, x, y
845  break;
846 
847  case 3:
848  // 3D:
849  num_recovery_terms = 4; // 1, x, y, z
850  break;
851 
852  default:
853  throw OomphLibError("Dim must be 1, 2 or 3",
856  }
857  break;
858 
859  case 2:
860 
861  // Quadratic recovery shape functions
862  //-----------------------------------
863 
864  switch (dim)
865  {
866  case 1:
867  // 1D:
868  num_recovery_terms = 3; // 1, x, x^2
869  break;
870 
871  case 2:
872  // 2D:
873  num_recovery_terms = 6; // 1, x, y, x^2, xy, y^2
874  break;
875 
876  case 3:
877  // 3D:
878  num_recovery_terms = 10; // 1, x, y, z, x^2, y^2, z^2, xy, xz, yz
879  break;
880 
881  default:
882  throw OomphLibError("Dim must be 1, 2 or 3",
885  }
886  break;
887 
888 
889  case 3:
890 
891  // Cubic recovery shape functions
892  //--------------------------------
893 
894  switch (dim)
895  {
896  case 1:
897  // 1D:
898  num_recovery_terms = 4; // 1, x, x^2, x^3
899  break;
900 
901  case 2:
902  // 2D:
903  num_recovery_terms =
904  10; // 1, x, y, x^2, xy, y^2, x^3, y^3, x^2 y, x y^2
905  break;
906 
907  case 3:
908  // 3D:
909  num_recovery_terms = 20; // 1, x, y, z, x^2, y^2, z^2, xy, xz, yz,
910  // x^3, y^3, z^3,
911  // x^2 y, x^2 z,
912  // y^2 x, y^2 z,
913  // z^2 x, z^2 y
914  // xyz
915  break;
916 
917  default:
918  throw OomphLibError("Dim must be 1, 2 or 3",
921  }
922  break;
923 
924 
925  default:
926 
927  // Any other recovery order?
928  //--------------------------
929  std::ostringstream error_stream;
930  error_stream << "Wrong Recovery_order " << Recovery_order << std::endl;
931 
932  throw OomphLibError(
933  error_stream.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
934  }
935 
936  return num_recovery_terms;
937  }
std::string string(const unsigned &i)
Definition: oomph_definitions.cc:286

References OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION, Recovery_order, and oomph::Global_string_for_annotation::string().

Referenced by get_element_errors().

◆ operator=()

void oomph::Z2ErrorEstimator::operator= ( const Z2ErrorEstimator )
delete

Broken assignment operator.

◆ recovery_order() [1/2]

unsigned& oomph::Z2ErrorEstimator::recovery_order ( )
inline

Access function for order of recovery polynomials.

323  {
324  return Recovery_order;
325  }

References Recovery_order.

Referenced by get_element_errors(), integral_rec(), and shape_rec().

◆ recovery_order() [2/2]

unsigned oomph::Z2ErrorEstimator::recovery_order ( ) const
inline

Access function for order of recovery polynomials (const version)

329  {
330  return Recovery_order;
331  }

References Recovery_order.

◆ reference_flux_norm() [1/2]

double& oomph::Z2ErrorEstimator::reference_flux_norm ( )
inline

Access function for prescribed reference flux norm.

356  {
357  return Reference_flux_norm;
358  }

References Reference_flux_norm.

Referenced by main(), and RotatingCylinderProblem< ELEMENT, TIMESTEPPER >::unsteady_run().

◆ reference_flux_norm() [2/2]

double oomph::Z2ErrorEstimator::reference_flux_norm ( ) const
inline

Access function for prescribed reference flux norm (const. version)

362  {
363  return Reference_flux_norm;
364  }

References Reference_flux_norm.

◆ setup_patches()

void oomph::Z2ErrorEstimator::setup_patches ( Mesh *&  mesh_pt,
std::map< Node *, Vector< ElementWithZ2ErrorEstimator * > * > &  adjacent_elements_pt,
Vector< Node * > &  vertex_node_pt 
)

Setup patches: For each vertex node pointed to by nod_pt, adjacent_elements_pt[nod_pt] contains the pointer to the vector that contains the pointers to the elements that the node is part of.

Setup patches: For each vertex node pointed to by nod_pt, adjacent_elements_pt[nod_pt] contains the pointer to the vector that contains the pointers to the elements that the node is part of. Also returns a Vector of vertex nodes for use in get_element_errors.

510  {
511  // (see also note at the end of get_element_errors below)
512  // NOTE FOR FUTURE REFERENCE - revisit in case of adaptivity problems in
513  // parallel jobs at the boundaries between processes
514  //
515  // The current method for distributed problems neglects recovered flux
516  // contributions from patches that cannot be assembled from (vertex) nodes
517  // on the current process, but would be assembled if the problem was not
518  // distributed. The only nodes for which this is the case are nodes which
519  // lie on the exact boundary between processes.
520  //
521  // The suggested method for fixing this requires the current process (A) to
522  // receive information from the process (B) on which the patch is
523  // assembled. These patches are precisely the patches on process B which
524  // contain no halo elements. The contribution from these patches needs to
525  // be sent to the nodes (on process A) that are on the boundary between
526  // A and B. Therefore a map is required to denote such nodes; a node is on
527  // the boundary of a process if it is a member of at least one halo element
528  // and one non-halo element for that process. Create a vector of bools
529  // which is the size of the number of processes and make the entry true if
530  // the node (through a map(nod_pt,vector<bools> node_bnd)) is on the
531  // boundary for a process and false otherwise. This should be done here in
532  // setup_patches.
533  //
534  // When it comes to the error calculation in get_element_errors (see later)
535  // the communication needs to take place after rec_flux_map(nod_pt,i) has
536  // been assembled for all other patches. A separate vector for the patches
537  // to be sent needs to be assembled: the recovered flux contribution at
538  // a node on process B is sent to the equivalent node on process A if the
539  // patch contains ONLY halo elements AND the node is on the boundary between
540  // A and B (i.e. both the entries in the mapped vector are set to true).
541  //
542  // If anyone else read this and has any questions, I have a fuller
543  // explanation written out (with some relevant diagrams in the 2D case).
544  //
545  // Andrew.Gait@manchester.ac.uk
546 
547  // Auxiliary map that contains element-adjacency for ALL nodes
548  std::map<Node*, Vector<ElementWithZ2ErrorEstimator*>*>
549  aux_adjacent_elements_pt;
550 
551 #ifdef PARANOID
552  // Check if all elements request the same recovery order
553  unsigned ndisagree = 0;
554 #endif
555 
556  // Loop over all elements to setup adjacency for all nodes.
557  // Need to do this because midside nodes can be corner nodes for
558  // adjacent smaller elements! Admittedly, the inclusion of interior
559  // nodes is wasteful...
560  unsigned nelem = mesh_pt->nelement();
561  for (unsigned e = 0; e < nelem; e++)
562  {
563  ElementWithZ2ErrorEstimator* el_pt =
564  dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(e));
565 
566 #ifdef PARANOID
567  // Check if all elements request the same recovery order
568  if (el_pt->nrecovery_order() != Recovery_order)
569  {
570  ndisagree++;
571  }
572 #endif
573 
574  // Loop all nodes in element
575  unsigned nnod = el_pt->nnode();
576  for (unsigned n = 0; n < nnod; n++)
577  {
578  Node* nod_pt = el_pt->node_pt(n);
579 
580  // Has this node been considered before?
581  if (aux_adjacent_elements_pt[nod_pt] == 0)
582  {
583  // Create Vector of pointers to its adjacent elements
584  aux_adjacent_elements_pt[nod_pt] =
585  new Vector<ElementWithZ2ErrorEstimator*>;
586  }
587 
588  // Add pointer to adjacent element
589  (*aux_adjacent_elements_pt[nod_pt]).push_back(el_pt);
590  // }
591  }
592  } // end element loop
593 
594 #ifdef PARANOID
595  // Check if all elements request the same recovery order
596  if (ndisagree != 0)
597  {
598  oomph_info
599  << "\n\n========================================================\n";
600  oomph_info << "WARNING: " << std::endl;
601  oomph_info << ndisagree << " out of " << mesh_pt->nelement()
602  << " elements\n";
603  oomph_info
604  << "have different preferences for the order of the recovery\n";
605  oomph_info << "shape functions. We are using: Recovery_order="
606  << Recovery_order << std::endl;
607  oomph_info
608  << "========================================================\n\n";
609  }
610 #endif
611 
612  // Loop over all elements, extract adjacency for corner nodes only
613  nelem = mesh_pt->nelement();
614  for (unsigned e = 0; e < nelem; e++)
615  {
616  ElementWithZ2ErrorEstimator* el_pt =
617  dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(e));
618 
619  // Loop over corner nodes
620  unsigned n_node = el_pt->nvertex_node();
621  for (unsigned n = 0; n < n_node; n++)
622  {
623  Node* nod_pt = el_pt->vertex_node_pt(n);
624 
625  // Has this node been considered before?
626  if (adjacent_elements_pt[nod_pt] == 0)
627  {
628  // Add the node pointer to the vertex node container
629  vertex_node_pt.push_back(nod_pt);
630 
631  // Create Vector of pointers to its adjacent elements
632  adjacent_elements_pt[nod_pt] =
633  new Vector<ElementWithZ2ErrorEstimator*>;
634 
635  // Copy across:
636  unsigned nel = (*aux_adjacent_elements_pt[nod_pt]).size();
637  for (unsigned e = 0; e < nel; e++)
638  {
639  (*adjacent_elements_pt[nod_pt])
640  .push_back((*aux_adjacent_elements_pt[nod_pt])[e]);
641  }
642  }
643  }
644 
645  } // end of loop over elements
646 
647  // Cleanup
648  typedef std::map<Node*, Vector<ElementWithZ2ErrorEstimator*>*>::iterator
649  ITT;
650  for (ITT it = aux_adjacent_elements_pt.begin();
651  it != aux_adjacent_elements_pt.end();
652  it++)
653  {
654  delete it->second;
655  }
656  }
Scalar Scalar int size
Definition: benchVecAdd.cpp:17
OomphInfo oomph_info
Definition: oomph_definitions.cc:319

References e(), oomph::Mesh::element_pt(), n, oomph::Mesh::nelement(), oomph::FiniteElement::nnode(), oomph::FiniteElement::node_pt(), oomph::ElementWithZ2ErrorEstimator::nrecovery_order(), oomph::ElementWithZ2ErrorEstimator::nvertex_node(), oomph::oomph_info, Recovery_order, size, and oomph::ElementWithZ2ErrorEstimator::vertex_node_pt().

Referenced by get_element_errors().

◆ shape_rec()

void oomph::Z2ErrorEstimator::shape_rec ( const Vector< double > &  x,
const unsigned dim,
Vector< double > &  psi_r 
)
private

Recovery shape functions as functions of the global, Eulerian coordinate x of dimension dim. The recovery shape functions are complete polynomials of the order specified by Recovery_order.

Which spatial dimension are we dealing with?

Find order of recovery shape functions

Find order of recovery shape functions

Find order of recovery shape functions

47  {
48  std::ostringstream error_stream;
49 
51  switch (dim)
52  {
53  case 1:
54 
55  // 1D:
56  //====
57 
59  switch (recovery_order())
60  {
61  case 1:
62 
63  // Complete linear polynomial in 1D:
64  psi_r[0] = 1.0;
65  psi_r[1] = x[0];
66  break;
67 
68  case 2:
69 
70  // Complete quadratic polynomial in 1D:
71  psi_r[0] = 1.0;
72  psi_r[1] = x[0];
73  psi_r[2] = x[0] * x[0];
74  break;
75 
76  case 3:
77 
78  // Complete cubic polynomial in 1D:
79  psi_r[0] = 1.0;
80  psi_r[1] = x[0];
81  psi_r[2] = x[0] * x[0];
82  psi_r[3] = x[0] * x[0] * x[0];
83  break;
84 
85  default:
86 
87  error_stream << "Recovery shape functions for recovery order "
88  << recovery_order()
89  << " haven't yet been implemented for 1D" << std::endl;
90 
91  throw OomphLibError(error_stream.str(),
94  }
95  break;
96 
97  case 2:
98 
99  // 2D:
100  //====
101 
103  switch (recovery_order())
104  {
105  case 1:
106 
107  // Complete linear polynomial in 2D:
108  psi_r[0] = 1.0;
109  psi_r[1] = x[0];
110  psi_r[2] = x[1];
111  break;
112 
113  case 2:
114 
115  // Complete quadratic polynomial in 2D:
116  psi_r[0] = 1.0;
117  psi_r[1] = x[0];
118  psi_r[2] = x[1];
119  psi_r[3] = x[0] * x[0];
120  psi_r[4] = x[0] * x[1];
121  psi_r[5] = x[1] * x[1];
122  break;
123 
124  case 3:
125 
126  // Complete cubic polynomial in 2D:
127  psi_r[0] = 1.0;
128  psi_r[1] = x[0];
129  psi_r[2] = x[1];
130  psi_r[3] = x[0] * x[0];
131  psi_r[4] = x[0] * x[1];
132  psi_r[5] = x[1] * x[1];
133  psi_r[6] = x[0] * x[0] * x[0];
134  psi_r[7] = x[0] * x[0] * x[1];
135  psi_r[8] = x[0] * x[1] * x[1];
136  psi_r[9] = x[1] * x[1] * x[1];
137  break;
138 
139  default:
140 
141  error_stream << "Recovery shape functions for recovery order "
142  << recovery_order()
143  << " haven't yet been implemented for 2D" << std::endl;
144 
145  throw OomphLibError(error_stream.str(),
148  }
149  break;
150 
151  case 3:
152 
153  // 3D:
154  //====
156  switch (recovery_order())
157  {
158  case 1:
159 
160  // Complete linear polynomial in 3D:
161  psi_r[0] = 1.0;
162  psi_r[1] = x[0];
163  psi_r[2] = x[1];
164  psi_r[3] = x[2];
165  break;
166 
167  case 2:
168 
169  // Complete quadratic polynomial in 3D:
170  psi_r[0] = 1.0;
171  psi_r[1] = x[0];
172  psi_r[2] = x[1];
173  psi_r[3] = x[2];
174  psi_r[4] = x[0] * x[0];
175  psi_r[5] = x[0] * x[1];
176  psi_r[6] = x[0] * x[2];
177  psi_r[7] = x[1] * x[1];
178  psi_r[8] = x[1] * x[2];
179  psi_r[9] = x[2] * x[2];
180  break;
181 
182  case 3:
183 
184  // Complete cubic polynomial in 3D:
185  psi_r[0] = 1.0;
186  psi_r[1] = x[0];
187  psi_r[2] = x[1];
188  psi_r[3] = x[2];
189  psi_r[4] = x[0] * x[0];
190  psi_r[5] = x[0] * x[1];
191  psi_r[6] = x[0] * x[2];
192  psi_r[7] = x[1] * x[1];
193  psi_r[8] = x[1] * x[2];
194  psi_r[9] = x[2] * x[2];
195  psi_r[10] = x[0] * x[0] * x[0];
196  psi_r[11] = x[0] * x[0] * x[1];
197  psi_r[12] = x[0] * x[0] * x[2];
198  psi_r[13] = x[1] * x[1] * x[1];
199  psi_r[14] = x[0] * x[1] * x[1];
200  psi_r[15] = x[2] * x[1] * x[1];
201  psi_r[16] = x[2] * x[2] * x[2];
202  psi_r[17] = x[2] * x[2] * x[0];
203  psi_r[18] = x[2] * x[2] * x[1];
204  psi_r[19] = x[0] * x[1] * x[2];
205 
206  break;
207 
208  default:
209 
210  error_stream << "Recovery shape functions for recovery order "
211  << recovery_order()
212  << " haven't yet been implemented for 3D" << std::endl;
213 
214  throw OomphLibError(error_stream.str(),
217  }
218 
219 
220  break;
221 
222  default:
223 
224  // Any other dimension?
225  //=====================
226  error_stream << "No recovery shape functions for dim " << dim
227  << std::endl;
228  throw OomphLibError(
229  error_stream.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
230  break;
231  }
232  }

References OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION, recovery_order(), and plotDoE::x.

Referenced by get_element_errors(), and get_recovered_flux_in_patch().

Member Data Documentation

◆ Combined_error_fct_pt

CombinedErrorEstimateFctPt oomph::Z2ErrorEstimator::Combined_error_fct_pt
private

Function pointer to combined error estimator function.

Referenced by combined_error_fct_pt(), and get_combined_error_estimate().

◆ Recovery_order

unsigned oomph::Z2ErrorEstimator::Recovery_order
private

Order of recovery polynomials.

Referenced by get_element_errors(), nrecovery_terms(), recovery_order(), and setup_patches().

◆ Recovery_order_from_first_element

bool oomph::Z2ErrorEstimator::Recovery_order_from_first_element
private

Bool to indicate if recovery order is to be read out from first element in mesh or set globally

Referenced by get_element_errors().

◆ Reference_flux_norm

double oomph::Z2ErrorEstimator::Reference_flux_norm
private

Prescribed reference flux norm.

Referenced by get_element_errors(), and reference_flux_norm().


The documentation for this class was generated from the following files: