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-2025 Matthias Heil and Andrew Hazel
7// LIC//
8// LIC// This library is free software; you can redistribute it and/or
9// LIC// modify it under the terms of the GNU Lesser General Public
10// LIC// License as published by the Free Software Foundation; either
11// LIC// version 2.1 of the License, or (at your option) any later version.
12// LIC//
13// LIC// This library is distributed in the hope that it will be useful,
14// LIC// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// LIC// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16// LIC// Lesser General Public License for more details.
17// LIC//
18// LIC// You should have received a copy of the GNU Lesser General Public
19// LIC// License along with this library; if not, write to the Free Software
20// LIC// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21// LIC// 02110-1301 USA.
22// LIC//
23// LIC// The authors may be contacted at oomph-lib@maths.man.ac.uk.
24// LIC//
25// LIC//====================================================================
26#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());
1029 &dim_local, &dim, 1, MPI_UNSIGNED, MPI_MAX, comm_pt->mpi_comm());
1032 1,
1034 MPI_MAX,
1035 comm_pt->mpi_comm());
1036 }
1037 else
1038 {
1040 dim = dim_local;
1042 }
1043
1044 // Do we need to determine the recovery order from first element?
1046 {
1048 }
1049
1050#else // !OOMPH_HAS_MPI
1051
1052 // Extract a few vital parameters from first element in mesh:
1054 dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(0));
1055#ifdef PARANOID
1056 if (el_pt == 0)
1057 {
1058 throw OomphLibError(
1059 "Element needs to inherit from ElementWithZ2ErrorEstimator!",
1062 }
1063#endif
1064
1065 // Number of 'flux'-like terms to be recovered
1067
1068 // Determine spatial dimension of all elements from first element
1069 dim = el_pt->dim();
1070
1071 // Do we need to determine the recovery order from first element?
1073 {
1075 }
1076
1077#endif
1078
1079 // Determine number of coefficients for expansion of recovered fluxes
1080 // Use complete polynomial of given order for recovery
1081 unsigned num_recovery_terms = nrecovery_terms(dim);
1082
1083
1084 // Setup patches (also returns Vector of vertex nodes)
1085 //====================================================
1086 std::map<Node*, Vector<ElementWithZ2ErrorEstimator*>*> adjacent_elements_pt;
1087 Vector<Node*> vertex_node_pt;
1088 setup_patches(mesh_pt, adjacent_elements_pt, vertex_node_pt);
1089
1090 // Loop over all patches to get recovered flux value coefficients
1091 //===============================================================
1092
1093 // Map to store sets of pointers to the recovered flux coefficient matrices
1094 // for each node.
1095 std::map<Node*, std::set<DenseMatrix<double>*>> flux_coeff_pt;
1096
1097 // We store the pointers to the recovered flux coefficient matrices for
1098 // various patches in a vector so we can delete them later
1100
1101 typedef std::map<Node*, Vector<ElementWithZ2ErrorEstimator*>*>::iterator IT;
1102
1103 // Need to translate ElementWithZ2ErrorEstimator pointer to element number
1104 // in order to give each processor elements to work on if the problem
1105 // has not yet been distributed. In order to reduce the use of #ifdef this
1106 // is also done for the serial problem and the code is amended accordingly.
1107
1108 std::map<ElementWithZ2ErrorEstimator*, int> elem_num;
1109 unsigned nelem = mesh_pt->nelement();
1110 for (unsigned e = 0; e < nelem; e++)
1111 {
1113 mesh_pt->element_pt(e))] = e;
1114 }
1115
1116 // This isn't a global variable
1117 int n_patch = adjacent_elements_pt.size(); // also needed by serial version
1118
1119 // Default values for serial AND parallel distributed problem
1120 int itbegin = 0;
1121
1122 int itend = n_patch;
1123
1124#ifdef OOMPH_HAS_MPI
1125 int range = n_patch;
1126
1127 // Work out values for parallel non-distributed problem
1128 if (!(mesh_pt->is_mesh_distributed()))
1129 {
1130 // setup the loop variables
1131 range = n_patch / n_proc; // number of patches on each proc
1132
1133 itbegin = my_rank * range;
1134 itend = (my_rank + 1) * range;
1135
1136 // if on the last processor, ensure the end matches
1137 if (my_rank == (n_proc - 1))
1138 {
1139 itend = n_patch;
1140 }
1141 }
1142#endif
1143
1144 // Set up matrices and vectors which will be sent later
1145 // - full matrix of all recovered coefficients
1148 // - vectors containing element numbers in each patch
1150
1151 // Now we can loop over the patches on the current process
1152 for (int i = itbegin; i < itend; i++)
1153 {
1154 // Which vertex node are we at?
1155 Node* nod_pt = vertex_node_pt[i];
1156
1157 // Pointer to vector of pointers to elements that make up
1158 // the patch.
1161
1162 // Is the corner node that is central to the patch surrounded by
1163 // at least two elements?
1164 unsigned nelem = (*el_vec_pt).size();
1165
1166 if (nelem >= 2)
1167 {
1168 // store the number of elements in the patch
1170 for (unsigned e = 0; e < nelem; e++)
1171 {
1173 }
1174
1175 // put them into storage vector ready to send
1177
1178 // Given the vector of elements that make up the patch,
1179 // the number of recovery and flux terms, and the spatial
1180 // dimension of the problem, compute
1181 // the matrix of recovered flux coefficients and return
1182 // a pointer to it.
1187 dim,
1189
1190 // Store pointer to recovered flux coefficients for
1191 // current patch in vector so we can send and then delete it later
1194
1195 } // end of if(nelem>=2)
1196 }
1197
1198 // Now broadcast the result from each process to every other process
1199 // if the mesh has not yet been distributed and MPI is initialised
1200
1201#ifdef OOMPH_HAS_MPI
1202
1203 if (!mesh_pt->is_mesh_distributed() &&
1205 {
1206 // Get communicator from namespace
1208
1209 // All local recovered fluxes have been calculated, so now share result
1210 for (int iproc = 0; iproc < n_proc; iproc++)
1211 {
1212 // Broadcast number of patches processed
1214 MPI_Bcast(&n_patches, 1, MPI_INT, iproc, comm_pt->mpi_comm());
1215
1216 // Loop over these patches, broadcast recovered flux coefficients
1217 for (int ipatch = 0; ipatch < n_patches; ipatch++)
1218 {
1219 // Number of elements in this patch
1221 unsigned nelements = 0;
1222
1223 // Which processor are we on?
1224 if (my_rank == iproc)
1225 {
1228 }
1229
1230 // Broadcast elements
1231 comm_pt->broadcast(iproc, elements);
1232
1233 // Now get recovered flux coefficients
1235
1236 // Which processor are we on?
1237 if (my_rank == iproc)
1238 {
1241 }
1242 else
1243 {
1245 }
1246
1247 // broadcast this matrix from the loop processor
1249 comm_pt->broadcast(iproc, mattosend);
1250
1251 // Set pointer on all processors
1253
1254 // End of parallel broadcasting
1257
1258 // Loop over elements in patch (work out nelements again after bcast)
1260
1261 for (unsigned e = 0; e < nelements; e++)
1262 {
1263 // Get pointer to element
1265 dynamic_cast<ElementWithZ2ErrorEstimator*>(
1266 mesh_pt->element_pt(elements[e]));
1267
1268 // Loop over all nodes in element
1269 unsigned num_nod = el_pt->nnode();
1270 for (unsigned n = 0; n < num_nod; n++)
1271 {
1272 // Get the node
1273 Node* nod_pt = el_pt->node_pt(n);
1274 // Add the pointer to the current flux coefficient matrix
1275 // to the set for the node
1276 // Mesh not distributed here so nod_pt cannot be halo
1278 }
1279 }
1280
1281 } // end loop over patches on current processor
1282
1283 } // end loop over processors
1284 }
1285 else // is_mesh_distributed=true
1286 {
1287#endif // end ifdef OOMPH_HAS_MPI for parallel job without mesh distribution
1288
1289 // Do the same for a distributed mesh as for a serial job
1290 // up to the point where the elemental error is calculated
1291 // and then communicate that (see below)
1293
1294 // Loop over these patches
1295 for (int ipatch = 0; ipatch < n_patches; ipatch++)
1296 {
1297 // Number of elements in this patch
1299 int nelements; // Needs to be int for elem_num call later
1302
1303 // Now get recovered flux coefficients
1307
1310
1311 for (int e = 0; e < nelements; e++)
1312 {
1313 // Get pointer to element
1315 dynamic_cast<ElementWithZ2ErrorEstimator*>(
1316 mesh_pt->element_pt(elements[e]));
1317
1318 // Loop over all nodes in element
1319 unsigned num_nod = el_pt->nnode();
1320 for (unsigned n = 0; n < num_nod; n++)
1321 {
1322 // Get the node
1323 Node* nod_pt = el_pt->node_pt(n);
1324 // Add the pointer to the current flux coefficient matrix
1325 // to the set for this node
1327 }
1328 }
1329
1330 } // End loop over patches on current processor
1331
1332
1333#ifdef OOMPH_HAS_MPI
1334 } // End if(is_mesh_distributed)
1335#endif
1336
1337 // Cleanup patch storage scheme
1338 for (IT it = adjacent_elements_pt.begin(); it != adjacent_elements_pt.end();
1339 it++)
1340 {
1341 delete it->second;
1342 }
1343 adjacent_elements_pt.clear();
1344
1345 // Loop over all nodes, take average of recovered flux values
1346 //-----------------------------------------------------------
1347 // and evaluate recovered flux at nodes
1348 //-------------------------------------
1349
1350 // Map of (averaged) recoverd flux values at nodes
1352
1353 // Loop over all nodes
1354 unsigned n_node = mesh_pt->nnode();
1355 for (unsigned n = 0; n < n_node; n++)
1356 {
1357 Node* nod_pt = mesh_pt->node_pt(n);
1358
1359 // How many patches is this node a member of?
1360 unsigned npatches = flux_coeff_pt[nod_pt].size();
1361
1362 // Matrix of averaged coefficients for this node
1365
1366 // Loop over matrices for different patches and add contributions
1367 typedef std::set<DenseMatrix<double>*>::iterator IT;
1368 for (IT it = flux_coeff_pt[nod_pt].begin();
1369 it != flux_coeff_pt[nod_pt].end();
1370 it++)
1371 {
1372 for (unsigned i = 0; i < num_recovery_terms; i++)
1373 {
1374 for (unsigned j = 0; j < num_flux_terms; j++)
1375
1376 {
1377 // ...just add it -- we'll divide by the number of patches later
1378 averaged_flux_coeff(i, j) += (*(*it))(i, j);
1379 }
1380 }
1381 }
1382
1383 // Now evaluate the recovered flux (based on the averaged coefficients)
1384 //---------------------------------------------------------------------
1385 // at the nodal position itself.
1386 //------------------------------
1387
1388 // Get global (Eulerian) nodal position
1389 Vector<double> x(dim);
1390 for (unsigned i = 0; i < dim; i++)
1391 {
1392 x[i] = nod_pt->x(i);
1393 }
1394
1395 // Evaluate global recovery functions at node
1397 shape_rec(x, dim, psi_r);
1398
1399 // Initialise nodal fluxes
1400 for (unsigned i = 0; i < num_flux_terms; i++)
1401 {
1402 rec_flux_map(nod_pt, i) = 0.0;
1403 }
1404
1405 // Loop over coefficients for flux recovery
1406 for (unsigned i = 0; i < num_flux_terms; i++)
1407 {
1408 for (unsigned icoeff = 0; icoeff < num_recovery_terms; icoeff++)
1409 {
1410 rec_flux_map(nod_pt, i) +=
1412 }
1413 // Now take averaging into account
1415 }
1416
1417 } // end loop over nodes
1418
1419 // We're done with the recovered flux coefficient matrices for
1420 // the various patches and can delete them
1422 for (unsigned p = 0; p < npatch; p++)
1423 {
1425 }
1426
1427 // NOTE FOR FUTURE REFERENCE - revisit in case of adaptivity problems in
1428 // parallel jobs
1429 //
1430 // The current method for distributed problems neglects recovered flux
1431 // contributions from patches that cannot be assembled on the current
1432 // processor, but would be assembled if the problem was not distributed.
1433 // The only nodes for which this is the case are nodes which lie on the
1434 // exact boundary between processors.
1435 //
1436 // See the note at the start of setup_patches (above) for more details.
1437
1438 // Get error estimates for all elements
1439 //======================================
1440
1441 // Find the number of compound fluxes
1442 // Loop over all (non-halo) elements
1443 nelem = mesh_pt->nelement();
1444 // Initialise the number of compound fluxes
1445 // Must be an integer for an MPI call later on
1446 int n_compound_flux = 1;
1447 for (unsigned e = 0; e < nelem; e++)
1448 {
1450 dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(e));
1451
1452#ifdef OOMPH_HAS_MPI
1453 // Ignore halo elements
1454 if (!el_pt->is_halo())
1455 {
1456#endif
1457 // Find the number of compound fluxes in the element
1459 // If it's greater than the current (global) number of compound fluxes
1460 // bump up the global number
1462 {
1464 }
1465#ifdef OOMPH_HAS_MPI
1466 } // end if (!el_pt->is_halo())
1467#endif
1468 }
1469
1470 // Initialise a vector of flux norms
1472
1473 unsigned test_count = 0;
1474
1475 // Storage for the elemental compound flux error
1477 nelem, n_compound_flux, 0.0);
1478
1479 // Loop over all (non-halo) elements again
1480 for (unsigned e = 0; e < nelem; e++)
1481 {
1483 dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(e));
1484
1485#ifdef OOMPH_HAS_MPI
1486 // Ignore halo elements
1487 if (!el_pt->is_halo())
1488 {
1489#endif
1490
1491 Vector<double> s(dim);
1492
1493 // Initialise elemental error one for each compound flux in the element
1494 const unsigned n_compound_flux_el = el_pt->ncompound_fluxes();
1496
1498
1499 // Set the value of Nintpt
1500 const unsigned n_intpt = integ_pt->nweight();
1501
1502 // Loop over the integration points
1503 for (unsigned ipt = 0; ipt < n_intpt; ipt++)
1504 {
1505 // Assign values of s
1506 for (unsigned i = 0; i < dim; i++)
1507 {
1508 s[i] = integ_pt->knot(ipt, i);
1509 }
1510
1511 // Get the integral weight
1512 double w = integ_pt->weight(ipt);
1513
1514 // Jacobian of mapping
1515 double J = el_pt->J_eulerian(s);
1516
1517 // Get the Eulerian position
1518 Vector<double> x(dim);
1519 el_pt->interpolated_x(s, x);
1520
1521 // Premultiply the weights and the Jacobian
1522 // and the geometric jacobian weight (used in axisymmetric
1523 // and spherical coordinate systems)
1524 double W = w * J * (el_pt->geometric_jacobian(x));
1525
1526 // Number of FE nodes
1527 unsigned n_node = el_pt->nnode();
1528
1529 // FE shape function
1530 Shape psi(n_node);
1531
1532 // Get values of FE shape function
1533 el_pt->shape(s, psi);
1534
1535 // Initialise recovered flux Vector
1537
1538 // Loop over all nodes (incl. halo nodes) to assemble contribution
1539 for (unsigned n = 0; n < n_node; n++)
1540 {
1541 Node* nod_pt = el_pt->node_pt(n);
1542
1543 // Loop over components
1544 for (unsigned i = 0; i < num_flux_terms; i++)
1545 {
1546 rec_flux[i] += rec_flux_map(nod_pt, i) * psi[n];
1547 }
1548 }
1549
1550 // FE flux
1553 // Get compound flux indices. Initialised to zero
1554 Vector<unsigned> flux_index(num_flux_terms, 0);
1556
1557 // Add to RMS errors for each compound flux:
1560 for (unsigned i = 0; i < num_flux_terms; i++)
1561 {
1562 sum[flux_index[i]] +=
1563 (rec_flux[i] - fe_flux[i]) * (rec_flux[i] - fe_flux[i]);
1564 sum2[flux_index[i]] += rec_flux[i] * rec_flux[i];
1565 }
1566
1567 for (unsigned i = 0; i < n_compound_flux_el; i++)
1568 {
1569 // Add the errors to the appropriate compound flux error
1570 error[i] += sum[i] * W;
1571 // Add to flux norm
1572 flux_norm[i] += sum2[i] * W;
1573 }
1574 }
1575 // Unscaled elemental RMS error:
1576 test_count++; // counting elements visited
1577
1578 // elemental_error[e]=sqrt(error);
1579 // Take the square-root of the appropriate flux error and
1580 // store the result
1581 for (unsigned i = 0; i < n_compound_flux_el; i++)
1582 {
1584 }
1585
1586#ifdef OOMPH_HAS_MPI
1587 } // end if (!el_pt->is_halo())
1588#endif
1589 } // end of loop over elements
1590
1591 // Communicate the error for haloed elements to halo elements:
1592 // - loop over processors
1593 // - if current process, receive to halo element error
1594 // - if not current process, send haloed element error
1595 // How do we know which part of elemental_error to send?
1596 // Loop over haloed elements and find element number using elem_num map;
1597 // send this - order preservation of halo/haloed elements
1598 // guarantees that they get through in the correct order
1599#ifdef OOMPH_HAS_MPI
1600
1601 if (mesh_pt->is_mesh_distributed())
1602 {
1603 // Get communicator from mesh
1604 OomphCommunicator* comm_pt = mesh_pt->communicator_pt();
1605
1606 for (int iproc = 0; iproc < n_proc; iproc++)
1607 {
1608 if (iproc != my_rank) // Not current process, so send
1609 {
1610 // Get the haloed elements
1612 mesh_pt->haloed_element_pt(iproc);
1613 // Find the number of haloed elements
1615
1616 // If there are some haloed elements, assemble and send the
1617 // errors
1618 if (nelem_haloed != 0)
1619 {
1620 // Find the number of error entires:
1621 // number of haloed elements x number of compound fluxes
1623 // Vector for elemental errors
1625 // Counter for the vector index
1626 unsigned count = 0;
1627 for (int e = 0; e < nelem_haloed; e++)
1628 {
1629 // Find element number
1630 int element_num =
1632 haloed_elem_pt[e])];
1633 // Put the error in a vector to send
1634 for (int i = 0; i < n_compound_flux; i++)
1635 {
1638 ++count;
1639 }
1640 }
1641 // Send the errors
1644 MPI_DOUBLE,
1645 iproc,
1646 0,
1647 comm_pt->mpi_comm());
1648 }
1649 }
1650 else // iproc=my_rank, so receive errors from others
1651 {
1652 for (int send_rank = 0; send_rank < n_proc; send_rank++)
1653 {
1654 if (iproc != send_rank) // iproc=my_rank already!
1655 {
1657 mesh_pt->halo_element_pt(send_rank);
1658 // Find number of halo elements
1660 // If there are some halo elements, receive errors and
1661 // put in the appropriate places
1662 if (nelem_halo != 0)
1663 {
1664 // Find the number of error entires:
1665 // number of haloed elements x number of compound fluxes
1667 // Vector for elemental errors
1669
1670
1671 // Receive the errors from processor send_rank
1674 MPI_DOUBLE,
1675 send_rank,
1676 0,
1677 comm_pt->mpi_comm(),
1678 &status);
1679
1680 // Counter for the vector index
1681 unsigned count = 0;
1682 for (int e = 0; e < nelem_halo; e++)
1683 {
1684 // Find element number
1685 int element_num =
1687 halo_elem_pt[e])];
1688 // Put the error in the correct location
1689 for (int i = 0; i < n_compound_flux; i++)
1690 {
1693 ++count;
1694 }
1695 }
1696 }
1697 }
1698 } // End of interior loop over processors
1699 }
1700 } // End of exterior loop over processors
1701
1702 } // End of if (mesh has been distributed)
1703
1704#endif
1705
1706 // NOTE FOR FUTURE REFERENCE - revisit in case of adaptivity problems in
1707 // parallel jobs
1708 //
1709 // The current method for distributed problems neglects recovered flux
1710 // contributions from patches that cannot be assembled on the current
1711 // processor, but would be assembled if the problem was not distributed.
1712 // The only nodes for which this is the case are nodes which lie on the
1713 // exact boundary between processors.
1714 //
1715 // See the note at the start of setup_patches (above) for more details.
1716
1717 // Use computed flux norm or externally imposed reference value?
1718 if (Reference_flux_norm != 0.0)
1719 {
1720 // At the moment assume that all fluxes have the same reference norm
1721 for (int i = 0; i < n_compound_flux; i++)
1722 {
1724 }
1725 }
1726 else
1727 {
1728 // In parallel, perform reduction operation to get global value
1729#ifdef OOMPH_HAS_MPI
1730 if (mesh_pt->is_mesh_distributed())
1731 {
1732 // Get communicator from mesh
1733 OomphCommunicator* comm_pt = mesh_pt->communicator_pt();
1734
1736 // every process needs to know the sum
1738 &total_flux_norm[0],
1740 MPI_DOUBLE,
1741 MPI_SUM,
1742 comm_pt->mpi_comm());
1743 // take sqrt
1744 for (int i = 0; i < n_compound_flux; i++)
1745 {
1747 }
1748 }
1749 else // mesh has not been distributed, so flux_norm already global
1750 {
1751 for (int i = 0; i < n_compound_flux; i++)
1752 {
1753 flux_norm[i] = sqrt(flux_norm[i]);
1754 }
1755 }
1756#else // serial problem, so flux_norm already global
1757 for (int i = 0; i < n_compound_flux; i++)
1758 {
1759 flux_norm[i] = sqrt(flux_norm[i]);
1760 }
1761#endif
1762 }
1763
1764 // Now loop over (all!) elements again and
1765 // normalise errors by global flux norm
1766
1767 nelem = mesh_pt->nelement();
1768 for (unsigned e = 0; e < nelem; e++)
1769 {
1770 // Get the vector of normalised compound fluxes
1772 for (int i = 0; i < n_compound_flux; i++)
1773 {
1774 if (flux_norm[i] != 0.0)
1775 {
1778 }
1779 else
1780 {
1783 }
1784 }
1785
1786 // calculate the combined error estimate
1789 }
1790
1791 // Doc global fluxes?
1792 if (doc_info.is_doc_enabled())
1793 {
1794 doc_flux(
1795 mesh_pt, num_flux_terms, rec_flux_map, elemental_error, doc_info);
1796 }
1797 }
1798
1799
1800 //==================================================================
1801 /// Doc FE and recovered flux
1802 //==================================================================
1804 Mesh* mesh_pt,
1805 const unsigned& num_flux_terms,
1808 DocInfo& doc_info)
1809 {
1810#ifdef OOMPH_HAS_MPI
1811
1812 // Get communicator from mesh
1813 OomphCommunicator* comm_pt = mesh_pt->communicator_pt();
1814
1815#else
1816
1817 // Dummy communicator
1819
1820#endif
1821
1822 // File suffix identifying processor rank. If comm_pt is null (because
1823 // oomph-lib was built with MPI but this mesh is not distributed) the
1824 // string is empty.
1825 std::string rank_string = "";
1826 if (comm_pt != 0)
1827 {
1828 rank_string =
1829 std::string("_on_proc_") + std::to_string(comm_pt->my_rank());
1830 }
1831
1832 // Setup output files
1833 std::ofstream some_file, feflux_file;
1834 std::ostringstream filename;
1835 filename << doc_info.directory() << "/flux_rec" << doc_info.number()
1836 << rank_string << ".dat";
1837 some_file.open(filename.str().c_str());
1838 filename.str("");
1839 filename << doc_info.directory() << "/flux_fe" << doc_info.number()
1840 << rank_string << ".dat";
1841 feflux_file.open(filename.str().c_str());
1842
1843 unsigned nel = mesh_pt->nelement();
1844 if (nel > 0)
1845 {
1846 // Extract first element to determine spatial dimension
1847 FiniteElement* el_pt = mesh_pt->finite_element_pt(0);
1848 unsigned dim = el_pt->dim();
1849 Vector<double> s(dim);
1850
1851 // Decide on the number of plot points
1852 unsigned nplot = 5;
1853
1854 // Loop over all elements
1855 for (unsigned e = 0; e < nel; e++)
1856 {
1858 dynamic_cast<ElementWithZ2ErrorEstimator*>(mesh_pt->element_pt(e));
1859
1860 // Write tecplot header
1863
1865 for (unsigned iplot = 0; iplot < num_plot_points; iplot++)
1866 {
1867 // Get local coordinates of plot point
1869
1870 // Coordinate
1871 Vector<double> x(dim);
1872 el_pt->interpolated_x(s, x);
1873
1874 // Number of FE nodes
1875 unsigned n_node = el_pt->nnode();
1876
1877 // FE shape function
1878 Shape psi(n_node);
1879
1880 // Get values of FE shape function
1881 el_pt->shape(s, psi);
1882
1883 // Initialise recovered flux Vector
1885
1886 // Loop over nodes to assemble contribution
1887 for (unsigned n = 0; n < n_node; n++)
1888 {
1889 Node* nod_pt = el_pt->node_pt(n);
1890
1891 // Loop over components
1892 for (unsigned i = 0; i < num_flux_terms; i++)
1893 {
1894 rec_flux[i] += rec_flux_map(nod_pt, i) * psi[n];
1895 }
1896 }
1897
1898 // FE flux
1901
1902 for (unsigned i = 0; i < dim; i++)
1903 {
1904 some_file << x[i] << " ";
1905 }
1906 for (unsigned i = 0; i < num_flux_terms; i++)
1907 {
1908 some_file << rec_flux[i] << " ";
1909 }
1910 some_file << elemental_error[e] << " " << std::endl;
1911
1912
1913 for (unsigned i = 0; i < dim; i++)
1914 {
1915 feflux_file << x[i] << " ";
1916 }
1917 for (unsigned i = 0; i < num_flux_terms; i++)
1918 {
1919 feflux_file << fe_flux[i] << " ";
1920 }
1921 feflux_file << elemental_error[e] << " " << std::endl;
1922 }
1923
1924 // Write tecplot footer (e.g. FE connectivity)
1925 // Do this for each element so the output is compatible with
1926 // oomph-convert
1929 }
1930 }
1931
1932 some_file.close();
1933 feflux_file.close();
1934 }
1935
1936
1937} // 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:1150
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:1596
FiniteElement * finite_element_pt(const unsigned &e) const
Upcast (downcast?) to FiniteElement (needed to access FiniteElement member functions).
Definition mesh.h:477
Node *& node_pt(const unsigned long &n)
Return pointer to global node n.
Definition mesh.h:440
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:1748
unsigned long nnode() const
Return number of nodes in the mesh.
Definition mesh.h:604
GeneralisedElement *& element_pt(const unsigned long &e)
Return pointer to element e.
Definition mesh.h:452
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:1608
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:1787
unsigned long nelement() const
Return number of elements in the mesh.
Definition mesh.h:598
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
TAdvectionDiffusionReactionElement<NREAGENT,DIM,NNODE_1D> elements are isoparametric triangular DIM-d...
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.
Empty base class for Telements (created so that we can use dynamic_cast<>() to figure out if a an ele...
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.
DRAIG: Change all instances of (SPATIAL_DIM) to (DIM-1).
OomphInfo oomph_info
Single (global) instantiation of the OomphInfo object – this is used throughout the library as a "rep...