error_estimator.cc
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-2024 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#ifdef OOMPH_HAS_MPI
27#include "mpi.h"
28#endif
29
30
32#include "error_estimator.h"
33#include "shape.h"
34#include "Telements.h"
35
36namespace oomph
37{
38 //====================================================================
39 /// Recovery shape functions as functions of the global, Eulerian
40 /// coordinate x of dimension dim.
41 /// The recovery shape functions are complete polynomials of
42 /// the order specified by Recovery_order.
43 //====================================================================
45 const unsigned& dim,
47 {
48 std::ostringstream error_stream;
49
50 /// Which spatial dimension are we dealing with?
51 switch (dim)
52 {
53 case 1:
54
55 // 1D:
56 //====
57
58 /// Find order of recovery shape functions
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 "
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
102 /// Find order of recovery shape functions
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 //====
155 /// Find order of recovery shape functions
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(
230 break;
231 }
232 }
233
234 //====================================================================
235 /// Integation scheme associated with the recovery shape functions
236 /// must be of sufficiently high order to integrate the mass matrix
237 /// associated with the recovery shape functions. The argument
238 /// is the dimension of the elements.
239 /// The integration is performed locally over the elements, so the
240 /// integration scheme does depend on the geometry of the element.
241 /// The type of element is specified by the boolean which is
242 /// true if elements in the patch are QElements and false if they are
243 /// TElements (will need change if we ever have other element types)
244 //====================================================================
246 const bool& is_q_mesh)
247 {
248 std::ostringstream error_stream;
249
250 /// Which spatial dimension are we dealing with?
251 switch (dim)
252 {
253 case 1:
254
255 // 1D:
256 //====
257
258 /// Find order of recovery shape functions
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
320 /// Find order of recovery shape functions
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 //====
378 /// Find order of recovery shape functions
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(
443 break;
444 }
445
446 // Dummy return (never get here)
447 return 0;
448 }
449
450
451 //==========================================================================
452 /// Return a combined error estimate from all compound flux errors
453 /// The default is to return the maximum of the compound flux errors
454 /// which will always force refinment if any field is above the single
455 /// mesh error threshold and unrefinement if both are below the lower limit.
456 /// Any other fancy combinations can be selected by
457 /// specifying a user-defined combined estimate by setting a function
458 /// pointer.
459 //==========================================================================
462 {
463 // If the function pointer has been set, call that function
464 if (Combined_error_fct_pt != 0)
465 {
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 }
498
499 //======================================================================
500 /// Setup patches: For each vertex node pointed to by nod_pt,
501 /// adjacent_elements_pt[nod_pt] contains the pointer to the vector that
502 /// contains the pointers to the elements that the node is part of.
503 /// Also returns a Vector of vertex nodes for use in get_element_errors.
504 //======================================================================
506 Mesh*& mesh_pt,
509 Vector<Node*>& vertex_node_pt)
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*>*>
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 {
564 dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(e));
565
566#ifdef PARANOID
567 // Check if all elements request the same 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 {
579
580 // Has this node been considered before?
582 {
583 // Create Vector of pointers to its adjacent elements
586 }
587
588 // Add pointer to adjacent element
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 {
599 << "\n\n========================================================\n";
600 oomph_info << "WARNING: " << std::endl;
601 oomph_info << ndisagree << " out of " << mesh_pt->nelement()
602 << " elements\n";
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;
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 {
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 {
624
625 // Has this node been considered before?
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
634
635 // Copy across:
636 unsigned nel = (*aux_adjacent_elements_pt[nod_pt]).size();
637 for (unsigned e = 0; e < nel; e++)
638 {
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();
652 it++)
653 {
654 delete it->second;
655 }
656 }
657
658
659 //======================================================================
660 /// Given the vector of elements that make up a patch,
661 /// the number of recovery and flux terms, and the
662 /// spatial dimension of the problem, compute
663 /// the matrix of recovered flux coefficients and return
664 /// a pointer to it.
665 //======================================================================
668 const unsigned& num_recovery_terms,
669 const unsigned& num_flux_terms,
670 const unsigned& dim,
672 {
673 // Create/initialise matrix for linear system
675
676 // Ceate/initialise vector of RHSs
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
708
709 // Create storage for the recovery shape function values
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);
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:
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.
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 }
805
806
807 //==================================================================
808 /// Number of coefficients for expansion of recovered fluxes
809 /// for given spatial dimension of elements.
810 /// Use complete polynomial of given order for recovery
811 //==================================================================
812 unsigned Z2ErrorEstimator::nrecovery_terms(const unsigned& dim)
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:
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(
934 }
935
936 return num_recovery_terms;
937 }
938
939
940 //======================================================================
941 /// Get Vector of Z2-based error estimates for all elements in mesh.
942 /// If doc_info.is_doc_enabled()=true, doc FE and recovered fluxes in
943 /// - flux_fe*.dat
944 /// - flux_rec*.dat
945 //======================================================================
948 DocInfo& doc_info)
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 {
963 }
964
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:
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
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 {
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
1024 1,
1026 MPI_MAX,
1027 comm_pt->mpi_comm());
1028 MPI_Allreduce(&dim_local, &dim, 1, MPI_INT, MPI_MAX, comm_pt->mpi_comm());
1031 1,
1033 MPI_MAX,
1034 comm_pt->mpi_comm());
1035 }
1036 else
1037 {
1039 dim = dim_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:
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
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 {
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
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 {
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
1147 // - vectors containing element numbers in each patch
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.
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
1169 for (unsigned e = 0; e < nelem; e++)
1170 {
1172 }
1173
1174 // put them into storage vector ready to send
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.
1186 dim,
1188
1189 // Store pointer to recovered flux coefficients for
1190 // current patch in vector so we can send and then delete it later
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
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
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
1220 unsigned nelements = 0;
1221
1222 // Which processor are we on?
1223 if (my_rank == iproc)
1224 {
1227 }
1228
1229 // Broadcast elements
1230 comm_pt->broadcast(iproc, elements);
1231
1232 // Now get recovered flux coefficients
1234
1235 // Which processor are we on?
1236 if (my_rank == iproc)
1237 {
1240 }
1241 else
1242 {
1244 }
1245
1246 // broadcast this matrix from the loop processor
1248 comm_pt->broadcast(iproc, mattosend);
1249
1250 // Set pointer on all processors
1252
1253 // End of parallel broadcasting
1256
1257 // Loop over elements in patch (work out nelements again after bcast)
1259
1260 for (unsigned e = 0; e < nelements; e++)
1261 {
1262 // Get pointer to element
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
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)
1292
1293 // Loop over these patches
1294 for (int ipatch = 0; ipatch < n_patches; ipatch++)
1295 {
1296 // Number of elements in this patch
1298 int nelements; // Needs to be int for elem_num call later
1301
1302 // Now get recovered flux coefficients
1306
1309
1310 for (int e = 0; e < nelements; e++)
1311 {
1312 // Get pointer to element
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
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
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
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
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) +=
1411 }
1412 // Now take averaging into account
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
1421 for (unsigned p = 0; p < npatch; p++)
1422 {
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 {
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
1458 // If it's greater than the current (global) number of compound fluxes
1459 // bump up the global number
1461 {
1463 }
1464#ifdef OOMPH_HAS_MPI
1465 } // end if (!el_pt->is_halo())
1466#endif
1467 }
1468
1469 // Initialise a vector of flux norms
1471
1472 unsigned test_count = 0;
1473
1474 // Storage for the 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 {
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();
1495
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
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
1552 // Get compound flux indices. Initialised to zero
1553 Vector<unsigned> flux_index(num_flux_terms, 0);
1555
1556 // Add to RMS errors for each compound flux:
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 {
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
1611 mesh_pt->haloed_element_pt(iproc);
1612 // Find the number of haloed elements
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
1622 // Vector for elemental errors
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 =
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 {
1637 ++count;
1638 }
1639 }
1640 // Send the errors
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 {
1656 mesh_pt->halo_element_pt(send_rank);
1657 // Find number of halo elements
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
1666 // Vector for elemental errors
1668
1669
1670 // Receive the errors from processor send_rank
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 =
1686 halo_elem_pt[e])];
1687 // Put the error in the correct location
1688 for (int i = 0; i < n_compound_flux; i++)
1689 {
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 {
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
1735 // every process needs to know the sum
1737 &total_flux_norm[0],
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 {
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
1771 for (int i = 0; i < n_compound_flux; i++)
1772 {
1773 if (flux_norm[i] != 0.0)
1774 {
1777 }
1778 else
1779 {
1782 }
1783 }
1784
1785 // calculate the combined error estimate
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 }
1797
1798
1799 //==================================================================
1800 /// Doc FE and recovered flux
1801 //==================================================================
1803 Mesh* mesh_pt,
1804 const unsigned& num_flux_terms,
1807 DocInfo& doc_info)
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
1818
1819#endif
1820
1821 // File suffix identifying processor rank. If comm_pt is null (because
1822 // oomph-lib was built with MPI but this mesh is not distributed) the
1823 // string is empty.
1824 std::string rank_string = "";
1825 if (comm_pt != 0)
1826 {
1827 rank_string = "_on_proc_" + comm_pt->my_rank();
1828 }
1829
1830 // Setup output files
1831 std::ofstream some_file, feflux_file;
1832 std::ostringstream filename;
1833 filename << doc_info.directory() << "/flux_rec" << doc_info.number()
1834 << rank_string << ".dat";
1835 some_file.open(filename.str().c_str());
1836 filename.str("");
1837 filename << doc_info.directory() << "/flux_fe" << doc_info.number()
1838 << rank_string << ".dat";
1839 feflux_file.open(filename.str().c_str());
1840
1841 unsigned nel = mesh_pt->nelement();
1842 if (nel > 0)
1843 {
1844 // Extract first element to determine spatial dimension
1845 FiniteElement* el_pt = mesh_pt->finite_element_pt(0);
1846 unsigned dim = el_pt->dim();
1847 Vector<double> s(dim);
1848
1849 // Decide on the number of plot points
1850 unsigned nplot = 5;
1851
1852 // Loop over all elements
1853 for (unsigned e = 0; e < nel; e++)
1854 {
1856 dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(e));
1857
1858 // Write tecplot header
1861
1863 for (unsigned iplot = 0; iplot < num_plot_points; iplot++)
1864 {
1865 // Get local coordinates of plot point
1867
1868 // Coordinate
1869 Vector<double> x(dim);
1870 el_pt->interpolated_x(s, x);
1871
1872 // Number of FE nodes
1873 unsigned n_node = el_pt->nnode();
1874
1875 // FE shape function
1876 Shape psi(n_node);
1877
1878 // Get values of FE shape function
1879 el_pt->shape(s, psi);
1880
1881 // Initialise recovered flux Vector
1883
1884 // Loop over nodes to assemble contribution
1885 for (unsigned n = 0; n < n_node; n++)
1886 {
1887 Node* nod_pt = el_pt->node_pt(n);
1888
1889 // Loop over components
1890 for (unsigned i = 0; i < num_flux_terms; i++)
1891 {
1892 rec_flux[i] += rec_flux_map(nod_pt, i) * psi[n];
1893 }
1894 }
1895
1896 // FE flux
1899
1900 for (unsigned i = 0; i < dim; i++)
1901 {
1902 some_file << x[i] << " ";
1903 }
1904 for (unsigned i = 0; i < num_flux_terms; i++)
1905 {
1906 some_file << rec_flux[i] << " ";
1907 }
1908 some_file << elemental_error[e] << " " << std::endl;
1909
1910
1911 for (unsigned i = 0; i < dim; i++)
1912 {
1913 feflux_file << x[i] << " ";
1914 }
1915 for (unsigned i = 0; i < num_flux_terms; i++)
1916 {
1917 feflux_file << fe_flux[i] << " ";
1918 }
1919 feflux_file << elemental_error[e] << " " << std::endl;
1920 }
1921
1922 // Write tecplot footer (e.g. FE connectivity)
1923 // Do this for each element so the output is compatible with
1924 // oomph-convert
1927 }
1928 }
1929
1930 some_file.close();
1931 feflux_file.close();
1932 }
1933
1934
1935} // namespace oomph
e
Definition cfortran.h:571
static char t char * s
Definition cfortran.h:568
cstr elem_len * i
Definition cfortran.h:603
Class of matrices containing doubles, and stored as a DenseMatrix<double>, but with solving functiona...
Definition matrices.h:1271
Information for documentation of results: Directory and file number to enable output in the form RESL...
bool is_doc_enabled() const
Are we documenting?
std::string directory() const
Output directory.
unsigned & number()
Number used (e.g.) for labeling output files.
Base class for finite elements that can compute the quantities that are required for the Z2 error est...
virtual unsigned ncompound_fluxes()
A stuitable error estimator for a multi-physics elements may require one Z2 error estimate for each f...
virtual void get_Z2_compound_flux_indices(Vector< unsigned > &flux_index)
Return the compound flux index of each flux component The default (do nothing behaviour) will mean th...
virtual double geometric_jacobian(const Vector< double > &x)
Return the geometric jacobian (should be overloaded in cylindrical and spherical geometries)....
A general Finite Element class.
Definition elements.h:1317
virtual double J_eulerian(const Vector< double > &s) const
Return the Jacobian of mapping from local to global coordinates at local position s.
Definition elements.cc:4133
Integral *const & integral_pt() const
Return the pointer to the integration scheme (const version)
Definition elements.h:1967
virtual std::string tecplot_zone_string(const unsigned &nplot) const
Return string for tecplot zone header (when plotting nplot points in each "coordinate direction")
Definition elements.h:3165
double size() const
Calculate the size of the element (length, area, volume,...) in Eulerian computational coordinates....
Definition elements.cc:4320
virtual void shape(const Vector< double > &s, Shape &psi) const =0
Calculate the geometric shape functions at local coordinate s. This function must be overloaded for e...
virtual double interpolated_x(const Vector< double > &s, const unsigned &i) const
Return FE interpolated coordinate x[i] at local coordinate s.
Definition elements.cc:3992
unsigned dim() const
Return the spatial dimension of the element, i.e. the number of local coordinates required to paramet...
Definition elements.h:2615
unsigned nnode() const
Return the number of nodes.
Definition elements.h:2214
virtual void get_s_plot(const unsigned &i, const unsigned &nplot, Vector< double > &s, const bool &shifted_to_interior=false) const
Get cector of local coordinates of plot point i (when plotting nplot points in each "coordinate direc...
Definition elements.h:3152
virtual unsigned nplot_points(const unsigned &nplot) const
Return total number of plot points (when plotting nplot points in each "coordinate direction")
Definition elements.h:3190
Node *& node_pt(const unsigned &n)
Return a pointer to the local node n.
Definition elements.h:2179
virtual void write_tecplot_zone_footer(std::ostream &outfile, const unsigned &nplot) const
Add tecplot zone "footer" to output stream (when plotting nplot points in each "coordinate direction"...
Definition elements.h:3178
bool is_halo() const
Is this element a halo?
Definition elements.h:1167
Generic class for numerical integration schemes:
Definition integral.h:49
static bool mpi_has_been_initialised()
return true if MPI has been initialised
static OomphCommunicator * communicator_pt()
access to global communicator. This is the oomph-lib equivalent of MPI_COMM_WORLD
A general mesh class.
Definition mesh.h:67
bool is_mesh_distributed() const
Boolean to indicate if Mesh has been distributed.
Definition mesh.h:1588
FiniteElement * finite_element_pt(const unsigned &e) const
Upcast (downcast?) to FiniteElement (needed to access FiniteElement member functions).
Definition mesh.h:473
Node *& node_pt(const unsigned long &n)
Return pointer to global node n.
Definition mesh.h:436
Vector< GeneralisedElement * > halo_element_pt(const unsigned &p)
Return vector of halo elements in this Mesh whose non-halo counterpart is held on processor p.
Definition mesh.h:1740
unsigned long nnode() const
Return number of nodes in the mesh.
Definition mesh.h:596
GeneralisedElement *& element_pt(const unsigned long &e)
Return pointer to element e.
Definition mesh.h:448
OomphCommunicator * communicator_pt() const
Read-only access fct to communicator (Null if mesh is not distributed, i.e. if we don't have mpi).
Definition mesh.h:1600
Vector< GeneralisedElement * > haloed_element_pt(const unsigned &p)
Return vector of haloed elements in this Mesh whose haloing counterpart is held on processor p.
Definition mesh.h:1779
unsigned long nelement() const
Return number of elements in the mesh.
Definition mesh.h:590
Nodes are derived from Data, but, in addition, have a definite (Eulerian) position in a space of a gi...
Definition nodes.h:906
An oomph-lib wrapper to the MPI_Comm communicator object. Just contains an MPI_Comm object (which is ...
An OomphLibError object which should be thrown when an run-time error is encountered....
A Class for shape functions. In simple cases, the shape functions have only one index that can be tho...
Definition shape.h:76
//////////////////////////////////////////////////////////////////////
Node * vertex_node_pt(const unsigned &j) const
Pointer to the j-th vertex node in the element.
unsigned nrecovery_order()
Order of recovery shape functions for Z2 error estimation: Same order as shape functions.
unsigned nvertex_node() const
Number of vertex nodes in the element.
void get_Z2_flux(const Vector< double > &s, Vector< double > &flux)
Get 'flux' for Z2 error recovery: Standard flux.from AdvectionDiffusionReaction equations.
unsigned num_Z2_flux_terms()
Number of 'flux' terms for Z2 error estimation.
/////////////////////////////////////////////////////////////////// /////////////////////////////////...
Definition Telements.h:1130
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)
Given the vector of elements that make up a patch, the number of recovery and flux terms,...
unsigned & recovery_order()
Access function for order of recovery polynomials.
unsigned nrecovery_terms(const unsigned &dim)
Return number of coefficients for expansion of recovered fluxes for given spatial dimension of elemen...
CombinedErrorEstimateFctPt Combined_error_fct_pt
Function pointer to combined error estimator function.
double get_combined_error_estimate(const Vector< double > &compound_error)
Return a combined error estimate from all compound errors.
void 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 p...
unsigned Recovery_order
Order of recovery polynomials.
double Reference_flux_norm
Prescribed reference flux norm.
void get_element_errors(Mesh *&mesh_pt, Vector< double > &elemental_error)
Compute the elemental error measures for a given mesh and store them in a vector.
void shape_rec(const Vector< double > &x, const unsigned &dim, Vector< double > &psi_r)
Recovery shape functions as functions of the global, Eulerian coordinate x of dimension dim....
Integral * integral_rec(const unsigned &dim, const bool &is_q_mesh)
Integation scheme associated with the recovery shape functions must be of sufficiently high order to ...
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.
bool Recovery_order_from_first_element
Bool to indicate if recovery order is to be read out from first element in mesh or set globally.
//////////////////////////////////////////////////////////////////// ////////////////////////////////...
OomphInfo oomph_info
Single (global) instantiation of the OomphInfo object – this is used throughout the library as a "rep...