problem.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
27#include <cmath>
28#ifdef OOMPH_HAS_MPI
29#include "mpi.h"
30#endif
31
32#include <list>
33#include <algorithm>
34#include <string>
35
36#include "oomph_utilities.h"
37#include "problem.h"
38#include "timesteppers.h"
41#include "refineable_mesh.h"
42#include "triangle_mesh.h"
43#include "linear_solver.h"
44#include "eigen_solver.h"
45#include "assembly_handler.h"
46#include "dg_elements.h"
47#include "partitioning.h"
48#include "spines.h"
49
50
51#ifdef OOMPH_HAS_MUMPS
52#include "mumps_solver.h"
53#endif
54
55namespace oomph
56{
57 /// ///////////////////////////////////////////////////////////////
58 // Non-inline functions for the problem class
59 /// ///////////////////////////////////////////////////////////////
60
61 //=================================================================
62 /// The continuation timestepper object
63 //=================================================================
64 ContinuationStorageScheme Problem::Continuation_time_stepper;
65
66 //================================================================
67 /// Constructor: Allocate space for one time stepper
68 /// and set all pointers to NULL and set defaults for all
69 /// parameters.
70 //===============================================================
72 : Mesh_pt(0),
73 Time_pt(0),
74 Explicit_time_stepper_pt(0),
75 Saved_dof_pt(0),
76 Default_set_initial_condition_called(false),
77 Use_globally_convergent_newton_method(false),
78 Empty_actions_before_read_unstructured_meshes_has_been_called(false),
79 Empty_actions_after_read_unstructured_meshes_has_been_called(false),
80 Store_local_dof_pt_in_elements(false),
81 Calculate_hessian_products_analytic(false),
83 Doc_imbalance_in_parallel_assembly(false),
84 Use_default_partition_in_load_balance(false),
85 Must_recompute_load_balance_for_assembly(true),
86 Halo_scheme_pt(0),
87#endif
88 Relaxation_factor(1.0),
89 Newton_solver_tolerance(1.0e-8),
90 Max_newton_iterations(10),
91 Nnewton_iter_taken(0),
92 Max_residuals(10.0),
93 Time_adaptive_newton_crash_on_solve_fail(false),
94 Jacobian_reuse_is_enabled(false),
95 Jacobian_has_been_computed(false),
96 Problem_is_nonlinear(true),
97 Pause_at_end_of_sparse_assembly(false),
98 Doc_time_in_distribute(false),
99 Sparse_assembly_method(Perform_assembly_using_vectors_of_pairs),
100 Sparse_assemble_with_arrays_initial_allocation(400),
101 Sparse_assemble_with_arrays_allocation_increment(150),
102 Numerical_zero_for_sparse_assembly(0.0),
103 FD_step_used_in_get_hessian_vector_products(1.0e-8),
104 Mass_matrix_reuse_is_enabled(false),
105 Mass_matrix_has_been_computed(false),
106 Discontinuous_element_formulation(false),
107 Minimum_dt(1.0e-12),
108 Maximum_dt(1.0e12),
109 DTSF_max_increase(4.0),
110 DTSF_min_decrease(0.8),
111 Target_error_safety_factor(1.0),
112 Minimum_dt_but_still_proceed(-1.0),
113 Scale_arc_length(true),
114 Desired_proportion_of_arc_length(0.5),
115 Theta_squared(1.0),
116 Sign_of_jacobian(0),
117 Continuation_direction(1.0),
118 Parameter_derivative(1.0),
119 Parameter_current(0.0),
120 Use_continuation_timestepper(false),
121 Dof_derivative_offset(1),
122 Dof_current_offset(2),
123 Ds_current(0.0),
124 Desired_newton_iterations_ds(5),
125 Minimum_ds(1.0e-10),
126 Bifurcation_detection(false),
127 Bisect_to_find_bifurcation(false),
128 First_jacobian_sign_change(false),
129 Arc_length_step_taken(false),
130 Use_finite_differences_for_continuation_derivatives(false),
132 Dist_problem_matrix_distribution(Uniform_matrix_distribution),
133 Parallel_sparse_assemble_previous_allocation(0),
134 Problem_has_been_distributed(false),
135 Bypass_increase_in_dof_check_during_pruning(false),
136 Max_permitted_error_for_halo_check(1.0e-14),
137#endif
138 Shut_up_in_newton_solve(false),
139 Always_take_one_newton_step(false),
140 Timestep_reduction_factor_after_nonconvergence(0.5),
141 Keep_temporal_error_below_tolerance(true)
142 {
144
145 /// Setup terminate helper
147
148 // By default no submeshes:
149 Sub_mesh_pt.resize(0);
150
151 // No timesteppers
152 Time_stepper_pt.resize(0);
153
154 // Set the linear solvers, eigensolver and assembly handler
155#if defined(OOMPH_HAS_MUMPS) && \
156 defined(OOMPH_ENABLE_MUMPS_AS_DEFAULT_LINEAR_SOLVER)
158#else
160#endif
161
163
165
167
168 // setup the communicator
169#ifdef OOMPH_HAS_MPI
171 {
173 }
174 else
175 {
177 }
178#else
180#endif
181
182 // just create an empty linear algebra distribution for the
183 // DOFs
184 // this is setup when assign_eqn_numbers(...) is called.
186 }
187
188 //================================================================
189 /// Destructor to clean up memory
190 //================================================================
192 {
193 // Delete the memory assigned for the global time
194 // (it's created on the fly in Problem::add_time_stepper_pt()
195 // so we are entitled to delete it.
196 if (Time_pt != 0)
197 {
198 delete Time_pt;
199 Time_pt = 0;
200 }
201
202 // We're not using the default linear solver,
203 // somebody else must have built it, so that person
204 // must be in charge of killing it.
205 // We can safely delete the defaults, however
207
210 delete Communicator_pt;
211 delete Dof_distribution_pt;
212
213 // Delete any copies of the problem that have been created for
214 // use in adaptive bifurcation tracking.
215 // ALH: This will eventually go
216 unsigned n_copies = Copy_of_problem_pt.size();
217 for (unsigned c = 0; c < n_copies; c++)
218 {
219 delete Copy_of_problem_pt[c];
220 }
221
222 // if this problem has sub meshes then we must delete the Mesh_pt
223 if (Sub_mesh_pt.size() != 0)
224 {
226 delete Mesh_pt;
227 }
228
229 // Since we called the TerminateHelper setup function in the constructor,
230 // we need to delete anything that was dynamically allocated (as it's
231 // just a namespace and so doesn't have it's own destructor) in the function
233 }
234
235 //=================================================================
236 /// Setup the count vector that records how many elements contribute
237 /// to each degree of freedom. Returns the total number of elements
238 /// in the problem
239 //=================================================================
241 {
242 // Now set the element counter to have the current Dof distribution
244 // We need to use the halo scheme (assuming it has been setup)
245#ifdef OOMPH_HAS_MPI
247#endif
248
249 // Loop over the elements and count the entries
250 // and number of (non-halo) elements
251 const unsigned n_element = this->mesh_pt()->nelement();
252 unsigned n_non_halo_element_local = 0;
253 for (unsigned e = 0; e < n_element; e++)
254 {
256#ifdef OOMPH_HAS_MPI
257 // Ignore halo elements
258 if (!elem_pt->is_halo())
259 {
260#endif
261 // Increment the number of non halo elements
263 // Now count the number of times the element contributes to a value
264 // using the current assembly handler
265 unsigned n_var = this->Assembly_handler_pt->ndof(elem_pt);
266 for (unsigned n = 0; n < n_var; n++)
267 {
269 this->Assembly_handler_pt->eqn_number(elem_pt, n));
270 }
271#ifdef OOMPH_HAS_MPI
272 }
273#endif
274 }
275
276 // Storage for the total number of elements
277 unsigned Nelement = 0;
278
279 // Add together all the counts if we are in parallel
280#ifdef OOMPH_HAS_MPI
282
283 // If distributed, find the total number of elements in the problem
285 {
286 // Need to gather the total number of non halo elements
288 &Nelement,
289 1,
291 MPI_SUM,
292 this->communicator_pt()->mpi_comm());
293 }
294 // Otherwise the total number is the same on each processor
295 else
296#endif
297 {
298 Nelement = n_non_halo_element_local;
299 }
300
301 return Nelement;
302 }
303
304
305 //==================================================================
306 /// Build new LinearAlgebraDistribution. Note: you're in charge of
307 /// deleting it!
308 //==================================================================
311 {
312 // Find the number of rows
313 const unsigned nrow = this->ndof();
314
315#ifdef OOMPH_HAS_MPI
316
317 unsigned nproc = Communicator_pt->nproc();
318
319 // if problem is only one one processor assemble non-distributed
320 // distribution
321 if (nproc == 1)
322 {
324 }
325 // if the problem is not distributed then assemble the jacobian with
326 // a uniform distributed distribution
328 {
330 }
331 // otherwise the problem is a distributed problem
332 else
333 {
335 {
337
339 break;
340
342
344 break;
345
347
348 // Put in its own scope to avoid warnings about "local" variables
349 {
352 bool use_problem_dist = true;
353 for (unsigned p = 0; p < nproc; p++)
354 {
355 // hierher Andrew: what's the logic behind this?
356 if ((double)Dof_distribution_pt->nrow_local(p) >
357 ((double)uniform_dist_pt->nrow_local(p)) * 1.1)
358 {
359 use_problem_dist = false;
360 }
361 }
363 {
365 }
366 else
367 {
369 }
370 delete uniform_dist_pt;
371 }
372 break;
373
374 default:
375
376 std::ostringstream error_stream;
377 error_stream << "Never get here. Dist_problem_matrix_distribution = "
378 << Dist_problem_matrix_distribution << std::endl;
379 throw OomphLibError(error_stream.str(),
382 break;
383 }
384 }
385#else
387#endif
388 }
389
390
391#ifdef OOMPH_HAS_MPI
392
393 //==================================================================
394 /// Setup the halo scheme for the degrees of freedom
395 //==================================================================
397 {
398 // Find the number of elements stored on this processor
399 const unsigned n_element = this->mesh_pt()->nelement();
400
401 // Work out the all global equations to which this processor
402 // contributes
404 this->get_my_eqns(this->Assembly_handler_pt, 0, n_element - 1, my_eqns);
405
406 // Build the halo scheme, based on the equations to which this
407 // processor contributes
410
411 // Find pointers to all the halo dofs
412 // There may be more of these than required by my_eqns
413 //(but should not be less)
414 std::map<unsigned, double*> halo_data_pt;
415 this->get_all_halo_data(halo_data_pt);
416
417 // Now setup the Halo_dofs
419 }
420
421 //==================================================================
422 /// Distribute the problem without doc; report stats if required.
423 /// Returns actual partitioning used, e.g. for restart.
424 //==================================================================
426 {
427 // Set dummy doc paramemters
428 DocInfo doc_info;
429 doc_info.disable_doc();
430
431 // Set the sizes of the input and output vectors
432 unsigned n_element = mesh_pt()->nelement();
434
435 // Distribute and return partitioning
436 return distribute(element_partition, doc_info, report_stats);
437 }
438
439 //==================================================================
440 /// Distribute the problem according to specified partition.
441 /// If all entries in partitioning vector are zero we use METIS
442 /// to do the partitioning after all.
443 /// Returns actual partitioning used, e.g. for restart.
444 //==================================================================
447 {
448#ifdef PARANOID
449 bool has_non_zero_entry = false;
450 unsigned n = element_partition.size();
451 for (unsigned i = 0; i < n; i++)
452 {
453 if (element_partition[i] != 0)
454 {
455 has_non_zero_entry = true;
456 break;
457 }
458 }
460 {
461 std::ostringstream warn_message;
462 warn_message << "WARNING: All entries in specified partitioning vector \n"
463 << " are zero -- will ignore this and use METIS\n"
464 << " to perform the partitioning\n";
466 warn_message.str(), "Problem::distribute()", OOMPH_EXCEPTION_LOCATION);
467 }
468#endif
469 // Set dummy doc paramemters
470 DocInfo doc_info;
471 doc_info.disable_doc();
472
473 // Distribute and return partitioning
474 return distribute(element_partition, doc_info, report_stats);
475 }
476
477 //==================================================================
478 /// Distribute the problem and doc to specified DocInfo.
479 /// Returns actual partitioning used, e.g. for restart.
480 //==================================================================
482 const bool& report_stats)
483 {
484 // Set the sizes of the input and output vectors
485 unsigned n_element = mesh_pt()->nelement();
486
487 // Dummy input vector
489
490 // Distribute and return partitioning
491 return distribute(element_partition, doc_info, report_stats);
492 }
493
494 //==================================================================
495 /// Distribute the problem according to specified partition.
496 /// (If all entries in partitioning vector are zero we use METIS
497 /// to do the partitioning after all) and doc.
498 /// Returns actual partitioning used, e.g. for restart.
499 //==================================================================
502 DocInfo& doc_info,
503 const bool& report_stats)
504 {
505 // Storage for number of processors and number of elements in global mesh
506 int n_proc = this->communicator_pt()->nproc();
507 int my_rank = this->communicator_pt()->my_rank();
508 int n_element = mesh_pt()->nelement();
509
510 // Vector to be returned
512
513 // Buffer extreme cases
514 if (n_proc == 1) // single-process job - don't do anything
515 {
516 if (report_stats)
517 {
518 std::ostringstream warn_message;
519 warn_message << "WARNING: You've tried to distribute a problem over\n"
520 << "only one processor: this would make METIS crash.\n"
521 << "Ignoring your request for distribution.\n";
523 "Problem::distribute()",
525 }
526 }
527 else if (n_proc > n_element) // more processors than elements
528 {
529 // Throw an error
530 std::ostringstream error_stream;
531 error_stream << "You have tried to distribute a problem\n"
532 << "but there are less elements than processors.\n"
533 << "Please re-run with more elements!\n"
534 << "Please also ensure that actions_before_distribute().\n"
535 << "and actions_after_distribute() are correctly set up.\n"
536 << std::endl;
537 throw OomphLibError(
539 }
540 else
541 {
542 // We only distribute uniformly-refined meshes; buffer the case where
543 // either mesh is not uniformly refined
545 unsigned n_mesh = nsub_mesh();
546 if (n_mesh == 0)
547 {
548 // Check refinement levels
550 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(0)))
551 {
552 unsigned min_ref_level = 0;
553 unsigned max_ref_level = 0;
554 mmesh_pt->get_refinement_levels(min_ref_level, max_ref_level);
555 // If they are not the same
557 {
559 }
560 }
561 }
562 else
563 {
564 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
565 {
566 // Check refinement levels for each mesh individually
567 // (one mesh is allowed to be "more uniformly refined" than another)
570 {
571 unsigned min_ref_level = 0;
572 unsigned max_ref_level = 0;
573 mmesh_pt->get_refinement_levels(min_ref_level, max_ref_level);
574 // If they are not the same
576 {
578 }
579 }
580 }
581 }
582
583 // If any mesh is not uniformly refined
585 {
586 // Again it may make more sense to throw an error here as the user
587 // will probably not be running a problem that is small enough to
588 // fit the whole of on each processor
589 std::ostringstream error_stream;
590 error_stream << "You have tried to distribute a problem\n"
591 << "but at least one of your meshes is no longer\n"
592 << "uniformly refined. In order to preserve the Tree\n"
593 << "and TreeForest structure, Problem::distribute() can\n"
594 << "only be called while meshes are uniformly refined.\n"
595 << std::endl;
596 throw OomphLibError(
598 }
599 else
600 {
601 // Is there any global data? If so, distributing the problem won't work
602 if (nglobal_data() > 0)
603 {
604 std::ostringstream error_stream;
605 error_stream << "You have tried to distribute a problem\n"
606 << "and there is some global data.\n"
607 << "This is not likely to work...\n"
608 << std::endl;
609 throw OomphLibError(error_stream.str(),
612 }
613
614 double t_start = 0;
616 {
618 }
619
620
621#ifdef PARANOID
622 unsigned old_ndof = ndof();
623#endif
624
625 // Need to partition the global mesh before distributing
627
628 // Vector listing the affiliation of each element
629 unsigned nelem = global_mesh_pt->nelement();
631
632 // Number of elements that I'm in charge of, based on any
633 // incoming partitioning
634 unsigned n_my_elements = 0;
635
636 // Have we used the pre-set partitioning
637 bool used_preset_partitioning = false;
638
639 // Partition the mesh, unless the partition has already been passed in
640 // If it hasn't then the sum of all the entries of the vector should be
641 // 0
642 unsigned sum_element_partition = 0;
643 unsigned n_part = element_partition.size();
644 for (unsigned e = 0; e < n_part; e++)
645 {
646 // ... another one for me.
648
650 }
651 if (sum_element_partition == 0)
652 {
653 oomph_info << "INFO: using METIS to partition elements" << std::endl;
656 }
657 else
658 {
659 oomph_info << "INFO: using pre-set partition of elements"
660 << std::endl;
663 }
664
665 // Set the GLOBAL Mesh as being distributed
666 global_mesh_pt->set_communicator_pt(this->communicator_pt());
667
668 double t_end = 0.0;
670 {
672 oomph_info << "Time for partitioning of global mesh: "
673 << t_end - t_start << std::endl;
675 }
676
677 // Store how many elements we had in the various sub-meshes
678 // before actions_before_distribute() (which may empty some of
679 // them).
681 if (n_mesh != 0)
682 {
683 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
684 {
685 unsigned nsub_elem = mesh_pt(i_mesh)->nelement();
687 }
688 }
689
690 // Partitioning complete; call actions before distribute
692
694 {
696 oomph_info << "Time for actions before distribute: "
697 << t_end - t_start << std::endl;
698 }
699
700 // This next bit is cheap -- omit timing
701 // t_start = TimingHelpers::timer();
702
703 // Number of submeshes (NB: some may have been deleted in
704 // actions_after_distribute())
705 n_mesh = nsub_mesh();
706
707
708 // Prepare vector of vectors for submesh element domains
710
711 // The submeshes need to know their own element domains.
712 // Also if any meshes have been emptied we ignore their
713 // partitioning in the vector that we return from here
715 if (n_mesh != 0)
716 {
717 unsigned count = 0;
718 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
719 {
720 unsigned nsub_elem = mesh_pt(i_mesh)->nelement();
723 for (unsigned e = 0; e < nsub_elem_old; e++)
724 {
726 {
729 }
730 // return_element_domain.push_back(element_domain[count]);
731 count++;
732 }
733 }
734 }
735 else
736 {
738 }
739
741 {
743 }
744
745 // Setup the map between "root" element and number in global mesh
746 // (currently used in the load_balance() routines)
747
748 // This map is only established for structured meshes, then we
749 // need to check here the type of mesh
750 if (n_mesh == 0)
751 {
752 // Check if the only one mesh is an structured mesh
753 bool structured_mesh = true;
755 dynamic_cast<TriangleMeshBase*>(mesh_pt(0));
756 if (tri_mesh_pt != 0)
757 {
758 structured_mesh = false;
759 } // if (tri_mesh_pt != 0)
760 if (structured_mesh)
761 {
762 const unsigned n_ele = global_mesh_pt->nelement();
765 for (unsigned e = 0; e < n_ele; e++)
766 {
770 } // for (e<n_ele)
771 } // A TreeBaseMesh mesh
772 } // if (n_mesh==0)
773 else
774 {
775 // If we have submeshes then we only add those elements that
776 // belong to structured meshes, but first compute the number
777 // of total elements in the structured meshes
778 unsigned nglobal_element = 0;
779 // Store which submeshes are structured
780 std::vector<bool> is_structured_mesh(n_mesh);
781 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
782 {
784 dynamic_cast<TriangleMeshBase*>(mesh_pt(i_mesh));
785 if (tri_mesh_pt != 0)
786 {
787 // Set the flags to indicate this is not an structured
788 // mesh
789 is_structured_mesh[i_mesh] = false;
790 } // if (tri_mesh_pt != 0)
791 else
792 {
793 // Set the flags to indicate this is an structured
794 // mesh
796 } // else if (tri_mesh_pt != 0)
797 // Check if mesh is an structured mesh
799 {
801 } // A TreeBaseMesh mesh
802 } // for (i_mesh<n_mesh)
803
804 // Once computed the number of elements, then resize the
805 // structure
808 unsigned counter = 0;
809 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
810 {
811 // Check if mesh is an structured mesh
813 {
814 const unsigned n_ele = mesh_pt(i_mesh)->nelement();
815 for (unsigned e = 0; e < n_ele; e++)
816 {
820 // Inrease the global element number
821 counter++;
822 } // for (e<n_ele)
823 } // An structured mesh
824 } // for (i_mesh<n_mesh)
825
826#ifdef PARANOID
828 {
829 std::ostringstream error_stream;
831 << "The number of global elements (" << nglobal_element
832 << ") is not the sameas the number of\nadded elements ("
833 << counter << ") to the Base_mesh_element_pt data "
834 << "structure!!!\n\n";
835 throw OomphLibError(error_stream.str(),
836 "Problem::distribute()",
838 } // if (counter != nglobal_element)
839#endif // #ifdef PARANOID
840
841 } // else if (n_mesh==0)
842
843 // Wipe everything if a pre-determined partitioning
844 // didn't specify ANY elements for this processor
845 // (typically happens during restarts with larger number
846 // of processors -- in this case we really want an empty
847 // processor rather than one with any "kept" halo elements)
850 {
851 oomph_info << "INFO: We're over-ruling the \"keep as halo element\"\n"
852 << " status because the preset partitioning\n"
853 << " didn't place ANY elements on this processor,\n"
854 << " probably because of a restart on a larger \n"
855 << " number of processors\n";
857 }
858
859
860 // Distribute the (sub)meshes (i.e. sort out their halo lookup schemes)
862 if (n_mesh == 0)
863 {
864 global_mesh_pt->distribute(this->communicator_pt(),
865 element_domain,
867 doc_info,
870 }
871 else // There are submeshes, "distribute" each one separately
872 {
873 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
874 {
875 if (report_stats)
876 {
877 oomph_info << "Distributing submesh " << i_mesh << std::endl
878 << "--------------------" << std::endl;
879 }
880 // Set the doc_info number to reflect the submesh
881 doc_info.number() = i_mesh;
883 submesh_element_domain[i_mesh],
885 doc_info,
888 }
889 // Rebuild the global mesh
891 }
892
893 // Null out information associated with deleted elements
894 unsigned n_del = deleted_element_pt.size();
895 for (unsigned e = 0; e < n_del; e++)
896 {
901 }
902
904 {
906 oomph_info << "Time for mesh-level distribution: " << t_end - t_start
907 << std::endl;
909 }
910
911 // Now the problem has been distributed
913
914 // Call actions after distribute
916
918 {
920 oomph_info << "Time for actions after distribute: " << t_end - t_start
921 << std::endl;
923 }
924
925 // Re-assign the equation numbers (incl synchronisation if reqd)
926 unsigned n_dof = assign_eqn_numbers();
927 oomph_info << "Number of equations: " << n_dof << std::endl;
928
930 {
932 oomph_info << "Time for re-assigning eqn numbers (in distribute): "
933 << t_end - t_start << std::endl;
934 }
935
936
937#ifdef PARANOID
938 if (n_dof != old_ndof)
939 {
940 std::ostringstream error_stream;
942 << "Number of dofs in distribute() has changed "
943 << "from " << old_ndof << " to " << n_dof << "\n"
944 << "Check that you've implemented any necessary "
945 "actions_before/after\n"
946 << "distribute functions, e.g. to pin redundant pressure dofs"
947 << " etc.\n";
948 throw OomphLibError(error_stream.str(),
951 }
952#endif
953
954 } // end if to check for uniformly refined mesh(es)
955
956 } // end if to check number of processors vs. number of elements etc.
957
958
959 // Force re-analysis of time spent on assembly each
960 // elemental Jacobian
963
964 // Return the partition vector used in the distribution
966 }
967
968 //==================================================================
969 /// Partition the global mesh, return vector specifying the processor
970 /// number for each element. Virtual so that it can be overloaded by
971 /// any user; the default is to use METIS to perform the partitioning
972 /// (with a bit of cleaning up afterwards to sort out "special cases").
973 //==================================================================
975 DocInfo& doc_info,
977 const bool& report_stats)
978 {
979 // Storage for number of processors and current processor
980 int n_proc = this->communicator_pt()->nproc();
981 int rank = this->communicator_pt()->my_rank();
982
983 std::ostringstream filename;
984 std::ofstream some_file;
985
986 // Doc the original mesh on proc 0
987 //--------------------------------
988 if (doc_info.is_doc_enabled())
989 {
990 if (rank == 0)
991 {
992 filename << doc_info.directory() << "/complete_mesh"
993 << doc_info.number() << ".dat";
994 global_mesh_pt->output(filename.str().c_str(), 5);
995 }
996 }
997
998 // Partition the mesh
999 //-------------------
1000 // METIS Objective (0: minimise edge cut; 1: minimise total comm volume)
1001 unsigned objective = 0;
1002
1003 // Do the partitioning
1004 unsigned nelem = 0;
1005 if (this->communicator_pt()->my_rank() == 0)
1006 {
1009 }
1011 element_domain.resize(nelem);
1013 nelem,
1015 0,
1016 this->communicator_pt()->mpi_comm());
1017
1018 // On very coarse meshes with larger numbers of processors, METIS
1019 // occasionally returns an element_domain Vector for which a particular
1020 // processor has no elements affiliated to it; the following fixes this
1021
1022 // Convert element_domain to integer storage
1024 for (unsigned e = 0; e < nelem; e++)
1025 {
1027 }
1028
1029 // Global storage for number of elements on each process
1030 int my_number_of_elements = 0;
1032
1033 for (unsigned e = 0; e < nelem; e++)
1034 {
1035 if (int_element_domain[e] == rank)
1036 {
1038 }
1039 }
1040
1041 // Communicate the correct value for each single process into
1042 // the global storage vector
1044 1,
1045 MPI_INT,
1047 1,
1048 MPI_INT,
1049 this->communicator_pt()->mpi_comm());
1050
1051 // If a process has no elements then switch an element with the
1052 // process with the largest number of elements, assuming
1053 // that it still has enough elements left to share
1054 int max_number_of_elements = 0;
1056 for (int d = 0; d < n_proc; d++)
1057 {
1058 if (number_of_elements[d] == 0)
1059 {
1060 // Find the process with maximum number of elements
1061 if (max_number_of_elements <= 1)
1062 {
1063 for (int dd = 0; dd < n_proc; dd++)
1064 {
1066 {
1069 }
1070 }
1071 }
1072
1073 // Check that this number of elements is okay for sharing
1074 if (max_number_of_elements <= 1)
1075 {
1076 // Throw an error if elements can't be shared
1077 std::ostringstream error_stream;
1078 error_stream << "No process has more than 1 element, and\n"
1079 << "at least one process has no elements!\n"
1080 << "Suggest rerunning with more refinement.\n"
1081 << std::endl;
1082 throw OomphLibError(error_stream.str(),
1085 }
1086
1087 // Loop over the element domain vector and switch
1088 // one value for process "process_with_max_elements" with d
1089 for (unsigned e = 0; e < nelem; e++)
1090 {
1092 {
1093 int_element_domain[e] = d;
1094 // Change the numbers associated with these processes
1095 number_of_elements[d]++;
1097 // Reduce the number of elements available on "max" process
1099 // Inform the user that a switch has taken place
1100 if (report_stats)
1101 {
1102 oomph_info << "INFO: Switched element domain at position " << e
1103 << std::endl
1104 << "from process " << process_with_max_elements
1105 << " to process " << d << std::endl
1106 << "which was given no elements by METIS partition"
1107 << std::endl;
1108 }
1109 // Only need to do this once for this element loop, otherwise
1110 // this will take all the elements from "max" process and put them
1111 // in process d, thus leaving essentially the same problem!
1112 break;
1113 }
1114 }
1115 }
1116 }
1117
1118 // Reassign new values to the element_domain vector
1119 for (unsigned e = 0; e < nelem; e++)
1120 {
1122 }
1123
1124 unsigned count_elements = 0;
1125 for (unsigned e = 0; e < nelem; e++)
1126 {
1127 if (int(element_domain[e]) == rank)
1128 {
1130 }
1131 }
1132
1133 if (report_stats)
1134 {
1135 oomph_info << "I have " << count_elements
1136 << " elements from this partition" << std::endl
1137 << std::endl;
1138 }
1139 }
1140
1141 //==================================================================
1142 /// (Irreversibly) prune halo(ed) elements and nodes, usually
1143 /// after another round of refinement, to get rid of
1144 /// excessively wide halo layers. Note that the current
1145 /// mesh will be now regarded as the base mesh and no unrefinement
1146 /// relative to it will be possible once this function
1147 /// has been called.
1148 //==================================================================
1150 const bool& report_stats)
1151 {
1152 // Storage for number of processors and current processor
1153 int n_proc = this->communicator_pt()->nproc();
1154
1155 // Has the problem been distributed yet?
1157 {
1159 << "WARNING: Problem::prune_halo_elements_and_nodes() was called on a "
1160 << "non-distributed Problem!" << std::endl;
1161 oomph_info << "Ignoring your request..." << std::endl;
1162 }
1163 else
1164 {
1165 // There are no halo layers to prune if it's a single-process job
1166 if (n_proc == 1)
1167 {
1169 << "WARNING: You've tried to prune halo layers on a problem\n"
1170 << "with only one processor: this is unnecessary.\n"
1171 << "Ignoring your request." << std::endl
1172 << std::endl;
1173 }
1174 else
1175 {
1176#ifdef PARANOID
1177 unsigned old_ndof = ndof();
1178#endif
1179
1180 double t_start = 0.0;
1182 {
1184 }
1185
1186 // Call actions before distribute
1188
1189 double t_end = 0.0;
1191 {
1193 oomph_info << "Time for actions_before_distribute() in "
1194 << "Problem::prune_halo_elements_and_nodes(): "
1195 << t_end - t_start << std::endl;
1197 }
1198
1199 // Associate all elements with root in current Base mesh
1200 unsigned nel = Base_mesh_element_pt.size();
1201 std::map<GeneralisedElement*, unsigned>
1203 std::vector<bool> old_root_is_halo_or_non_existent(nel, true);
1204 for (unsigned e = 0; e < nel; e++)
1205 {
1206 // Get the base element
1208
1209 // Does it exist locally?
1210 if (base_el_pt != 0)
1211 {
1212 // Check if it's a halo element
1213 if (!base_el_pt->is_halo())
1214 {
1216 }
1217
1218 // Not refineable: It's only the element iself
1220 ref_el_pt = dynamic_cast<RefineableElement*>(base_el_pt);
1221 if (ref_el_pt == 0)
1222 {
1224 }
1225 // Refineable: Get entire tree of elements
1226 else
1227 {
1228 Vector<Tree*> tree_pt;
1229 ref_el_pt->tree_pt()->stick_all_tree_nodes_into_vector(tree_pt);
1230 unsigned ntree = tree_pt.size();
1231 for (unsigned t = 0; t < ntree; t++)
1232 {
1233 old_base_element_number_plus_one[tree_pt[t]->object_pt()] =
1234 e + 1;
1235 }
1236 }
1237 }
1238 }
1239
1240
1242 {
1244 oomph_info << "Time for setup old root elements in "
1245 << "Problem::prune_halo_elements_and_nodes(): "
1246 << t_end - t_start << std::endl;
1248 }
1249
1250
1251 // Now remember the old number of base elements
1252 unsigned nel_base_old = nel;
1253
1254
1255 // Prune the halo elements and nodes of the mesh(es)
1257 unsigned n_mesh = nsub_mesh();
1258 if (n_mesh == 0)
1259 {
1260 // Prune halo elements and nodes for the (single) global mesh
1262 deleted_element_pt, doc_info, report_stats);
1263 }
1264 else
1265 {
1266 // Loop over individual submeshes and prune separately
1267 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
1268 {
1270 deleted_element_pt, doc_info, report_stats);
1271 }
1272
1273 // Rebuild the global mesh
1275 }
1276
1278 {
1280 oomph_info << "Total time for all mesh-level prunes in "
1281 << "Problem::prune_halo_elements_and_nodes(): "
1282 << t_end - t_start << std::endl;
1284 }
1285
1286 // Loop over all elements in newly rebuilt mesh (which contains
1287 // all element in "tree order"), find the roots
1288 // (which are either non-refineable elements or refineable elements
1289 // whose tree representations are TreeRoots)
1290 std::map<FiniteElement*, bool> root_el_done;
1291
1292 // Vector storing vectors of pointers to new base elements associated
1293 // with the same old base element
1296
1297 unsigned n_meshes = n_mesh;
1298 // Change the value for the number of submeshes if there is only
1299 // one mesh so that the loop below works if we have only one
1300 // mesh
1301 if (n_meshes == 0)
1302 {
1303 n_meshes = 1;
1304 }
1305
1306 // Store which submeshes, if there are some are structured
1307 // meshes
1308 std::vector<bool> is_structured_mesh(n_meshes);
1309
1310 // Loop over all elements in the rebuilt mesh, but make sure
1311 // that we are only looping over the structured meshes
1312 nel = 0;
1313 for (unsigned i_mesh = 0; i_mesh < n_meshes; i_mesh++)
1314 {
1316 dynamic_cast<TriangleMeshBase*>(mesh_pt(i_mesh));
1317 if (!(tri_mesh_pt != 0))
1318 {
1319 // Mark the mesh as structured mesh
1320 is_structured_mesh[i_mesh] = true;
1321 // Add the number of elements
1322 nel += mesh_pt(i_mesh)->nelement();
1323 } // if (!(tri_mesh_pt!=0))
1324 else
1325 {
1326 // Mark the mesh as nonstructured mesh
1327 is_structured_mesh[i_mesh] = false;
1328 } // else if (!(tri_mesh_pt!=0))
1329 } // for (i_mesh < n_mesh)
1330
1331 // Go for all the meshes (if there are submeshes)
1332 for (unsigned i_mesh = 0; i_mesh < n_meshes; i_mesh++)
1333 {
1334 // Only work with the elements in the mesh if it is an
1335 // structured mesh
1337 {
1338 // Get the number of elements in the submesh
1339 const unsigned nele_submesh = mesh_pt(i_mesh)->nelement();
1340 for (unsigned e = 0; e < nele_submesh; e++)
1341 {
1342 // Get the element
1344
1345 // Not refineable: It's definitely a new base element
1347 ref_el_pt = dynamic_cast<RefineableElement*>(el_pt);
1348 if (ref_el_pt == 0)
1349 {
1350 unsigned old_base_el_no =
1354 .push_back(el_pt);
1355 }
1356 // Refineable
1357 else
1358 {
1359 // Is it a tree root (after pruning)? In that case it's
1360 // a new base element
1361 if (dynamic_cast<TreeRoot*>(ref_el_pt->tree_pt()))
1362 {
1363 unsigned old_base_el_no =
1367 .push_back(el_pt);
1368 }
1369 else
1370 {
1371 // Get associated root element
1373 ref_el_pt->tree_pt()->root_pt()->object_pt();
1374
1376 {
1377 root_el_done[root_el_pt] = true;
1378 unsigned old_base_el_no =
1382 .push_back(root_el_pt);
1383 }
1384 }
1385 }
1386 } // for (e < nele_submesh)
1387 } // if (is_structured_mesh[i_mesh])
1388 } // for (i_mesh < n_mesh)
1389
1390 // Create a vector that stores how many new root/base elements
1391 // got spawned from each old root/base element in the global mesh
1393#ifdef PARANOID
1395#endif
1396 for (unsigned e = 0; e < nel_base_old; e++)
1397 {
1400
1401#ifdef PARANOID
1402 // Backup so we can check that halo data was consistent
1404#endif
1405 }
1406
1408 {
1410 oomph_info << "Time for setup of new base elements in "
1411 << "Problem::prune_halo_elements_and_nodes(): "
1412 << t_end - t_start << std::endl;
1414 }
1415
1416 // Now do reduce operation to get information for all
1417 // old root/base elements -- the pruned (halo!) base elements contain
1418 // fewer associated new roots.
1421 &n_new_root[0],
1424 MPI_MAX,
1425 this->communicator_pt()->mpi_comm());
1426
1427
1429 {
1431 oomph_info << "Time for allreduce in "
1432 << "Problem::prune_halo_elements_and_nodes(): "
1433 << t_end - t_start << std::endl;
1435 }
1436
1437 // Find out total number of base elements
1438 unsigned nel_base_new = 0;
1439 for (unsigned e = 0; e < nel_base_old; e++)
1440 {
1441 // Increment
1443
1444#ifdef PARANOID
1445 // If we already had data for this root previously then
1446 // the data ought to be consistent afterwards (since taking
1447 // the max of consistent numbers shouldn't change things -- this
1448 // deals with halo/haloed elements)
1450 {
1451 if (n_new_root_back[e] != 0)
1452 {
1453 if (n_new_root_back[e] != n_new_root[e])
1454 {
1455 std::ostringstream error_stream;
1457 << "Number of new root elements spawned from old root " << e
1458 << ": " << n_new_root[e] << "\nis not consistent"
1459 << " with previous value: " << n_new_root_back[e]
1460 << std::endl;
1461 throw OomphLibError(error_stream.str(),
1464 }
1465 }
1466 }
1467
1468#endif
1469 }
1470
1471 // Reset base_mesh information
1472 Base_mesh_element_pt.clear();
1475
1476 // Now enumerate the new base/root elements consistently
1477 unsigned count = 0;
1478 for (unsigned e = 0; e < nel_base_old; e++)
1479 {
1480 // Old root is non-halo: Just add the new roots into the
1481 // new lookup scheme consecutively
1483 {
1484 // Loop over new root/base element
1485 unsigned n_new_root =
1487 for (unsigned j = 0; j < n_new_root; j++)
1488 {
1489 // Store new root/base element
1494
1495 // Bump counter
1496 count++;
1497 }
1498 }
1499 // Old root element is halo so skip insertion (i.e. leave
1500 // entries in lookup schemes nulled) but increase counter to
1501 // ensure consistency between processors
1502 else
1503 {
1504 unsigned nskip = n_new_root[e];
1505 count += nskip;
1506 }
1507 }
1508
1509 // Re-setup the map between "root" element and number in global mesh
1510 // (used in the load_balance() routines)
1512
1513
1515 {
1517 oomph_info << "Time for finishing off base mesh info "
1518 << "Problem::prune_halo_elements_and_nodes(): "
1519 << t_end - t_start << std::endl;
1521 }
1522
1523
1524 // Call actions after distribute
1526
1527
1529 {
1531 oomph_info << "Time for actions_after_distribute() "
1532 << "Problem::prune_halo_elements_and_nodes(): "
1533 << t_end - t_start << std::endl;
1535 }
1536
1537
1538 // Re-assign the equation numbers (incl synchronisation if reqd)
1539#ifdef PARANOID
1540 unsigned n_dof = assign_eqn_numbers();
1541#else
1543#endif
1544
1545
1547 {
1549 oomph_info << "Time for assign_eqn_numbers() "
1550 << "Problem::prune_halo_elements_and_nodes(): "
1551 << t_end - t_start << std::endl;
1553 }
1554
1555
1556#ifdef PARANOID
1558 {
1559 if (n_dof != old_ndof)
1560 {
1561 std::ostringstream error_stream;
1563 << "Number of dofs in prune_halo_elements_and_nodes() has "
1564 "changed "
1565 << "from " << old_ndof << " to " << n_dof << "\n"
1566 << "Check that you've implemented any necessary "
1567 "actions_before/after"
1568 << "\nadapt/distribute functions, e.g. to pin redundant pressure"
1569 << " dofs etc.\n";
1570 throw OomphLibError(error_stream.str(),
1573 }
1574 }
1575#endif
1576 }
1577 }
1578 }
1579
1580
1581#endif
1582
1583
1584 //===================================================================
1585 /// Build a single (global) mesh from a number
1586 /// of submeshes which are passed as a vector of pointers to the
1587 /// submeshes. The ordering is not necessarily optimal.
1588 //==============================================================
1590 {
1591#ifdef PARANOID
1592 // Has a global mesh already been built
1593 if (Mesh_pt != 0)
1594 {
1595 std::string error_message = "Problem::build_global_mesh() called,\n";
1596 error_message += " but a global mesh has already been built:\n";
1597 error_message += "Problem::Mesh_pt is not zero!\n";
1598
1599 throw OomphLibError(
1601 }
1602 // Check that there are submeshes
1603 if (Sub_mesh_pt.size() == 0)
1604 {
1605 std::string error_message = "Problem::build_global_mesh() called,\n";
1606 error_message += " but there are no submeshes:\n";
1607 error_message += "Problem::Sub_mesh_pt has no entries\n";
1608
1609 throw OomphLibError(
1611 }
1612#endif
1613
1614 // Create an empty mesh
1615 Mesh_pt = new Mesh();
1616
1617 // Call the rebuild function to construct the mesh
1619 }
1620
1621 //====================================================================
1622 /// If one of the submeshes has changed (e.g. by
1623 /// mesh adaptation) we need to update the global mesh.
1624 /// \b Note: The nodes boundary information refers to the
1625 /// boundary numbers within the submesh!
1626 /// N.B. This is essentially the same function as the Mesh constructor
1627 /// that assembles a single global mesh from submeshes
1628 //=====================================================================
1630 {
1631 // Use the function in mesh to merge the submeshes into this one
1633 }
1634
1635
1636 //================================================================
1637 /// Add a timestepper to the problem. The function will automatically
1638 /// create or resize the Time object so that it contains the appropriate
1639 /// number of levels of storage.
1640 //================================================================
1641 void Problem::add_time_stepper_pt(TimeStepper* const& time_stepper_pt)
1642 {
1643 // Add the timestepper to the vector
1645
1646 // Find the number of timesteps required by the timestepper
1647 unsigned ndt = time_stepper_pt->ndt();
1648
1649 // If time has not been allocated, create time object with the
1650 // required number of time steps
1651 if (Time_pt == 0)
1652 {
1653 Time_pt = new Time(ndt);
1654 oomph_info << "Created Time with " << ndt << " timesteps" << std::endl;
1655 }
1656 else
1657 {
1658 // If the required number of time steps is greater than currently stored
1659 // resize the time storage
1660 if (ndt > Time_pt->ndt())
1661 {
1662 Time_pt->resize(ndt);
1663 oomph_info << "Resized Time to include " << ndt << " timesteps"
1664 << std::endl;
1665 }
1666 // Otherwise report that we are OK
1667 else
1668 {
1669 oomph_info << "Time object already has storage for " << ndt
1670 << " timesteps" << std::endl;
1671 }
1672 }
1673
1674 // Pass the pointer to time to the timestepper
1676 }
1677
1678 //================================================================
1679 /// Set the explicit time stepper for the problem and also
1680 /// ensure that a time object has been created.
1681 //================================================================
1683 ExplicitTimeStepper* const& explicit_time_stepper_pt)
1684 {
1685 // Set the explicit time stepper
1687
1688 // If time has not been allocated, create time object with the
1689 // required number of time steps
1690 if (Time_pt == 0)
1691 {
1692 Time_pt = new Time(0);
1693 oomph_info << "Created Time with storage for no previous timestep"
1694 << std::endl;
1695 }
1696 else
1697 {
1698 oomph_info << "Time object already exists " << std::endl;
1699 }
1700 }
1701
1702
1703#ifdef OOMPH_HAS_MPI
1704
1705 //================================================================
1706 /// Set default first and last elements for parallel assembly
1707 /// of non-distributed problem.
1708 //================================================================
1710 {
1712 {
1713 // Minimum number of elements per processor if there are fewer elements
1714 // than processors
1715 unsigned min_el = 10;
1716
1717 // Resize and make default assignments
1718 int n_proc = this->communicator_pt()->nproc();
1719 unsigned n_elements = Mesh_pt->nelement();
1720 First_el_for_assembly.resize(n_proc, 0);
1722
1723 // In the absence of any better knowledge distribute work evenly
1724 // over elements
1725 unsigned range = 0;
1726 unsigned lo_proc = 0;
1727 unsigned hi_proc = n_proc - 1;
1728 if (int(n_elements) >= n_proc)
1729 {
1730 range = unsigned(double(n_elements) / double(n_proc));
1731 }
1732 else
1733 {
1734 range = min_el;
1735 lo_proc = 0;
1736 hi_proc = unsigned(double(n_elements) / double(min_el));
1737 }
1738
1739 for (int p = lo_proc; p <= int(hi_proc); p++)
1740 {
1742
1743 unsigned last_el_plus_one = (p + 1) * range;
1746 }
1747
1748 // Last one needs to incorporate any dangling elements
1749 if (int(n_elements) >= n_proc)
1750 {
1752 }
1753
1754 // Doc
1755 if (n_proc > 1)
1756 {
1758 {
1759 oomph_info << "Problem is not distributed. Parallel assembly of "
1760 << "Jacobian uses default partitioning: " << std::endl;
1761 for (int p = 0; p < n_proc; p++)
1762 {
1764 {
1765 oomph_info << "Proc " << p << " assembles from element "
1766 << First_el_for_assembly[p] << " to "
1767 << Last_el_plus_one_for_assembly[p] - 1 << " \n";
1768 }
1769 else
1770 {
1771 oomph_info << "Proc " << p << " assembles no elements\n";
1772 }
1773 }
1774 }
1775 }
1776 }
1777 }
1778
1779
1780 //=======================================================================
1781 /// Helper function to re-assign the first and last elements to be
1782 /// assembled by each processor during parallel assembly for
1783 /// non-distributed problem.
1784 //=======================================================================
1786 {
1787 // Wait until all processes have completed/timed their assembly
1789
1790 // Storage for number of processors and current processor
1791 int n_proc = this->communicator_pt()->nproc();
1792 int rank = this->communicator_pt()->my_rank();
1793
1794 // Don't bother to update if we've got fewer elements than
1795 // processors
1796 unsigned nel = Elemental_assembly_time.size();
1797 if (int(nel) < n_proc)
1798 {
1799 oomph_info << "Not re-computing distribution of elemental assembly\n"
1800 << "because there are fewer elements than processors\n";
1801 return;
1802 }
1803
1804 // Setup vectors storing the number of element timings to be sent
1805 // and the offset in the final vector
1808 int offset = 0;
1809 for (int p = 0; p < n_proc; p++)
1810 {
1811 // Default distribution of labour
1812 unsigned el_lo = First_el_for_assembly[p];
1813 unsigned el_hi = Last_el_plus_one_for_assembly[p] - 1;
1814
1815 // Number of timings to be sent and offset from start in
1816 // final vector
1817 receive_count[p] = el_hi - el_lo + 1;
1818 displacement[p] = offset;
1819 offset += el_hi - el_lo + 1;
1820 }
1821
1822 // Make temporary c-style array to avoid over-writing in Gatherv below
1823 double* el_ass_time = new double[nel];
1824 for (unsigned e = 0; e < nel; e++)
1825 {
1827 }
1828
1829 // Gather timings on root processor
1830 unsigned nel_local =
1833 nel_local,
1834 MPI_DOUBLE,
1836 &receive_count[0],
1837 &displacement[0],
1838 MPI_DOUBLE,
1839 0,
1840 this->communicator_pt()->mpi_comm());
1841 delete[] el_ass_time;
1842
1843 // Vector of first and last elements for each processor
1845 for (int p = 0; p < n_proc; p++)
1846 {
1847 first_and_last_element[p].resize(2);
1848 }
1849
1850 // Re-distribute work
1851 if (rank == 0)
1852 {
1854 {
1856 << std::endl
1857 << "Re-assigning distribution of element assembly over processors:"
1858 << std::endl;
1859 }
1860
1861 // Get total assembly time
1862 double total = 0.0;
1863 unsigned n_elements = Mesh_pt->nelement();
1864 for (unsigned e = 0; e < n_elements; e++)
1865 {
1867 }
1868
1869 // Target load per processor
1870 double target_load = total / double(n_proc);
1871
1872 // We're on the root processor: Always start with the first element
1873 int proc = 0;
1874 first_and_last_element[0][0] = 0;
1875
1876 // Highest element we can help ourselves to if we want to leave
1877 // at least one element for all subsequent processors
1878 unsigned max_el_avail = n_elements - n_proc;
1879
1880 // Initialise total work allocated
1881 total = 0.0;
1882 for (unsigned e = 0; e < n_elements; e++)
1883 {
1885
1886 // Once we have reached the target load or we've used up practically
1887 // all the elements...
1888 if ((total > target_load) || (e == max_el_avail))
1889 {
1890 // Last element for current processor
1892
1893 // Provided that we are not on the last processor
1894 if (proc < (n_proc - 1))
1895 {
1896 // Set first element for next one
1897 first_and_last_element[proc + 1][0] = e + 1;
1898
1899 // Move on to the next processor
1900 proc++;
1901 }
1902
1903 // Can have one more...
1904 max_el_avail++;
1905
1906 // Re-initialise the time
1907 total = 0.0;
1908 } // end of test for "total exceeds target"
1909 }
1910
1911
1912 // Last element for last processor
1914
1915
1916 // The following block should probably be paranoidified away
1917 // but we've screwed the logic up so many times that I feel
1918 // it's safer to keep it...
1919 bool wrong = false;
1920 std::ostringstream error_stream;
1921 for (int p = 0; p < n_proc - 1; p++)
1922 {
1926 {
1927 wrong = true;
1928 error_stream << "Error: First/last element of proc " << p << ": "
1930 << std::endl;
1931 }
1932 unsigned first_of_next = first_and_last_element[p + 1][0];
1933 if (first_of_next != (last_of_current + 1))
1934 {
1935 wrong = true;
1936 error_stream << "Error: First element of proc " << p + 1 << ": "
1937 << first_of_next << " and last element of proc " << p
1938 << ": " << last_of_current << std::endl;
1939 }
1940 }
1941 if (wrong)
1942 {
1943 throw OomphLibError(
1945 }
1946
1947
1948 // THIS TIDY UP SHOULD NO LONGER BE REQUIRED AND CAN GO AT SOME POINT
1949
1950 // //If we haven't got to the end of the processor list then
1951 // //need to shift things about slightly because the processors
1952 // //at the end will be empty.
1953 // //This can occur when you have very fast assembly times and the
1954 // //rounding errors mean that the targets are achieved before all
1955 // processors
1956 // //have been visited.
1957 // //Happens a lot when you massively oversubscribe the CPUs (which was
1958 // //only ever for testing!)
1959 // if (proc!=n_proc-1)
1960 // {
1961 // oomph_info
1962 // << "First pass did not allocate elements on every processor\n";
1963 // oomph_info <<
1964 // "Moving elements so that each processor has at least one\n";
1965
1966 // //Work out number of empty processos
1967 // unsigned n_empty_processors = n_proc - proc + 1;
1968
1969 // //Loop over the processors that do have elements
1970 // //and work out how many we need to steal elements from
1971 // unsigned n_element_on_processors=0;
1972 // do
1973 // {
1974 // //Step down the processors
1975 // --proc;
1976 // //Add the current processor to the number of empty processors
1977 // //because the elements have to be shared between processors
1978 // //including the one(s) on which they are currently stored.
1979 // ++n_empty_processors;
1980 // n_element_on_processors +=
1981 // (first_and_last_element[proc][1] -
1982 // first_and_last_element[proc][0] + 1);
1983 // }
1984 // while(n_element_on_processors < n_empty_processors);
1985
1986 // //Should now be able to put one element on each processor
1987 // //Start from the end and do so
1988 // unsigned current_element = n_elements-1;
1989 // for(int p=n_proc-1;p>proc;p--)
1990 // {
1991 // first_and_last_element[p][1] = current_element;
1992 // first_and_last_element[p][0] = --current_element;
1993 // }
1994
1995 // //Now for the last processor we touched, just adjust the final value
1996 // first_and_last_element[proc][1] = current_element;
1997 // }
1998 // //Otherwise just put the rest of the elements on the final
1999 // //processor
2000 // else
2001 // {
2002 // // Last one
2003 // first_and_last_element[n_proc-1][1]=n_elements-1;
2004 // }
2005
2006
2007 // END PRESUMED-TO-BE-UNNECESSARY BLOCK...
2008
2009
2010 // Now communicate the information
2011
2012 // Set local informationt for this (root) processor
2015
2017 {
2018 oomph_info << "Processor " << 0 << " assembles Jacobians"
2019 << " from elements " << first_and_last_element[0][0]
2020 << " to " << first_and_last_element[0][1] << " "
2021 << std::endl;
2022 }
2023
2024 // Only now can we send the information to the other processors
2025 for (int p = 1; p < n_proc; ++p)
2026 {
2028 2,
2029 MPI_INT,
2030 p,
2031 0,
2032 this->communicator_pt()->mpi_comm());
2033
2034
2036 {
2037 oomph_info << "Processor " << p << " assembles Jacobians"
2038 << " from elements " << first_and_last_element[p][0]
2039 << " to " << first_and_last_element[p][1] << " "
2040 << std::endl;
2041 }
2042 }
2043 }
2044 // Receive first and last element from root on non-master processors
2045 else
2046 {
2047 Vector<int> aux(2);
2049 MPI_Recv(&aux[0],
2050 2,
2051 MPI_INT,
2052 0,
2053 0,
2054 this->communicator_pt()->mpi_comm(),
2055 &status);
2058 }
2059
2060 // Wipe all others
2061 for (int p = 0; p < n_proc; p++)
2062 {
2063 if (p != rank)
2064 {
2067 }
2068 }
2069
2070 // The equations assembled by this processor may have changed so
2071 // we must resize the sparse assemble with arrays previous allocation
2073 }
2074
2075#endif
2076
2077 //================================================================
2078 /// Assign all equation numbers for problem: Deals with global
2079 /// data (= data that isn't attached to any elements) and then
2080 /// does the equation numbering for the elements. Bool argument
2081 /// can be set to false to ignore assigning local equation numbers
2082 /// (necessary in the parallel implementation of locate_zeta
2083 /// between multiple meshes).
2084 //================================================================
2086 const bool& assign_local_eqn_numbers)
2087 {
2088 // Check that the global mesh has been build
2089#ifdef PARANOID
2090 if (Mesh_pt == 0)
2091 {
2092 std::ostringstream error_stream;
2093 error_stream << "Global mesh does not exist, so equation numbers cannot "
2094 "be assigned.\n";
2095 // Check for sub meshes
2096 if (nsub_mesh() == 0)
2097 {
2098 error_stream << "There aren't even any sub-meshes in the Problem.\n"
2099 << "You can set the global mesh directly by using\n"
2100 << "Problem::mesh_pt() = my_mesh_pt;\n"
2101 << "OR you can use Problem::add_sub_mesh(mesh_pt); "
2102 << "to add a sub mesh.\n";
2103 }
2104 else
2105 {
2106 error_stream << "There are " << nsub_mesh() << " sub-meshes.\n";
2107 }
2108 error_stream << "You need to call Problem::build_global_mesh() to create "
2109 "a global mesh\n"
2110 << "from the sub-meshes.\n\n";
2111
2112 throw OomphLibError(
2114 }
2115#endif
2116
2117 // Number of submeshes
2118 unsigned n_sub_mesh = Sub_mesh_pt.size();
2119
2120#ifdef OOMPH_HAS_MPI
2121
2122 // Storage for number of processors
2123 int n_proc = this->communicator_pt()->nproc();
2124
2125
2126 if (n_proc > 1)
2127 {
2128 // Force re-analysis of time spent on assembly each
2129 // elemental Jacobian
2132 }
2133 else
2134 {
2136 }
2137
2138 // Re-distribution of elements over processors during assembly
2139 // must be recomputed
2141 {
2142 // Set default first and last elements for parallel assembly
2143 // of non-distributed problem.
2145 }
2146
2147#endif
2148
2149
2150 double t_start = 0.0;
2152 {
2154 }
2155
2156 // Loop over all elements in the mesh and set up any additional
2157 // dependencies that they may have (e.g. storing the geometric
2158 // Data, i.e. Data that affects an element's shape in elements
2159 // with algebraic node-update functions
2160 unsigned nel = Mesh_pt->nelement();
2161 for (unsigned e = 0; e < nel; e++)
2162 {
2164 }
2165
2166#ifdef OOMPH_HAS_MPI
2167 // Complete setup of dependencies for external halo elements too
2168 unsigned n_mesh = this->nsub_mesh();
2169 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
2170 {
2171 for (int iproc = 0; iproc < n_proc; iproc++)
2172 {
2174 for (unsigned e = 0; e < n_ext_halo_el; e++)
2175 {
2179 }
2180 }
2181 }
2182#endif
2183
2184
2185 double t_end = 0.0;
2187 {
2190 << "Time for complete setup of dependencies in assign_eqn_numbers: "
2191 << t_end - t_start << std::endl;
2192 }
2193
2194
2195 // Initialise number of dofs for reserve below
2196 unsigned n_dof = 0;
2197
2198 // Potentially loop over remainder of routine, possible re-visiting all
2199 // those parts that must be redone, following the removal of duplicate
2200 // external halo data.
2201 for (unsigned loop_count = 0; loop_count < 2; loop_count++)
2202 {
2203 //(Re)-set the dof pointer to zero length because entries are
2204 // pushed back onto it -- if it's not reset here then we get into
2205 // trouble during mesh refinement when we reassign all dofs
2206 Dof_pt.resize(0);
2207
2208 // Reserve from previous allocation if we're going around again
2209 Dof_pt.reserve(n_dof);
2210
2211 // Reset the equation number
2212 unsigned long equation_number = 0;
2213
2214 // Now set equation numbers for the global Data
2215 unsigned Nglobal_data = nglobal_data();
2216 for (unsigned i = 0; i < Nglobal_data; i++)
2217 {
2218 Global_data_pt[i]->assign_eqn_numbers(equation_number, Dof_pt);
2219 }
2220
2222 {
2224 }
2225
2226 // Call assign equation numbers on the global mesh
2228
2229 // Deal with the spine meshes additional numbering
2230 // If there is only one mesh
2231 if (n_sub_mesh == 0)
2232 {
2233 if (SpineMesh* const spine_mesh_pt = dynamic_cast<SpineMesh*>(Mesh_pt))
2234 {
2235 n_dof = spine_mesh_pt->assign_global_spine_eqn_numbers(Dof_pt);
2236 }
2237 }
2238 // Otherwise loop over the sub meshes
2239 else
2240 {
2241 // Assign global equation numbers first
2242 for (unsigned i = 0; i < n_sub_mesh; i++)
2243 {
2244 if (SpineMesh* const spine_mesh_pt =
2245 dynamic_cast<SpineMesh*>(Sub_mesh_pt[i]))
2246 {
2247 n_dof = spine_mesh_pt->assign_global_spine_eqn_numbers(Dof_pt);
2248 }
2249 }
2250 }
2251
2253 {
2256 << "Time for assign_global_eqn_numbers in assign_eqn_numbers: "
2257 << t_end - t_start << std::endl;
2259 }
2260
2261
2262#ifdef OOMPH_HAS_MPI
2263
2264 // reset previous allocation
2266
2267 // Only synchronise if the problem has actually been
2268 // distributed.
2270 {
2271 // Synchronise the equation numbers and return the total
2272 // number of degrees of freedom in the overall problem
2273 // Do not assign local equation numbers -- we're doing this
2274 // below.
2276 }
2277 // ..else just setup the Dof_distribution_pt
2278 // NOTE: this is setup by synchronise_eqn_numbers(...)
2279 // if Problem_has_been_distributed
2280 else
2281#endif
2282 {
2284 }
2285
2287 {
2289 oomph_info << "Time for Problem::synchronise_eqn_numbers in "
2290 << "Problem::assign_eqn_numbers: " << t_end - t_start
2291 << std::endl;
2292 }
2293
2294
2295#ifdef OOMPH_HAS_MPI
2296
2297
2298 // Now remove duplicate data in external halo elements
2300 {
2302 {
2304 }
2305
2306 // Monitor if we've actually changed anything
2307 bool actually_removed_some_data = false;
2308
2309 // Only do it once!
2310 if (loop_count == 0)
2311 {
2312 if (n_sub_mesh == 0)
2313 {
2315 }
2316 else
2317 {
2318 for (unsigned i = 0; i < n_sub_mesh; i++)
2319 {
2320 bool tmp_actually_removed_some_data = false;
2325 }
2326 }
2327 }
2328
2329
2331 {
2333 std::stringstream tmp;
2334 tmp << "Time for calls to Problem::remove_duplicate_data in "
2335 << "Problem::assign_eqn_numbers: " << t_end - t_start
2336 << " ; have ";
2338 {
2339 tmp << " not ";
2340 }
2341 tmp << " removed some/any data.\n";
2342 oomph_info << tmp.str();
2344 }
2345
2346 // Break out of the loop if we haven't done anything here.
2347 unsigned status = 0;
2349
2350 // Allreduce to check if anyone has removed any data
2351 unsigned overall_status = 0;
2354 1,
2356 MPI_MAX,
2357 this->communicator_pt()->mpi_comm());
2358
2359
2361 {
2363 std::stringstream tmp;
2364 tmp
2365 << "Time for MPI_Allreduce after Problem::remove_duplicate_data in "
2366 << "Problem::assign_eqn_numbers: " << t_end - t_start << std::endl;
2367 oomph_info << tmp.str();
2369 }
2370
2371 // Bail out if we haven't done anything here
2372 if (overall_status != 1)
2373 {
2374 break;
2375 }
2376
2377 // Big tidy up: Remove null pointers from halo/haloed node storage
2378 // for all meshes (this involves comms and therefore must be
2379 // performed outside loop over meshes so the all-to-all is only
2380 // done once)
2382
2383 // Time it...
2385 {
2386 double t_end = TimingHelpers::timer();
2387 oomph_info << "Total time for "
2388 << "Problem::remove_null_pointers_from_external_halo_node_"
2389 "storage(): "
2390 << t_end - t_start << std::endl;
2391 }
2392 }
2393 else
2394 {
2395 // Problem not distributed; no need for another loop
2396 break;
2397 }
2398
2399#else
2400
2401 // Serial run: Again no need for a second loop
2402 break;
2403
2404#endif
2405
2406 } // end of loop over fcts that need to be re-executed if
2407 // we've removed duplicate external data
2408
2409
2410 // Resize the sparse assemble with arrays previous allocation
2412
2413
2415 {
2417 }
2418
2419 // Finally assign local equations
2420 if (assign_local_eqn_numbers)
2421 {
2422 if (n_sub_mesh == 0)
2423 {
2425 }
2426 else
2427 {
2428 for (unsigned i = 0; i < n_sub_mesh; i++)
2429 {
2432 }
2433 }
2434 }
2435
2437 {
2439 oomph_info << "Total time for all Mesh::assign_local_eqn_numbers in "
2440 << "Problem::assign_eqn_numbers: " << t_end - t_start
2441 << std::endl;
2442 }
2443
2444
2445 // and return the total number of DOFs
2446 return n_dof;
2447 }
2448 //================================================================
2449 /// Function to describe the dofs in terms of the global
2450 /// equation number, i.e. what type of value (nodal value of
2451 /// a Node; value in a Data object; value of internal Data in an
2452 /// element; etc) is the unknown with a certain global equation number.
2453 /// Output stream defaults to oomph_info.
2454 //================================================================
2455 void Problem::describe_dofs(std::ostream& out) const
2456 {
2457 // Check that the global mesh has been build
2458#ifdef PARANOID
2459 if (Mesh_pt == 0)
2460 {
2461 std::ostringstream error_stream;
2463 << "Global mesh does not exist, so equation numbers cannot be found.\n";
2464 // Check for sub meshes
2465 if (nsub_mesh() == 0)
2466 {
2467 error_stream << "There aren't even any sub-meshes in the Problem.\n"
2468 << "You can set the global mesh directly by using\n"
2469 << "Problem::mesh_pt() = my_mesh_pt;\n"
2470 << "OR you can use Problem::add_sub_mesh(mesh_pt); "
2471 << "to add a sub mesh.\n";
2472 }
2473 else
2474 {
2475 error_stream << "There are " << nsub_mesh() << " sub-meshes.\n";
2476 }
2477 error_stream << "You need to call Problem::build_global_mesh() to create "
2478 "a global mesh\n"
2479 << "from the sub-meshes.\n\n";
2480
2481 throw OomphLibError(
2483 }
2484#endif
2485
2486 out
2487 << "Although this program will describe the degrees of freedom in the \n"
2488 << "problem, it will do so using the typedef for the elements. This is \n"
2489 << "not neccesarily human readable, but there is a solution.\n"
2490 << "Pipe your program's output through c++filt, with the argument -t.\n"
2491 << "e.g. \"./two_d_multi_poisson | c++filt -t > ReadableOutput.txt\".\n "
2492 << "(Disregarding the quotes)\n\n\n";
2493
2494 out << "Classifying Global Equation Numbers" << std::endl;
2495 out << std::string(80, '-') << std::endl;
2496
2497 // Number of submeshes
2498 unsigned n_sub_mesh = Sub_mesh_pt.size();
2499
2500 // Classify Global dofs
2501 unsigned Nglobal_data = nglobal_data();
2502 for (unsigned i = 0; i < Nglobal_data; i++)
2503 {
2504 std::stringstream conversion;
2505 conversion << " in Global Data " << i << ".";
2506 std::string in(conversion.str());
2508 }
2509
2510 // Put string in limiting scope.
2511 {
2512 // Descend into assignment for mesh.
2513 std::string in(" in Problem's Only Mesh.");
2515 }
2516
2517 // Deal with the spine meshes additional numbering:
2518 // If there is only one mesh:
2519 if (n_sub_mesh == 0)
2520 {
2521 if (SpineMesh* const spine_mesh_pt = dynamic_cast<SpineMesh*>(Mesh_pt))
2522 {
2523 std::string in(" in Problem's Only SpineMesh.");
2524 spine_mesh_pt->describe_spine_dofs(out, in);
2525 }
2526 }
2527 // Otherwise loop over the sub meshes
2528 else
2529 {
2530 // Assign global equation numbers first
2531 for (unsigned i = 0; i < n_sub_mesh; i++)
2532 {
2533 if (SpineMesh* const spine_mesh_pt =
2534 dynamic_cast<SpineMesh*>(Sub_mesh_pt[i]))
2535 {
2536 std::stringstream conversion;
2537 conversion << " in Sub-SpineMesh " << i << ".";
2538 std::string in(conversion.str());
2539 spine_mesh_pt->describe_spine_dofs(out, in);
2540 } // end if.
2541 } // end for.
2542 } // end else.
2543
2544
2545 out << std::string(80, '\\') << std::endl;
2546 out << std::string(80, '\\') << std::endl;
2547 out << std::string(80, '\\') << std::endl;
2548 out << "Classifying global eqn numbers in terms of elements." << std::endl;
2549 out << std::string(80, '-') << std::endl;
2550 out << "Eqns | Source" << std::endl;
2551 out << std::string(80, '-') << std::endl;
2552
2553 if (n_sub_mesh == 0)
2554 {
2555 std::string in(" in Problem's Only Mesh.");
2557 }
2558 else
2559 {
2560 for (unsigned i = 0; i < n_sub_mesh; i++)
2561 {
2562 std::stringstream conversion;
2563 conversion << " in Sub-Mesh " << i << ".";
2564 std::string in(conversion.str());
2566 } // End for
2567 } // End else
2568 } // End problem::describe_dofs(...)
2569
2570
2571 //================================================================
2572 /// Get the vector of dofs, i.e. a vector containing the current
2573 /// values of all unknowns.
2574 //================================================================
2576 {
2577 // Find number of dofs
2578 const unsigned long n_dof = ndof();
2579
2580 // Resize the vector
2581 dofs.build(Dof_distribution_pt, 0.0);
2582
2583 // Copy dofs into vector
2584 for (unsigned long l = 0; l < n_dof; l++)
2585 {
2586 dofs[l] = *Dof_pt[l];
2587 }
2588 }
2589
2590 /// Get history values of dofs in a double vector.
2591 void Problem::get_dofs(const unsigned& t, DoubleVector& dofs) const
2592 {
2593#ifdef PARANOID
2594 if (distributed())
2595 {
2596 throw OomphLibError("Not designed for distributed problems",
2599 // might work, not sure
2600 }
2601#endif
2602
2603 // Resize the vector
2604 dofs.build(Dof_distribution_pt, 0.0);
2605
2606 // First deal with global data
2607 unsigned Nglobal_data = nglobal_data();
2608 for (unsigned i = 0; i < Nglobal_data; i++)
2609 {
2610 for (unsigned j = 0, nj = Global_data_pt[i]->nvalue(); j < nj; j++)
2611 {
2612 // For each data get the equation number and copy out the value.
2613 int eqn_number = Global_data_pt[i]->eqn_number(j);
2614 if (eqn_number >= 0)
2615 {
2616 dofs[eqn_number] = Global_data_pt[i]->value(t, j);
2617 }
2618 }
2619 }
2620
2621 // Next element internal data
2622 for (unsigned i = 0, ni = mesh_pt()->nelement(); i < ni; i++)
2623 {
2625 for (unsigned j = 0, nj = ele_pt->ninternal_data(); j < nj; j++)
2626 {
2627 Data* d_pt = ele_pt->internal_data_pt(j);
2628 for (unsigned k = 0, nk = d_pt->nvalue(); k < nk; k++)
2629 {
2630 int eqn_number = d_pt->eqn_number(k);
2631 if (eqn_number >= 0)
2632 {
2633 dofs[eqn_number] = d_pt->value(t, k);
2634 }
2635 }
2636 }
2637 }
2638
2639 // Now the nodes
2640 for (unsigned i = 0, ni = mesh_pt()->nnode(); i < ni; i++)
2641 {
2642 Node* node_pt = mesh_pt()->node_pt(i);
2643 for (unsigned j = 0, nj = node_pt->nvalue(); j < nj; j++)
2644 {
2645 // For each node get the equation number and copy out the value.
2646 int eqn_number = node_pt->eqn_number(j);
2647 if (eqn_number >= 0)
2648 {
2649 dofs[eqn_number] = node_pt->value(t, j);
2650 }
2651 }
2652 }
2653 }
2654
2655
2656#ifdef OOMPH_HAS_MPI
2657
2658 //=======================================================================
2659 /// Private helper function to remove repeated data
2660 /// in external haloed elements associated with specified mesh.
2661 /// Bool is true if some data was removed -- this usually requires
2662 /// re-running through certain parts of the equation numbering procedure.
2663 //======================================================================
2666 {
2667 // // // Taken out again by MH -- clutters up output
2668 // // Doc timings if required
2669 // double t_start=0.0;
2670 // if (Global_timings::Doc_comprehensive_timings)
2671 // {
2672 // t_start=TimingHelpers::timer();
2673 // }
2674
2675 int n_proc = this->communicator_pt()->nproc();
2676 int my_rank = this->communicator_pt()->my_rank();
2677
2678 // Initialise
2679 actually_removed_some_data = false;
2680
2681 // Each individual container of external halo nodes has unique
2682 // nodes/equation numbers, but there may be some duplication between
2683 // two or more different containers; the following code checks for this
2684 // and removes the duplication by overwriting any data point with an already
2685 // existing eqn number with the original data point which had the eqn no.
2686
2687 // // Storage for existing nodes, enumerated by first non-negative
2688 // // global equation number
2689 // unsigned n_dof=ndof();
2690
2691 // Note: This used to be
2692 // Vector<Node*> global_node_pt(n_dof,0);
2693 // but this is a total killer! Memory allocation is extremely
2694 // costly and only relatively few entries are used so use
2695 // map:
2696 std::map<unsigned, Node*> global_node_pt;
2697
2698 // Only do each retained node once
2699 std::map<Node*, bool> node_done;
2700
2701 // Loop over existing "normal" elements in mesh
2702 unsigned n_element = mesh_pt->nelement();
2703 for (unsigned e = 0; e < n_element; e++)
2704 {
2706 dynamic_cast<FiniteElement*>(mesh_pt->element_pt(e));
2707 if (el_pt != 0)
2708 {
2709 // Loop over nodes
2710 unsigned n_node = el_pt->nnode();
2711 for (unsigned j = 0; j < n_node; j++)
2712 {
2713 Node* nod_pt = el_pt->node_pt(j);
2714
2715 // Have we already done the node?
2716 if (!node_done[nod_pt])
2717 {
2718 node_done[nod_pt] = true;
2719
2720 // Loop over values stored at node (if any) to find
2721 // the first non-negative eqn number
2723 unsigned n_val = nod_pt->nvalue();
2724 for (unsigned i_val = 0; i_val < n_val; i_val++)
2725 {
2726 int eqn_no = nod_pt->eqn_number(i_val);
2727 if (eqn_no >= 0)
2728 {
2730 break;
2731 }
2732 }
2733
2734 // If we haven't found a non-negative eqn number check
2735 // eqn numbers associated with solid data (if any)
2737 {
2738 // Is it a solid node?
2739 SolidNode* solid_nod_pt = dynamic_cast<SolidNode*>(nod_pt);
2740 if (solid_nod_pt != 0)
2741 {
2742 // Loop over values stored at node (if any) to find
2743 // the first non-negative eqn number
2744 unsigned n_val = solid_nod_pt->variable_position_pt()->nvalue();
2745 for (unsigned i_val = 0; i_val < n_val; i_val++)
2746 {
2747 int eqn_no =
2748 solid_nod_pt->variable_position_pt()->eqn_number(i_val);
2749 if (eqn_no >= 0)
2750 {
2752 break;
2753 }
2754 }
2755 }
2756 }
2757
2758 // Associate node with first non negative global eqn number
2760 {
2762 nod_pt;
2763 }
2764
2765
2766 // Take into account master nodes too
2767 if (dynamic_cast<RefineableElement*>(el_pt) != 0)
2768 {
2769 int n_cont_int_values = dynamic_cast<RefineableElement*>(el_pt)
2770 ->ncont_interpolated_values();
2771 for (int i_cont = -1; i_cont < n_cont_int_values; i_cont++)
2772 {
2773 if (nod_pt->is_hanging(i_cont))
2774 {
2775 HangInfo* hang_pt = nod_pt->hanging_pt(i_cont);
2776 unsigned n_master = hang_pt->nmaster();
2777 for (unsigned m = 0; m < n_master; m++)
2778 {
2779 Node* master_nod_pt = hang_pt->master_node_pt(m);
2781 {
2782 node_done[master_nod_pt] = true;
2783
2784 // Loop over values stored at node (if any) to find
2785 // the first non-negative eqn number
2787 unsigned n_val = master_nod_pt->nvalue();
2788 for (unsigned i_val = 0; i_val < n_val; i_val++)
2789 {
2791 if (eqn_no >= 0)
2792 {
2794 break;
2795 }
2796 }
2797
2798 // If we haven't found a non-negative eqn number check
2799 // eqn numbers associated with solid data (if any)
2801 {
2802 // If this master is a SolidNode then add its extra
2803 // eqn numbers
2805 dynamic_cast<SolidNode*>(master_nod_pt);
2806 if (master_solid_nod_pt != 0)
2807 {
2808 // Loop over values stored at node (if any) to find
2809 // the first non-negative eqn number
2810 unsigned n_val =
2811 master_solid_nod_pt->variable_position_pt()
2812 ->nvalue();
2813 for (unsigned i_val = 0; i_val < n_val; i_val++)
2814 {
2815 int eqn_no =
2816 master_solid_nod_pt->variable_position_pt()
2817 ->eqn_number(i_val);
2818 if (eqn_no >= 0)
2819 {
2821 eqn_no + 1;
2822 break;
2823 }
2824 }
2825 }
2826 }
2827 // Associate node with first non negative global
2828 // eqn number
2830 {
2832 1] = master_nod_pt;
2833 }
2834
2835 } // End of not-yet-done hang node
2836 }
2837 }
2838 }
2839 }
2840 } // endif for node already done
2841 } // End of loop over nodes
2842 } // End of FiniteElement
2843
2844 // Internal data equation numbers do not need to be added since
2845 // internal data cannot be shared between distinct elements, so
2846 // internal data on locally-stored elements can never be halo.
2847 }
2848
2849 // Set to record duplicate nodes scheduled to be killed
2850 std::set<Node*> killed_nodes;
2851
2852 // Now loop over the other processors from highest to lowest
2853 // (i.e. if there is a duplicate between these containers
2854 // then this will use the node on the highest numbered processor)
2855 for (int iproc = n_proc - 1; iproc >= 0; iproc--)
2856 {
2857 // Don't have external halo elements with yourself!
2858 if (iproc != my_rank)
2859 {
2860 // Loop over external halo elements with iproc
2861 // to remove the duplicates
2863 for (unsigned e_ext = 0; e_ext < n_element; e_ext++)
2864 {
2867 if (finite_ext_el_pt != 0)
2868 {
2869 // Loop over nodes
2870 unsigned n_node = finite_ext_el_pt->nnode();
2871 for (unsigned j = 0; j < n_node; j++)
2872 {
2874
2875 // Loop over values stored at node (if any) to find
2876 // the first non-negative eqn number
2878 unsigned n_val = nod_pt->nvalue();
2879 for (unsigned i_val = 0; i_val < n_val; i_val++)
2880 {
2881 int eqn_no = nod_pt->eqn_number(i_val);
2882 if (eqn_no >= 0)
2883 {
2885 break;
2886 }
2887 }
2888
2889 // If we haven't found a non-negative eqn number check
2890 // eqn numbers associated with solid data (if any)
2892 {
2893 // Is it a solid node?
2894 SolidNode* solid_nod_pt = dynamic_cast<SolidNode*>(nod_pt);
2895 if (solid_nod_pt != 0)
2896 {
2897 // Loop over values stored at node (if any) to find
2898 // the first non-negative eqn number
2899 unsigned n_val =
2900 solid_nod_pt->variable_position_pt()->nvalue();
2901 for (unsigned i_val = 0; i_val < n_val; i_val++)
2902 {
2903 int eqn_no =
2904 solid_nod_pt->variable_position_pt()->eqn_number(i_val);
2905 if (eqn_no >= 0)
2906 {
2908 break;
2909 }
2910 }
2911 }
2912 }
2913
2914 // Identified which node we're dealing with via first non-negative
2915 // global eqn number (if there is none, everything is pinned
2916 // and we don't give a damn...)
2918 {
2921
2922 // Does this node already exist?
2923 if (existing_node_pt != 0)
2924 {
2925 // Record that we're about to cull one
2927
2928 // It's a duplicate, so store the duplicated one for
2929 // later killing...
2932 {
2933 // Remove node from all boundaries
2934 std::set<unsigned>* boundaries_pt;
2935 duplicated_node_pt->get_boundaries_pt(boundaries_pt);
2936 if (boundaries_pt != 0)
2937 {
2939 unsigned nb = (*boundaries_pt).size();
2940 bound.reserve(nb);
2941 for (std::set<unsigned>::iterator it =
2942 (*boundaries_pt).begin();
2943 it != (*boundaries_pt).end();
2944 it++)
2945 {
2946 bound.push_back((*it));
2947 }
2948 for (unsigned i = 0; i < nb; i++)
2949 {
2952 }
2953 }
2954
2955 // Get ready to kill it
2957 unsigned i_proc = unsigned(iproc);
2960 }
2961
2962
2963 // Note: For now we're leaving the "dangling" (no longer
2964 // accessed masters where they are; they get cleaned
2965 // up next time we delete all the external storage
2966 // for the meshes so it's a temporary "leak" only...
2967 // At some point we should probably delete them properly too
2968
2969#ifdef PARANOID
2970
2971 // Check that hang status of exiting and replacement node
2972 // matches
2973 if (dynamic_cast<RefineableElement*>(finite_ext_el_pt) != 0)
2974 {
2976 dynamic_cast<RefineableElement*>(finite_ext_el_pt)
2977 ->ncont_interpolated_values();
2978 for (int i_cont = -1; i_cont < n_cont_inter_values;
2979 i_cont++)
2980 {
2981 unsigned n_master_orig = 0;
2983 {
2986 ->nmaster();
2987
2988 // Temporary leak: Resolve like this:
2989 // loop over all external halo nodes and identify the
2990 // the ones that are still reached by any of the
2991 // external elements. Kill the dangling ones.
2992 }
2993 unsigned n_master_replace = 0;
2994 if (existing_node_pt->is_hanging(i_cont))
2995 {
2997 existing_node_pt->hanging_pt(i_cont)->nmaster();
2998 }
2999
3001 {
3002 std::ostringstream error_stream;
3004 << "Number of master nodes for node to be replaced, "
3005 << n_master_orig << ", doesn't match"
3006 << "those of replacement node, " << n_master_replace
3007 << " for i_cont=" << i_cont << std::endl;
3008 {
3010 << "Nodal coordinates of replacement node:";
3011 unsigned ndim = existing_node_pt->ndim();
3012 for (unsigned i = 0; i < ndim; i++)
3013 {
3014 error_stream << existing_node_pt->x(i) << " ";
3015 }
3016 error_stream << "\n";
3017 error_stream << "The coordinates of its "
3019 << " master nodes are: \n";
3020 for (unsigned k = 0; k < n_master_replace; k++)
3021 {
3023 existing_node_pt->hanging_pt(i_cont)
3024 ->master_node_pt(k);
3025 unsigned ndim = master_nod_pt->ndim();
3026 for (unsigned i = 0; i < ndim; i++)
3027 {
3028 error_stream << master_nod_pt->x(i) << " ";
3029 }
3030 error_stream << "\n";
3031 }
3032 }
3033
3034 {
3036 << "Nodal coordinates of node to be replaced:";
3037 unsigned ndim = finite_ext_el_pt->node_pt(j)->ndim();
3038 for (unsigned i = 0; i < ndim; i++)
3039 {
3041 << " ";
3042 }
3043 error_stream << "\n";
3044 error_stream << "The coordinates of its "
3045 << n_master_orig
3046 << " master nodes are: \n";
3047 for (unsigned k = 0; k < n_master_orig; k++)
3048 {
3051 ->master_node_pt(k);
3052 unsigned ndim = master_nod_pt->ndim();
3053 for (unsigned i = 0; i < ndim; i++)
3054 {
3055 error_stream << master_nod_pt->x(i) << " ";
3056 }
3057 error_stream << "\n";
3058 }
3059 }
3060
3061
3062 throw OomphLibError(error_stream.str(),
3065 }
3066 }
3067 }
3068#endif
3069 // ...and point to the existing one
3071 }
3072 // If it doesn't add it to the list of existing ones
3073 else
3074 {
3076 nod_pt;
3077 node_done[nod_pt] = true;
3078 }
3079 }
3080
3081
3082 // Do the same for any master nodes of that (possibly replaced)
3083 // node
3084 if (dynamic_cast<RefineableElement*>(finite_ext_el_pt) != 0)
3085 {
3087 dynamic_cast<RefineableElement*>(finite_ext_el_pt)
3088 ->ncont_interpolated_values();
3089 for (int i_cont = -1; i_cont < n_cont_inter_values; i_cont++)
3090 {
3092 {
3093 HangInfo* hang_pt =
3095 unsigned n_master = hang_pt->nmaster();
3096 for (unsigned m = 0; m < n_master; m++)
3097 {
3098 Node* master_nod_pt = hang_pt->master_node_pt(m);
3099 unsigned n_val = master_nod_pt->nvalue();
3101 for (unsigned i_val = 0; i_val < n_val; i_val++)
3102 {
3104 if (eqn_no >= 0)
3105 {
3107 break;
3108 }
3109 }
3110
3111 // If we haven't found a non-negative eqn number check
3112 // eqn numbers associated with solid data (if any)
3114 {
3116 dynamic_cast<SolidNode*>(master_nod_pt);
3117 if (solid_master_nod_pt != 0)
3118 {
3119 // Loop over values stored at node (if any) to find
3120 // the first non-negative eqn number
3121 unsigned n_val =
3122 solid_master_nod_pt->variable_position_pt()
3123 ->nvalue();
3124 for (unsigned i_val = 0; i_val < n_val; i_val++)
3125 {
3126 int eqn_no =
3127 solid_master_nod_pt->variable_position_pt()
3128 ->eqn_number(i_val);
3129 if (eqn_no >= 0)
3130 {
3132 eqn_no + 1;
3133 break;
3134 }
3135 }
3136 }
3137 }
3138
3139 // Identified which node we're dealing with via
3140 // first non-negative global eqn number (if there
3141 // is none, everything is pinned and we don't give a
3142 // damn...)
3144 {
3147
3148 // Does this node already exist?
3149 if (existing_node_pt != 0)
3150 {
3151 // Record that we're about to cull one
3153
3154 // It's a duplicate, so store the duplicated one for
3155 // later killing...
3157
3159 {
3160 // Remove node from all boundaries
3161 std::set<unsigned>* boundaries_pt;
3162 duplicated_node_pt->get_boundaries_pt(
3164 if (boundaries_pt != 0)
3165 {
3166 for (std::set<unsigned>::iterator it =
3167 (*boundaries_pt).begin();
3168 it != (*boundaries_pt).end();
3169 it++)
3170 {
3173 }
3174 }
3175
3177 unsigned i_proc = unsigned(iproc);
3180 }
3181
3182 // Weight of the original node
3183 double m_weight = hang_pt->master_weight(m);
3184
3185
3186#ifdef PARANOID
3187 // Sanity check: setting replacement master
3188 // node for non-hanging node? Sign of really
3189 // f***ed up code.
3191 if (!tmp_nod_pt->is_hanging(i_cont))
3192 {
3193 std::ostringstream error_stream;
3195 << "About to re-set master for i_cont= " << i_cont
3196 << " for external node (with proc " << iproc
3197 << " )" << tmp_nod_pt << " at ";
3198 unsigned n = tmp_nod_pt->ndim();
3199 for (unsigned jj = 0; jj < n; jj++)
3200 {
3201 error_stream << tmp_nod_pt->x(jj) << " ";
3202 }
3204 << " which is not hanging --> About to die!"
3205 << "Outputting offending element into oomph-info "
3206 << "stream. \n\n";
3207 oomph_info << "\n\n";
3209 oomph_info << "\n\n";
3210 oomph_info.stream_pt()->flush();
3211 throw OomphLibError(error_stream.str(),
3214 }
3215#endif
3216
3217
3218 // And re-set master
3222 }
3223 // If it doesn't, add it to the list of existing ones
3224 else
3225 {
3229 node_done[master_nod_pt] = true;
3230 }
3231 }
3232 } // End of loop over master nodes
3233 } // end of hanging
3234 } // end of loop over continously interpolated variables
3235 } // end refineable element (with potentially hanging node
3236
3237 } // end loop over nodes on external halo elements
3238
3239 } // End of check for finite element
3240
3241 } // end loop over external halo elements
3242 }
3243 } // end loop over processors
3244
3245
3246 // Now kill all the deleted nodes
3247 for (std::set<Node*>::iterator it = killed_nodes.begin();
3248 it != killed_nodes.end();
3249 it++)
3250 {
3251 delete (*it);
3252 }
3253
3254
3255 // oomph_info << "Number of nonzero entries in global_node_pt: "
3256 // << global_node_pt.size() << std::endl;
3257
3258 // // Time it...
3259 // // Taken out again by MH -- clutters up output
3260 // if (Global_timings::Doc_comprehensive_timings)
3261 // {
3262 // double t_end = TimingHelpers::timer();
3263 // oomph_info
3264 // << "Total time for Problem::remove_duplicate_data: "
3265 // << t_end-t_start << std::endl;
3266 // }
3267 }
3268
3269
3270 //========================================================================
3271 /// Consolidate external halo node storage by removing nulled out
3272 /// pointers in external halo and haloed schemes for all meshes.
3273 //========================================================================
3275 {
3276 // Do we have submeshes?
3277 unsigned n_mesh_loop = 1;
3278 unsigned nmesh = nsub_mesh();
3279 if (nmesh > 0)
3280 {
3281 n_mesh_loop = nmesh;
3282 }
3283
3284 // Storage for number of processors and current processor
3285 int n_proc = this->communicator_pt()->nproc();
3286 int my_rank = this->communicator_pt()->my_rank();
3287
3288 // If only one processor then return
3289 if (n_proc == 1)
3290 {
3291 return;
3292 }
3293
3294 // Loop over all (other) processors and store index of any nulled-out
3295 // external halo nodes in storage scheme.
3296
3297 // Data to be sent to each processor
3299
3300 // Storage for all values to be sent to all processors
3302
3303 // Start location within send_data for data to be sent to each processor
3305
3306 // Check missing ones
3307 for (int domain = 0; domain < n_proc; domain++)
3308 {
3309 // Set the offset for the current processor
3311
3312 // Don't bother to do anything if the processor in the loop is the
3313 // current processor
3314 if (domain != my_rank)
3315 {
3316 // Deal with sub-meshes one-by-one if required
3317 Mesh* my_mesh_pt = 0;
3318
3319 // Loop over submeshes
3320 for (unsigned imesh = 0; imesh < n_mesh_loop; imesh++)
3321 {
3322 if (nmesh == 0)
3323 {
3324 my_mesh_pt = mesh_pt();
3325 }
3326 else
3327 {
3329 }
3330
3331 // Make backup of external halo node pointers with this domain
3332 Vector<Node*> backup_pt(my_mesh_pt->external_halo_node_pt(domain));
3333
3334 // How many do we have currently?
3335 unsigned nnod = backup_pt.size();
3336
3337 // Prepare storage for updated halo nodes
3340
3341 // Loop over external halo nodes with this domain
3342 for (unsigned j = 0; j < nnod; j++)
3343 {
3344 // Get pointer to node
3345 Node* nod_pt = backup_pt[j];
3346
3347 // Has it been nulled out?
3348 if (nod_pt == 0)
3349 {
3350 // Save index of nulled out one
3351 send_data.push_back(j);
3352 }
3353 else
3354 {
3355 // Still alive: Copy across
3357 }
3358 }
3359
3360 // Set new external halo node vector
3361 my_mesh_pt->set_external_halo_node_pt(domain,
3363
3364 // End of data for this mesh
3365 send_data.push_back(-1);
3366
3367 } // end of loop over meshes
3368
3369 } // end skip own domain
3370
3371 // Find the number of data added to the vector
3373 }
3374
3375 // Storage for the number of data to be received from each processor
3377
3378 // Now send numbers of data to be sent between all processors
3379 MPI_Alltoall(&send_n[0],
3380 1,
3381 MPI_INT,
3382 &receive_n[0],
3383 1,
3384 MPI_INT,
3385 this->communicator_pt()->mpi_comm());
3386
3387
3388 // We now prepare the data to be received
3389 // by working out the displacements from the received data
3391 int receive_data_count = 0;
3392 for (int rank = 0; rank < n_proc; ++rank)
3393 {
3394 // Displacement is number of data received so far
3397 }
3398
3399 // Now resize the receive buffer for all data from all processors
3400 // Make sure that it has a size of at least one
3401 if (receive_data_count == 0)
3402 {
3404 }
3406
3407 // Make sure that the send buffer has size at least one
3408 // so that we don't get a segmentation fault
3409 if (send_data.size() == 0)
3410 {
3411 send_data.resize(1);
3412 }
3413
3414 // Now send the data between all the processors
3416 &send_n[0],
3418 MPI_INT,
3419 &receive_data[0],
3420 &receive_n[0],
3422 MPI_INT,
3423 this->communicator_pt()->mpi_comm());
3424
3425 // Now use the received data
3426 for (int send_rank = 0; send_rank < n_proc; send_rank++)
3427 {
3428 // Don't bother to do anything for the processor corresponding to the
3429 // current processor or if no data were received from this processor
3430 if ((send_rank != my_rank) && (receive_n[send_rank] != 0))
3431 {
3432 // Counter for the data within the large array
3434
3435 // Deal with sub-meshes one-by-one if required
3436 Mesh* my_mesh_pt = 0;
3437
3438 // Loop over submeshes
3439 for (unsigned imesh = 0; imesh < n_mesh_loop; imesh++)
3440 {
3441 if (nmesh == 0)
3442 {
3443 my_mesh_pt = mesh_pt();
3444 }
3445 else
3446 {
3448 }
3449
3450 // Make backup of external haloed node pointers with this domain
3452 my_mesh_pt->external_haloed_node_pt(send_rank);
3453
3454 // Unpack until we reach "end of data" indicator (-1) for this mesh
3455 while (true)
3456 {
3457 // Read next entry
3458 int next_one = receive_data[count++];
3459
3460 if (next_one == -1)
3461 {
3462 break;
3463 }
3464 else
3465 {
3466 // Null out the entry
3467 backup_pt[next_one] = 0;
3468 }
3469 }
3470
3471 // How many do we have currently?
3472 unsigned nnod = backup_pt.size();
3473
3474 // Prepare storage for updated haloed nodes
3477
3478 // Loop over external haloed nodes with this domain
3479 for (unsigned j = 0; j < nnod; j++)
3480 {
3481 // Get pointer to node
3482 Node* nod_pt = backup_pt[j];
3483
3484 // Has it been nulled out?
3485 if (nod_pt != 0)
3486 {
3487 // Still alive: Copy across
3489 }
3490 }
3491
3492 // Set new external haloed node vector
3493 my_mesh_pt->set_external_haloed_node_pt(send_rank,
3495 }
3496 }
3497
3498 } // End of data is received
3499 }
3500
3501#endif
3502
3503
3504 //=======================================================================
3505 /// Function that sets the values of the dofs in the object
3506 //======================================================================
3508 {
3509 const unsigned long n_dof = this->ndof();
3510#ifdef PARANOID
3511 if (n_dof != dofs.nrow())
3512 {
3513 std::ostringstream error_stream;
3514 error_stream << "Number of degrees of freedom in vector argument "
3515 << dofs.nrow() << "\n"
3516 << "does not equal number of degrees of freedom in problem "
3517 << n_dof;
3518 throw OomphLibError(
3520 }
3521#endif
3522 for (unsigned long l = 0; l < n_dof; l++)
3523 {
3524 *Dof_pt[l] = dofs[l];
3525 }
3526 }
3527
3528 /// Set history values of dofs
3529 void Problem::set_dofs(const unsigned& t, DoubleVector& dofs)
3530 {
3531#ifdef PARANOID
3532 if (distributed())
3533 {
3534 throw OomphLibError("Not designed for distributed problems",
3537 // might work if the dofs vector is distributed in the right way...
3538 }
3539#endif
3540
3541 // First deal with global data
3542 unsigned Nglobal_data = nglobal_data();
3543 for (unsigned i = 0; i < Nglobal_data; i++)
3544 {
3545 for (unsigned j = 0, nj = Global_data_pt[i]->nvalue(); j < nj; j++)
3546 {
3547 // For each data get the equation number and copy out the value.
3548 int eqn_number = Global_data_pt[i]->eqn_number(j);
3549 if (eqn_number >= 0)
3550 {
3551 Global_data_pt[i]->set_value(t, j, dofs[eqn_number]);
3552 }
3553 }
3554 }
3555
3556 // Next element internal data
3557 for (unsigned i = 0, ni = mesh_pt()->nelement(); i < ni; i++)
3558 {
3560 for (unsigned j = 0, nj = ele_pt->ninternal_data(); j < nj; j++)
3561 {
3562 Data* d_pt = ele_pt->internal_data_pt(j);
3563 for (unsigned k = 0, nk = d_pt->nvalue(); k < nk; k++)
3564 {
3565 int eqn_number = d_pt->eqn_number(k);
3566 if (eqn_number >= 0)
3567 {
3568 d_pt->set_value(t, k, dofs[eqn_number]);
3569 }
3570 }
3571 }
3572 }
3573
3574 // Now the nodes
3575 for (unsigned i = 0, ni = mesh_pt()->nnode(); i < ni; i++)
3576 {
3577 Node* node_pt = mesh_pt()->node_pt(i);
3578 for (unsigned j = 0, nj = node_pt->nvalue(); j < nj; j++)
3579 {
3580 // For each node get the equation number and copy out the value.
3581 int eqn_number = node_pt->eqn_number(j);
3582 if (eqn_number >= 0)
3583 {
3584 node_pt->set_value(t, j, dofs[eqn_number]);
3585 }
3586 }
3587 }
3588 }
3589
3590
3591 /// Set history values of dofs from the type of vector stored in
3592 /// problem::Dof_pt.
3593 void Problem::set_dofs(const unsigned& t, Vector<double*>& dof_pt)
3594 {
3595#ifdef PARANOID
3596 if (distributed())
3597 {
3598 throw OomphLibError("Not implemented for distributed problems!",
3601 }
3602#endif
3603
3604 // If we have any spine meshes I think there might be more degrees
3605 // of freedom there. I don't use them though so I'll let someone who
3606 // knows what they are doing handle it. --David Shepherd
3607
3608 // First deal with global data
3609 unsigned Nglobal_data = nglobal_data();
3610 for (unsigned i = 0; i < Nglobal_data; i++)
3611 {
3612 for (unsigned j = 0, nj = Global_data_pt[i]->nvalue(); j < nj; j++)
3613 {
3614 // For each data get the equation number and copy in the value.
3615 int eqn_number = Global_data_pt[i]->eqn_number(j);
3616 if (eqn_number >= 0)
3617 {
3618 Global_data_pt[i]->set_value(t, j, *(dof_pt[eqn_number]));
3619 }
3620 }
3621 }
3622
3623 // Now the mesh data
3624 // nodes
3625 for (unsigned i = 0, ni = mesh_pt()->nnode(); i < ni; i++)
3626 {
3627 Node* node_pt = mesh_pt()->node_pt(i);
3628 for (unsigned j = 0, nj = node_pt->nvalue(); j < nj; j++)
3629 {
3630 // For each node get the equation number and copy in the value.
3631 int eqn_number = node_pt->eqn_number(j);
3632 if (eqn_number >= 0)
3633 {
3634 node_pt->set_value(t, j, *(dof_pt[eqn_number]));
3635 }
3636 }
3637 }
3638
3639 // and non-nodal data inside elements
3640 for (unsigned i = 0, ni = mesh_pt()->nelement(); i < ni; i++)
3641 {
3643 for (unsigned j = 0, nj = ele_pt->ninternal_data(); j < nj; j++)
3644 {
3646 // For each node get the equation number and copy in the value.
3647 int eqn_number = data_pt->eqn_number(j);
3648 if (eqn_number >= 0)
3649 {
3650 data_pt->set_value(t, j, *(dof_pt[eqn_number]));
3651 }
3652 }
3653 }
3654 }
3655
3656
3657 //===================================================================
3658 /// Function that adds the values to the dofs
3659 //==================================================================
3660 void Problem::add_to_dofs(const double& lambda,
3662 {
3663 const unsigned long n_dof = this->ndof();
3664 for (unsigned long l = 0; l < n_dof; l++)
3665 {
3666 *Dof_pt[l] += lambda * increment_dofs[l];
3667 }
3668 }
3669
3670
3671 //=========================================================================
3672 /// Return the residual vector multiplied by the inverse mass matrix
3673 /// Virtual so that it can be overloaded for mpi problems
3674 //=========================================================================
3676 {
3677 // This function does not make sense for assembly handlers other than the
3678 // default, so complain if we try to call it with another handler
3679
3680#ifdef PARANOID
3681 // If we are not the default, then complain
3683 {
3684 std::ostringstream error_stream;
3685 error_stream << "The function get_inverse_mass_matrix_times_residuals() "
3686 "can only be\n"
3687 << "used with the default assembly handler\n\n";
3688 throw OomphLibError(
3690 }
3691#endif
3692
3693 // Find the number of degrees of freedom in the problem
3694 const unsigned n_dof = this->ndof();
3695
3696 // Resize the vector
3697 LinearAlgebraDistribution dist(this->communicator_pt(), n_dof, false);
3698 Mres.build(&dist, 0.0);
3699
3700 // If we have discontinuous formulation
3701 // We can invert the mass matrix element by element
3703 {
3704 // Loop over the elements and get their residuals
3705 const unsigned n_element = Problem::mesh_pt()->nelement();
3707 for (unsigned e = 0; e < n_element; e++)
3708 {
3709 // Cache the element
3710 DGElement* const elem_pt =
3711 dynamic_cast<DGElement*>(Problem::mesh_pt()->element_pt(e));
3712
3713 // Find the elemental inverse mass matrix times residuals
3714 const unsigned n_el_dofs = elem_pt->ndof();
3715 elem_pt->get_inverse_mass_matrix_times_residuals(element_Mres);
3716
3717 // Add contribution to global matrix
3718 for (unsigned i = 0; i < n_el_dofs; i++)
3719 {
3721 }
3722 }
3723 }
3724 // Otherwise it's continous and we must invert the full
3725 // mass matrix via a global linear solve.
3726 else
3727 {
3728 // Now do the linear solve -- recycling Mass matrix if requested
3729 // If we already have the factorised mass matrix, then resolve
3731 {
3733 {
3734 oomph_info << "Not recomputing Mass Matrix " << std::endl;
3735 }
3736
3737 // Get the residuals
3739 this->get_residuals(residuals);
3740
3741 // Resolve the linear system
3743 residuals, Mres);
3744 }
3745 // Otherwise solve for the first time
3746 else
3747 {
3748 // If we wish to reuse the mass matrix, then enable resolve
3750 {
3752 {
3753 oomph_info << "Enabling resolve in explicit timestep" << std::endl;
3754 }
3756 ->enable_resolve();
3757 }
3758
3759 // Use a custom assembly handler to assemble and invert the mass matrix
3760
3761 // Store the old assembly handler
3763 // Set the assembly handler to the explicit timestep handler
3765
3766 // Solve the linear system
3768 Mres);
3769 // The mass matrix has now been computed
3771
3772 // Delete the Explicit Timestep handler
3773 delete this->assembly_handler_pt();
3774 // Reset the assembly handler to the original handler
3775 this->assembly_handler_pt() = old_assembly_handler_pt;
3776 }
3777 }
3778 }
3779
3781 {
3782 // Loop over timesteppers: make them (temporarily) steady and store their
3783 // is_steady status.
3784 unsigned n_time_steppers = this->ntime_stepper();
3785 std::vector<bool> was_steady(n_time_steppers);
3786 for (unsigned i = 0; i < n_time_steppers; i++)
3787 {
3790 }
3791
3792 // Calculate f using the residual/jacobian machinary.
3794
3795 // Reset the is_steady status of all timesteppers that weren't already
3796 // steady when we came in here and reset their weights
3797 for (unsigned i = 0; i < n_time_steppers; i++)
3798 {
3799 if (!was_steady[i])
3800 {
3802 }
3803 }
3804 }
3805
3806
3807 //================================================================
3808 /// Get the total residuals Vector for the problem
3809 //================================================================
3811 {
3812 // Three different cases; if MPI_Helpers::MPI_has_been_initialised=true
3813 // this means MPI_Helpers::init() has been called. This could happen on a
3814 // code compiled with MPI but run serially; in this instance the
3815 // get_residuals function still works on one processor.
3816 //
3817 // Secondly, if a code has been compiled with MPI, but MPI_Helpers::init()
3818 // has not been called, then MPI_Helpers::MPI_has_been_initialised=false
3819 // and the code calls...
3820 //
3821 // Thirdly, the serial version (compiled by all, but only run when compiled
3822 // with MPI if MPI_Helpers::MPI_has_been_initialised=false
3823
3824 // Check that the residuals has the correct number of rows if it has been
3825 // setup
3826#ifdef PARANOID
3827 if (residuals.built())
3828 {
3829 if (residuals.distribution_pt()->nrow() != this->ndof())
3830 {
3831 std::ostringstream error_stream;
3832 error_stream << "The distribution of the residuals vector does not "
3833 "have the correct\n"
3834 << "number of global rows\n";
3835
3836 throw OomphLibError(
3838 }
3839 }
3840#endif
3841
3842 // Determine the distribution for the residuals vector
3843 // IF the vector has distribution setup then use that
3844 // ELSE determine the distribution based on the
3845 // distributed_matrix_distribution enum
3847 if (residuals.built())
3848 {
3849 dist_pt = new LinearAlgebraDistribution(residuals.distribution_pt());
3850 }
3851 else
3852 {
3854 }
3855
3856 // Locally cache pointer to assembly handler
3858
3859 // Build and zero the residuals
3860 residuals.build(dist_pt, 0.0);
3861
3862 // Serial (or one processor case)
3863#ifdef OOMPH_HAS_MPI
3864 if (this->communicator_pt()->nproc() == 1)
3865 {
3866#endif // OOMPH_HAS_MPI
3867 // Loop over all the elements
3868 unsigned long Element_pt_range = Mesh_pt->nelement();
3869 for (unsigned long e = 0; e < Element_pt_range; e++)
3870 {
3871 // Get the pointer to the element
3873
3874 // Find number of dofs in the element
3876
3877 // Set up an array
3879
3880 // Fill the array
3882
3883 // Now loop over the dofs and assign values to global Vector
3884 for (unsigned l = 0; l < n_element_dofs; l++)
3885 {
3888 }
3889 }
3890 // Otherwise parallel case
3891#ifdef OOMPH_HAS_MPI
3892 }
3893 else
3894 {
3895 // Store the current assembly handler
3897 // Create a new assembly handler that only assembles the residuals
3900
3901 // Setup memory for parallel sparse assemble
3902 // No matrix so all size zero
3903 Vector<int*> column_index;
3904 Vector<int*> row_start;
3905 Vector<double*> value;
3906 Vector<unsigned> nnz;
3907 // One set of residuals of sizer one
3909
3910 // Call the parallel sparse assemble, that should only assemble residuals
3912 dist_pt, column_index, row_start, value, nnz, res);
3913 // Fill in the residuals data
3914 residuals.set_external_values(res[0], true);
3915
3916 // Delete new assembly handler
3917 delete Assembly_handler_pt;
3918 // Reset the assembly handler to the original
3920 }
3921#endif
3922
3923 // Delete the distribution
3924 delete dist_pt;
3925 }
3926
3927 //=============================================================================
3928 /// Get the fully assembled residual vector and Jacobian matrix
3929 /// in dense storage. The DoubleVector residuals returned will be
3930 /// non-distributed. If on calling this method the DoubleVector residuals is
3931 /// setup then it must be non-distributed and of the correct length.
3932 /// The matrix type DenseDoubleMatrix is not distributable and therefore
3933 /// the residual vector is also assumed to be non distributable.
3934 //=============================================================================
3936 DenseDoubleMatrix& jacobian)
3937 {
3938 // get the number of degrees of freedom
3939 unsigned n_dof = ndof();
3940
3941#ifdef PARANOID
3942 // PARANOID checks : if the distribution of residuals is setup then it must
3943 // must not be distributed, have the right number of rows, and the same
3944 // communicator as the problem
3945 if (residuals.built())
3946 {
3947 if (residuals.distribution_pt()->distributed())
3948 {
3949 std::ostringstream error_stream;
3951 << "If the DoubleVector residuals is setup then it must not "
3952 << "be distributed.";
3953 throw OomphLibError(
3955 }
3956 if (residuals.distribution_pt()->nrow() != n_dof)
3957 {
3958 std::ostringstream error_stream;
3960 << "If the DoubleVector residuals is setup then it must have"
3961 << " the correct number of rows";
3962 throw OomphLibError(
3964 }
3965 if (!(*Communicator_pt ==
3966 *residuals.distribution_pt()->communicator_pt()))
3967 {
3968 std::ostringstream error_stream;
3970 << "If the DoubleVector residuals is setup then it must have"
3971 << " the same communicator as the problem.";
3972 throw OomphLibError(
3974 }
3975 }
3976#endif
3977
3978 // set the residuals distribution if it is not setup
3979 if (!residuals.built())
3980 {
3982 residuals.build(&dist, 0.0);
3983 }
3984 // else just zero the residuals
3985 else
3986 {
3987 residuals.initialise(0.0);
3988 }
3989
3990 // Resize the matrices -- this cannot always be done externally
3991 // because get_jacobian exists in many different versions for
3992 // different storage formats -- resizing a CC or CR matrix doesn't
3993 // make sense.
3994
3995 // resize the jacobian
3996 jacobian.resize(n_dof, n_dof);
3997 jacobian.initialise(0.0);
3998
3999 // Locally cache pointer to assembly handler
4001
4002 // Loop over all the elements
4003 unsigned long n_element = Mesh_pt->nelement();
4004 for (unsigned long e = 0; e < n_element; e++)
4005 {
4006 // Get the pointer to the element
4008 // Find number of dofs in the element
4010 // Set up an array
4012 // Set up a matrix
4014 // Fill the array
4017 // Now loop over the dofs and assign values to global Vector
4018 for (unsigned l = 0; l < n_element_dofs; l++)
4019 {
4020 unsigned long eqn_number = assembly_handler_pt->eqn_number(elem_pt, l);
4021 residuals[eqn_number] += element_residuals[l];
4022 for (unsigned l2 = 0; l2 < n_element_dofs; l2++)
4023 {
4024 jacobian(eqn_number, assembly_handler_pt->eqn_number(elem_pt, l2)) +=
4026 }
4027 }
4028 }
4029 }
4030
4031 //=============================================================================
4032 /// Return the fully-assembled Jacobian and residuals for the problem,
4033 /// in the case where the Jacobian matrix is in a distributable
4034 /// row compressed storage format.
4035 /// 1. If the distribution of the jacobian and residuals is setup then, they
4036 /// will be returned with that distribution.
4037 /// Note. the jacobian and residuals must have the same distribution.
4038 /// 2. If the distribution of the jacobian and residuals are not setup then
4039 /// their distribution will computed based on:
4040 /// Distributed_problem_matrix_distribution.
4041 //=============================================================================
4043 {
4044 // Three different cases; if MPI_Helpers::MPI_has_been_initialised=true
4045 // this means MPI_Helpers::setup() has been called. This could happen on a
4046 // code compiled with MPI but run serially; in this instance the
4047 // get_residuals function still works on one processor.
4048 //
4049 // Secondly, if a code has been compiled with MPI, but MPI_Helpers::setup()
4050 // has not been called, then MPI_Helpers::MPI_has_been_initialised=false
4051 // and the code calls...
4052 //
4053 // Thirdly, the serial version (compiled by all, but only run when compiled
4054 // with MPI if MPI_Helpers::MPI_has_been_initialised=false
4055 //
4056 // The only case where an MPI code cannot run serially at present
4057 // is one where the distribute function is used (i.e. METIS is called)
4058
4059 // Allocate storage for the matrix entries
4060 // The generalised Vector<Vector<>> structure is required
4061 // for the most general interface to sparse_assemble() which allows
4062 // the assembly of multiple matrices at once.
4063 Vector<int*> column_index(1);
4064 Vector<int*> row_start(1);
4065 Vector<double*> value(1);
4066 Vector<unsigned> nnz(1);
4067
4068#ifdef PARANOID
4069 // PARANOID checks that the distribution of the jacobian matches that of the
4070 // residuals (if they are setup) and that they have the right number of rows
4071 if (residuals.built() && jacobian.distribution_built())
4072 {
4073 if (!(*residuals.distribution_pt() == *jacobian.distribution_pt()))
4074 {
4075 std::ostringstream error_stream;
4076 error_stream << "The distribution of the residuals must "
4077 << "be the same as the distribution of the jacobian.";
4078 throw OomphLibError(
4080 }
4081 if (jacobian.distribution_pt()->nrow() != this->ndof())
4082 {
4083 std::ostringstream error_stream;
4085 << "The distribution of the jacobian and residuals does not"
4086 << "have the correct number of global rows.";
4087 throw OomphLibError(
4089 }
4090 }
4091 else if (residuals.built() != jacobian.distribution_built())
4092 {
4093 std::ostringstream error_stream;
4094 error_stream << "The distribution of the jacobian and residuals must "
4095 << "both be setup or both not setup";
4096 throw OomphLibError(
4098 }
4099#endif
4100
4101
4102 // Allocate generalised storage format for passing to sparse_assemble()
4104
4105 // determine the distribution for the jacobian.
4106 // IF the jacobian has distribution setup then use that
4107 // ELSE determine the distribution based on the
4108 // distributed_matrix_distribution enum
4110 if (jacobian.distribution_built())
4111 {
4113 }
4114 else
4115 {
4117 }
4118
4119
4120 // The matrix is in compressed row format
4121 bool compressed_row_flag = true;
4122
4123#ifdef OOMPH_HAS_MPI
4124 //
4125 if (Communicator_pt->nproc() == 1)
4126 {
4127#endif
4129 column_index, row_start, value, nnz, res, compressed_row_flag);
4130 jacobian.build(dist_pt);
4131 jacobian.build_without_copy(
4132 dist_pt->nrow(), nnz[0], value[0], column_index[0], row_start[0]);
4133 residuals.build(dist_pt, 0.0);
4134 residuals.set_external_values(res[0], true);
4135#ifdef OOMPH_HAS_MPI
4136 }
4137 else
4138 {
4139 if (dist_pt->distributed())
4140 {
4142 dist_pt, column_index, row_start, value, nnz, res);
4143 jacobian.build(dist_pt);
4144 jacobian.build_without_copy(
4145 dist_pt->nrow(), nnz[0], value[0], column_index[0], row_start[0]);
4146 residuals.build(dist_pt, 0.0);
4147 residuals.set_external_values(res[0], true);
4148 }
4149 else
4150 {
4154 temp_dist_pt, column_index, row_start, value, nnz, res);
4155 jacobian.build(temp_dist_pt);
4156 jacobian.build_without_copy(
4157 dist_pt->nrow(), nnz[0], value[0], column_index[0], row_start[0]);
4158 jacobian.redistribute(dist_pt);
4159 residuals.build(temp_dist_pt, 0.0);
4160 residuals.set_external_values(res[0], true);
4161 residuals.redistribute(dist_pt);
4162 delete temp_dist_pt;
4163 }
4164 }
4165#endif
4166
4167 // clean up dist_pt and residuals_vector pt
4168 delete dist_pt;
4169 }
4170
4171 //=============================================================================
4172 /// Return the fully-assembled Jacobian and residuals for the problem,
4173 /// in the case when the jacobian matrix is in column-compressed storage
4174 /// format.
4175 //=============================================================================
4177 {
4178 // Three different cases; if MPI_Helpers::MPI_has_been_initialised=true
4179 // this means MPI_Helpers::setup() has been called. This could happen on a
4180 // code compiled with MPI but run serially; in this instance the
4181 // get_residuals function still works on one processor.
4182 //
4183 // Secondly, if a code has been compiled with MPI, but MPI_Helpers::setup()
4184 // has not been called, then MPI_Helpers::MPI_has_been_initialised=false
4185 // and the code calls...
4186 //
4187 // Thirdly, the serial version (compiled by all, but only run when compiled
4188 // with MPI if MPI_Helpers::MPI_has_been_5Binitialised=false
4189 //
4190 // The only case where an MPI code cannot run serially at present
4191 // is one where the distribute function is used (i.e. METIS is called)
4192
4193 // get the number of degrees of freedom
4194 unsigned n_dof = ndof();
4195
4196#ifdef PARANOID
4197 // PARANOID checks : if the distribution of residuals is setup then it must
4198 // must not be distributed, have the right number of rows, and the same
4199 // communicator as the problem
4200 if (residuals.built())
4201 {
4202 if (residuals.distribution_pt()->distributed())
4203 {
4204 std::ostringstream error_stream;
4206 << "If the DoubleVector residuals is setup then it must not "
4207 << "be distributed.";
4208 throw OomphLibError(
4210 }
4211 if (residuals.distribution_pt()->nrow() != n_dof)
4212 {
4213 std::ostringstream error_stream;
4215 << "If the DoubleVector residuals is setup then it must have"
4216 << " the correct number of rows";
4217 throw OomphLibError(
4219 }
4220 if (!(*Communicator_pt ==
4221 *residuals.distribution_pt()->communicator_pt()))
4222 {
4223 std::ostringstream error_stream;
4225 << "If the DoubleVector residuals is setup then it must have"
4226 << " the same communicator as the problem.";
4227 throw OomphLibError(
4229 }
4230 }
4231#endif
4232
4233 // Allocate storage for the matrix entries
4234 // The generalised Vector<Vector<>> structure is required
4235 // for the most general interface to sparse_assemble() which allows
4236 // the assembly of multiple matrices at once.
4237 Vector<int*> row_index(1);
4238 Vector<int*> column_start(1);
4239 Vector<double*> value(1);
4240
4241 // Allocate generalised storage format for passing to sparse_assemble()
4243
4244 // allocate storage for the number of non-zeros in each matrix
4245 Vector<unsigned> nnz(1);
4246
4247 // The matrix is in compressed column format
4248 bool compressed_row_flag = false;
4249
4250 // get the distribution for the residuals
4252 if (!residuals.built())
4253 {
4254 dist_pt =
4255 new LinearAlgebraDistribution(Communicator_pt, this->ndof(), false);
4256 }
4257 else
4258 {
4259 dist_pt = new LinearAlgebraDistribution(residuals.distribution_pt());
4260 }
4261
4262#ifdef OOMPH_HAS_MPI
4263 if (communicator_pt()->nproc() == 1)
4264 {
4265#endif
4267 row_index, column_start, value, nnz, res, compressed_row_flag);
4268 jacobian.build_without_copy(
4269 value[0], row_index[0], column_start[0], nnz[0], n_dof, n_dof);
4270 residuals.build(dist_pt, 0.0);
4271 residuals.set_external_values(res[0], true);
4272#ifdef OOMPH_HAS_MPI
4273 }
4274 else
4275 {
4276 std::ostringstream error_stream;
4277 error_stream << "Cannot assemble a CCDoubleMatrix Jacobian on more "
4278 << "than one processor.";
4279 throw OomphLibError(
4281 }
4282#endif
4283
4284 // clean up
4285 delete dist_pt;
4286 }
4287
4288
4289 //===================================================================
4290 /// Set all pinned values to zero.
4291 /// Used to set boundary conditions to be homogeneous in the copy
4292 /// of the problem used in adaptive bifurcation tracking
4293 /// (ALH: TEMPORARY HACK, WILL BE FIXED)
4294 //==================================================================
4296 {
4297 // NOTE THIS DOES NOT ZERO ANY SPINE DATA, but otherwise everything else
4298 // should be zeroed
4299
4300 // Zero any pinned global Data
4301 const unsigned n_global_data = nglobal_data();
4302 for (unsigned i = 0; i < n_global_data; i++)
4303 {
4305 const unsigned n_value = local_data_pt->nvalue();
4306 for (unsigned j = 0; j < n_value; j++)
4307 {
4308 // If the data value is pinned set the value to zero
4309 if (local_data_pt->is_pinned(j))
4310 {
4311 local_data_pt->set_value(j, 0.0);
4312 }
4313 }
4314 }
4315
4316 // Loop over the submeshes:
4317 const unsigned n_sub_mesh = Sub_mesh_pt.size();
4318 if (n_sub_mesh == 0)
4319 {
4320 // Loop over the nodes in the element
4321 const unsigned n_node = Mesh_pt->nnode();
4322 for (unsigned n = 0; n < n_node; n++)
4323 {
4324 Node* const local_node_pt = Mesh_pt->node_pt(n);
4325 const unsigned n_value = local_node_pt->nvalue();
4326 for (unsigned j = 0; j < n_value; j++)
4327 {
4328 // If the data value is pinned set the value to zero
4329 if (local_node_pt->is_pinned(j))
4330 {
4331 local_node_pt->set_value(j, 0.0);
4332 }
4333 }
4334
4335 // Try to cast to a solid node
4337 dynamic_cast<SolidNode*>(local_node_pt);
4338 // If we are successful
4340 {
4341 // Find the dimension of the node
4342 const unsigned n_dim = local_solid_node_pt->ndim();
4343 // Find number of positions
4344 const unsigned n_position_type =
4345 local_solid_node_pt->nposition_type();
4346
4347 for (unsigned k = 0; k < n_position_type; k++)
4348 {
4349 for (unsigned i = 0; i < n_dim; i++)
4350 {
4351 // If the generalised position is pinned,
4352 // set the value to zero
4353 if (local_solid_node_pt->position_is_pinned(k, i))
4354 {
4355 local_solid_node_pt->x_gen(k, i) = 0.0;
4356 }
4357 }
4358 }
4359 }
4360 }
4361
4362 // Now loop over the element's and zero the internal data
4363 const unsigned n_element = Mesh_pt->nelement();
4364 for (unsigned e = 0; e < n_element; e++)
4365 {
4367 const unsigned n_internal = local_element_pt->ninternal_data();
4368 for (unsigned i = 0; i < n_internal; i++)
4369 {
4371 const unsigned n_value = local_data_pt->nvalue();
4372 for (unsigned j = 0; j < n_value; j++)
4373 {
4374 // If the data value is pinned set the value to zero
4375 if (local_data_pt->is_pinned(j))
4376 {
4377 local_data_pt->set_value(j, 0.0);
4378 }
4379 }
4380 }
4381 } // End of loop over elements
4382 }
4383 else
4384 {
4385 // Alternatively loop over all sub meshes
4386 for (unsigned m = 0; m < n_sub_mesh; m++)
4387 {
4388 // Loop over the nodes in the element
4389 const unsigned n_node = Sub_mesh_pt[m]->nnode();
4390 for (unsigned n = 0; n < n_node; n++)
4391 {
4393 const unsigned n_value = local_node_pt->nvalue();
4394 for (unsigned j = 0; j < n_value; j++)
4395 {
4396 // If the data value is pinned set the value to zero
4397 if (local_node_pt->is_pinned(j))
4398 {
4399 local_node_pt->set_value(j, 0.0);
4400 }
4401 }
4402
4403 // Try to cast to a solid node
4405 dynamic_cast<SolidNode*>(local_node_pt);
4406 // If we are successful
4408 {
4409 // Find the dimension of the node
4410 const unsigned n_dim = local_solid_node_pt->ndim();
4411 // Find number of positions
4412 const unsigned n_position_type =
4413 local_solid_node_pt->nposition_type();
4414
4415 for (unsigned k = 0; k < n_position_type; k++)
4416 {
4417 for (unsigned i = 0; i < n_dim; i++)
4418 {
4419 // If the generalised position is pinned,
4420 // set the value to zero
4421 if (local_solid_node_pt->position_is_pinned(k, i))
4422 {
4423 local_solid_node_pt->x_gen(k, i) = 0.0;
4424 }
4425 }
4426 }
4427 }
4428 }
4429
4430 // Now loop over the element's and zero the internal data
4431 const unsigned n_element = Sub_mesh_pt[m]->nelement();
4432 for (unsigned e = 0; e < n_element; e++)
4433 {
4435 Sub_mesh_pt[m]->element_pt(e);
4436 const unsigned n_internal = local_element_pt->ninternal_data();
4437 for (unsigned i = 0; i < n_internal; i++)
4438 {
4440 const unsigned n_value = local_data_pt->nvalue();
4441 for (unsigned j = 0; j < n_value; j++)
4442 {
4443 // If the data value is pinned set the value to zero
4444 if (local_data_pt->is_pinned(j))
4445 {
4446 local_data_pt->set_value(j, 0.0);
4447 }
4448 }
4449 }
4450 } // End of loop over elements
4451 }
4452 }
4453 }
4454
4455
4456 //=====================================================================
4457 /// This is a (private) helper function that is used to assemble system
4458 /// matrices in compressed row or column format
4459 /// and compute residual vectors.
4460 /// The default action is to assemble the jacobian matrix and
4461 /// residuals for the Newton method. The action can be
4462 /// overloaded at an elemental level by changing the default
4463 /// behaviour of the function Element::get_all_vectors_and_matrices().
4464 /// column_or_row_index: Column [or row] index of given entry
4465 /// row_or_column_start: Index of first entry for given row [or column]
4466 /// value : Vector of nonzero entries
4467 /// residuals : Residual vector
4468 /// compressed_row_flag: Bool flag to indicate if storage format is
4469 /// compressed row [if false interpretation of
4470 /// arguments is as stated in square brackets].
4471 /// We provide four different assembly methods, each with different
4472 /// memory requirements/execution speeds. The method is set by
4473 /// the public flag Problem::Sparse_assembly_method.
4474 //=====================================================================
4478 Vector<double*>& value,
4479 Vector<unsigned>& nnz,
4482 {
4483 // Choose the actual method
4484 switch (Sparse_assembly_method)
4485 {
4487
4491 value,
4492 nnz,
4493 residuals,
4495
4496 break;
4497
4499
4503 value,
4504 nnz,
4505 residuals,
4507
4508 break;
4509
4511
4514 value,
4515 nnz,
4516 residuals,
4518
4519 break;
4520
4522
4526 value,
4527 nnz,
4528 residuals,
4530
4531 break;
4532
4534
4538 value,
4539 nnz,
4540 residuals,
4542
4543 break;
4544
4545 default:
4546
4547 std::ostringstream error_stream;
4549 << "Error: Incorrect value for Problem::Sparse_assembly_method"
4550 << Sparse_assembly_method << std::endl
4551 << "It should be one of the enumeration Problem::Assembly_method"
4552 << std::endl;
4553 throw OomphLibError(
4555 }
4556 }
4557
4558
4559 //=====================================================================
4560 /// This is a (private) helper function that is used to assemble system
4561 /// matrices in compressed row or column format
4562 /// and compute residual vectors, using maps
4563 /// The default action is to assemble the jacobian matrix and
4564 /// residuals for the Newton method. The action can be
4565 /// overloaded at an elemental level by chaging the default
4566 /// behaviour of the function Element::get_all_vectors_and_matrices().
4567 /// column_or_row_index: Column [or row] index of given entry
4568 /// row_or_column_start: Index of first entry for given row [or column]
4569 /// value : Vector of nonzero entries
4570 /// residuals : Residual vector
4571 /// compressed_row_flag: Bool flag to indicate if storage format is
4572 /// compressed row [if false interpretation of
4573 /// arguments is as stated in square brackets].
4574 //=====================================================================
4578 Vector<double*>& value,
4579 Vector<unsigned>& nnz,
4582 {
4583 // Total number of elements
4584 const unsigned long n_elements = mesh_pt()->nelement();
4585
4586 // Default range of elements for distributed problems
4587 unsigned long el_lo = 0;
4588 unsigned long el_hi = n_elements - 1;
4589
4590#ifdef OOMPH_HAS_MPI
4591 // Otherwise just loop over a fraction of the elements
4592 // (This will either have been initialised in
4593 // Problem::set_default_first_and_last_element_for_assembly() or
4594 // will have been re-assigned during a previous assembly loop
4595 // Note that following the re-assignment only the entries
4596 // for the current processor are relevant.
4598 {
4601 }
4602#endif
4603
4604 // number of dofs
4605 unsigned ndof = this->ndof();
4606
4607 // Find the number of vectors to be assembled
4608 const unsigned n_vector = residuals.size();
4609
4610 // Find the number of matrices to be assembled
4611 const unsigned n_matrix = column_or_row_index.size();
4612
4613 // Locally cache pointer to assembly handler
4615
4616#ifdef OOMPH_HAS_MPI
4617 bool doing_residuals = false;
4618 if (dynamic_cast<ParallelResidualsHandler*>(Assembly_handler_pt) != 0)
4619 {
4620 doing_residuals = true;
4621 }
4622#endif
4623
4624// Error check dimensions
4625#ifdef PARANOID
4627 {
4628 std::ostringstream error_stream;
4629 error_stream << "Error: " << std::endl
4630 << "row_or_column_start.size() "
4631 << row_or_column_start.size() << " does not equal "
4632 << "column_or_row_index.size() "
4633 << column_or_row_index.size() << std::endl;
4634 throw OomphLibError(
4636 }
4637
4638 if (value.size() != n_matrix)
4639 {
4640 std::ostringstream error_stream;
4642 << "Error in Problem::sparse_assemble_row_or_column_compressed "
4643 << std::endl
4644 << "value.size() " << value.size() << " does not equal "
4645 << "column_or_row_index.size() " << column_or_row_index.size()
4646 << std::endl
4647 << std::endl
4648 << std::endl;
4649 throw OomphLibError(
4651 }
4652#endif
4653
4654
4655 // The idea behind this sparse assembly routine is to use a vector of
4656 // maps for the entries in each row or column of the complete matrix.
4657 // The key for each map is the global row or column number and
4658 // the default comparison operator for integers means that each map
4659 // is ordered by the global row or column number. Thus, we need not
4660 // sort the maps, that happens at each insertion of a new entry. The
4661 // price we pay is that for large maps, inseration is not a
4662 // cheap operation. Hash maps can be used to increase the speed, but then
4663 // the ordering is lost and we would have to sort anyway. The solution if
4664 // speed is required is to use lists, see below.
4665
4666
4667 // Set up a vector of vectors of maps of entries of each matrix,
4668 // indexed by either the column or row. The entries of the vector for
4669 // each matrix correspond to all the rows or columns of that matrix.
4670 // The use of the map storage
4671 // scheme, with its implicit ordering on the first index, gives
4672 // a sparse ordered list of the entries in the given row or column.
4674 // Loop over the number of matrices being assembled and resize
4675 // each vector of maps to the number of rows or columns of the matrix
4676 for (unsigned m = 0; m < n_matrix; m++)
4677 {
4678 matrix_data_map[m].resize(ndof);
4679 }
4680
4681 // Resize the residuals vectors
4682 for (unsigned v = 0; v < n_vector; v++)
4683 {
4684 residuals[v] = new double[ndof];
4685 for (unsigned i = 0; i < ndof; i++)
4686 {
4687 residuals[v][i] = 0;
4688 }
4689 }
4690
4691
4692#ifdef OOMPH_HAS_MPI
4693
4694
4695 // Storage for assembly time for elements
4696 double t_assemble_start = 0.0;
4697
4698 // Storage for assembly times
4700 {
4702 }
4703
4704#endif
4705
4706 //----------------Assemble and populate the maps-------------------------
4707 {
4708 // Allocate local storage for the element's contribution to the
4709 // residuals vectors and system matrices of the size of the maximum
4710 // number of dofs in any element.
4711 // This means that the storage is only allocated (and deleted) once
4714
4715 // Loop over the elements for this processor
4716 for (unsigned long e = el_lo; e <= el_hi; e++)
4717 {
4718#ifdef OOMPH_HAS_MPI
4719 // Time it?
4721 {
4723 }
4724#endif
4725
4726 // Get the pointer to the element
4728
4729#ifdef OOMPH_HAS_MPI
4730 // Ignore halo elements
4731 if (!elem_pt->is_halo())
4732 {
4733#endif
4734
4735 // Find number of degrees of freedom in the element
4736 const unsigned nvar = assembly_handler_pt->ndof(elem_pt);
4737
4738 // Resize the storage for elemental jacobian and residuals
4739 for (unsigned v = 0; v < n_vector; v++)
4740 {
4741 el_residuals[v].resize(nvar);
4742 }
4743 for (unsigned m = 0; m < n_matrix; m++)
4744 {
4745 el_jacobian[m].resize(nvar);
4746 }
4747
4748 // Now get the residuals and jacobian for the element
4751
4752 //---------------Insert the values into the maps--------------
4753
4754 // Loop over the first index of local variables
4755 for (unsigned i = 0; i < nvar; i++)
4756 {
4757 // Get the local equation number
4758 unsigned eqn_number = assembly_handler_pt->eqn_number(elem_pt, i);
4759
4760 // Add the contribution to the residuals
4761 for (unsigned v = 0; v < n_vector; v++)
4762 {
4763 // Fill in each residuals vector
4764 residuals[v][eqn_number] += el_residuals[v][i];
4765 }
4766
4767 // Now loop over the other index
4768 for (unsigned j = 0; j < nvar; j++)
4769 {
4770 // Get the number of the unknown
4772
4773 // Loop over the matrices
4774 for (unsigned m = 0; m < n_matrix; m++)
4775 {
4776 // Get the value of the matrix at this point
4777 double value = el_jacobian[m](i, j);
4778 // Only bother to add to the map if it's non-zero
4779 if (std::fabs(value) > Numerical_zero_for_sparse_assembly)
4780 {
4781 // If it's compressed row storage, then our vector of maps
4782 // is indexed by row (equation number)
4784 {
4785 // Add the data into the map using the unknown as the map
4786 // key
4787 matrix_data_map[m][eqn_number][unknown] += value;
4788 }
4789 // Otherwise it's compressed column storage and our vector is
4790 // indexed by column (the unknown)
4791 else
4792 {
4793 // Add the data into the map using the eqn_numbe as the map
4794 // key
4795 matrix_data_map[m][unknown][eqn_number] += value;
4796 }
4797 }
4798 } // End of loop over matrices
4799 }
4800 }
4801
4802#ifdef OOMPH_HAS_MPI
4803 } // endif halo element
4804#endif
4805
4806
4807#ifdef OOMPH_HAS_MPI
4808 // Time it?
4810 {
4813 }
4814#endif
4815
4816 } // End of loop over the elements
4817
4818 } // End of map assembly
4819
4820
4821#ifdef OOMPH_HAS_MPI
4822
4823 // Postprocess timing information and re-allocate distribution of
4824 // elements during subsequent assemblies.
4827 {
4829 }
4830
4831 // We have determined load balancing for current setup.
4832 // This can remain the same until assign_eqn_numbers() is called
4833 // again -- the flag is re-set to true there.
4835 {
4837 }
4838
4839#endif
4840
4841
4842 //-----------Finally we need to convert the beautiful map storage scheme
4843 //------------------------to the containers required by SuperLU
4844
4845 // Loop over the number of matrices
4846 for (unsigned m = 0; m < n_matrix; m++)
4847 {
4848 // Set the number of rows or columns
4849 row_or_column_start[m] = new int[ndof + 1];
4850 // Counter for the total number of entries in the storage scheme
4851 unsigned long entry_count = 0;
4853
4854 // first we compute the number of non-zeros
4855 nnz[m] = 0;
4856 for (unsigned long i_global = 0; i_global < ndof; i_global++)
4857 {
4858 nnz[m] += matrix_data_map[m][i_global].size();
4859 }
4860
4861 // and then resize the storage
4862 column_or_row_index[m] = new int[nnz[m]];
4863 value[m] = new double[nnz[m]];
4864
4865 // Now we merely loop over the number of rows or columns
4866 for (unsigned long i_global = 0; i_global < ndof; i_global++)
4867 {
4868 // Start index for the present row
4870 // If there are no entries in the map then skip the rest of the loop
4872 {
4873 continue;
4874 }
4875
4876 // Loop over all the entries in the map corresponding to the given
4877 // row or column. It will be ordered
4878
4879 for (std::map<unsigned, double>::iterator it =
4881 it != matrix_data_map[m][i_global].end();
4882 ++it)
4883 {
4884 // The first value is the column or row index
4886 // The second value is the actual data value
4887 value[m][entry_count] = it->second;
4888 // Increase the value of the counter
4889 entry_count++;
4890 }
4891 }
4892
4893 // Final entry in the row/column start vector
4895 } // End of the loop over the matrices
4896
4898 {
4899 oomph_info << "Pausing at end of sparse assembly." << std::endl;
4900 pause("Check memory usage now.");
4901 }
4902 }
4903
4904
4905 //=====================================================================
4906 /// This is a (private) helper function that is used to assemble system
4907 /// matrices in compressed row or column format
4908 /// and compute residual vectors using lists
4909 /// The default action is to assemble the jacobian matrix and
4910 /// residuals for the Newton method. The action can be
4911 /// overloaded at an elemental level by chaging the default
4912 /// behaviour of the function Element::get_all_vectors_and_matrices().
4913 /// column_or_row_index: Column [or row] index of given entry
4914 /// row_or_column_start: Index of first entry for given row [or column]
4915 /// value : Vector of nonzero entries
4916 /// residuals : Residual vector
4917 /// compressed_row_flag: Bool flag to indicate if storage format is
4918 /// compressed row [if false interpretation of
4919 /// arguments is as stated in square brackets].
4920 //=====================================================================
4924 Vector<double*>& value,
4925 Vector<unsigned>& nnz,
4928 {
4929 // Total number of elements
4930 const unsigned long n_elements = mesh_pt()->nelement();
4931
4932 // Default range of elements for distributed problems
4933 unsigned long el_lo = 0;
4934 unsigned long el_hi = n_elements - 1;
4935
4936#ifdef OOMPH_HAS_MPI
4937 // Otherwise just loop over a fraction of the elements
4938 // (This will either have been initialised in
4939 // Problem::set_default_first_and_last_element_for_assembly() or
4940 // will have been re-assigned during a previous assembly loop
4941 // Note that following the re-assignment only the entries
4942 // for the current processor are relevant.
4944 {
4947 }
4948#endif
4949
4950 // number of dofs
4951 unsigned ndof = this->ndof();
4952
4953 // Find the number of vectors to be assembled
4954 const unsigned n_vector = residuals.size();
4955
4956 // Find the number of matrices to be assembled
4957 const unsigned n_matrix = column_or_row_index.size();
4958
4959 // Locally cache pointer to assembly handler
4961
4962#ifdef OOMPH_HAS_MPI
4963 bool doing_residuals = false;
4964 if (dynamic_cast<ParallelResidualsHandler*>(Assembly_handler_pt) != 0)
4965 {
4966 doing_residuals = true;
4967 }
4968#endif
4969
4970// Error check dimensions
4971#ifdef PARANOID
4973 {
4974 std::ostringstream error_stream;
4975 error_stream << "Error: " << std::endl
4976 << "row_or_column_start.size() "
4977 << row_or_column_start.size() << " does not equal "
4978 << "column_or_row_index.size() "
4979 << column_or_row_index.size() << std::endl;
4980 throw OomphLibError(
4982 }
4983
4984 if (value.size() != n_matrix)
4985 {
4986 std::ostringstream error_stream;
4988 << "Error in Problem::sparse_assemble_row_or_column_compressed "
4989 << std::endl
4990 << "value.size() " << value.size() << " does not equal "
4991 << "column_or_row_index.size() " << column_or_row_index.size()
4992 << std::endl
4993 << std::endl
4994 << std::endl;
4995 throw OomphLibError(
4997 }
4998#endif
4999
5000 // The idea behind this sparse assembly routine is to use a vector of
5001 // lists for the entries in each row or column of the complete matrix.
5002 // The lists contain pairs of entries (global row/column number, value).
5003 // All non-zero contributions from each element are added to the lists.
5004 // We then sort each list by global row/column number and then combine
5005 // the entries corresponding to each row/column before adding to the
5006 // vectors column_or_row_index and value.
5007
5008 // Note the trade off for "fast assembly" is that we will require
5009 // more memory during the assembly phase. Then again, if we can
5010 // only just assemble the sparse matrix, we're in real trouble.
5011
5012 // Set up a vector of lists of paired entries of
5013 //(row/column index, jacobian matrix entry).
5014 // The entries of the vector correspond to all the rows or columns.
5015 // The use of the list storage scheme, should give fast insertion
5016 // and fast sorts later.
5018 n_matrix);
5019 // Loop over the number of matrices and resize
5020 for (unsigned m = 0; m < n_matrix; m++)
5021 {
5022 matrix_data_list[m].resize(ndof);
5023 }
5024
5025 // Resize the residuals vectors
5026 for (unsigned v = 0; v < n_vector; v++)
5027 {
5028 residuals[v] = new double[ndof];
5029 for (unsigned i = 0; i < ndof; i++)
5030 {
5031 residuals[v][i] = 0;
5032 }
5033 }
5034
5035#ifdef OOMPH_HAS_MPI
5036
5037
5038 // Storage for assembly time for elements
5039 double t_assemble_start = 0.0;
5040
5041 // Storage for assembly times
5043 {
5045 }
5046
5047#endif
5048
5049 //------------Assemble and populate the lists-----------------------
5050 {
5051 // Allocate local storage for the element's contribution to the
5052 // residuals vectors and system matrices of the size of the maximum
5053 // number of dofs in any element.
5054 // This means that the stored is only allocated (and deleted) once
5057
5058
5059 // Pointer to a single list to be used during the assembly
5060 std::list<std::pair<unsigned, double>>* list_pt;
5061
5062 // Loop over the all elements
5063 for (unsigned long e = el_lo; e <= el_hi; e++)
5064 {
5065#ifdef OOMPH_HAS_MPI
5066 // Time it?
5068 {
5070 }
5071#endif
5072
5073 // Get the pointer to the element
5075
5076#ifdef OOMPH_HAS_MPI
5077 // Ignore halo elements
5078 if (!elem_pt->is_halo())
5079 {
5080#endif
5081
5082 // Find number of degrees of freedom in the element
5083 const unsigned nvar = assembly_handler_pt->ndof(elem_pt);
5084
5085 // Resize the storage for the elemental jacobian and residuals
5086 for (unsigned v = 0; v < n_vector; v++)
5087 {
5088 el_residuals[v].resize(nvar);
5089 }
5090 for (unsigned m = 0; m < n_matrix; m++)
5091 {
5092 el_jacobian[m].resize(nvar);
5093 }
5094
5095 // Now get the residuals and jacobian for the element
5098
5099 //---------------- Insert the values into the lists -----------
5100
5101 // Loop over the first index of local variables
5102 for (unsigned i = 0; i < nvar; i++)
5103 {
5104 // Get the local equation number
5105 unsigned eqn_number = assembly_handler_pt->eqn_number(elem_pt, i);
5106
5107 // Add the contribution to the residuals
5108 for (unsigned v = 0; v < n_vector; v++)
5109 {
5110 // Fill in the residuals vector
5111 residuals[v][eqn_number] += el_residuals[v][i];
5112 }
5113
5114 // Now loop over the other index
5115 for (unsigned j = 0; j < nvar; j++)
5116 {
5117 // Get the number of the unknown
5119
5120 // Loop over the matrices
5121 for (unsigned m = 0; m < n_matrix; m++)
5122 {
5123 // Get the value of the matrix at this point
5124 double value = el_jacobian[m](i, j);
5125 // Only add to theif it's non-zero
5126 if (std::fabs(value) > Numerical_zero_for_sparse_assembly)
5127 {
5128 // If it's compressed row storage, then our vector is indexed
5129 // by row (the equation number)
5131 {
5132 // Find the list that corresponds to the desired row
5133 list_pt = &matrix_data_list[m][eqn_number];
5134 // Insert the data into the list, the first entry
5135 // in the pair is the unknown (column index),
5136 // the second is the value itself.
5137 list_pt->insert(list_pt->end(),
5138 std::make_pair(unknown, value));
5139 }
5140 // Otherwise it's compressed column storage, and our
5141 // vector is indexed by column (the unknown)
5142 else
5143 {
5144 // Find the list that corresponds to the desired column
5146 // Insert the data into the list, the first entry
5147 // in the pair is the equation number (row index),
5148 // the second is the value itself.
5149 list_pt->insert(list_pt->end(),
5150 std::make_pair(eqn_number, value));
5151 }
5152 }
5153 }
5154 }
5155 }
5156
5157#ifdef OOMPH_HAS_MPI
5158 } // endif halo element
5159#endif
5160
5161
5162#ifdef OOMPH_HAS_MPI
5163 // Time it?
5165 {
5168 }
5169#endif
5170
5171 } // End of loop over the elements
5172
5173 } // list_pt goes out of scope
5174
5175
5176#ifdef OOMPH_HAS_MPI
5177
5178 // Postprocess timing information and re-allocate distribution of
5179 // elements during subsequent assemblies.
5182 {
5184 }
5185
5186 // We have determined load balancing for current setup.
5187 // This can remain the same until assign_eqn_numbers() is called
5188 // again -- the flag is re-set to true there.
5190 {
5192 }
5193
5194#endif
5195
5196
5197 //----Finally we need to convert the beautiful list storage scheme---
5198 //----------to the containers required by SuperLU--------------------
5199
5200 // Loop over the number of matrices
5201 for (unsigned m = 0; m < n_matrix; m++)
5202 {
5203 // Set the number of rows or columns
5204 row_or_column_start[m] = new int[ndof + 1];
5205 // Counter for the total number of entries in the storage scheme
5206 unsigned long entry_count = 0;
5207 // The first entry is 0
5209
5210 // first we compute the number of non-zeros
5211 nnz[m] = 0;
5212 for (unsigned long i_global = 0; i_global < ndof; i_global++)
5213 {
5214 nnz[m] += matrix_data_list[m][i_global].size();
5215 }
5216
5217 // and then resize the storage
5218 column_or_row_index[m] = new int[nnz[m]];
5219 value[m] = new double[nnz[m]];
5220
5221 // Now we merely loop over the number of rows or columns
5222 for (unsigned long i_global = 0; i_global < ndof; i_global++)
5223 {
5224 // Start index for the present row is the number of entries so far
5226 // If there are no entries in the list then skip the loop
5228 {
5229 continue;
5230 }
5231
5232 // Sort the list corresponding to this row or column by the
5233 // column or row index (first entry in the pair).
5234 // This might be inefficient, but we only have to do the sort ONCE
5235 // for each list. This is faster than using a map storage scheme, where
5236 // we are sorting for every insertion (although the map structure
5237 // is cleaner and more memory efficient)
5238 matrix_data_list[m][i_global].sort();
5239
5240 // Set up an iterator for start of the list
5241 std::list<std::pair<unsigned, double>>::iterator it =
5242 matrix_data_list[m][i_global].begin();
5243
5244 // Get the first row or column index in the list...
5245 unsigned current_index = it->first;
5246 //...and the corresponding value
5247 double current_value = it->second;
5248
5249 // Loop over all the entries in the sorted list
5250 // Increase the iterator so that we start at the second entry
5251 for (++it; it != matrix_data_list[m][i_global].end(); ++it)
5252 {
5253 // If the index has not changed, then we must add the contribution
5254 // of the present entry to the value.
5255 // Additionally check that the entry is non-zero
5256 if ((it->first == current_index) &&
5257 (std::fabs(it->second) > Numerical_zero_for_sparse_assembly))
5258 {
5259 current_value += it->second;
5260 }
5261 // Otherwise, we have added all the contributions to the index
5262 // to current_value, so add it to the SuperLU data structure
5263 else
5264 {
5265 // Add the row or column index to the vector
5267 // Add the actual value to the vector
5268 value[m][entry_count] = current_value;
5269 // Increase the counter for the number of entries in each vector
5270 entry_count++;
5271
5272 // Set the index and value to be those of the current entry in the
5273 // list
5274 current_index = it->first;
5275 current_value = it->second;
5276 }
5277 } // End of loop over all list entries for this global row or column
5278
5279 // There are TWO special cases to consider.
5280 // If there is only one equation number in the list, then it
5281 // will NOT have been added. We test this case by comparing the
5282 // number of entries with those stored in row_or_column_start[i_global]
5283 // Otherwise
5284 // If the final entry in the list has the same index as the penultimate
5285 // entry, then it will NOT have been added to the SuperLU storage scheme
5286 // Check this by comparing the current_index with the final index
5287 // stored in the SuperLU scheme. If they are not the same, then
5288 // add the current_index and value.
5289
5290 // If single equation number in list
5291 if ((static_cast<int>(entry_count) == row_or_column_start[m][i_global])
5292 // If we have a single equation number, this will not be evaluated.
5293 // If we don't then we do the test to check that the final
5294 // entry is added
5295 || (static_cast<int>(current_index) !=
5297 {
5298 // Add the row or column index to the vector
5300 // Add the actual value to the vector
5301 value[m][entry_count] = current_value;
5302 // Increase the counter for the number of entries in each vector
5303 entry_count++;
5304 }
5305
5306 } // End of loop over the rows or columns of the entire matrix
5307
5308 // Final entry in the row/column start vector
5310 } // End of loop over matrices
5311
5313 {
5314 oomph_info << "Pausing at end of sparse assembly." << std::endl;
5315 pause("Check memory usage now.");
5316 }
5317 }
5318
5319
5320 //=====================================================================
5321 /// This is a (private) helper function that is used to assemble system
5322 /// matrices in compressed row or column format
5323 /// and compute residual vectors using vectors of pairs
5324 /// The default action is to assemble the jacobian matrix and
5325 /// residuals for the Newton method. The action can be
5326 /// overloaded at an elemental level by chaging the default
5327 /// behaviour of the function Element::get_all_vectors_and_matrices().
5328 /// column_or_row_index: Column [or row] index of given entry
5329 /// row_or_column_start: Index of first entry for given row [or column]
5330 /// value : Vector of nonzero entries
5331 /// residuals : Residual vector
5332 /// compressed_row_flag: Bool flag to indicate if storage format is
5333 /// compressed row [if false interpretation of
5334 /// arguments is as stated in square brackets].
5335 //=====================================================================
5339 Vector<double*>& value,
5340 Vector<unsigned>& nnz,
5343 {
5344 // Total number of elements
5345 const unsigned long n_elements = mesh_pt()->nelement();
5346
5347 // Default range of elements for distributed problems
5348 unsigned long el_lo = 0;
5349 unsigned long el_hi = n_elements - 1;
5350
5351#ifdef OOMPH_HAS_MPI
5352 // Otherwise just loop over a fraction of the elements
5353 // (This will either have been initialised in
5354 // Problem::set_default_first_and_last_element_for_assembly() or
5355 // will have been re-assigned during a previous assembly loop
5356 // Note that following the re-assignment only the entries
5357 // for the current processor are relevant.
5359 {
5362 }
5363#endif
5364
5365 // number of local eqns
5366 unsigned ndof = this->ndof();
5367
5368 // Find the number of vectors to be assembled
5369 const unsigned n_vector = residuals.size();
5370
5371 // Find the number of matrices to be assembled
5372 const unsigned n_matrix = column_or_row_index.size();
5373
5374 // Locally cache pointer to assembly handler
5376
5377#ifdef OOMPH_HAS_MPI
5378 bool doing_residuals = false;
5379 if (dynamic_cast<ParallelResidualsHandler*>(Assembly_handler_pt) != 0)
5380 {
5381 doing_residuals = true;
5382 }
5383#endif
5384
5385// Error check dimensions
5386#ifdef PARANOID
5388 {
5389 std::ostringstream error_stream;
5390 error_stream << "Error: " << std::endl
5391 << "row_or_column_start.size() "
5392 << row_or_column_start.size() << " does not equal "
5393 << "column_or_row_index.size() "
5394 << column_or_row_index.size() << std::endl;
5395 throw OomphLibError(
5397 }
5398
5399 if (value.size() != n_matrix)
5400 {
5401 std::ostringstream error_stream;
5402 error_stream << "Error: " << std::endl
5403 << "value.size() " << value.size() << " does not equal "
5404 << "column_or_row_index.size() "
5405 << column_or_row_index.size() << std::endl
5406 << std::endl
5407 << std::endl;
5408 throw OomphLibError(
5410 }
5411#endif
5412
5413
5414 // The idea behind this sparse assembly routine is to use a Vector of
5415 // Vectors of pairs for each complete matrix.
5416 // Each inner Vector stores pairs and holds the row (or column) index
5417 // and the value of the matrix entry.
5418
5419 // Set up Vector of Vectors to store the entries of each matrix,
5420 // indexed by either the column or row.
5422
5423 // Loop over the number of matrices being assembled and resize
5424 // each Vector of Vectors to the number of rows or columns of the matrix
5425 for (unsigned m = 0; m < n_matrix; m++)
5426 {
5427 matrix_data[m].resize(ndof);
5428 }
5429
5430 // Resize the residuals vectors
5431 for (unsigned v = 0; v < n_vector; v++)
5432 {
5433 residuals[v] = new double[ndof];
5434 for (unsigned i = 0; i < ndof; i++)
5435 {
5436 residuals[v][i] = 0;
5437 }
5438 }
5439
5440#ifdef OOMPH_HAS_MPI
5441
5442 // Storage for assembly time for elements
5443 double t_assemble_start = 0.0;
5444
5445 // Storage for assembly times
5447 {
5449 }
5450
5451#endif
5452
5453 //----------------Assemble and populate the vector storage scheme--------
5454 {
5455 // Allocate local storage for the element's contribution to the
5456 // residuals vectors and system matrices of the size of the maximum
5457 // number of dofs in any element
5458 // This means that the storage is only allocated (and deleted) once
5461
5462 // Loop over the elements
5463 for (unsigned long e = el_lo; e <= el_hi; e++)
5464 {
5465#ifdef OOMPH_HAS_MPI
5466 // Time it?
5468 {
5470 }
5471#endif
5472
5473 // Get the pointer to the element
5475
5476#ifdef OOMPH_HAS_MPI
5477 // Ignore halo elements
5478 if (!elem_pt->is_halo())
5479 {
5480#endif
5481
5482 // Find number of degrees of freedom in the element
5483 const unsigned nvar = assembly_handler_pt->ndof(elem_pt);
5484
5485 // Resize the storage for elemental jacobian and residuals
5486 for (unsigned v = 0; v < n_vector; v++)
5487 {
5488 el_residuals[v].resize(nvar);
5489 }
5490 for (unsigned m = 0; m < n_matrix; m++)
5491 {
5492 el_jacobian[m].resize(nvar);
5493 }
5494
5495 // Now get the residuals and jacobian for the element
5498
5499 //---------------Insert the values into the vectors--------------
5500
5501 // Loop over the first index of local variables
5502 for (unsigned i = 0; i < nvar; i++)
5503 {
5504 // Get the local equation number
5505 unsigned eqn_number = assembly_handler_pt->eqn_number(elem_pt, i);
5506
5507 // Add the contribution to the residuals
5508 for (unsigned v = 0; v < n_vector; v++)
5509 {
5510 // Fill in each residuals vector
5511 residuals[v][eqn_number] += el_residuals[v][i];
5512 }
5513
5514 // Now loop over the other index
5515 for (unsigned j = 0; j < nvar; j++)
5516 {
5517 // Get the number of the unknown
5519
5520 // Loop over the matrices
5521 // If it's compressed row storage, then our vector of maps
5522 // is indexed by row (equation number)
5523 for (unsigned m = 0; m < n_matrix; m++)
5524 {
5525 // Get the value of the matrix at this point
5526 double value = el_jacobian[m](i, j);
5527 // Only bother to add to the vector if it's non-zero
5528 if (std::fabs(value) > Numerical_zero_for_sparse_assembly)
5529 {
5530 // If it's compressed row storage, then our vector of maps
5531 // is indexed by row (equation number)
5533 {
5534 // Find the correct position and add the data into the
5535 // vectors
5536 const unsigned size = matrix_data[m][eqn_number].size();
5537 for (unsigned k = 0; k <= size; k++)
5538 {
5539 if (k == size)
5540 {
5541 matrix_data[m][eqn_number].push_back(
5542 std::make_pair(unknown, value));
5543 break;
5544 }
5545 else if (matrix_data[m][eqn_number][k].first == unknown)
5546 {
5547 matrix_data[m][eqn_number][k].second += value;
5548 break;
5549 }
5550 }
5551 }
5552 // Otherwise it's compressed column storage and our vector is
5553 // indexed by column (the unknown)
5554 else
5555 {
5556 // Add the data into the vectors in the correct position
5557 const unsigned size = matrix_data[m][unknown].size();
5558 for (unsigned k = 0; k <= size; k++)
5559 {
5560 if (k == size)
5561 {
5562 matrix_data[m][unknown].push_back(
5563 std::make_pair(eqn_number, value));
5564 break;
5565 }
5566 else if (matrix_data[m][unknown][k].first == eqn_number)
5567 {
5568 matrix_data[m][unknown][k].second += value;
5569 break;
5570 }
5571 }
5572 }
5573 }
5574 } // End of loop over matrices
5575 }
5576 }
5577
5578#ifdef OOMPH_HAS_MPI
5579 } // endif halo element
5580#endif
5581
5582
5583#ifdef OOMPH_HAS_MPI
5584 // Time it?
5586 {
5589 }
5590#endif
5591
5592 } // End of loop over the elements
5593
5594
5595 } // End of vector assembly
5596
5597
5598#ifdef OOMPH_HAS_MPI
5599
5600 // Postprocess timing information and re-allocate distribution of
5601 // elements during subsequent assemblies.
5604 {
5606 }
5607
5608 // We have determined load balancing for current setup.
5609 // This can remain the same until assign_eqn_numbers() is called
5610 // again -- the flag is re-set to true there.
5612 {
5614 }
5615
5616#endif
5617
5618
5619 //-----------Finally we need to convert this vector storage scheme
5620 //------------------------to the containers required by SuperLU
5621
5622 // Loop over the number of matrices
5623 for (unsigned m = 0; m < n_matrix; m++)
5624 {
5625 // Set the number of rows or columns
5626 row_or_column_start[m] = new int[ndof + 1];
5627
5628 // fill row_or_column_start and find the number of entries
5629 row_or_column_start[m][0] = 0;
5630 for (unsigned long i = 0; i < ndof; i++)
5631 {
5632 row_or_column_start[m][i + 1] =
5634 }
5635 const unsigned entries = row_or_column_start[m][ndof];
5636
5637 // resize vectors
5638 column_or_row_index[m] = new int[entries];
5639 value[m] = new double[entries];
5640 nnz[m] = entries;
5641
5642 // Now we merely loop over the number of rows or columns
5643 for (unsigned long i_global = 0; i_global < ndof; i_global++)
5644 {
5645 // If there are no entries in the vector then skip the rest of the loop
5646 if (matrix_data[m][i_global].empty())
5647 {
5648 continue;
5649 }
5650
5651 // Loop over all the entries in the vectors corresponding to the given
5652 // row or column. It will NOT be ordered
5653 unsigned p = 0;
5654 for (int j = row_or_column_start[m][i_global];
5656 j++)
5657 {
5659 value[m][j] = matrix_data[m][i_global][p].second;
5660 ++p;
5661 }
5662 }
5663 } // End of the loop over the matrices
5664
5666 {
5667 oomph_info << "Pausing at end of sparse assembly." << std::endl;
5668 pause("Check memory usage now.");
5669 }
5670 }
5671
5672
5673 //=====================================================================
5674 /// This is a (private) helper function that is used to assemble system
5675 /// matrices in compressed row or column format
5676 /// and compute residual vectors using two vectors.
5677 /// The default action is to assemble the jacobian matrix and
5678 /// residuals for the Newton method. The action can be
5679 /// overloaded at an elemental level by chaging the default
5680 /// behaviour of the function Element::get_all_vectors_and_matrices().
5681 /// column_or_row_index: Column [or row] index of given entry
5682 /// row_or_column_start: Index of first entry for given row [or column]
5683 /// value : Vector of nonzero entries
5684 /// residuals : Residual vector
5685 /// compressed_row_flag: Bool flag to indicate if storage format is
5686 /// compressed row [if false interpretation of
5687 /// arguments is as stated in square brackets].
5688 //=====================================================================
5692 Vector<double*>& value,
5693 Vector<unsigned>& nnz,
5696 {
5697 // Total number of elements
5698 const unsigned long n_elements = mesh_pt()->nelement();
5699
5700 // Default range of elements for distributed problems
5701 unsigned long el_lo = 0;
5702 unsigned long el_hi = n_elements - 1;
5703
5704
5705#ifdef OOMPH_HAS_MPI
5706 // Otherwise just loop over a fraction of the elements
5707 // (This will either have been initialised in
5708 // Problem::set_default_first_and_last_element_for_assembly() or
5709 // will have been re-assigned during a previous assembly loop
5710 // Note that following the re-assignment only the entries
5711 // for the current processor are relevant.
5713 {
5716 }
5717#endif
5718
5719 // number of local eqns
5720 unsigned ndof = this->ndof();
5721
5722 // Find the number of vectors to be assembled
5723 const unsigned n_vector = residuals.size();
5724
5725 // Find the number of matrices to be assembled
5726 const unsigned n_matrix = column_or_row_index.size();
5727
5728 // Locally cache pointer to assembly handler
5730
5731#ifdef OOMPH_HAS_MPI
5732 bool doing_residuals = false;
5733 if (dynamic_cast<ParallelResidualsHandler*>(Assembly_handler_pt) != 0)
5734 {
5735 doing_residuals = true;
5736 }
5737#endif
5738
5739// Error check dimensions
5740#ifdef PARANOID
5742 {
5743 std::ostringstream error_stream;
5744 error_stream << "Error: " << std::endl
5745 << "row_or_column_start.size() "
5746 << row_or_column_start.size() << " does not equal "
5747 << "column_or_row_index.size() "
5748 << column_or_row_index.size() << std::endl;
5749 throw OomphLibError(
5751 }
5752
5753 if (value.size() != n_matrix)
5754 {
5755 std::ostringstream error_stream;
5756 error_stream << "Error: " << std::endl
5757 << "value.size() " << value.size() << " does not equal "
5758 << "column_or_row_index.size() "
5759 << column_or_row_index.size() << std::endl
5760 << std::endl
5761 << std::endl;
5762 throw OomphLibError(
5764 }
5765#endif
5766
5767 // The idea behind this sparse assembly routine is to use Vectors of
5768 // Vectors for the entries in each complete matrix. And a second
5769 // Vector of Vectors stores the global row (or column) indeces. This
5770 // will not have the memory overheads associated with the methods using
5771 // lists or maps, but insertion will be more costly.
5772
5773 // Set up two vector of vectors to store the entries of each matrix,
5774 // indexed by either the column or row. The entries of the vector for
5775 // each matrix correspond to all the rows or columns of that matrix.
5778
5779 // Loop over the number of matrices being assembled and resize
5780 // each vector of vectors to the number of rows or columns of the matrix
5781 for (unsigned m = 0; m < n_matrix; m++)
5782 {
5784 matrix_values[m].resize(ndof);
5785 }
5786
5787 // Resize the residuals vectors
5788 for (unsigned v = 0; v < n_vector; v++)
5789 {
5790 residuals[v] = new double[ndof];
5791 for (unsigned i = 0; i < ndof; i++)
5792 {
5793 residuals[v][i] = 0;
5794 }
5795 }
5796
5797#ifdef OOMPH_HAS_MPI
5798
5799 // Storage for assembly time for elements
5800 double t_assemble_start = 0.0;
5801
5802 // Storage for assembly times
5804 {
5806 }
5807
5808#endif
5809
5810
5811 //----------------Assemble and populate the vector storage scheme-------
5812 {
5813 // Allocate local storage for the element's contribution to the
5814 // residuals vectors and system matrices of the size of the maximum
5815 // number of dofs in any element
5816 // This means that the storage will only be allocated (and deleted) once
5819
5820 // Loop over the elements
5821 for (unsigned long e = el_lo; e <= el_hi; e++)
5822 {
5823#ifdef OOMPH_HAS_MPI
5824 // Time it?
5826 {
5828 }
5829#endif
5830
5831 // Get the pointer to the element
5833
5834#ifdef OOMPH_HAS_MPI
5835 // Ignore halo elements
5836 if (!elem_pt->is_halo())
5837 {
5838#endif
5839
5840 // Find number of degrees of freedom in the element
5841 const unsigned nvar = assembly_handler_pt->ndof(elem_pt);
5842
5843 // Resize the storage for elemental jacobian and residuals
5844 for (unsigned v = 0; v < n_vector; v++)
5845 {
5846 el_residuals[v].resize(nvar);
5847 }
5848 for (unsigned m = 0; m < n_matrix; m++)
5849 {
5850 el_jacobian[m].resize(nvar);
5851 }
5852
5853 // Now get the residuals and jacobian for the element
5856
5857 //---------------Insert the values into the vectors--------------
5858
5859 // Loop over the first index of local variables
5860 for (unsigned i = 0; i < nvar; i++)
5861 {
5862 // Get the local equation number
5863 unsigned eqn_number = assembly_handler_pt->eqn_number(elem_pt, i);
5864
5865 // Add the contribution to the residuals
5866 for (unsigned v = 0; v < n_vector; v++)
5867 {
5868 // Fill in each residuals vector
5869 residuals[v][eqn_number] += el_residuals[v][i];
5870 }
5871
5872 // Now loop over the other index
5873 for (unsigned j = 0; j < nvar; j++)
5874 {
5875 // Get the number of the unknown
5877
5878 // Loop over the matrices
5879 // If it's compressed row storage, then our vector of maps
5880 // is indexed by row (equation number)
5881 for (unsigned m = 0; m < n_matrix; m++)
5882 {
5883 // Get the value of the matrix at this point
5884 double value = el_jacobian[m](i, j);
5885 // Only bother to add to the vector if it's non-zero
5886 if (std::fabs(value) > Numerical_zero_for_sparse_assembly)
5887 {
5888 // If it's compressed row storage, then our vector of maps
5889 // is indexed by row (equation number)
5891 {
5892 // Find the correct position and add the data into the
5893 // vectors
5894 const unsigned size =
5895 matrix_row_or_col_indices[m][eqn_number].size();
5896
5897 for (unsigned k = 0; k <= size; k++)
5898 {
5899 if (k == size)
5900 {
5901 matrix_row_or_col_indices[m][eqn_number].push_back(
5902 unknown);
5903 matrix_values[m][eqn_number].push_back(value);
5904 break;
5905 }
5906 else if (matrix_row_or_col_indices[m][eqn_number][k] ==
5907 unknown)
5908 {
5909 matrix_values[m][eqn_number][k] += value;
5910 break;
5911 }
5912 }
5913 }
5914 // Otherwise it's compressed column storage and our vector is
5915 // indexed by column (the unknown)
5916 else
5917 {
5918 // Add the data into the vectors in the correct position
5919 const unsigned size =
5921 for (unsigned k = 0; k <= size; k++)
5922 {
5923 if (k == size)
5924 {
5926 eqn_number);
5927 matrix_values[m][unknown].push_back(value);
5928 break;
5929 }
5930 else if (matrix_row_or_col_indices[m][unknown][k] ==
5931 eqn_number)
5932 {
5933 matrix_values[m][unknown][k] += value;
5934 break;
5935 }
5936 }
5937 }
5938 }
5939 } // End of loop over matrices
5940 }
5941 }
5942
5943#ifdef OOMPH_HAS_MPI
5944 } // endif halo element
5945#endif
5946
5947
5948#ifdef OOMPH_HAS_MPI
5949 // Time it?
5951 {
5954 }
5955#endif
5956
5957 } // End of loop over the elements
5958
5959 } // End of vector assembly
5960
5961
5962#ifdef OOMPH_HAS_MPI
5963
5964 // Postprocess timing information and re-allocate distribution of
5965 // elements during subsequent assemblies.
5968 {
5970 }
5971
5972 // We have determined load balancing for current setup.
5973 // This can remain the same until assign_eqn_numbers() is called
5974 // again -- the flag is re-set to true there.
5976 {
5978 }
5979
5980#endif
5981
5982 //-----------Finally we need to convert this lousy vector storage scheme
5983 //------------------------to the containers required by SuperLU
5984
5985 // Loop over the number of matrices
5986 for (unsigned m = 0; m < n_matrix; m++)
5987 {
5988 // Set the number of rows or columns
5989 row_or_column_start[m] = new int[ndof + 1];
5990
5991 // fill row_or_column_start and find the number of entries
5992 row_or_column_start[m][0] = 0;
5993 for (unsigned long i = 0; i < ndof; i++)
5994 {
5995 row_or_column_start[m][i + 1] =
5997 }
5998 const unsigned entries = row_or_column_start[m][ndof];
5999
6000 // resize vectors
6001 column_or_row_index[m] = new int[entries];
6002 value[m] = new double[entries];
6003 nnz[m] = entries;
6004
6005 // Now we merely loop over the number of rows or columns
6006 for (unsigned long i_global = 0; i_global < ndof; i_global++)
6007 {
6008 // If there are no entries in the vector then skip the rest of the loop
6009 if (matrix_values[m][i_global].empty())
6010 {
6011 continue;
6012 }
6013
6014 // Loop over all the entries in the vectors corresponding to the given
6015 // row or column. It will NOT be ordered
6016 unsigned p = 0;
6017 for (int j = row_or_column_start[m][i_global];
6019 j++)
6020 {
6022 value[m][j] = matrix_values[m][i_global][p];
6023 ++p;
6024 }
6025 }
6026 } // End of the loop over the matrices
6027
6029 {
6030 oomph_info << "Pausing at end of sparse assembly." << std::endl;
6031 pause("Check memory usage now.");
6032 }
6033 }
6034
6035
6036 //=====================================================================
6037 /// This is a (private) helper function that is used to assemble system
6038 /// matrices in compressed row or column format
6039 /// and compute residual vectors using two vectors.
6040 /// The default action is to assemble the jacobian matrix and
6041 /// residuals for the Newton method. The action can be
6042 /// overloaded at an elemental level by chaging the default
6043 /// behaviour of the function Element::get_all_vectors_and_matrices().
6044 /// column_or_row_index: Column [or row] index of given entry
6045 /// row_or_column_start: Index of first entry for given row [or column]
6046 /// value : Vector of nonzero entries
6047 /// residuals : Residual vector
6048 /// compressed_row_flag: Bool flag to indicate if storage format is
6049 /// compressed row [if false interpretation of
6050 /// arguments is as stated in square brackets].
6051 //=====================================================================
6055 Vector<double*>& value,
6056 Vector<unsigned>& nnz,
6059 {
6060 // Total number of elements
6061 const unsigned long n_elements = mesh_pt()->nelement();
6062
6063 // Default range of elements for distributed problems
6064 unsigned long el_lo = 0;
6065 unsigned long el_hi = n_elements - 1;
6066
6067
6068#ifdef OOMPH_HAS_MPI
6069 // Otherwise just loop over a fraction of the elements
6070 // (This will either have been initialised in
6071 // Problem::set_default_first_and_last_element_for_assembly() or
6072 // will have been re-assigned during a previous assembly loop
6073 // Note that following the re-assignment only the entries
6074 // for the current processor are relevant.
6076 {
6079 }
6080#endif
6081
6082 // number of local eqns
6083 unsigned ndof = this->ndof();
6084
6085 // Find the number of vectors to be assembled
6086 const unsigned n_vector = residuals.size();
6087
6088 // Find the number of matrices to be assembled
6089 const unsigned n_matrix = column_or_row_index.size();
6090
6091 // Locally cache pointer to assembly handler
6093
6094#ifdef OOMPH_HAS_MPI
6095 bool doing_residuals = false;
6096 if (dynamic_cast<ParallelResidualsHandler*>(Assembly_handler_pt) != 0)
6097 {
6098 doing_residuals = true;
6099 }
6100#endif
6101
6102// Error check dimensions
6103#ifdef PARANOID
6105 {
6106 std::ostringstream error_stream;
6107 error_stream << "Error: " << std::endl
6108 << "row_or_column_start.size() "
6109 << row_or_column_start.size() << " does not equal "
6110 << "column_or_row_index.size() "
6111 << column_or_row_index.size() << std::endl;
6112 throw OomphLibError(
6114 }
6115
6116 if (value.size() != n_matrix)
6117 {
6118 std::ostringstream error_stream;
6119 error_stream << "Error: " << std::endl
6120 << "value.size() " << value.size() << " does not equal "
6121 << "column_or_row_index.size() "
6122 << column_or_row_index.size() << std::endl
6123 << std::endl
6124 << std::endl;
6125 throw OomphLibError(
6127 }
6128#endif
6129
6130 // The idea behind this sparse assembly routine is to use Vectors of
6131 // Vectors for the entries in each complete matrix. And a second
6132 // Vector of Vectors stores the global row (or column) indeces. This
6133 // will not have the memory overheads associated with the methods using
6134 // lists or maps, but insertion will be more costly.
6135
6136 // Set up two vector of vectors to store the entries of each matrix,
6137 // indexed by either the column or row. The entries of the vector for
6138 // each matrix correspond to all the rows or columns of that matrix.
6141
6142 // Loop over the number of matrices being assembled and resize
6143 // each vector of vectors to the number of rows or columns of the matrix
6144 for (unsigned m = 0; m < n_matrix; m++)
6145 {
6146 matrix_row_or_col_indices[m] = new unsigned*[ndof];
6147 matrix_values[m] = new double*[ndof];
6148 }
6149
6150 // Resize the residuals vectors
6151 for (unsigned v = 0; v < n_vector; v++)
6152 {
6153 residuals[v] = new double[ndof];
6154 for (unsigned i = 0; i < ndof; i++)
6155 {
6156 residuals[v][i] = 0;
6157 }
6158 }
6159
6160#ifdef OOMPH_HAS_MPI
6161
6162 // Storage for assembly time for elements
6163 double t_assemble_start = 0.0;
6164
6165 // Storage for assembly times
6167 {
6169 }
6170
6171#endif
6172
6173 // number of coefficients in each row
6175 for (unsigned m = 0; m < n_matrix; m++)
6176 {
6177 ncoef[m].resize(ndof, 0);
6178 }
6179
6181 {
6183 for (unsigned m = 0; m < n_matrix; m++)
6184 {
6186 }
6187 }
6188
6189 //----------------Assemble and populate the vector storage scheme-------
6190 {
6191 // Allocate local storage for the element's contribution to the
6192 // residuals vectors and system matrices of the size of the maximum
6193 // number of dofs in any element
6194 // This means that the storage will only be allocated (and deleted) once
6197
6198 // Loop over the elements
6199 for (unsigned long e = el_lo; e <= el_hi; e++)
6200 {
6201#ifdef OOMPH_HAS_MPI
6202 // Time it?
6204 {
6206 }
6207#endif
6208
6209 // Get the pointer to the element
6211
6212#ifdef OOMPH_HAS_MPI
6213 // Ignore halo elements
6214 if (!elem_pt->is_halo())
6215 {
6216#endif
6217
6218 // Find number of degrees of freedom in the element
6219 const unsigned nvar = assembly_handler_pt->ndof(elem_pt);
6220
6221 // Resize the storage for elemental jacobian and residuals
6222 for (unsigned v = 0; v < n_vector; v++)
6223 {
6224 el_residuals[v].resize(nvar);
6225 }
6226 for (unsigned m = 0; m < n_matrix; m++)
6227 {
6228 el_jacobian[m].resize(nvar);
6229 }
6230
6231 // Now get the residuals and jacobian for the element
6234
6235 //---------------Insert the values into the vectors--------------
6236
6237 // Loop over the first index of local variables
6238 for (unsigned i = 0; i < nvar; i++)
6239 {
6240 // Get the local equation number
6241 unsigned eqn_number = assembly_handler_pt->eqn_number(elem_pt, i);
6242
6243 // Add the contribution to the residuals
6244 for (unsigned v = 0; v < n_vector; v++)
6245 {
6246 // Fill in each residuals vector
6247 residuals[v][eqn_number] += el_residuals[v][i];
6248 }
6249
6250 // Now loop over the other index
6251 for (unsigned j = 0; j < nvar; j++)
6252 {
6253 // Get the number of the unknown
6255
6256 // Loop over the matrices
6257 // If it's compressed row storage, then our vector of maps
6258 // is indexed by row (equation number)
6259 for (unsigned m = 0; m < n_matrix; m++)
6260 {
6261 // Get the value of the matrix at this point
6262 double value = el_jacobian[m](i, j);
6263 // Only bother to add to the vector if it's non-zero
6264 if (std::fabs(value) > Numerical_zero_for_sparse_assembly)
6265 {
6266 // number of entrys in this row
6267 const unsigned size = ncoef[m][eqn_number];
6268
6269 // if no data has been allocated for this row then allocate
6270 if (size == 0)
6271 {
6272 // do we have previous allocation data
6274 [m][eqn_number] != 0)
6275 {
6276 matrix_row_or_col_indices[m][eqn_number] = new unsigned
6278 [m][eqn_number]];
6279 matrix_values[m][eqn_number] = new double
6281 [m][eqn_number]];
6282 }
6283 else
6284 {
6285 matrix_row_or_col_indices[m][eqn_number] = new unsigned
6287 matrix_values[m][eqn_number] = new double
6290 [m][eqn_number] =
6292 }
6293 }
6294
6295 // If it's compressed row storage, then our vector of maps
6296 // is indexed by row (equation number)
6298 {
6299 // next add the data
6300 for (unsigned k = 0; k <= size; k++)
6301 {
6302 if (k == size)
6303 {
6304 // do we need to allocate more storage
6306 [m][eqn_number] == ncoef[m][eqn_number])
6307 {
6308 unsigned new_allocation =
6309 ncoef[m][eqn_number] +
6311 double* new_values = new double[new_allocation];
6312 unsigned* new_indices = new unsigned[new_allocation];
6313 for (unsigned c = 0; c < ncoef[m][eqn_number]; c++)
6314 {
6315 new_values[c] = matrix_values[m][eqn_number][c];
6316 new_indices[c] =
6317 matrix_row_or_col_indices[m][eqn_number][c];
6318 }
6319 delete[] matrix_values[m][eqn_number];
6320 delete[] matrix_row_or_col_indices[m][eqn_number];
6321 matrix_values[m][eqn_number] = new_values;
6322 matrix_row_or_col_indices[m][eqn_number] =
6325 [m][eqn_number] = new_allocation;
6326 }
6327 // and now add the data
6328 unsigned entry = ncoef[m][eqn_number];
6329 ncoef[m][eqn_number]++;
6330 matrix_row_or_col_indices[m][eqn_number][entry] =
6331 unknown;
6332 matrix_values[m][eqn_number][entry] = value;
6333 break;
6334 }
6335 else if (matrix_row_or_col_indices[m][eqn_number][k] ==
6336 unknown)
6337 {
6338 matrix_values[m][eqn_number][k] += value;
6339 break;
6340 }
6341 }
6342 }
6343 // Otherwise it's compressed column storage and our vector is
6344 // indexed by column (the unknown)
6345 else
6346 {
6347 // Add the data into the vectors in the correct position
6348 for (unsigned k = 0; k <= size; k++)
6349 {
6350 if (k == size)
6351 {
6352 // do we need to allocate more storage
6354 [m][unknown] == ncoef[m][unknown])
6355 {
6356 unsigned new_allocation =
6357 ncoef[m][unknown] +
6359 double* new_values = new double[new_allocation];
6360 unsigned* new_indices = new unsigned[new_allocation];
6361 for (unsigned c = 0; c < ncoef[m][unknown]; c++)
6362 {
6364 new_indices[c] =
6366 }
6367 delete[] matrix_values[m][unknown];
6371 }
6372 // and now add the data
6373 unsigned entry = ncoef[m][unknown];
6374 ncoef[m][unknown]++;
6376 eqn_number;
6377 matrix_values[m][unknown][entry] = value;
6378 break;
6379 }
6380 else if (matrix_row_or_col_indices[m][unknown][k] ==
6381 eqn_number)
6382 {
6383 matrix_values[m][unknown][k] += value;
6384 break;
6385 }
6386 }
6387 }
6388 }
6389 } // End of loop over matrices
6390 }
6391 }
6392
6393#ifdef OOMPH_HAS_MPI
6394 } // endif halo element
6395#endif
6396
6397
6398#ifdef OOMPH_HAS_MPI
6399 // Time it?
6401 {
6404 }
6405#endif
6406
6407 } // End of loop over the elements
6408
6409 } // End of vector assembly
6410
6411
6412#ifdef OOMPH_HAS_MPI
6413
6414 // Postprocess timing information and re-allocate distribution of
6415 // elements during subsequent assemblies.
6418 {
6420 }
6421
6422 // We have determined load balancing for current setup.
6423 // This can remain the same until assign_eqn_numbers() is called
6424 // again -- the flag is re-set to true there.
6426 {
6428 }
6429
6430#endif
6431
6432 //-----------Finally we need to convert this lousy vector storage scheme
6433 //------------------------to the containers required by SuperLU
6434
6435 // Loop over the number of matrices
6436 for (unsigned m = 0; m < n_matrix; m++)
6437 {
6438 // Set the number of rows or columns
6439 row_or_column_start[m] = new int[ndof + 1];
6440
6441 // fill row_or_column_start and find the number of entries
6442 row_or_column_start[m][0] = 0;
6443 for (unsigned long i = 0; i < ndof; i++)
6444 {
6447 }
6448 const unsigned entries = row_or_column_start[m][ndof];
6449
6450 // resize vectors
6451 column_or_row_index[m] = new int[entries];
6452 value[m] = new double[entries];
6453 nnz[m] = entries;
6454
6455 // Now we merely loop over the number of rows or columns
6456 for (unsigned long i_global = 0; i_global < ndof; i_global++)
6457 {
6458 // If there are no entries in the vector then skip the rest of the loop
6459 if (ncoef[m][i_global] == 0)
6460 {
6461 continue;
6462 }
6463
6464 // Loop over all the entries in the vectors corresponding to the given
6465 // row or column. It will NOT be ordered
6466 unsigned p = 0;
6467 for (int j = row_or_column_start[m][i_global];
6469 j++)
6470 {
6472 value[m][j] = matrix_values[m][i_global][p];
6473 ++p;
6474 }
6475
6476 // and delete
6478 delete[] matrix_values[m][i_global];
6479 }
6480
6481 //
6482 delete[] matrix_row_or_col_indices[m];
6483 delete[] matrix_values[m];
6484 } // End of the loop over the matrices
6485
6487 {
6488 oomph_info << "Pausing at end of sparse assembly." << std::endl;
6489 pause("Check memory usage now.");
6490 }
6491 }
6492
6493
6494#ifdef OOMPH_HAS_MPI
6495 //=======================================================================
6496 /// Helper method that returns the global equations to which
6497 /// the elements in the range el_lo to el_hi contribute on this
6498 /// processor
6499 //=======================================================================
6500 void Problem::get_my_eqns(AssemblyHandler* const& assembly_handler_pt,
6501 const unsigned& el_lo,
6502 const unsigned& el_hi,
6504 {
6505 // Index to keep track of the equations counted
6506 unsigned my_eqns_index = 0;
6507
6508 // Loop over the selection of elements
6509 for (unsigned long e = el_lo; e <= el_hi; e++)
6510 {
6511 // Get the pointer to the element
6513
6514 // Ignore halo elements
6515 if (!elem_pt->is_halo())
6516 {
6517 // Find number of degrees of freedom in the element
6518 const unsigned nvar = assembly_handler_pt->ndof(elem_pt);
6519 // Add the number of dofs to the current size of my_eqns
6520 my_eqns.resize(my_eqns_index + nvar);
6521
6522 // Loop over the first index of local variables
6523 for (unsigned i = 0; i < nvar; i++)
6524 {
6525 // Get the local equation number
6526 unsigned global_eqn_number =
6528 // Add into the vector
6529 my_eqns[my_eqns_index + i] = global_eqn_number;
6530 }
6531 // Update the number of elements in the vector
6533 }
6534 }
6535
6536 // now sort and remove duplicate entries in the vector
6537 std::sort(my_eqns.begin(), my_eqns.end());
6538 Vector<unsigned>::iterator it = std::unique(my_eqns.begin(), my_eqns.end());
6539 my_eqns.resize(it - my_eqns.begin());
6540 }
6541
6542
6543 //=============================================================================
6544 /// Helper method to assemble CRDoubleMatrices from distributed
6545 /// on multiple processors.
6546 //=============================================================================
6550 Vector<int*>& row_start,
6551 Vector<double*>& values,
6552 Vector<unsigned>& nnz,
6554 {
6555 // Time assembly
6556 double t_start = TimingHelpers::timer();
6557
6558 // my rank and nproc
6559 unsigned my_rank = Communicator_pt->my_rank();
6560 unsigned nproc = Communicator_pt->nproc();
6561
6562 // Total number of elements
6563 const unsigned long n_elements = mesh_pt()->nelement();
6564
6565#ifdef PARANOID
6566 // No elements? This is usually a sign that the problem distribution has
6567 // led to one processor not having any elements. Either
6568 // a sign of something having gone wrong or a relatively small
6569 // problem on a huge number of processors
6570 if (n_elements == 0)
6571 {
6572 std::ostringstream error_stream;
6573 error_stream << "Processsor " << my_rank << " has no elements. \n"
6574 << "This is usually a sign that the problem distribution \n"
6575 << "or the load balancing have gone wrong.";
6577 "Problem::parallel_sparse_assemble()",
6579 }
6580#endif
6581
6582
6583 // Default range of elements for distributed problems.
6584 unsigned long el_lo = 0;
6585 unsigned long el_hi_plus_one = n_elements;
6586
6587 // Otherwise just loop over a fraction of the elements
6588 // (This will either have been initialised in
6589 // Problem::set_default_first_and_last_element_for_assembly() or
6590 // will have been re-assigned during a previous assembly loop
6591 // Note that following the re-assignment only the entries
6592 // for the current processor are relevant.
6594 {
6597 }
6598
6599 // Find the number of vectors to be assembled
6600 const unsigned n_vector = residuals.size();
6601
6602 // Find the number of matrices to be assembled
6603 const unsigned n_matrix = column_indices.size();
6604
6605 // Locally cache pointer to assembly handler
6607
6608 bool doing_residuals = false;
6609 if (dynamic_cast<ParallelResidualsHandler*>(Assembly_handler_pt) != 0)
6610 {
6611 doing_residuals = true;
6612 }
6613
6614// Error check dimensions
6615#ifdef PARANOID
6616 if (row_start.size() != n_matrix)
6617 {
6618 std::ostringstream error_stream;
6619 error_stream << "Error: " << std::endl
6620 << "row_or_column_start.size() " << row_start.size()
6621 << " does not equal "
6622 << "column_or_row_index.size() " << column_indices.size()
6623 << std::endl;
6624 throw OomphLibError(
6626 }
6627
6628 if (values.size() != n_matrix)
6629 {
6630 std::ostringstream error_stream;
6631 error_stream << "Error: " << std::endl
6632 << "value.size() " << values.size() << " does not equal "
6633 << "column_or_row_index.size() " << column_indices.size()
6634 << std::endl
6635 << std::endl
6636 << std::endl;
6637 throw OomphLibError(
6639 }
6640#endif
6641
6642
6643 // start by assembling the sorted set of equations to which this processor
6644 // contributes. Essentially this is every global equation that features in
6645 // all the non-halo elements. This may not be the same as the locally-stored
6646 // dofs because some of the Nodes in non-halo elements may actually
6647 // be halos.
6648 //======================================================================
6650 if (n_elements != 0)
6651 {
6652 this->get_my_eqns(
6653 assembly_handler_pt, el_lo, el_hi_plus_one - 1, my_eqns);
6654 }
6655
6656 // number of equations
6657 unsigned my_n_eqn = my_eqns.size();
6658
6659 // next we assemble the data into an array of arrays
6660 // =================================================
6661 // The idea behind this sparse assembly routine is to use an array of
6662 // arrays for the entries in each complete matrix. And a second
6663 // array of arrays stores the global row (or column) indeces.
6664
6665 // Set up two vector of vectors to store the entries of each matrix,
6666 // indexed by either the column or row. The entries of the vector for
6667 // each matrix correspond to all the rows or columns of that matrix.
6670
6671 // Loop over the number of matrices being assembled and resize
6672 // each vector of vectors to the number of rows or columns of the matrix
6673 for (unsigned m = 0; m < n_matrix; m++)
6674 {
6675 matrix_col_indices[m] = new unsigned*[my_n_eqn];
6676 matrix_values[m] = new double*[my_n_eqn];
6677 for (unsigned i = 0; i < my_n_eqn; i++)
6678 {
6679 matrix_col_indices[m][i] = 0;
6680 matrix_values[m][i] = 0;
6681 }
6682 }
6683
6684 // Resize the residuals vectors
6686 for (unsigned v = 0; v < n_vector; v++)
6687 {
6688 residuals_data[v] = new double[my_n_eqn];
6689 for (unsigned i = 0; i < my_n_eqn; i++)
6690 {
6691 residuals_data[v][i] = 0;
6692 }
6693 }
6694
6695 // Storage for assembly time for elements
6696 double t_assemble_start = 0.0;
6697
6698 // Storage for assembly times
6700 {
6702 }
6703
6704 // number of coefficients in each row
6706 for (unsigned m = 0; m < n_matrix; m++)
6707 {
6708 ncoef[m].resize(my_n_eqn, 0);
6709 }
6710
6711 // Sparse_assemble_with_arrays_previous_allocation stores the number of
6712 // coefs in each row.
6713 // if a matrix of this size has not been assembled before then resize this
6714 // storage
6716 {
6718 for (unsigned m = 0; m < n_matrix; m++)
6719 {
6721 }
6722 }
6723
6724
6725 // assemble and populate an array based storage scheme
6726 {
6727 // Allocate local storage for the element's contribution to the
6728 // residuals vectors and system matrices of the size of the maximum
6729 // number of dofs in any element
6730 // This means that the storage will only be allocated (and deleted) once
6733
6734 // Loop over the elements
6735 for (unsigned long e = el_lo; e < el_hi_plus_one; e++)
6736 {
6737 // Time it?
6739 {
6741 }
6742
6743 // Get the pointer to the element
6745
6746 // Ignore halo elements
6747 if (!elem_pt->is_halo())
6748 {
6749 // Find number of degrees of freedom in the element
6750 const unsigned nvar = assembly_handler_pt->ndof(elem_pt);
6751
6752 // Resize the storage for elemental jacobian and residuals
6753 for (unsigned v = 0; v < n_vector; v++)
6754 {
6755 el_residuals[v].resize(nvar);
6756 }
6757 for (unsigned m = 0; m < n_matrix; m++)
6758 {
6759 el_jacobian[m].resize(nvar);
6760 }
6761
6762 // Now get the residuals and jacobian for the element
6765
6766 //---------------Insert the values into the vectors--------------
6767
6768 // Loop over the first index of local variables
6769 for (unsigned i = 0; i < nvar; i++)
6770 {
6771 // Get the local equation number
6772 unsigned global_eqn_number =
6774
6775 // determine the element number in my set of eqns using the
6776 // bisection method
6777 int left = 0;
6778 int right = my_n_eqn - 1;
6779 int eqn_number = right / 2;
6780 while (my_eqns[eqn_number] != global_eqn_number)
6781 {
6782 if (left == right)
6783 {
6784 // Check that the residuals associated with the
6785 // eqn number that can't be found are all zero
6786 bool broken = false;
6787 for (unsigned v = 0; v < n_vector; v++)
6788 {
6789 if (el_residuals[v][i] != 0.0)
6790 {
6791 broken = true;
6792 break;
6793 }
6794 }
6795
6796 // Now loop over the other index to check the entries
6797 // in the appropriate row of the Jacobians are zero too
6798 for (unsigned j = 0; j < nvar; j++)
6799 {
6800 // Get the number of the unknown
6801 // unsigned unknown =
6802 // assembly_handler_pt->eqn_number(elem_pt,j);
6803
6804 // Loop over the matrices
6805 // If it's compressed row storage, then our vector of maps
6806 // is indexed by row (equation number)
6807 for (unsigned m = 0; m < n_matrix; m++)
6808 {
6809 // Get the value of the matrix at this point
6810 double value = el_jacobian[m](i, j);
6811 if (value != 0.0)
6812 {
6813 broken = true;
6814 break;
6815 }
6816 if (broken) break;
6817 }
6818 }
6819
6820 if (broken)
6821 {
6822 std::ostringstream error_stream;
6824 << "Internal Error: " << std::endl
6825 << "Could not find global equation number "
6826 << global_eqn_number
6827 << " in my_eqns vector of equation numbers but\n"
6828 << "at least one entry in the residual vector is nonzero.";
6829 throw OomphLibError(error_stream.str(),
6832 }
6833 else
6834 {
6835 break;
6836 }
6837 }
6838 if (my_eqns[eqn_number] > global_eqn_number)
6839 {
6840 right = std::max(eqn_number - 1, left);
6841 }
6842 else
6843 {
6844 left = std::min(eqn_number + 1, right);
6845 }
6846 eqn_number = (right + left) / 2;
6847 }
6848
6849 // Add the contribution to the residuals
6850 for (unsigned v = 0; v < n_vector; v++)
6851 {
6852 // Fill in each residuals vector
6853 residuals_data[v][eqn_number] += el_residuals[v][i];
6854 }
6855
6856 // Now loop over the other index
6857 for (unsigned j = 0; j < nvar; j++)
6858 {
6859 // Get the number of the unknown
6861
6862 // Loop over the matrices
6863 // If it's compressed row storage, then our vector of maps
6864 // is indexed by row (equation number)
6865 for (unsigned m = 0; m < n_matrix; m++)
6866 {
6867 // Get the value of the matrix at this point
6868 double value = el_jacobian[m](i, j);
6869 // Only bother to add to the vector if it's non-zero
6870 if (std::fabs(value) > Numerical_zero_for_sparse_assembly)
6871 {
6872 // number of entrys in this row
6873 const unsigned size = ncoef[m][eqn_number];
6874
6875 // if no data has been allocated for this row then allocate
6876 if (size == 0)
6877 {
6878 // do we have previous allocation data
6880 [m][eqn_number] != 0)
6881 {
6882 matrix_col_indices[m][eqn_number] = new unsigned
6884 [m][eqn_number]];
6885
6886 matrix_values[m][eqn_number] = new double
6888 [m][eqn_number]];
6889 }
6890 else
6891 {
6892 matrix_col_indices[m][eqn_number] = new unsigned
6894
6895 matrix_values[m][eqn_number] = new double
6897
6899 [m][eqn_number] =
6901 }
6902 }
6903
6904 // next add the data
6905 for (unsigned k = 0; k <= size; k++)
6906 {
6907 if (k == size)
6908 {
6909 // do we need to allocate more storage
6911 [m][eqn_number] == ncoef[m][eqn_number])
6912 {
6913 unsigned new_allocation =
6914 ncoef[m][eqn_number] +
6916 double* new_values = new double[new_allocation];
6917 unsigned* new_indices = new unsigned[new_allocation];
6918 for (unsigned c = 0; c < ncoef[m][eqn_number]; c++)
6919 {
6920 new_values[c] = matrix_values[m][eqn_number][c];
6921 new_indices[c] = matrix_col_indices[m][eqn_number][c];
6922 }
6923 delete[] matrix_values[m][eqn_number];
6924 delete[] matrix_col_indices[m][eqn_number];
6925
6926 matrix_values[m][eqn_number] = new_values;
6927 matrix_col_indices[m][eqn_number] = new_indices;
6928
6930 [m][eqn_number] = new_allocation;
6931 }
6932 // and now add the data
6933 unsigned entry = ncoef[m][eqn_number];
6934 ncoef[m][eqn_number]++;
6935 matrix_col_indices[m][eqn_number][entry] = unknown;
6936 matrix_values[m][eqn_number][entry] = value;
6937 break;
6938 }
6939 else if (matrix_col_indices[m][eqn_number][k] == unknown)
6940 {
6941 matrix_values[m][eqn_number][k] += value;
6942 break;
6943 }
6944 }
6945 } // numerical zero check
6946 } // End of loop over matrices
6947 }
6948 }
6949 } // endif halo element
6950
6951 // Time it?
6953 {
6956 }
6957 } // End of loop over the elements
6958 } // End of vector assembly
6959
6960
6961 // Doc?
6962 double t_end = 0.0;
6963 double t_local = 0.0;
6964 double t_max = 0.0;
6965 double t_min = 0.0;
6966 double t_sum = 0.0;
6968 {
6970 t_local = t_end - t_start;
6971 t_max = 0.0;
6972 t_min = 0.0;
6973 t_sum = 0.0;
6975 &t_max,
6976 1,
6977 MPI_DOUBLE,
6978 MPI_MAX,
6979 this->communicator_pt()->mpi_comm());
6981 &t_min,
6982 1,
6983 MPI_DOUBLE,
6984 MPI_MIN,
6985 this->communicator_pt()->mpi_comm());
6987 &t_sum,
6988 1,
6989 MPI_DOUBLE,
6990 MPI_SUM,
6991 this->communicator_pt()->mpi_comm());
6992 double imbalance = (t_max - t_min) / (t_sum / double(nproc)) * 100.0;
6993
6994 if (doing_residuals)
6995 {
6996 oomph_info << "\nCPU for residual computation (loc/max/min/imbal): ";
6997 }
6998 else
6999 {
7000 oomph_info << "\nCPU for Jacobian computation (loc/max/min/imbal): ";
7001 }
7002 oomph_info << t_local << " " << t_max << " " << t_min << " " << imbalance
7003 << "%\n";
7004
7006 }
7007
7008
7009 // Adjust number of coefficients in each row
7010 for (unsigned m = 0; m < n_matrix; m++)
7011 {
7012 unsigned max = 0;
7013 unsigned min = INT_MAX;
7014 unsigned sum = 0;
7015 unsigned sum_total = 0;
7016 for (unsigned e = 0; e < my_n_eqn; e++)
7017 {
7018 sum += ncoef[m][e];
7020 if (ncoef[m][e] > max) max = ncoef[m][e];
7021 if (ncoef[m][e] < min) min = ncoef[m][e];
7022
7023 // Now shrink the storage to what we actually need
7024 unsigned new_allocation = ncoef[m][e];
7025 double* new_values = new double[new_allocation];
7026 unsigned* new_indices = new unsigned[new_allocation];
7027 for (unsigned c = 0; c < ncoef[m][e]; c++)
7028 {
7029 new_values[c] = matrix_values[m][e][c];
7031 }
7032 delete[] matrix_values[m][e];
7033 delete[] matrix_col_indices[m][e];
7034
7037 }
7038 }
7039
7040
7041 // Postprocess timing information and re-allocate distribution of
7042 // elements during subsequent assemblies.
7045 {
7047 }
7048
7049 // We have determined load balancing for current setup.
7050 // This can remain the same until assign_eqn_numbers() is called
7051 // again -- the flag is re-set to true there.
7053 {
7055 }
7056
7057
7058 // next we compute the number of equations and number of non-zeros to be
7059 // sent to each processor, and send/recv that information
7060 // =====================================================================
7061
7062 // determine the number of eqns to be sent to each processor
7065 // If no equations are assembled then we don't need to do any of this
7066 if (my_n_eqn > 0)
7067 {
7068 unsigned current_p = target_dist_pt->rank_of_global_row(my_eqns[0]);
7071 for (unsigned i = 1; i < my_n_eqn; i++)
7072 {
7073 unsigned next_p = target_dist_pt->rank_of_global_row(my_eqns[i]);
7074 if (next_p != current_p)
7075 {
7076 current_p = next_p;
7078 }
7080 }
7081 }
7082
7083 // determine the number of non-zeros to be sent to each processor for each
7084 // matrix (if n_eqn_for_proc[p]=0, then nothing will be assembled)
7086 for (unsigned p = 0; p < nproc; p++)
7087 {
7090 for (unsigned m = 0; m < n_matrix; m++)
7091 {
7092 for (int i = first_eqn_element; i <= last_eqn_element; i++)
7093 {
7094 nnz_for_proc(p, m) += ncoef[m][i];
7095 }
7096 }
7097 }
7098
7099 // next post the sends and recvs to the corresponding processors
7104 for (unsigned p = 0; p < nproc; p++)
7105 {
7106 if (p != my_rank)
7107 {
7108 temp_send_storage[p] = new unsigned[n_matrix + 1];
7110 for (unsigned m = 0; m < n_matrix; m++)
7111 {
7112 temp_send_storage[p][m + 1] = nnz_for_proc(p, m);
7113 }
7116 n_matrix + 1,
7118 p,
7119 0,
7120 Communicator_pt->mpi_comm(),
7121 &sreq);
7122 send_nnz_reqs.push_back(sreq);
7123 temp_recv_storage[p] = new unsigned[n_matrix + 1];
7126 n_matrix + 1,
7128 p,
7129 0,
7130 Communicator_pt->mpi_comm(),
7131 &rreq);
7132 recv_nnz_reqs.push_back(rreq);
7133 }
7134 }
7135
7136 // assemble the data to be sent to each processor
7137 // ==============================================
7138
7139 // storage
7145
7146 // equation numbers
7147 for (unsigned p = 0; p < nproc; p++)
7148 {
7149 unsigned n_eqns_p = n_eqn_for_proc[p];
7150 if (n_eqns_p > 0)
7151 {
7153 unsigned first_row = target_dist_pt->first_row(p);
7154 eqns_for_proc[p] = new unsigned[n_eqns_p];
7155 for (unsigned i = 0; i < n_eqns_p; i++)
7156 {
7157 eqns_for_proc[p][i] = my_eqns[i + first_eqn_element] - first_row;
7158 }
7159 }
7160 }
7161
7162 // residuals for p
7163 for (unsigned v = 0; v < n_vector; v++)
7164 {
7165 for (unsigned p = 0; p < nproc; p++)
7166 {
7167 unsigned n_eqns_p = n_eqn_for_proc[p];
7168 if (n_eqns_p > 0)
7169 {
7171 residuals_for_proc(p, v) = new double[n_eqns_p];
7172 for (unsigned i = 0; i < n_eqns_p; i++)
7173 {
7174 residuals_for_proc(p, v)[i] =
7176 }
7177 }
7178 }
7179 delete[] residuals_data[v];
7180 }
7181
7182 // matrices for p
7183 for (unsigned m = 0; m < n_matrix; m++)
7184 {
7185 for (unsigned p = 0; p < nproc; p++)
7186 {
7187 unsigned n_eqns_p = n_eqn_for_proc[p];
7188 if (n_eqns_p > 0)
7189 {
7191 row_start_for_proc(p, m) = new unsigned[n_eqns_p + 1];
7192 column_indices_for_proc(p, m) = new unsigned[nnz_for_proc(p, m)];
7193 values_for_proc(p, m) = new double[nnz_for_proc(p, m)];
7194 unsigned entry = 0;
7195 for (unsigned i = 0; i < n_eqns_p; i++)
7196 {
7197 row_start_for_proc(p, m)[i] = entry;
7198 unsigned n_coef_in_row = ncoef[m][first_eqn_element + i];
7199 for (unsigned j = 0; j < n_coef_in_row; j++)
7200 {
7201 column_indices_for_proc(p, m)[entry] =
7203 values_for_proc(p, m)[entry] =
7205 entry++;
7206 }
7207 }
7208 row_start_for_proc(p, m)[n_eqns_p] = entry;
7209 }
7210 }
7211 for (unsigned i = 0; i < my_n_eqn; i++)
7212 {
7213 delete[] matrix_col_indices[m][i];
7214 delete[] matrix_values[m][i];
7215 }
7216 delete[] matrix_col_indices[m];
7217 delete[] matrix_values[m];
7218 }
7219
7220 // need to wait for the recv nnzs to complete
7221 // before we can allocate storage for the matrix recvs
7222 // ===================================================
7223
7224 // recv and copy the datafrom the recv storage to
7225 // + nnz_from_proc
7226 // + n_eqn_from_proc
7231 for (unsigned p = 0; p < nproc; p++)
7232 {
7233 if (p != my_rank)
7234 {
7236 for (unsigned m = 0; m < n_matrix; m++)
7237 {
7239 }
7240 delete[] temp_recv_storage[p];
7241 }
7242 else
7243 {
7245 for (unsigned m = 0; m < n_matrix; m++)
7246 {
7248 }
7249 }
7250 }
7251 recv_nnz_stat.clear();
7252 recv_nnz_reqs.clear();
7253
7254 // allocate the storage for the data to be recv and post the sends recvs
7255 // =====================================================================
7256
7257 // storage
7263
7264 // allocate and post sends and recvs
7265 double base;
7268 unsigned n_comm_types = 1 + 1 * n_vector + 3 * n_matrix;
7271 for (unsigned p = 0; p < nproc; p++)
7272 {
7273 if (p != my_rank)
7274 {
7275 // allocate
7276 if (n_eqn_from_proc[p] > 0)
7277 {
7278 eqns_from_proc[p] = new unsigned[n_eqn_from_proc[p]];
7279 for (unsigned v = 0; v < n_vector; v++)
7280 {
7281 residuals_from_proc(p, v) = new double[n_eqn_from_proc[p]];
7282 }
7283 for (unsigned m = 0; m < n_matrix; m++)
7284 {
7285 row_start_from_proc(p, m) = new unsigned[n_eqn_from_proc[p] + 1];
7286 column_indices_from_proc(p, m) = new unsigned[nnz_from_proc(p, m)];
7287 values_from_proc(p, m) = new double[nnz_from_proc(p, m)];
7288 }
7289 }
7290
7291 // recv
7292 if (n_eqn_from_proc[p] > 0)
7293 {
7296 int count[n_comm_types];
7297 int pt = 0;
7298
7299 // equations
7300 count[pt] = 1;
7305 pt++;
7306
7307 // vectors
7308 for (unsigned v = 0; v < n_vector; v++)
7309 {
7310 count[pt] = 1;
7315 pt++;
7316 }
7317
7318 // matrices
7319 for (unsigned m = 0; m < n_matrix; m++)
7320 {
7321 // row start
7322 count[pt] = 1;
7328 pt++;
7329
7330
7331 // column indices
7332 count[pt] = 1;
7337 pt++;
7338
7339 // values
7340 count[pt] = 1;
7345 pt++;
7346 }
7347
7348 // build the combined type
7353 for (unsigned t = 0; t < n_comm_types; t++)
7354 {
7356 }
7358 MPI_Irecv(
7359 &base, 1, recv_type, p, 1, Communicator_pt->mpi_comm(), &req);
7361 recv_reqs.push_back(req);
7362 }
7363
7364 // send
7365 if (n_eqn_for_proc[p] > 0)
7366 {
7369 int count[n_comm_types];
7370 int pt = 0;
7371
7372 // equations
7373 count[pt] = 1;
7378 pt++;
7379
7380 // vectors
7381 for (unsigned v = 0; v < n_vector; v++)
7382 {
7383 count[pt] = 1;
7388 pt++;
7389 }
7390
7391 // matrices
7392 for (unsigned m = 0; m < n_matrix; m++)
7393 {
7394 // row start
7395 count[pt] = 1;
7401 pt++;
7402
7403
7404 // column indices
7405 count[pt] = 1;
7410 pt++;
7411
7412 // values
7413 count[pt] = 1;
7418 pt++;
7419 }
7420
7421 // build the combined type
7426 for (unsigned t = 0; t < n_comm_types; t++)
7427 {
7429 }
7431 MPI_Isend(
7432 &base, 1, send_type, p, 1, Communicator_pt->mpi_comm(), &req);
7434 send_reqs.push_back(req);
7435 }
7436 }
7437 // otherwise send to self
7438 else
7439 {
7441 for (unsigned v = 0; v < n_vector; v++)
7442 {
7444 }
7445 for (unsigned m = 0; m < n_matrix; m++)
7446 {
7450 }
7451 }
7452 }
7453
7454 // wait for the recvs to complete
7455 unsigned n_recv_req = recv_reqs.size();
7456 if (n_recv_req > 0)
7457 {
7460 }
7461
7462 // ==============================================
7463 unsigned target_nrow_local = target_dist_pt->nrow_local();
7464
7465 // loop over the matrices
7466 for (unsigned m = 0; m < n_matrix; m++)
7467 {
7468 // allocate row_start
7469 row_start[m] = new int[target_nrow_local + 1];
7470 row_start[m][0] = 0;
7471
7472 // initially allocate storage based on the maximum number of non-zeros
7473 // from any one processor
7475 for (unsigned p = 0; p < nproc; p++)
7476 {
7478 }
7480 values_chunk[0] = new double[nnz_allocation];
7486 unsigned current_chunk = 0;
7487
7488 // for each row on this processor
7489 for (unsigned i = 0; i < target_nrow_local; i++)
7490 {
7491 row_start[m][i] = 0;
7492
7493 // determine the processors that this row is on
7495 for (unsigned p = 0; p < nproc; p++)
7496 {
7497 if (n_eqn_from_proc[p] == 0)
7498 {
7499 row_on_proc[p] = -1;
7500 }
7501 else
7502 {
7503 int left = 0;
7504 int right = n_eqn_from_proc[p] - 1;
7505 int midpoint = right / 2;
7506 bool complete = false;
7507 while (!complete)
7508 {
7509 midpoint = (right + left) / 2;
7510 if (midpoint > right)
7511 {
7512 midpoint = right;
7513 }
7514 if (midpoint < left)
7515 {
7516 midpoint = left;
7517 }
7518 if (left == right)
7519 {
7520 if (eqns_from_proc[p][midpoint] == i)
7521 {
7522 midpoint = left;
7523 }
7524 else
7525 {
7526 midpoint = -1;
7527 }
7528 complete = true;
7529 }
7530 else if (eqns_from_proc[p][midpoint] == i)
7531 {
7532 complete = true;
7533 }
7534 else if (eqns_from_proc[p][midpoint] > i)
7535 {
7536 right = std::max(midpoint - 1, left);
7537 }
7538 else
7539 {
7540 left = std::min(midpoint + 1, right);
7541 }
7542 }
7544 }
7545 }
7546
7547 // for each processor build this row of the matrix
7549 unsigned check_last = check_first;
7550 for (unsigned p = 0; p < nproc; p++)
7551 {
7552 if (row_on_proc[p] != -1)
7553 {
7554 int row = row_on_proc[p];
7555 unsigned first = row_start_from_proc(p, m)[row];
7556 unsigned last = row_start_from_proc(p, m)[row + 1];
7557 for (unsigned l = first; l < last; l++)
7558 {
7559 bool done = false;
7560 for (unsigned j = check_first; j <= check_last && !done; j++)
7561 {
7562 if (j == check_last)
7563 {
7564 // is this temp array full, do we need to allocate
7565 // a new temp array
7568 {
7569 // number of chunks allocated
7570 unsigned n_chunk = values_chunk.size();
7571
7572 // determine the number of non-zeros added so far
7573 // (excluding the current row)
7574 unsigned nnz_so_far = 0;
7575 for (unsigned c = 0; c < n_chunk; c++)
7576 {
7578 }
7579 nnz_so_far -= row_start[m][i];
7580
7581 // average number of non-zeros per row
7582 unsigned avg_nnz = nnz_so_far / (i + 1);
7583
7584 // number of rows left +1
7585 unsigned nrows_left = target_nrow_local - i;
7586
7587 // allocation for next chunk
7588 unsigned next_chunk_size =
7589 avg_nnz * nrows_left + row_start[m][i];
7590
7591 // allocate storage in next chunk
7592 current_chunk++;
7593 n_chunk++;
7594 values_chunk.resize(n_chunk);
7598 new int[next_chunk_size];
7599 size_of_chunk.resize(n_chunk);
7601 ncoef_in_chunk.resize(n_chunk);
7602
7603 // copy current row from previous chunk to new chunk
7604 for (unsigned k = check_first; k < check_last; k++)
7605 {
7610 }
7611 ncoef_in_chunk[current_chunk - 1] -= row_start[m][i];
7612 ncoef_in_chunk[current_chunk] = row_start[m][i];
7613
7614 // update first_check and last_check
7615 check_first = 0;
7616 check_last = row_start[m][i];
7617 j = check_last;
7618 }
7619
7620 // add the coefficient
7625 row_start[m][i]++;
7626 check_last++;
7627 done = true;
7628 }
7630 (int)column_indices_from_proc(p, m)[l])
7631 {
7633 done = true;
7634 }
7635 }
7636 }
7637 }
7638 }
7639 }
7640
7641 // delete recv data for this matrix
7642 for (unsigned p = 0; p < nproc; p++)
7643 {
7644 if (n_eqn_from_proc[p] > 0)
7645 {
7646 delete[] row_start_from_proc(p, m);
7647 delete[] column_indices_from_proc(p, m);
7648 delete[] values_from_proc(p, m);
7649 }
7650 }
7651
7652 // next we take the chunk base storage of the column indices and values
7653 // and copy into a single contiguous block of memory
7654 // ====================================================================
7655 unsigned n_chunk = values_chunk.size();
7656 nnz[m] = 0;
7657 for (unsigned c = 0; c < n_chunk; c++)
7658 {
7659 nnz[m] += ncoef_in_chunk[c];
7660 }
7662
7663 // allocate
7664 values[m] = new double[nnz[m]];
7665 column_indices[m] = new int[nnz[m]];
7666
7667 // copy
7668 unsigned pt = 0;
7669 for (unsigned c = 0; c < n_chunk; c++)
7670 {
7671 unsigned nc = ncoef_in_chunk[c];
7672 for (unsigned i = 0; i < nc; i++)
7673 {
7674 values[m][pt + i] = values_chunk[c][i];
7676 }
7677 pt += nc;
7678 delete[] values_chunk[c];
7679 delete[] column_indices_chunk[c];
7680 }
7681
7682 // the row_start vector currently contains the number of coefs in each
7683 // row. Update
7684 // ===================================================================
7685 unsigned g = row_start[m][0];
7686 row_start[m][0] = 0;
7687 for (unsigned i = 1; i < target_nrow_local; i++)
7688 {
7689 unsigned h = g + row_start[m][i];
7690 row_start[m][i] = g;
7691 g = h;
7692 }
7693 row_start[m][target_nrow_local] = g;
7694 }
7695
7696 // next accumulate the residuals
7697 for (unsigned v = 0; v < n_vector; v++)
7698 {
7699 residuals[v] = new double[target_nrow_local];
7700 for (unsigned i = 0; i < target_nrow_local; i++)
7701 {
7702 residuals[v][i] = 0;
7703 }
7704 for (unsigned p = 0; p < nproc; p++)
7705 {
7706 if (n_eqn_from_proc[p] > 0)
7707 {
7708 unsigned n_eqn_p = n_eqn_from_proc[p];
7709 for (unsigned i = 0; i < n_eqn_p; i++)
7710 {
7712 }
7713 delete[] residuals_from_proc(p, v);
7714 }
7715 }
7716 }
7717
7718 // delete list of eqns from proc
7719 for (unsigned p = 0; p < nproc; p++)
7720 {
7721 if (n_eqn_from_proc[p] > 0)
7722 {
7723 delete[] eqns_from_proc[p];
7724 }
7725 }
7726
7727 // and wait for sends to complete
7730 for (unsigned p = 0; p < nproc; p++)
7731 {
7732 if (p != my_rank)
7733 {
7734 delete[] temp_send_storage[p];
7735 }
7736 }
7737 send_nnz_stat.clear();
7738 send_nnz_reqs.clear();
7739
7740 // wait for the matrix data sends to complete and delete the data
7741 unsigned n_send_reqs = send_reqs.size();
7742 if (n_send_reqs > 0)
7743 {
7746 for (unsigned p = 0; p < nproc; p++)
7747 {
7748 if (p != my_rank)
7749 {
7750 if (n_eqn_for_proc[p])
7751 {
7752 delete[] eqns_for_proc[p];
7753 for (unsigned m = 0; m < n_matrix; m++)
7754 {
7755 delete[] row_start_for_proc(p, m);
7756 delete[] column_indices_for_proc(p, m);
7757 delete[] values_for_proc(p, m);
7758 }
7759 for (unsigned v = 0; v < n_vector; v++)
7760 {
7761 delete[] residuals_for_proc(p, v);
7762 }
7763 }
7764 }
7765 }
7766 }
7767
7768 // Doc?
7770 {
7772 t_local = t_end - t_start;
7773 t_max = 0.0;
7774 t_min = 0.0;
7775 t_sum = 0.0;
7777 &t_max,
7778 1,
7779 MPI_DOUBLE,
7780 MPI_MAX,
7781 this->communicator_pt()->mpi_comm());
7783 &t_min,
7784 1,
7785 MPI_DOUBLE,
7786 MPI_MIN,
7787 this->communicator_pt()->mpi_comm());
7789 &t_sum,
7790 1,
7791 MPI_DOUBLE,
7792 MPI_SUM,
7793 this->communicator_pt()->mpi_comm());
7794 double imbalance = (t_max - t_min) / (t_sum / double(nproc)) * 100.0;
7795 if (doing_residuals)
7796 {
7797 oomph_info << "CPU for residual distribut. (loc/max/min/imbal): ";
7798 }
7799 else
7800 {
7801 oomph_info << "CPU for Jacobian distribut. (loc/max/min/imbal): ";
7802 }
7803 oomph_info << t_local << " " << t_max << " " << t_min << " " << imbalance
7804 << "%\n\n";
7805 }
7806 }
7807
7808#endif
7809
7810
7811 //================================================================
7812 /// Get the full Jacobian by finite differencing
7813 //================================================================
7815 DenseMatrix<double>& jacobian)
7816 {
7817#ifdef OOMPH_HAS_MPI
7818
7820 {
7821 OomphLibWarning("This is unlikely to work with a distributed problem",
7822 " Problem::get_fd_jacobian()",
7824 }
7825#endif
7826
7827
7828 // Find number of dofs
7829 const unsigned long n_dof = ndof();
7830
7831 // Advanced residuals
7833
7834 // Get reference residuals
7836
7837 const double FD_step = 1.0e-8;
7838
7839 // Make sure the Jacobian is the right size (since we don't care about
7840 // speed).
7841 jacobian.resize(n_dof, n_dof);
7842
7843 // Loop over all dofs
7844 for (unsigned long jdof = 0; jdof < n_dof; jdof++)
7845 {
7846 double backup = *Dof_pt[jdof];
7847 *Dof_pt[jdof] += FD_step;
7848
7849 // We're checking if the new values for Dof_pt[] actually
7850 // solve the entire problem --> update as if problem had
7851 // been solved
7855
7856 // Get advanced residuals
7858
7859 for (unsigned long ieqn = 0; ieqn < n_dof; ieqn++)
7860 {
7861 jacobian(ieqn, jdof) =
7862 (residuals_pls[ieqn] - residuals[ieqn]) / FD_step;
7863 }
7864
7865 *Dof_pt[jdof] = backup;
7866 }
7867
7868 // Reset problem to state it was in
7872 }
7873
7874 //======================================================================
7875 /// Get derivative of the residuals vector wrt a global parameter
7876 /// This is required in continuation problems
7877 //=======================================================================
7880 {
7881 // If we are doing the calculation analytically then call the appropriate
7882 // handler and then calling get_residuals
7884 {
7885 // Locally cache pointer to assembly handler
7887 // Create a new assembly handler that replaces get_residuals by
7888 // get_dresiduals_dparameter for each element
7891 // Get the residuals, which will be dresiduals by dparameter
7892 this->get_residuals(result);
7893 // Delete the parameter derivative handler
7894 delete Assembly_handler_pt;
7895 // Reset the assembly handler to the original handler
7897
7898 /*AssemblyHandler* const assembly_handler_pt = Assembly_handler_pt;
7899 //Loop over all the elements
7900 unsigned long Element_pt_range = Mesh_pt->nelement();
7901 for(unsigned long e=0;e<Element_pt_range;e++)
7902 {
7903 //Get the pointer to the element
7904 GeneralisedElement* elem_pt = Mesh_pt->element_pt(e);
7905 //Find number of dofs in the element
7906 unsigned n_element_dofs = assembly_handler_pt->ndof(elem_pt);
7907 //Set up an array
7908 Vector<double> element_residuals(n_element_dofs);
7909 //Fill the array
7910 assembly_handler_pt->get_dresiduals_dparameter(elem_pt,parameter_pt,
7911 element_residuals);
7912 //Now loop over the dofs and assign values to global Vector
7913 for(unsigned l=0;l<n_element_dofs;l++)
7914 {
7915 result[assembly_handler_pt->eqn_number(elem_pt,l)]
7916 += element_residuals[l];
7917 }
7918 }*/
7919
7920 // for(unsigned n=0;n<n_dof;n++)
7921 // {std::cout << "BLA " << n << " " << result[n] << "\n";}
7922 }
7923 // Otherwise use the finite difference default
7924 else
7925 {
7926 // Get the (global) residuals and store in the result vector
7928
7929 // Storage for the new residuals
7931
7932 // Increase the global parameter
7933 const double FD_step = 1.0e-8;
7934
7935 // Store the current value of the parameter
7936 double param_value = *parameter_pt;
7937
7938 // Increase the parameter
7939 *parameter_pt += FD_step;
7940
7941 // Do any possible updates
7943
7944 // Get the new residuals
7946
7947 // Find the number of local rows
7948 //(I think it's a global vector, so that should be fine)
7949 const unsigned ndof_local = result.nrow_local();
7950
7951 // Do the finite differencing in the local variables
7952 for (unsigned n = 0; n < ndof_local; ++n)
7953 {
7954 result[n] = (newres[n] - result[n]) / FD_step;
7955 }
7956
7957 // Reset the value of the parameter
7959
7960 // Do any possible updates
7962 }
7963 }
7964
7965
7966 //======================================================================
7967 /// Return the product of the global hessian (derivative of Jacobian
7968 /// matrix with respect to all variables) with
7969 /// an eigenvector, Y, and any number of other specified vectors C
7970 /// (d(J_{ij})/d u_{k}) Y_{j} C_{k}.
7971 /// This function is used in assembling and solving the augmented systems
7972 /// associated with bifurcation tracking.
7973 /// The default implementation is to use finite differences at the global
7974 /// level.
7975 //========================================================================
7980 {
7981 // How many vector products must we construct
7982 const unsigned n_vec = C.size();
7983
7984 // currently only global (non-distributed) distributions are allowed
7985 // LinearAlgebraDistribution* dist_pt = new
7986 // LinearAlgebraDistribution(Communicator_pt,n_dof,false);
7987
7988 // Cache the assembly hander
7990
7991 // Rebuild the results vectors and initialise to zero
7992 // use the same distribution of the vector Y
7993 for (unsigned i = 0; i < n_vec; i++)
7994 {
7995 product[i].build(Y.distribution_pt(), 0.0);
7996 product[i].initialise(0.0);
7997 }
7998
7999// Setup the halo schemes for the result
8000#ifdef OOMPH_HAS_MPI
8002 {
8003 for (unsigned i = 0; i < n_vec; i++)
8004 {
8005 product[i].build_halo_scheme(this->Halo_scheme_pt);
8006 }
8007 }
8008#endif
8009
8010 // If we are doing the calculation analytically then call the appropriate
8011 // handler
8012 // A better way to do this is probably to hook into the get_residuals
8013 // framework but with a different member function of the assembly
8014 // handler
8016 {
8017 // Loop over all the elements
8018 unsigned long Element_pt_range = Mesh_pt->nelement();
8019 for (unsigned long e = 0; e < Element_pt_range; e++)
8020 {
8021 // Get the pointer to the element
8023// Do not loop over halo elements
8024#ifdef OOMPH_HAS_MPI
8025 if (!elem_pt->is_halo())
8026 {
8027#endif
8028 // Find number of dofs in the element
8030 // Set up a matrix for the input and output
8034
8035 // Translate the global input vectors into the local storage
8036 // Probably horribly inefficient, but otherwise things get really
8037 // messy at the elemental level
8038 for (unsigned l = 0; l < n_var; l++)
8039 {
8040 // Cache the global equation number
8041 const unsigned long eqn_number =
8043
8044 Y_local[l] = Y.global_value(eqn_number);
8045 for (unsigned i = 0; i < n_vec; i++)
8046 {
8047 C_local(i, l) = C[i].global_value(eqn_number);
8048 }
8049 }
8050
8051 // Fill the array
8054
8055 // Assign the local results to the global vector
8056 for (unsigned l = 0; l < n_var; l++)
8057 {
8058 const unsigned long eqn_number =
8060
8061 for (unsigned i = 0; i < n_vec; i++)
8062 {
8063 product[i].global_value(eqn_number) += product_local(i, l);
8064 // std::cout << "BLA " << e << " " << i << " "
8065 // << l << " " << product_local(i,l) << "\n";
8066 }
8067 }
8068#ifdef OOMPH_HAS_MPI
8069 }
8070#endif
8071 }
8072 }
8073 // Otherwise calculate using finite differences by
8074 // perturbing the jacobian along a particular direction
8075 else
8076 {
8077 // Cache the finite difference step
8078 /// Alice: My bifurcation tracking converges better with this FD_step
8079 /// as 1.0e-5. The default value remains at 1.0e-8.
8080 const double FD_step = FD_step_used_in_get_hessian_vector_products;
8081
8082 // We can now construct our multipliers
8083 const unsigned n_dof_local = this->Dof_distribution_pt->nrow_local();
8084 // Prepare to scale
8085 double dof_length = 0.0;
8087
8088 for (unsigned n = 0; n < n_dof_local; n++)
8089 {
8090 if (std::fabs(this->dof(n)) > dof_length)
8091 {
8092 dof_length = std::fabs(this->dof(n));
8093 }
8094 }
8095
8096 // C is assumed to have the same distribution as the dofs
8097 for (unsigned i = 0; i < n_vec; i++)
8098 {
8099 for (unsigned n = 0; n < n_dof_local; n++)
8100 {
8101 if (std::fabs(C[i][n]) > C_length[i])
8102 {
8103 C_length[i] = std::fabs(C[i][n]);
8104 }
8105 }
8106 }
8107
8108 // Now broadcast the information, if distributed
8109#ifdef OOMPH_HAS_MPI
8111 {
8112 const unsigned n_length = n_vec + 1;
8113 double all_length[n_length];
8115 for (unsigned i = 0; i < n_vec; i++)
8116 {
8117 all_length[i + 1] = C_length[i];
8118 }
8119
8120 // Do the MPI call
8124 n_length,
8125 MPI_DOUBLE,
8126 MPI_MAX,
8127 this->communicator_pt()->mpi_comm());
8128
8129 // Read out the information
8131 for (unsigned i = 0; i < n_vec; i++)
8132 {
8133 C_length[i] = all_length_reduce[i + 1];
8134 }
8135 }
8136#endif
8137
8138 // Form the multipliers
8140 for (unsigned i = 0; i < n_vec; i++)
8141 {
8143 C_mult[i] += FD_step;
8144 C_mult[i] *= FD_step;
8145 }
8146
8147
8148 // Dummy vector to stand in the place of the residuals
8150
8151 // Calculate the product of the jacobian matrices, etc by looping over the
8152 // elements
8153 const unsigned long n_element = this->mesh_pt()->nelement();
8154 for (unsigned long e = 0; e < n_element; e++)
8155 {
8157 // Ignore halo's of course
8158#ifdef OOMPH_HAS_MPI
8159 if (!elem_pt->is_halo())
8160 {
8161#endif
8162 // Loop over the ndofs in each element
8164 // Resize the dummy residuals vector
8165 dummy_res.resize(n_var);
8166 // Allocate storage for the unperturbed jacobian matrix
8168 // Get unperturbed jacobian
8170
8171 // Backup the dofs
8173 for (unsigned n = 0; n < n_var; n++)
8174 {
8175 unsigned eqn_number = assembly_handler_pt->eqn_number(elem_pt, n);
8176 dof_bac[n] = *this->global_dof_pt(eqn_number);
8177 }
8178
8179 // Now loop over all vectors C
8180 for (unsigned i = 0; i < n_vec; i++)
8181 {
8182 // Perturb the dofs by the appropriate vector
8183 for (unsigned n = 0; n < n_var; n++)
8184 {
8185 unsigned eqn_number = assembly_handler_pt->eqn_number(elem_pt, n);
8186 // Perturb by vector C[i]
8187 *this->global_dof_pt(eqn_number) +=
8188 C_mult[i] * C[i].global_value(eqn_number);
8189 }
8191
8192 // Allocate storage for the perturbed jacobian
8194
8195 // Now get the new jacobian
8197
8198 // Reset the dofs
8199 for (unsigned n = 0; n < n_var; n++)
8200 {
8201 unsigned eqn_number = assembly_handler_pt->eqn_number(elem_pt, n);
8202 *this->global_dof_pt(eqn_number) = dof_bac[n];
8203 }
8205
8206 // Now work out the products
8207 for (unsigned n = 0; n < n_var; n++)
8208 {
8209 unsigned eqn_number = assembly_handler_pt->eqn_number(elem_pt, n);
8210 double prod_c = 0.0;
8211 for (unsigned m = 0; m < n_var; m++)
8212 {
8214 prod_c += (jac_C(n, m) - jac(n, m)) * Y.global_value(unknown);
8215 }
8216 // std::cout << "FD " << e << " " << i << " "
8217 // << n << " " << prod_c/C_mult[i] << "\n";
8218 product[i].global_value(eqn_number) += prod_c / C_mult[i];
8219 }
8220 }
8221#ifdef OOMPH_HAS_MPI
8222 }
8223#endif
8224 } // End of loop over elements
8225 }
8226
8227 // If we have a distributed problem then gather all
8228 // values
8229#ifdef OOMPH_HAS_MPI
8231 {
8232 // Sum all values if distributed
8233 for (unsigned i = 0; i < n_vec; i++)
8234 {
8235 product[i].sum_all_halo_and_haloed_values();
8236 }
8237 }
8238#endif
8239 }
8240
8241
8242 //==================================================================
8243 /// Solve the eigenproblem
8244 //==================================================================
8246 Vector<std::complex<double>>& alpha,
8247 Vector<double>& beta,
8250 const bool& make_timesteppers_steady)
8251 {
8252 // If the boolean flag is steady, then make all the timesteppers steady
8253 // before solving the eigenproblem. This will "switch off" the
8254 // time-derivative terms in the jacobian matrix
8256 {
8257 // Find out how many timesteppers there are
8258 const unsigned n_time_steppers = ntime_stepper();
8259
8260 // Vector of bools to store the is_steady status of the various
8261 // timesteppers when we came in here
8262 std::vector<bool> was_steady(n_time_steppers);
8263
8264 // Loop over them all and make them (temporarily) static
8265 for (unsigned i = 0; i < n_time_steppers; i++)
8266 {
8269 }
8270
8271 const bool do_adjoint_problem = false;
8272 // Call the Eigenproblem for the eigensolver
8274 n_eval,
8275 alpha,
8276 beta,
8280
8281 // Reset the is_steady status of all timesteppers that
8282 // weren't already steady when we came in here and reset their
8283 // weights
8284 for (unsigned i = 0; i < n_time_steppers; i++)
8285 {
8286 if (!was_steady[i])
8287 {
8289 }
8290 }
8291 }
8292 // Otherwise if we don't want to make the problem steady, just
8293 // assemble and solve the eigensystem
8294 else
8295 {
8296 const bool do_adjoint_problem = false;
8297 // Call the Eigenproblem for the eigensolver
8299 n_eval,
8300 alpha,
8301 beta,
8305 }
8306 }
8307
8308
8309 //==================================================================
8310 /// Solve the eigenproblem
8311 //==================================================================
8313 Vector<std::complex<double>>& eigenvalue,
8316 const bool& make_timesteppers_steady)
8317 {
8318 // If the boolean flag is steady, then make all the timesteppers steady
8319 // before solving the eigenproblem. This will "switch off" the
8320 // time-derivative terms in the jacobian matrix
8322 {
8323 // Find out how many timesteppers there are
8324 const unsigned n_time_steppers = ntime_stepper();
8325
8326 // Vector of bools to store the is_steady status of the various
8327 // timesteppers when we came in here
8328 std::vector<bool> was_steady(n_time_steppers);
8329
8330 // Loop over them all and make them (temporarily) static
8331 for (unsigned i = 0; i < n_time_steppers; i++)
8332 {
8335 }
8336
8337 const bool do_adjoint_problem = false;
8338 // Call the Eigenproblem for the eigensolver
8340 n_eval,
8341 eigenvalue,
8345
8346 // Reset the is_steady status of all timesteppers that
8347 // weren't already steady when we came in here and reset their
8348 // weights
8349 for (unsigned i = 0; i < n_time_steppers; i++)
8350 {
8351 if (!was_steady[i])
8352 {
8354 }
8355 }
8356 }
8357 // Otherwise if we don't want to make the problem steady, just
8358 // assemble and solve the eigensystem
8359 else
8360 {
8361 const bool do_adjoint_problem = false;
8362 // Call the Eigenproblem for the eigensolver
8364 n_eval,
8365 eigenvalue,
8369 }
8370 }
8371
8372
8373 //==================================================================
8374 /// Solve the adjoint eigenproblem
8375 //==================================================================
8377 const unsigned& n_eval,
8378 Vector<std::complex<double>>& eigenvalue,
8381 const bool& make_timesteppers_steady)
8382 {
8383 // If the boolean flag is steady, then make all the timesteppers steady
8384 // before solving the eigenproblem. This will "switch off" the
8385 // time-derivative terms in the jacobian matrix
8387 {
8388 // Find out how many timesteppers there are
8389 const unsigned n_time_steppers = ntime_stepper();
8390
8391 // Vector of bools to store the is_steady status of the various
8392 // timesteppers when we came in here
8393 std::vector<bool> was_steady(n_time_steppers);
8394
8395 // Loop over them all and make them (temporarily) static
8396 for (unsigned i = 0; i < n_time_steppers; i++)
8397 {
8400 }
8401
8402 const bool do_adjoint_problem = true;
8403 // Call the Eigenproblem for the eigensolver
8405 n_eval,
8406 eigenvalue,
8410
8411 // Reset the is_steady status of all timesteppers that
8412 // weren't already steady when we came in here and reset their
8413 // weights
8414 for (unsigned i = 0; i < n_time_steppers; i++)
8415 {
8416 if (!was_steady[i])
8417 {
8419 }
8420 }
8421 }
8422 // Otherwise if we don't want to make the problem steady, just
8423 // assemble and solve the eigensystem
8424 else
8425 {
8426 const bool do_adjoint_problem = true;
8427 // Call the Eigenproblem for the eigensolver
8429 n_eval,
8430 eigenvalue,
8434 }
8435 }
8436
8437 //===================================================================
8438 /// Get the matrices required to solve an eigenproblem
8439 /// WARNING: temporarily this method only works with non-distributed
8440 /// matrices
8441 //===================================================================
8444 const double& shift)
8445 {
8446 // Three different cases again here:
8447 // 1) Compiled with MPI, but run in serial
8448 // 2) Compiled with MPI, but MPI not initialised in driver
8449 // 3) Serial version
8450
8451
8452#ifdef PARANOID
8453 if (mass_matrix.distribution_built() && main_matrix.distribution_built())
8454 {
8455 // Check that the distribution of the mass matrix and jacobian match
8456 if (!(*mass_matrix.distribution_pt() == *main_matrix.distribution_pt()))
8457 {
8458 std::ostringstream error_stream;
8460 << "The distributions of the jacobian and mass matrix are\n"
8461 << "not the same and they must be.\n";
8462 throw OomphLibError(
8464 }
8465
8466 if (mass_matrix.nrow() != this->ndof())
8467 {
8468 std::ostringstream error_stream;
8470 << "mass_matrix has a distribution, but the number of rows is not "
8471 << "equal to the number of degrees of freedom in the problem.";
8472 throw OomphLibError(
8474 }
8475
8476 if (main_matrix.nrow() != this->ndof())
8477 {
8478 std::ostringstream error_stream;
8480 << "main_matrix has a distribution, but the number of rows is not "
8481 << "equal to the number of degrees of freedom in the problem.";
8482 throw OomphLibError(
8484 }
8485 }
8486 // If the distributions are not the same, then complain
8487 else if (main_matrix.distribution_built() !=
8488 mass_matrix.distribution_built())
8489 {
8490 std::ostringstream error_stream;
8491 error_stream << "The distribution of the jacobian and mass matrix must "
8492 << "both be setup or both not setup";
8493 throw OomphLibError(
8495 }
8496#endif
8497
8498 // Store the old assembly handler
8500 // Now setup the eigenproblem handler, pass in the value of the shift
8502
8503 // Prepare the storage formats.
8506 Vector<double*> value(2);
8507 Vector<unsigned> nnz(2);
8508 // Allocate pointer to residuals, although not used in these problems
8510
8511 // determine the distribution for the jacobian (main matrix)
8512 // IF the jacobian has distribution setup then use that
8513 // ELSE determine the distribution based on the
8514 // distributed_matrix_distribution enum
8516 if (main_matrix.distribution_built())
8517 {
8518 dist_pt = new LinearAlgebraDistribution(main_matrix.distribution_pt());
8519 }
8520 else
8521 {
8523 }
8524
8525
8526 // The matrix is in compressed row format
8527 bool compressed_row_flag = true;
8528
8529#ifdef OOMPH_HAS_MPI
8530 //
8531 if (Communicator_pt->nproc() == 1)
8532 {
8533#endif
8534
8537 value,
8538 nnz,
8541
8542 // The main matrix is the first entry
8543 main_matrix.build(dist_pt);
8544 main_matrix.build_without_copy(dist_pt->nrow(),
8545 nnz[0],
8546 value[0],
8549 // The mass matrix is the second entry
8550 mass_matrix.build(dist_pt);
8551 mass_matrix.build_without_copy(dist_pt->nrow(),
8552 nnz[1],
8553 value[1],
8556#ifdef OOMPH_HAS_MPI
8557 }
8558 else
8559 {
8560 if (dist_pt->distributed())
8561 {
8565 value,
8566 nnz,
8568 // The main matrix is the first entry
8569 main_matrix.build(dist_pt);
8570 main_matrix.build_without_copy(dist_pt->nrow(),
8571 nnz[0],
8572 value[0],
8575 // The mass matrix is the second entry
8576 mass_matrix.build(dist_pt);
8577 mass_matrix.build_without_copy(dist_pt->nrow(),
8578 nnz[1],
8579 value[1],
8582 }
8583 else
8584 {
8590 value,
8591 nnz,
8593 // The main matrix is the first entry
8595 main_matrix.build_without_copy(dist_pt->nrow(),
8596 nnz[0],
8597 value[0],
8600 main_matrix.redistribute(dist_pt);
8601 // The mass matrix is the second entry
8603 mass_matrix.build_without_copy(dist_pt->nrow(),
8604 nnz[1],
8605 value[1],
8608 mass_matrix.redistribute(dist_pt);
8609 delete temp_dist_pt;
8610 }
8611 }
8612#endif
8613
8614 // clean up dist_pt and residuals_vector pt
8615 delete dist_pt;
8616
8617 // Delete the eigenproblem handler
8618 delete Assembly_handler_pt;
8619 // Reset the assembly handler to the original handler
8621 }
8622
8623
8624 //=======================================================================
8625 /// Stored the current values of the dofs
8626 //=======================================================================
8628 {
8629 // If memory has not been allocated, then allocated memory for the saved
8630 // dofs
8631 if (Saved_dof_pt == 0)
8632 {
8634 }
8635
8636#ifdef OOMPH_HAS_MPI
8637 // If the problem is distributed I have to do something different
8639 {
8640 // How many entries do we store locally?
8641 const unsigned n_row_local = Dof_distribution_pt->nrow_local();
8642
8643 // Resize the vector
8644 Saved_dof_pt->resize(n_row_local);
8645
8646 // Back 'em up
8647 for (unsigned i = 0; i < n_row_local; i++)
8648 {
8649 (*Saved_dof_pt)[i] = *(this->Dof_pt[i]);
8650 }
8651 }
8652 // Otherwise just store all the dofs
8653 else
8654#endif
8655 {
8656 // Find the number of dofs
8657 unsigned long n_dof = ndof();
8658
8659 // Resize the vector
8660 Saved_dof_pt->resize(n_dof);
8661
8662 // Transfer the values over
8663 for (unsigned long n = 0; n < n_dof; n++)
8664 {
8665 (*Saved_dof_pt)[n] = dof(n);
8666 }
8667 }
8668 }
8669
8670 //====================================================================
8671 /// Restore the saved dofs
8672 //====================================================================
8674 {
8675 // Check that we can do this
8676 if (Saved_dof_pt == 0)
8677 {
8678 throw OomphLibError(
8679 "There are no stored values, use store_current_dof_values()\n",
8682 }
8683
8684
8685#ifdef OOMPH_HAS_MPI
8686 // If the problem is distributed I have to do something different
8688 {
8689 // How many entries do we store locally?
8690 const unsigned n_row_local = Dof_distribution_pt->nrow_local();
8691
8692 if (Saved_dof_pt->size() != n_row_local)
8693 {
8694 throw OomphLibError("The number of stored values is not equal to the "
8695 "current number of dofs\n",
8698 }
8699
8700 // Transfer the values over
8701 for (unsigned long n = 0; n < n_row_local; n++)
8702 {
8703 *(this->Dof_pt[n]) = (*Saved_dof_pt)[n];
8704 }
8705 }
8706 // Otherwise just restore all the dofs
8707 else
8708#endif
8709 {
8710 // Find the number of dofs
8711 unsigned long n_dof = ndof();
8712
8713 if (Saved_dof_pt->size() != n_dof)
8714 {
8715 throw OomphLibError("The number of stored values is not equal to the "
8716 "current number of dofs\n",
8719 }
8720
8721 // Transfer the values over
8722 for (unsigned long n = 0; n < n_dof; n++)
8723 {
8724 dof(n) = (*Saved_dof_pt)[n];
8725 }
8726 }
8727
8728 // Delete the memory
8729 delete Saved_dof_pt;
8730 Saved_dof_pt = 0;
8731 }
8732
8733 //======================================================================
8734 /// Assign the eigenvector passed to the function to the dofs
8735 //======================================================================
8737 {
8738 unsigned long n_dof = ndof();
8739 // Check that the eigenvector has the correct size
8740 if (eigenvector.nrow() != n_dof)
8741 {
8742 std::ostringstream error_message;
8743 error_message << "Eigenvector has size " << eigenvector.nrow()
8744 << ", not equal to the number of dofs in the problem,"
8745 << n_dof << std::endl;
8746
8747 throw OomphLibError(
8748 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
8749 }
8750
8751 // Ensure that the eigenvector distribution matches the dof distribution
8752 // Copy vector
8754 // Redistribute the copy to the dof distribution
8755 eigenvector_dof.redistribute(this->Dof_distribution_pt);
8756
8757 // Loop over the dofs and assign the eigenvector
8758 for (unsigned long n = 0; n < eigenvector_dof.nrow_local(); n++)
8759 {
8760 dof(n) = eigenvector_dof[n];
8761 }
8762// Of course we now need to synchronise
8763#ifdef OOMPH_HAS_MPI
8764 this->synchronise_all_dofs();
8765#endif
8766 }
8767
8768
8769 //======================================================================
8770 /// Add the eigenvector passed to the function to the dofs with
8771 /// magnitude epsilon
8772 //======================================================================
8773 void Problem::add_eigenvector_to_dofs(const double& epsilon,
8775 {
8776 unsigned long n_dof = ndof();
8777 // Check that the eigenvector has the correct size
8778 if (eigenvector.nrow() != n_dof)
8779 {
8780 std::ostringstream error_message;
8781 error_message << "Eigenvector has size " << eigenvector.nrow()
8782 << ", not equal to the number of dofs in the problem,"
8783 << n_dof << std::endl;
8784
8785 throw OomphLibError(
8786 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
8787 }
8788
8789 // Ensure that the eigenvector distribution matches the dof distribution
8790 // Copy vector
8792 // Redistribute the copy to the dof distribution
8793 eigenvector_dof.redistribute(this->Dof_distribution_pt);
8794
8795
8796 // Loop over the dofs and add the eigenvector
8797 // Only use local values
8798 for (unsigned long n = 0; n < eigenvector.nrow_local(); n++)
8799 {
8800 dof(n) += epsilon * eigenvector[n];
8801 }
8802// Of course we now need to synchronise
8803#ifdef OOMPH_HAS_MPI
8804 this->synchronise_all_dofs();
8805#endif
8806 }
8807
8808
8809 //================================================================
8810 /// General Newton solver. Requires only a convergence tolerance.
8811 /// The linear solver takes a pointer to the problem (which defines
8812 /// the Jacobian \b J and the residual Vector \b r) and returns
8813 /// the solution \b x of the system
8814 /// \f[ {\bf J} {\bf x} = - \bf{r} \f].
8815 //================================================================
8817 {
8818 // Initialise timers
8819 double total_linear_solver_time = 0.0;
8820 double t_start = TimingHelpers::timer();
8821 Max_res.clear();
8822
8823 // Find total number of dofs
8824 unsigned long n_dofs = ndof();
8825
8826 // Set up the Vector to hold the solution
8828
8829 //-----Variables for the globally convergent Newton method------
8830
8831 // Set up the vector to hold the gradient
8833
8834 // Other variables
8835 double half_residual_squared = 0.0;
8836 double max_step = 0.0;
8837
8838 //--------------------------------------------------------------
8839
8840 // Set the counter
8841 unsigned count = 0;
8842 // Set the loop flag
8843 unsigned LOOP_FLAG = 1;
8844
8846 {
8847#ifdef OOMPH_HAS_MPI
8848 // Break if running in parallel
8850 {
8851 std::ostringstream error_stream;
8852 error_stream << "Globally convergent Newton method has not been "
8853 << "implemented in parallel yet!" << std::endl;
8854 throw OomphLibError(
8856 }
8857#endif
8858
8859 // Get gradient
8861 // Reset the gradient (clear it), since the number of dofs and
8862 // hence the size of the DoubleVector might have changed
8864 }
8865
8866 // Update anything that needs updating
8868
8869 // Reset number of Newton iterations taken
8871
8872 // Now do the Newton loop
8873 do
8874 {
8875 count++;
8876
8877 // Do any updates that are required
8879
8880
8881 // No degrees of freedom? What are you solving for?
8882 if (n_dofs == 0)
8883 {
8884 oomph_info << std::endl << std::endl << std::endl;
8885 oomph_info << "This is a bit bizarre: The problem has no dofs."
8886 << std::endl;
8888 << "I'll just return from the Newton solver without doing anything."
8889 << std::endl;
8890
8891 // Do any updates that would have been performed
8896
8897 oomph_info << "I hope this is what you intended me to do..."
8898 << std::endl;
8899 oomph_info << std::endl
8900 << "Note: All actions_...() functions were called"
8901 << std::endl;
8902 oomph_info << std::endl << " before returning." << std::endl;
8903 oomph_info << std::endl << std::endl << std::endl;
8904 return;
8905 }
8906
8907 // Calculate initial residuals
8908 if (count == 1)
8909 {
8910 // Is the problem nonlinear? If not ignore the pre-iteration
8911 // convergence check.
8913 {
8914#ifdef OOMPH_HAS_MPI
8915 // Synchronise the solution on different processors (on each submesh)
8916 this->synchronise_all_dofs();
8917#endif
8918
8920 dx.clear();
8922
8923 // Get half of squared residual and find maximum step length
8924 // for step length control
8926 {
8928 double sum = 0.0;
8929 for (unsigned i = 0; i < n_dofs; i++)
8930 {
8931 sum += (*Dof_pt[i]) * (*Dof_pt[i]);
8932 half_residual_squared += dx[i] * dx[i];
8933 }
8934 half_residual_squared *= 0.5;
8935 max_step = 100.0 * std::max(sqrt(sum), double(n_dofs));
8936 }
8937
8938 // Get maximum residuals
8939 double maxres = dx.max();
8940 Max_res.push_back(maxres);
8941
8943 {
8944 // Let's output the residuals
8945 // unsigned n_row_local = dx.distribution_pt()->nrow_local();
8946 // unsigned first_row = dx.distribution_pt()->first_row();
8947 // for(unsigned n=0;n<n_row_local;n++)
8948 //{
8949 // oomph_info << "residual: " << n + first_row << " " << dx[n] <<
8950 // "\n";
8951 //}
8952
8953 oomph_info << "\nInitial Maximum residuals " << maxres << std::endl;
8954 }
8955
8956 if ((maxres < Newton_solver_tolerance) &&
8958 {
8959 LOOP_FLAG = 0;
8960 continue;
8961 }
8962 }
8963 else
8964 {
8966 {
8968 << "Linear problem -- convergence in one iteration assumed."
8969 << std::endl;
8970 }
8971 }
8972 }
8973
8974
8975 // Increment number of Newton iterations taken
8977
8978 // Initialise timer for linear solver
8980
8981 // Now do the linear solve -- recycling Jacobian if requested
8983 {
8985 {
8986 oomph_info << "Not recomputing Jacobian! " << std::endl;
8987 }
8988
8989 // If we're doing the first iteration and the problem is nonlinear,
8990 // the residuals have already been computed above during the
8991 // initial convergence check. Otherwise compute them here.
8992 if ((count != 1) || (!Problem_is_nonlinear)) get_residuals(dx);
8993
8994 // Backup residuals
8996
8997 // Resolve
8999 }
9000 else
9001 {
9003 {
9005 {
9006 oomph_info << "Enabling resolve" << std::endl;
9007 }
9009 }
9010 Linear_solver_pt->solve(this, dx);
9012 }
9013
9014 // End of linear solver
9017
9019 {
9020 oomph_info << std::endl;
9021 oomph_info << "Time for linear solver (ndof=" << n_dofs << "): "
9024 << std::endl
9025 << std::endl;
9026 }
9027
9028 // Subtract the new values from the true dofs
9029 dx.redistribute(Dof_distribution_pt);
9030 double* dx_pt = dx.values_pt();
9032
9034 {
9035 // Get the gradient
9037
9038 for (unsigned i = 0; i < ndof_local; i++)
9039 {
9040 dx_pt[i] *= -1.0;
9041 }
9042
9043 // Update with steplength control
9045
9046 for (unsigned i = 0; i < ndof_local; i++)
9047 {
9048 unknowns_old[i] = *Dof_pt[i];
9049 }
9050
9054 gradient,
9055 dx,
9057 max_step);
9058 }
9059 // direct Newton update
9060 else
9061 {
9062 for (unsigned l = 0; l < ndof_local; l++)
9063 {
9065 }
9066 }
9067#ifdef OOMPH_HAS_MPI
9068 // Synchronise the solution on different processors (on each submesh)
9069 this->synchronise_all_dofs();
9070#endif
9071
9072 // Do any updates that are required
9075
9076 // Maximum residuals
9077 double maxres = 0.0;
9078 // If the user has declared that the Problem is linear
9079 // we ignore the convergence check
9081 {
9082 // Get the maximum residuals
9083 // maxres = std::fabs(*std::max_element(dx.begin(),dx.end(),
9084 // AbsCmp<double>()));
9085 // oomph_info << "Maxres correction " << maxres << "\n";
9086
9087 // Calculate the new residuals
9088 dx.clear();
9090
9091 // Get the maximum residuals
9092 maxres = dx.max();
9093 Max_res.push_back(maxres);
9094
9096 {
9097 oomph_info << "Newton Step " << count << ": Maximum residuals "
9098 << maxres << std::endl
9099 << std::endl;
9100 }
9101 }
9102
9103 // If we have converged jump straight to the test at the end of the loop
9104 if (maxres < Newton_solver_tolerance)
9105 {
9106 LOOP_FLAG = 0;
9107 continue;
9108 }
9109
9110 // This section will not be reached if we have converged already
9111 // If the maximum number of residuals is too high or the maximum number
9112 // of iterations has been reached
9113 if ((maxres > Max_residuals) || (count == Max_newton_iterations))
9114 {
9115 // Print a warning -- regardless of what the throw does
9116 if (maxres > Max_residuals)
9117 {
9118 oomph_info << "Max. residual (" << Max_residuals
9119 << ") has been exceeded in Newton solver." << std::endl;
9120 }
9122 {
9123 oomph_info << "Reached max. number of iterations ("
9124 << Max_newton_iterations << ") in Newton solver."
9125 << std::endl;
9126 }
9127 // Now throw...
9128 throw NewtonSolverError(count, maxres);
9129 }
9130
9131 } while (LOOP_FLAG);
9132
9133 // Now update anything that needs updating
9135
9136 // Finalise/doc timings
9138 {
9139 oomph_info << std::endl;
9140 oomph_info << "Total time for linear solver (ndof=" << n_dofs << "): "
9143 << std::endl;
9144 }
9145
9146 double t_end = TimingHelpers::timer();
9147 double total_time = t_end - t_start;
9148
9150 {
9151 oomph_info << "Total time for Newton solver (ndof=" << n_dofs << "): "
9153 << std::endl;
9154 }
9155 if (total_time > 0.0)
9156 {
9158 {
9159 oomph_info << "Time outside linear solver : "
9161 100.0
9162 << " %" << std::endl;
9163 }
9164 }
9165 else
9166 {
9168 {
9169 oomph_info << "Time outside linear solver : "
9170 << "[too fast]" << std::endl;
9171 }
9172 }
9173 if (!Shut_up_in_newton_solve) oomph_info << std::endl;
9174 }
9175
9176 //========================================================================
9177 /// Helper function for the globally convergent Newton solver
9178 //========================================================================
9180 const Vector<double>& x_old,
9181 const double& half_residual_squared_old,
9184 double& half_residual_squared,
9185 const double& stpmax)
9186 {
9187 const double min_fct_decrease = 1.0e-4;
9188 double convergence_tol_on_x = 1.0e-16;
9189 double f_aux = 0.0;
9190 double lambda_aux = 0.0;
9191 double proposed_lambda;
9192 unsigned long n_dof = ndof();
9193 double sum = 0.0;
9194 for (unsigned i = 0; i < n_dof; i++)
9195 {
9196 sum += newton_dir[i] * newton_dir[i];
9197 }
9198 sum = sqrt(sum);
9199 if (sum > stpmax)
9200 {
9201 for (unsigned i = 0; i < n_dof; i++)
9202 {
9203 newton_dir[i] *= stpmax / sum;
9204 }
9205 }
9206 double slope = 0.0;
9207 for (unsigned i = 0; i < n_dof; i++)
9208 {
9209 slope += gradient[i] * newton_dir[i];
9210 }
9211 if (slope >= 0.0)
9212 {
9213 std::ostringstream warn_message;
9214 warn_message << "WARNING: Non-negative slope, probably due to a "
9215 << " roundoff \nproblem in the linesearch: slope=" << slope
9216 << "\n";
9218 "Problem::globally_convergent_line_search()",
9220 }
9221 double test = 0.0;
9222 for (unsigned i = 0; i < n_dof; i++)
9223 {
9224 double temp =
9225 std::fabs(newton_dir[i]) / std::max(std::fabs(x_old[i]), 1.0);
9226 if (temp > test) test = temp;
9227 }
9229 double lambda = 1.0;
9230 while (true)
9231 {
9232 for (unsigned i = 0; i < n_dof; i++)
9233 {
9234 *Dof_pt[i] = x_old[i] + lambda * newton_dir[i];
9235 }
9236
9237 // Evaluate current residuals
9241 for (unsigned i = 0; i < n_dof; i++)
9242 {
9244 }
9245 half_residual_squared *= 0.5;
9246
9247 if (lambda < lambda_min)
9248 {
9249 for (unsigned i = 0; i < n_dof; i++) *Dof_pt[i] = x_old[i];
9250
9251 std::ostringstream warn_message;
9252 warn_message << "WARNING: Line search converged on x only!\n";
9254 "Problem::globally_convergent_line_search()",
9256 return;
9257 }
9258 else if (half_residual_squared <=
9260 {
9261 oomph_info << "Returning from linesearch with lambda=" << lambda
9262 << std::endl;
9263 return;
9264 }
9265 else
9266 {
9267 if (lambda == 1.0)
9268 {
9270 -slope /
9272 }
9273 else
9274 {
9275 double r1 =
9278 double a_poly =
9279 (r1 / (lambda * lambda) - r2 / (lambda_aux * lambda_aux)) /
9280 (lambda - lambda_aux);
9281 double b_poly = (-lambda_aux * r1 / (lambda * lambda) +
9282 lambda * r2 / (lambda_aux * lambda_aux)) /
9283 (lambda - lambda_aux);
9284 if (a_poly == 0.0)
9285 {
9286 proposed_lambda = -slope / (2.0 * b_poly);
9287 }
9288 else
9289 {
9290 double discriminant = b_poly * b_poly - 3.0 * a_poly * slope;
9291 if (discriminant < 0.0)
9292 {
9293 proposed_lambda = 0.5 * lambda;
9294 }
9295 else if (b_poly <= 0.0)
9296 {
9297 proposed_lambda = (-b_poly + sqrt(discriminant)) / (3.0 * a_poly);
9298 }
9299 else
9300 {
9302 }
9303 }
9304 if (proposed_lambda > 0.5 * lambda)
9305 {
9306 proposed_lambda = 0.5 * lambda;
9307 }
9308 }
9309 }
9310 lambda_aux = lambda;
9312 lambda = std::max(proposed_lambda, 0.1 * lambda);
9313 }
9314 }
9315
9316
9317 //========================================================================
9318 /// Solve a steady problem, in the context of an overall unsteady problem.
9319 /// This is achieved by setting the weights in the timesteppers to be zero
9320 /// which has the effect of rendering them steady timesteppers
9321 /// The optional argument max_adapt specifies the max. number of
9322 /// adaptations of all refineable submeshes are performed to
9323 /// achieve the the error targets specified in the refineable submeshes.
9324 //========================================================================
9326 {
9327 // Find out how many timesteppers there are
9328 unsigned n_time_steppers = ntime_stepper();
9329
9330 // Vector of bools to store the is_steady status of the various
9331 // timesteppers when we came in here
9332 std::vector<bool> was_steady(n_time_steppers);
9333
9334 // Loop over them all and make them (temporarily) static
9335 for (unsigned i = 0; i < n_time_steppers; i++)
9336 {
9339 }
9340
9341 try
9342 {
9343 // Solve the non-linear problem with Newton's method
9344 if (max_adapt == 0)
9345 {
9346 newton_solve();
9347 }
9348 else
9349 {
9351 }
9352 }
9353 // Catch any exceptions thrown in the Newton solver
9354 catch (NewtonSolverError& error)
9355 {
9356 oomph_info << std::endl
9357 << "USER-DEFINED ERROR IN NEWTON SOLVER " << std::endl;
9358 // Check whether it's the linear solver
9359 if (error.linear_solver_error())
9360 {
9361 oomph_info << "ERROR IN THE LINEAR SOLVER" << std::endl;
9362 }
9363 // Check to see whether we have reached Max_iterations
9364 else if (error.iterations() == Max_newton_iterations)
9365 {
9366 oomph_info << "MAXIMUM NUMBER OF ITERATIONS (" << error.iterations()
9367 << ") REACHED WITHOUT CONVERGENCE " << std::endl;
9368 }
9369 // If not, it must be that we have exceeded the maximum residuals
9370 else
9371 {
9372 oomph_info << "MAXIMUM RESIDUALS: " << error.maxres()
9373 << " EXCEEDS PREDEFINED MAXIMUM " << Max_residuals
9374 << std::endl;
9375 }
9376
9377 // Die horribly!!
9378 std::ostringstream error_stream;
9379 error_stream << "Error occured in Newton solver. " << std::endl;
9380 throw OomphLibError(
9382 }
9383
9384
9385 // Reset the is_steady status of all timesteppers that
9386 // weren't already steady when we came in here and reset their
9387 // weights
9388 for (unsigned i = 0; i < n_time_steppers; i++)
9389 {
9390 if (!was_steady[i])
9391 {
9393 }
9394 }
9395
9396 // Since we performed a steady solve, the history values
9397 // now have to be set as if we had performed an impulsive start from
9398 // the current solution. This ensures that the time-derivatives
9399 // evaluate to zero even now that the timesteppers have been
9400 // reactivated.
9402 }
9403
9404 //===========================================================================
9405 /// Perform a basic continuation step using Newton's method. The governing
9406 /// parameter of the problem is passed as a pointer to the routine. The
9407 /// number of Newton steps taken is returned
9408 //==========================================================================
9410 {
9411 // Set up memory for z
9412 // unsigned long n_dofs = ndof();
9413 // LinearAlgebraDistribution dist(Communicator_pt,n_dofs,false);
9414 // DoubleVector z(&dist,0.0);
9415 DoubleVector z;
9416 // Call the solver
9418 }
9419
9420
9421 //===================================================================
9422 /// This function performs a basic continuation step using the Newton method.
9423 /// The number of Newton steps taken is returned, to be used in any
9424 /// external step-size control routines.
9425 /// The governing parameter of the problem is passed as a pointer to the
9426 /// routine, as is the sign of the Jacobian and a Vector in which
9427 /// to store the derivatives wrt the parameter, if required.
9428 //==================================================================
9430 DoubleVector& z)
9431 {
9432 // Find the total number of dofs
9433 // unsigned long n_dofs = ndof();
9434
9435 // Find the local number of dofs
9437
9438 // create the distribution (not distributed)
9439 // LinearAlgebraDistribution dist(this->communicator_pt(),n_dofs,false);
9440
9441 // Assign memory for solutions of the equations
9442 // DoubleVector y(&dist,0.0);
9443 DoubleVector y;
9444
9445 // Assign memory for the dot products of the uderivatives and y and z
9446 double uderiv_dot_y = 0.0, uderiv_dot_z = 0.0;
9447 // Set and initialise the counter
9448 unsigned count = 0;
9449 // Set the loop flag
9450 unsigned LOOP_FLAG = 1;
9451
9452 // Update anything that needs updating
9454
9455 // Check the arc-length constraint
9456 double arc_length_constraint_residual = 0.0;
9457
9458 // Are we storing the matrix in the linear solve
9459 bool enable_resolve = Linear_solver_pt->is_resolve_enabled();
9460
9461 // For this problem, we must store the residuals
9463
9464 // Now do the Newton loop
9465 do
9466 {
9467 count++;
9468
9469 // Do any updates that are required
9471
9472 // Calculate initial residuals
9473 if (count == 1)
9474 {
9475#ifdef OOMPH_HAS_MPI
9476 // Synchronise the solution on different processors (on each submesh)
9477 this->synchronise_all_dofs();
9478#endif
9479
9481 y.clear();
9482 get_residuals(y);
9483 // Get maximum residuals, using our own abscmp function
9484 double maxres = y.max();
9485
9486 // Assemble the residuals for the arc-length step
9488 // Add the variables
9489 for (unsigned long l = 0; l < ndof_local; l++)
9490 {
9493 }
9494
9495 // Now reduce if we have been distributed
9496#ifdef OOMPH_HAS_MPI
9499 (Dof_distribution_pt->communicator_pt()->nproc() > 1))
9500 {
9503 1,
9504 MPI_DOUBLE,
9505 MPI_SUM,
9506 Dof_distribution_pt->communicator_pt()->mpi_comm());
9507 }
9509#endif
9510
9514 Ds_current;
9515
9516 // Is it the max
9517 if (std::fabs(arc_length_constraint_residual) > maxres)
9518 {
9519 maxres = std::fabs(arc_length_constraint_residual);
9520 }
9521
9522 // Find the max
9524 {
9525 oomph_info << "Initial Maximum residuals " << maxres << std::endl;
9526 }
9527
9528 // If we are below the Tolerance, then return immediately
9529 if ((maxres < Newton_solver_tolerance) &&
9531 {
9532 LOOP_FLAG = 0;
9533 count = 0;
9534 continue;
9535 }
9536 }
9537
9538 // If it's the block hopf solver we need to solve for both rhs's
9539 // simultaneously. This is because the block decomposition involves
9540 // solves with two different matrices and storing both at once to
9541 // allow general resolves would be more expensive than necessary.
9542 if (dynamic_cast<BlockHopfLinearSolver*>(Linear_solver_pt))
9543 {
9544 // Get the vector dresiduals/dparameter
9545 z.clear();
9547
9548 // Copy rhs vector into local storage so it doesn't get overwritten
9549 // if the linear solver decides to initialise the solution vector, say,
9550 // which it's quite entitled to do!
9552
9553 // Solve the system for the two right-hand sides.
9555 ->solve_for_two_rhs(this, y, input_z, z);
9556 }
9557 // Otherwise
9558 else
9559 {
9560 // Solve the standard problem
9561 Linear_solver_pt->solve(this, y);
9562
9563 // Get the vector dresiduals/dparameter
9564 z.clear();
9566
9567 // Copy rhs vector into local storage so it doesn't get overwritten
9568 // if the linear solver decides to initialise the solution vector, say,
9569 // which it's quite entitled to do!
9571
9572 // Redistribute the RHS to match the linear solver
9573 // input_z.redistribute(Linear_solver_pt->distribution_pt());
9574 // Do not clear z because we assume that it has dR/dparam
9575 z.clear();
9576 // Now resolve the system with the new RHS
9578 }
9579
9580 // Redistribute the results into the natural distribution
9583
9584 // Now we need to calculate dparam, for which we must calculate the
9585 // dot product of the derivatives and y and z
9586 // Reset these values to zero
9587 uderiv_dot_y = 0.0;
9588 uderiv_dot_z = 0.0;
9589 // Now calculate the dot products of the derivative and the solutions
9590 // of the linear system
9591 // Cache pointers to the data in the distributed vectors
9592 double* const y_pt = y.values_pt();
9593 double* const z_pt = z.values_pt();
9594 for (unsigned long l = 0; l < ndof_local; l++)
9595 {
9598 }
9599
9600 // Now reduce if we have been distributed
9601#ifdef OOMPH_HAS_MPI
9602 // Create send and receive arrays of size two
9603 double uderiv_dot[2];
9604 double uderiv_dot2[2];
9609 // Now reduce both together
9611 (Dof_distribution_pt->communicator_pt()->nproc() > 1))
9612 {
9615 2,
9616 MPI_DOUBLE,
9617 MPI_SUM,
9618 Dof_distribution_pt->communicator_pt()->mpi_comm());
9619 }
9622#endif
9623
9624 // Now scale the results
9627
9628 // The set the change in the parameter, given by the pseudo-arclength
9629 // equation. Note that here we are assuming that the arc-length
9630 // equation is always exactly zero,
9631 // which seems to work OK, and saves on some storage.
9632 // In fact, it's more subtle than that. If we include this
9633 // proper residual then we will have to solve the eigenproblem.
9634 // This will make the solver more robust and *should* be done
9635 // ... at some point.
9638
9639 // Set the new value of the parameter
9640 *parameter_pt -= dparam;
9641
9642 // Update the values of the other degrees of freedom
9643 for (unsigned long l = 0; l < ndof_local; l++)
9644 {
9645 *Dof_pt[l] -= y_pt[l] - dparam * z_pt[l];
9646 }
9647
9648 // Calculate the new residuals
9649#ifdef OOMPH_HAS_MPI
9650 // Synchronise the solution on different processors (on each submesh)
9651 this->synchronise_all_dofs();
9652#endif
9653
9654 // Do any updates that are required
9657
9658 y.clear();
9659 get_residuals(y);
9660
9661 // Get the maximum residuals
9662 double maxres = y.max();
9663
9664 // Assemble the residuals for the arc-length step
9666 // Add the variables
9667 for (unsigned long l = 0; l < ndof_local; l++)
9668 {
9671 }
9672
9673 // Now reduce if we have been distributed
9674#ifdef OOMPH_HAS_MPI
9677 (Dof_distribution_pt->communicator_pt()->nproc() > 1))
9678 {
9681 1,
9682 MPI_DOUBLE,
9683 MPI_SUM,
9684 Dof_distribution_pt->communicator_pt()->mpi_comm());
9685 }
9687#endif
9688
9692
9693 // Is it the max
9694 if (std::fabs(arc_length_constraint_residual) > maxres)
9695 {
9696 maxres = std::fabs(arc_length_constraint_residual);
9697 }
9698
9700 {
9701 oomph_info << "Continuation Step " << count << ": Maximum residuals "
9702 << maxres << "\n";
9703 }
9704
9705 // If we have converged jump straight to the test at the end of the loop
9706 if (maxres < Newton_solver_tolerance)
9707 {
9708 LOOP_FLAG = 0;
9709 continue;
9710 }
9711
9712 // This section will not be reached if we have converged already
9713 // If the maximum number of residuals is too high or the maximum number
9714 // of iterations has been reached
9715 if ((maxres > Max_residuals) || (count == Max_newton_iterations))
9716 {
9717 throw NewtonSolverError(count, maxres);
9718 }
9719
9720 } while (LOOP_FLAG);
9721
9722 // Now update anything that needs updating
9724
9725 // Reset the storage of the matrix on the linear solver to what it was
9726 // on entry to this routine
9727 if (enable_resolve)
9728 {
9730 }
9731 else
9732 {
9734 }
9735
9736 // Return the number of Newton Steps taken
9737 return count;
9738 }
9739
9740 //=========================================================================
9741 /// A function to calculate the derivatives wrt the arc-length. This version
9742 /// of the function actually does a linear solve so that the derivatives
9743 /// are calculated "exactly" rather than using the values at the Newton
9744 /// step just before convergence. This is only necessary in spatially adaptive
9745 /// problems, in which the number of degrees of freedom changes and so
9746 /// the appropriate derivatives must be calculated for the new variables.
9747 //=========================================================================
9749 {
9750 // Find the number of degrees of freedom in the problem
9751 const unsigned long n_dofs = ndof();
9752
9753 // create a non-distributed z vector
9755
9756 // Assign memory for solutions of the equations
9757 DoubleVector z(&dist, 0.0);
9758
9759 // If it's the block hopf solver need to solve for both RHS
9760 // at once, but this would all be alleviated if we have the solve
9761 // for the non-residuals RHS.
9762 if (dynamic_cast<BlockHopfLinearSolver*>(Linear_solver_pt))
9763 {
9764 // Get the vector dresiduals/dparameter
9766
9767 // Copy rhs vector into local storage so it doesn't get overwritten
9768 // if the linear solver decides to initialise the solution vector, say,
9769 // which it's quite entitled to do!
9770 DoubleVector dummy(&dist, 0.0), input_z(z);
9771
9772 // Solve for the two RHSs
9774 ->solve_for_two_rhs(this, dummy, input_z, z);
9775 }
9776 // Otherwise we can use the normal resolve
9777 else
9778 {
9779 // Save the status before entry to this routine
9780 bool enable_resolve = Linear_solver_pt->is_resolve_enabled();
9781
9782 // We need to do resolves
9784
9785 // Solve the standard problem, we only want to make sure that
9786 // we factorise the matrix, if it has not been factorised. We shall
9787 // ignore the return value of z.
9788 Linear_solver_pt->solve(this, z);
9789
9790 // Get the vector dresiduals/dparameter
9792
9793
9794 // Copy rhs vector into local storage so it doesn't get overwritten
9795 // if the linear solver decides to initialise the solution vector, say,
9796 // which it's quite entitled to do!
9798
9799 // Now resolve the system with the new RHS and overwrite the solution
9801
9802 // Restore the storage status of the linear solver
9803 if (enable_resolve)
9804 {
9806 }
9807 else
9808 {
9810 }
9811 }
9812
9813 // Now, we can calculate the derivatives, etc
9815 }
9816
9817 //=======================================================================
9818 /// A function to calculate the derivatives with respect to the arc-length
9819 /// required for continuation. The arguments is the solution of the
9820 /// linear system,
9821 /// Jz = dR/dparameter, that gives du/dparameter and the direction
9822 /// output from the newton_solve_continuation function. The derivatives
9823 /// are stored in the ContinuationParameters namespace.
9824 //===================================================================
9826 {
9827 // Calculate the continuation derivatives
9829
9830 // Scale the value of theta if the control flag is set
9831 if (Scale_arc_length)
9832 {
9833 // Don't divide by zero!
9834 if (Parameter_derivative != 1.0)
9835 {
9840
9841 // Recalculate the continuation derivatives with the new scaled values
9843 }
9844 }
9845 }
9846
9847 //=======================================================================
9848 /// A function to calculate the derivatives with respect to the arc-length
9849 /// required for continuation using finite differences.
9850 //===================================================================
9852 double* const& parameter_pt)
9853 {
9854 // Calculate the continuation derivatives
9856
9857 // Scale the value of theta if the control flag is set
9858 if (Scale_arc_length)
9859 {
9860 // Don't divide by zero!
9861 if (Parameter_derivative != 1.0)
9862 {
9867
9868 // Recalculate the continuation derivatives with the new scaled values
9870 }
9871 }
9872 }
9873
9874 //======================================================================
9875 /// Function that returns a boolean flag to indicate whether the pointer
9876 /// parameter_pt refers to memory that is a value in a Data object used
9877 /// within the problem
9878 //======================================================================
9880 double* const& parameter_pt)
9881 {
9882 // Firstly check the global data
9883 const unsigned n_global = Global_data_pt.size();
9884 for (unsigned i = 0; i < n_global; ++i)
9885 {
9886 // If we find it then return true
9887 if (Global_data_pt[i]->does_pointer_correspond_to_value(parameter_pt))
9888 {
9889 return true;
9890 }
9891 }
9892
9893 // If we find the pointer in the mesh data return true
9895 {
9896 return true;
9897 }
9898
9899 // Loop over the submeshes to handle the case of spine data
9900 const unsigned n_sub_mesh = this->nsub_mesh();
9901 // If there is only one mesh
9902 if (n_sub_mesh == 0)
9903 {
9904 if (SpineMesh* const spine_mesh_pt = dynamic_cast<SpineMesh*>(Mesh_pt))
9905 {
9906 if (spine_mesh_pt->does_pointer_correspond_to_spine_data(parameter_pt))
9907 {
9908 return true;
9909 }
9910 }
9911 }
9912 // Otherwise loop over the sub meshes
9913 else
9914 {
9915 // Assign global equation numbers first
9916 for (unsigned i = 0; i < n_sub_mesh; i++)
9917 {
9918 if (SpineMesh* const spine_mesh_pt =
9919 dynamic_cast<SpineMesh*>(Sub_mesh_pt[i]))
9920 {
9921 if (spine_mesh_pt->does_pointer_correspond_to_spine_data(
9922 parameter_pt))
9923 {
9924 return true;
9925 }
9926 }
9927 }
9928 }
9929
9930 // If we have got here then the data is not stored in the problem, so return
9931 // false
9932 return false;
9933 }
9934
9935
9936 //=======================================================================
9937 /// A private helper function to
9938 /// calculate the derivatives with respect to the arc-length
9939 /// required for continuation. The arguments is the solution of the
9940 /// linear system,
9941 /// Jz = dR/dparameter, that gives du/dparameter and the direction
9942 /// output from the newton_solve_continuation function. The derivatives
9943 /// are stored in the ContinuationParameters namespace.
9944 //===================================================================
9946 {
9947 // Find the number of degrees of freedom in the problem
9948 // unsigned long n_dofs = ndof();
9949 // Find the number of local dofs in the problem
9950 const unsigned long ndof_local = Dof_distribution_pt->nrow_local();
9951
9952 // Work out the continuation direction
9953 // The idea is that (du/ds)_{old} . (du/ds)_{new} >= 0
9954 // if the direction is to remain the same.
9955 // du/ds_{new} = [dlambda/ds; du/ds] = [dlambda/ds ; - dlambda/ds z]
9956 // so (du/ds)_{new} . (du/ds)_{old}
9957 // = dlambda/ds [1 ; - z] . [ Parameter_derivative ; Dof_derivatives]
9958 // = dlambda/ds (Parameter_derivative - Dof_derivative . z)
9959
9960 // Create a local copy of z that can be redistributed without breaking
9961 // the constness of z
9963
9964 // Redistribute z so that it has the (natural) dof distribution
9965 local_z.redistribute(Dof_distribution_pt);
9966
9967 // Calculate the local contribution to the Continuation direction
9969 // Cache the pointer to z
9970 double* const local_z_pt = local_z.values_pt();
9971 for (unsigned long l = 0; l < ndof_local; l++)
9972 {
9974 }
9975
9976 // Now reduce if we have been distributed
9977#ifdef OOMPH_HAS_MPI
9980 (Dof_distribution_pt->communicator_pt()->nproc() > 1))
9981 {
9983 &cont_dir2,
9984 1,
9985 MPI_DOUBLE,
9986 MPI_SUM,
9987 Dof_distribution_pt->communicator_pt()->mpi_comm());
9988 }
9990#endif
9991
9992 // Add parameter derivative
9994
9995 // Calculate the magnitude of the du/ds Vector
9996
9997 // Note that actually, we are usually approximating by using the value at
9998 // newton step just before convergence, which saves one additional
9999 // Newton solve.
10000
10001 // First calculate the magnitude of du/dparameter, chi
10002 double chi = local_z.dot(local_z);
10003
10004 // Calculate the current derivative of the parameter wrt the arc-length
10005 Parameter_derivative = 1.0 / sqrt(1.0 + Theta_squared * chi);
10006
10007 // If the dot product of the current derivative wrt the Direction
10008 // is less than zero, switch the sign of the derivative to ensure
10009 // smooth continuation
10011 {
10012 Parameter_derivative *= -1.0;
10013 }
10014
10015 // Resize the derivatives array, if necessary
10017 {
10018 if (Dof_derivative.size() != ndof_local)
10019 {
10020 Dof_derivative.resize(ndof_local, 0.0);
10021 }
10022 }
10023 // Calculate the new derivatives wrt the arc-length
10024 for (unsigned long l = 0; l < ndof_local; l++)
10025 {
10026 // This comes from the formulation J u_dot + dr/dlambda lambda_dot = 0
10027 // on the curve and then it follows that.
10029 }
10030 }
10031
10032 //=======================================================================
10033 /// A private helper function to
10034 /// calculate the derivatives with respect to the arc-length
10035 /// required for continuation using finite differences.
10036 //===================================================================
10038 double* const& parameter_pt)
10039 {
10040 // Find the number of values
10041 // const unsigned long n_dofs = this->ndof();
10042 // Find the number of local dofs in the problem
10043 const unsigned long ndof_local = Dof_distribution_pt->nrow_local();
10044
10045 // Temporary storage for the finite-difference approximation to the helper
10047 double length = 0.0;
10048 // Calculate the change in values and contribution to total length
10049 for (unsigned long l = 0; l < ndof_local; l++)
10050 {
10051 z[l] = (*Dof_pt[l] - Dof_current[l]) / Ds_current;
10052 length += Theta_squared * z[l] * z[l];
10053 }
10054
10055 // Reduce if parallel
10056#ifdef OOMPH_HAS_MPI
10057 double length2 = length;
10059 (Dof_distribution_pt->communicator_pt()->nproc() > 1))
10060 {
10062 &length2,
10063 1,
10064 MPI_DOUBLE,
10065 MPI_SUM,
10066 Dof_distribution_pt->communicator_pt()->mpi_comm());
10067 }
10068 length = length2;
10069#endif
10070
10071 // Calculate change in parameter
10073 length += Z * Z;
10074
10075 // Scale the approximations to the derivatives
10076 length = sqrt(length);
10077 for (unsigned long l = 0; l < ndof_local; l++)
10078 {
10079 dof_derivative(l) = z[l] / length;
10080 }
10082 }
10083
10084
10085 /// Virtual function that is used to symmetrise the problem so that
10086 /// the current solution exactly satisfies any symmetries within the system.
10087 /// Used when adpativly solving pitchfork detection problems when small
10088 /// asymmetries in the coarse solution can be magnified
10089 /// leading to very inaccurate answers on the fine mesh.
10090 /// This is always problem-specific and must be filled in by the user
10091 /// The default issues a warning
10093 {
10094 std::ostringstream warn_message;
10096 << "Warning: This function is called after spatially adapting the\n"
10097 << "eigenfunction associated with a pitchfork bifurcation and should\n"
10098 << "ensure that the exact (anti-)symmetries of problem are enforced\n"
10099 << "within that eigenfunction. It is problem specific and must be\n"
10100 << "filled in by the user if required.\n"
10101 << "A sign of problems is if the slack paramter gets too large and\n"
10102 << "if the solution at the Pitchfork is not symmetric.\n";
10104 warn_message.str(),
10105 "Problem::symmetrise_eigenfunction_for_adaptive_pitchfork_tracking()",
10107 }
10108
10109 //====================================================================
10110 /// Return pointer to the parameter that is used in the
10111 /// bifurcation detection. If we are not tracking a bifurcation then
10112 /// an error will be thrown by the AssemblyHandler
10113 //====================================================================
10115 {
10117 }
10118
10119 //====================================================================
10120 /// Return the eigenfunction calculated as part of a
10121 /// bifurcation tracking process. If we are not tracking a bifurcation
10122 /// then an error will be thrown by the AssemblyHandler
10123 //======================================================================
10126 {
10127 // Simply call the appropriate assembly handler function
10129 }
10130
10131 //============================================================
10132 /// Activate the fold tracking system by changing the assembly
10133 /// handler and initialising it using the parameter addressed
10134 /// by parameter_pt.
10135 //============================================================
10137 const bool& block_solve)
10138 {
10139 // Reset the assembly handler to default
10141 // Set the new assembly handler. Note that the constructor actually
10142 // solves the original problem to get some initial conditions, but
10143 // this is OK because the RHS is always evaluated before assignment.
10145
10146 // If we are using a block solver, we must set the linear solver pointer
10147 // to the block fold solver. The present linear solver is
10148 // used by the block solver and so must be passed as an argument.
10149 // The destructor of the Fold handler returns the linear
10150 // solver to the original non-block version.
10151 if (block_solve)
10152 {
10154 }
10155 }
10156
10157 //===============================================================
10158 /// Activate the generic bifurcation ///tracking system by changing the
10159 /// assembly handler and initialising it using the parameter addressed by
10160 /// parameter_pt.
10161 //============================================================
10164 const bool& block_solve)
10165 {
10166 // Reset the assembly handler to default
10168 // Set the new assembly handler. Note that the constructor actually
10169 // solves the original problem to get some initial conditions, but
10170 // this is OK because the RHS is always evaluated before assignment.
10172
10173 // If we are using a block solver, we must set the linear solver pointer
10174 // to the block fold solver. The present linear solver is
10175 // used by the block solver and so must be passed as an argument.
10176 // The destructor of the Fold handler returns the linear
10177 // solver to the original non-block version.
10178 if (block_solve)
10179 {
10181 }
10182 }
10183
10184
10185 //===============================================================
10186 /// Activate the generic bifurcation ///tracking system by changing the
10187 /// assembly handler and initialising it using the parameter addressed by
10188 /// parameter_pt.
10189 //============================================================
10193 const bool& block_solve)
10194 {
10195 // Reset the assembly handler to default
10197 // Set the new assembly handler. Note that the constructor actually
10198 // solves the original problem to get some initial conditions, but
10199 // this is OK because the RHS is always evaluated before assignment.
10202
10203 // If we are using a block solver, we must set the linear solver pointer
10204 // to the block fold solver. The present linear solver is
10205 // used by the block solver and so must be passed as an argument.
10206 // The destructor of the Fold handler returns the linear
10207 // solver to the original non-block version.
10208 if (block_solve)
10209 {
10211 }
10212 }
10213
10214
10215 //==================================================================
10216 /// Activate the pitchfork tracking system by changing the assembly
10217 /// handler and initialising it using the parameter addressed
10218 /// by parameter_pt and a symmetry vector. The boolean flag is
10219 /// used to specify whether a block solver is used, default is true.
10220 //===================================================================
10223 const bool& block_solve)
10224 {
10225 // Reset the assembly handler to default
10227
10228 // Set the new assembly handler. Note that the constructor actually
10229 // solves the original problem to get some initial conditions, but
10230 // this is OK because the RHS is always evaluated before assignment.
10232 this, this->assembly_handler_pt(), parameter_pt, symmetry_vector);
10233
10234 // If we are using a block solver, we must set the linear solver pointer
10235 // to the block pitchfork solver. The present linear solver is
10236 // used by the block solver and so must be passed as an argument.
10237 // The destructor of the PitchFork handler returns the linear
10238 // solver to the original non-block version.
10239 if (block_solve)
10240 {
10242 }
10243 }
10244
10245
10246 //============================================================
10247 /// Activate the hopf tracking system by changing the assembly
10248 /// handler and initialising it using the parameter addressed
10249 /// by parameter_pt.
10250 //============================================================
10252 const bool& block_solve)
10253 {
10254 // Reset the assembly handler to default
10256 // Set the new assembly handler. Note that the constructor actually
10257 // solves the original problem to get some initial conditions, but
10258 // this is OK because the RHS is always evaluated before assignment.
10260
10261 // If we are using a block solver, we must set the linear solver pointer
10262 // to the block hopf solver. The present linear solver is
10263 // used by the block solver and so must be passed as an argument.
10264 // The destructor of the Hopf handler returns the linear
10265 // solver to the original non-block version.
10266 if (block_solve)
10267 {
10269 }
10270 }
10271
10272
10273 //============================================================
10274 /// Activate the hopf tracking system by changing the assembly
10275 /// handler and initialising it using the parameter addressed
10276 /// by parameter_pt and the frequency and null vectors
10277 /// specified.
10278 //============================================================
10280 const double& omega,
10281 const DoubleVector& null_real,
10282 const DoubleVector& null_imag,
10283 const bool& block_solve)
10284 {
10285 // Reset the assembly handler to default
10287 // Set the new assembly handler. Note that the constructor actually
10288 // solves the original problem to get some initial conditions, but
10289 // this is OK because the RHS is always evaluated before assignment.
10291 new HopfHandler(this, parameter_pt, omega, null_real, null_imag);
10292
10293 // If we are using a block solver, we must set the linear solver pointer
10294 // to the block hopf solver. The present linear solver is
10295 // used by the block solver and so must be passed as an argument.
10296 // The destructor of the Hopf handler returns the linear
10297 // solver to the original non-block version.
10298 if (block_solve)
10299 {
10301 }
10302 }
10303
10304
10305 //===============================================================
10306 /// Reset the assembly handler to default
10307 //===============================================================
10309 {
10310 // If we have a non-default handler
10312 {
10313 // Delete the current assembly handler
10314 delete Assembly_handler_pt;
10315 // Reset the assembly handler
10317 }
10318 }
10319
10320 //===================================================================
10321 /// This function takes one step of length ds in pseudo-arclength.The
10322 /// argument parameter_pt is a pointer to the parameter (global variable)
10323 /// that is being traded for arc-length. The function returns the next desired
10324 /// arc-length according to criteria based upon the desired number of Newton
10325 /// Iterations per solve.
10326 //=====================================================================
10328 const double& ds,
10329 const unsigned& max_adapt)
10330 {
10331 // First check that we shouldn't use the other interface
10332 // by checking that the parameter isn't already stored as data
10334 {
10335 std::ostringstream error_message;
10336 error_message
10337 << "The parameter addressed by " << parameter_pt << " with the value "
10338 << *parameter_pt
10339 << "\n is supposed to be used for arc-length contiunation,\n"
10340 << " but it is stored in a Data object used by the problem.\n\n"
10341 << "This is bad for two reasons:\n"
10342 << "1. If it's a variable in the problem, it must already have an\n"
10343 "associated equation, so it can't be used for continuation;\n"
10344 << "2. The problem data will be reorganised in memory during "
10345 "continuation,\n"
10346 << " which means that the pointer will become invalid.\n\n"
10347 << "If you are sure that this is what you want to do you must:\n"
10348 << "A. Ensure that the value is pinned (don't worry we'll shout again "
10349 "if not)\n"
10350 << "B. Use the alternative interface\n"
10351 << " Problem::arc_length_step_solve(Data*,unsigned,...)\n"
10352 << " which uses a pointer to the data object and not the raw double "
10353 "pointer."
10354 << std::endl;
10355 throw OomphLibError(
10356 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
10357 }
10358
10359
10360 // If we are using the continuation timestepper
10362 {
10363 // Has the timestepper already been added to the problem
10365 const unsigned n_time_steppers = this->ntime_stepper();
10366 for (unsigned i = 0; i < n_time_steppers; i++)
10367 {
10369 {
10370 continuation_time_stepper_added = true;
10371 break;
10372 }
10373 }
10374
10375 // If not add it
10377 {
10378 oomph_info << "Adding the continuation time stepper\n";
10380 }
10381
10382 // Need to treat case of eigenproblems and bifurcation detection/tracking
10383 // here
10384
10385 // Backup the current timesteppers for each mesh!
10386
10387
10388 // If an arc length step has not been taken then set the timestepper
10390 {
10391 // Set the continuation timestepper for all data in the problem
10394 << " equation numbers allocated for continuation\n";
10395 }
10396
10397 } // End of continuation time stepper case
10398
10399
10400 // Just call the helper function (parameter is not from data)
10401 return arc_length_step_solve_helper(parameter_pt, ds, max_adapt);
10402 }
10403
10404
10405 //===================================================================
10406 /// This function takes one step of length ds in pseudo-arclength.The
10407 /// argument data_pt is a pointer to the data that holds the
10408 /// parameter (global variable)
10409 /// that is being traded for arc-length. The exact value is located at
10410 /// the location given by data_index.
10411 /// The function returns the next desired
10412 /// arc-length according to criteria based upon the desired number of Newton
10413 /// Iterations per solve.
10414 //=====================================================================
10416 const unsigned& data_index,
10417 const double& ds,
10418 const unsigned& max_adapt)
10419 {
10420 // Firstly check that the data is pinned
10421 if (!data_pt->is_pinned(data_index))
10422 {
10423 std::ostringstream error_stream;
10424 error_stream << "The value at index " << data_index
10425 << " in the data object to be used for continuation\n"
10426 << "is not pinned, which means that it is already a\n"
10427 << "variable in the problem "
10428 << "and cannot be used for continuation.\n\n"
10429 << "Please correct your formulation by either:\n"
10430 << "A. Pinning the value"
10431 << "\n or \n"
10432 << "B. Using a different parameter for continuation"
10433 << std::endl;
10434 throw OomphLibError(
10436 }
10437
10438
10439 // If we are using the continuation timestepper
10441 {
10442 // Has the timestepper already been added to the problem
10444 const unsigned n_time_steppers = this->ntime_stepper();
10445 for (unsigned i = 0; i < n_time_steppers; i++)
10446 {
10448 {
10449 continuation_time_stepper_added = true;
10450 break;
10451 }
10452 }
10453
10454 // If not add it
10456 {
10457 oomph_info << "Adding the continuation time stepper\n";
10459 }
10460
10461 // Need to treat case of eigenproblems and bifurcation detection/tracking
10462 // here
10463
10464
10465 // Backup the current timesteppers for each mesh!
10466
10467
10468 // If an arc length step has not been taken then set the timestepper
10470 {
10471 // Set the continuation timestepper for all data in the problem
10474 << " equation numbers allocated for continuation\n";
10475 }
10476
10477
10478 } // End of continuation time stepper case
10479
10480
10481 // Now make a pointer to the (newly allocated) data object
10482 double* parameter_pt = data_pt->value_pt(data_index);
10483 // Call the helper function, this will change the parameter_pt if
10484 // the data storage is changed (if the timestepper has to be changed,
10485 // which happens if this is the first time that a continuation step is
10486 // taken)
10487 // ALH: Don't think this is true because it has happened above....
10489 }
10490
10491 //======================================================================
10492 /// Private helper function that is used to set the appropriate
10493 /// pinned values for continuation. If the data is pinned, the its
10494 /// current value is always the same as the original value and
10495 /// the derivative is always zero. If these are not set properly
10496 /// then interpolation and projection in spatial adaptivity will
10497 /// not give the best answers.
10498 //=====================================================================
10500 {
10501 // Set the consistent values for the global mesh
10504
10505 // Deal with the spine meshes additional numbering separately
10506 const unsigned n_sub_mesh = this->nsub_mesh();
10507 // If there is only one mesh
10508 if (n_sub_mesh == 0)
10509 {
10510 if (SpineMesh* const spine_mesh_pt = dynamic_cast<SpineMesh*>(Mesh_pt))
10511 {
10512 spine_mesh_pt->set_consistent_pinned_spine_values_for_continuation(
10514 }
10515 // If it's a triangle mesh the we need to set the
10516 }
10517 // Otherwise loop over the sub meshes
10518 else
10519 {
10520 // Assign global equation numbers first
10521 for (unsigned i = 0; i < n_sub_mesh; i++)
10522 {
10523 if (SpineMesh* const spine_mesh_pt =
10524 dynamic_cast<SpineMesh*>(Sub_mesh_pt[i]))
10525 {
10526 spine_mesh_pt->set_consistent_pinned_spine_values_for_continuation(
10528 }
10529 }
10530 }
10531
10532 // Also set time stepper for global data
10533 const unsigned n_global = Global_data_pt.size();
10534 for (unsigned i = 0; i < n_global; ++i)
10535 {
10536 Continuation_time_stepper.set_consistent_pinned_values(Global_data_pt[i]);
10537 }
10538 }
10539
10540
10541 //===================================================================
10542 /// This function takes one step of length ds in pseudo-arclength.The
10543 /// argument parameter_pt is a pointer to the parameter (global variable)
10544 /// that is being traded for arc-length. The function returns the next desired
10545 /// arc-length according to criteria based upon the desired number of Newton
10546 /// Iterations per solve.
10547 //=====================================================================
10549 const double& ds,
10550 const unsigned& max_adapt)
10551 {
10552 //----------------------MAKE THE PROBLEM STEADY-----------------------
10553 // Loop over the timesteppers and make them (temporarily) steady.
10554 // We can only do continuation for steady problems!
10555 unsigned n_time_steppers = ntime_stepper();
10556 // Vector of bools to store the is_steady status of the various
10557 // timesteppers when we came in here
10558 std::vector<bool> was_steady(n_time_steppers);
10559
10560 // Loop over them all and make them (temporarily) static
10561 for (unsigned i = 0; i < n_time_steppers; i++)
10562 {
10565 }
10566
10567
10568 // Max number of solves
10569 unsigned max_solve = max_adapt + 1;
10570 // Storage for newton steps in each adaptation
10571 unsigned max_count_in_adapt_loop = 0;
10572
10573
10574 //----SET UP MEMORY FOR QUANTITIES THAT ARE REQUIRED OUTSIDE THE LOOP----
10575
10576 // Assign memory for solutions of the equations Jz = du/dparameter
10577 // This is needed here (outside the loop), so that we can save on
10578 // one linear solve when calculating the derivatives wrt the arc-length
10579 DoubleVector z;
10580
10581
10582 // Store sign of the Jacobian, used for bifurcation detection
10583 // If this is the first time that we are calling the arc-length solver,
10584 // this should not be used.
10586
10587 // Flag to indicate a sign change
10588 bool SIGN_CHANGE = false;
10589
10590
10591 // Adaptation loop
10592 for (unsigned isolve = 0; isolve < max_solve; ++isolve)
10593 {
10594 // Only adapt after the first solve has been done
10595 if (isolve > 0)
10596 {
10597 unsigned n_refined;
10598 unsigned n_unrefined;
10599
10600 // Adapt problem
10602
10603#ifdef OOMPH_HAS_MPI
10604 // Adaptation only converges if ALL the processes have no
10605 // refinement or unrefinement to perform
10606 unsigned total_refined = 0;
10607 unsigned total_unrefined = 0;
10609 {
10612 1,
10614 MPI_SUM,
10615 this->communicator_pt()->mpi_comm());
10619 1,
10621 MPI_SUM,
10622 this->communicator_pt()->mpi_comm());
10624 }
10625#endif
10626
10627 oomph_info << "---> " << n_refined << " elements were refined, and "
10628 << n_unrefined << " were unrefined"
10629#ifdef OOMPH_HAS_MPI
10630 << ", in total (over all processors).\n";
10631#else
10632 << ".\n";
10633#endif
10634
10635
10636 // Check convergence of adaptation cycle
10637 if ((n_refined == 0) && (n_unrefined == 0))
10638 {
10639 oomph_info << "\n \n Solution is fully converged in "
10640 << "Problem::newton_solver(). \n \n ";
10641 break;
10642 }
10643 }
10644
10645 //----------SAVE THE INITIAL VALUES, IN CASE THE STEP FAILS-----------
10646
10647 // Find the number of local dofs
10649
10650 // Only need to do this in the first loop
10651 if (isolve == 0)
10652 {
10654 {
10655 // Safety check, set up the array of dof derivatives, if necessary
10656 // The distribution is the same as the (natural) distribution of the
10657 // dofs
10658 if (Dof_derivative.size() != ndof_local)
10659 {
10660 Dof_derivative.resize(ndof_local, 0.0);
10661 }
10662
10663 // Safety check, set up the array of curren values, if necessary
10664 // Again the distribution reflects the (natural) distribution of the
10665 // dofs
10666 if (Dof_current.size() != ndof_local)
10667 {
10668 Dof_current.resize(ndof_local);
10669 }
10670 }
10671
10672 // Save the current value of the parameter
10674
10675 // Save the current values of the degrees of freedom
10676 for (unsigned long l = 0; l < ndof_local; l++)
10677 {
10678 dof_current(l) = *Dof_pt[l];
10679 }
10680
10681 // Set the value of ds_current
10682 Ds_current = ds;
10683 }
10684
10685 // Counter for the number of newton steps
10686 unsigned count = 0;
10687
10688 // Flag to indicate a successful step
10689 bool STEP_REJECTED = false;
10690
10691
10692 // Set the appropriate initial conditions for the pinned data
10694 {
10696 }
10697
10698 // Loop around the step in arc-length
10699 do
10700 {
10701 // Check that the step has not fallen below the minimum tolerance
10702 if (std::fabs(Ds_current) < Minimum_ds)
10703 {
10704 std::ostringstream error_message;
10705 error_message << "DESIRED ARC-LENGTH STEP " << Ds_current
10706 << " HAS FALLEN BELOW MINIMUM TOLERANCE, " << Minimum_ds
10707 << std::endl;
10708
10709 throw OomphLibError(error_message.str(),
10712 }
10713
10714 // Assume that we shall accept the step
10715 STEP_REJECTED = false;
10716
10717 // Set initial value of the parameter
10719
10720 // Perform any actions...
10722
10724
10725 // Loop over the (local) variables and set their initial values
10726 for (unsigned long l = 0; l < ndof_local; l++)
10727 {
10729 }
10730
10731 // Actually do the newton solve stage for the continuation problem
10732 try
10733 {
10735 }
10736 // Catch any exceptions thrown in the Newton solver
10737 catch (NewtonSolverError& error)
10738 {
10739 // Check whether it's the linear solver
10740 if (error.linear_solver_error())
10741 {
10742 std::ostringstream error_stream;
10743 error_stream << std::endl
10744 << "USER-DEFINED ERROR IN NEWTON SOLVER " << std::endl;
10745 oomph_info << "ERROR IN THE LINEAR SOLVER" << std::endl;
10746 throw OomphLibError(error_stream.str(),
10749 }
10750 // Otherwise mark the step as having failed
10751 else
10752 {
10753 oomph_info << "STEP REJECTED DUE TO NEWTON SOLVER --- TRYING AGAIN"
10754 << std::endl;
10755 STEP_REJECTED = true;
10756 // Let's take a smaller step
10757 Ds_current *= (2.0 / 3.0);
10758 }
10759 }
10760 catch (InvertedElementError const& error)
10761 {
10763 << "STEP REJECTED DUE TO INVERTED ELEMENTS --- TRYING AGAIN"
10764 << std::endl;
10765 STEP_REJECTED = true;
10766 // Let's take a smaller step
10767 Ds_current *= (2.0 / 3.0);
10768 }
10769 } while (STEP_REJECTED); // continue until a step is accepted
10770
10771 // Set the maximum count
10773 {
10775 }
10776 } /// end of adaptation loop
10777
10778 // Only recalculate the derivatives if there has been a Newton solve
10779 // If not, the previous values should be close enough
10781 {
10782 //--------------------CHECK FOR POTENTIAL BIFURCATIONS-------------
10784 {
10785 // If the sign of the jacobian is zero issue a warning
10786 if (Sign_of_jacobian == 0)
10787 {
10788 std::string error_message =
10789 "The sign of the jacobian is zero after a linear solve\n";
10790 error_message += "Either the matrix is singular (unlikely),\n";
10791 error_message += "or the linear solver cannot compute the "
10792 "determinant of the matrix;\n";
10793 error_message += "e.g. an iterative linear solver.\n";
10794 error_message +=
10795 "If the latter, bifurcation detection must be via an eigensolver\n";
10796 OomphLibWarning(error_message,
10797 "Problem::arc_length_step_solve",
10799 }
10800 // If this is the first step, we cannot rely on the previous value
10801 // of the jacobian so set the previous sign to the present sign
10803 {
10805 }
10806 // If we have detected a sign change in the last converged Jacobian,
10807 // it must be a turning point or bifurcation
10809 {
10810 // There has been, at least, one sign change
10812
10813 // The sign has changed this time
10814 SIGN_CHANGE = true;
10815
10816 // Calculate the dot product of the approximate null vector
10817 // of the Jacobian matrix ((badly) approximated by z)
10818 // and the vectors of derivatives of the residuals wrt the
10819 // global parameter
10820 // If this is small it is a bifurcation rather than a turning point.
10821 // Get the derivative wrt global parameter
10822 // DoubleVector dparam;
10823 // get_derivative_wrt_global_parameter(parameter_pt,dparam);
10824 // Calculate the dot product
10825 // double dot=0.0;
10826 // for(unsigned long n=0;n<n_dofs;++n) {dot += dparam[n]*z[n];}
10827 // z.dot(dparam);
10828
10829 // Write the output message
10830 std::ostringstream message;
10831 message
10832 << "-----------------------------------------------------------";
10833 message << std::endl
10834 << "SIGN CHANGE IN DETERMINANT OF JACOBIAN: " << std::endl;
10835 message << "BIFURCATION OR TURNING POINT DETECTED BETWEEN "
10836 << Parameter_current << " AND " << *parameter_pt << std::endl;
10837 // message << "APPROXIMATE DOT PRODUCT : " << dot << "," << std::endl;
10838 // message << "IF CLOSE TO ZERO WE HAVE A BIFURCATION; ";
10839 // message << "OTHERWISE A TURNING POINT" << std::endl;
10840 message
10841 << "-----------------------------------------------------------"
10842 << std::endl;
10843
10844 // Write the message to standard output
10845 oomph_info << message.str();
10846
10847 // Open the information file for appending
10848 std::ofstream bifurcation_info("bifurcation_info",
10849 std::ios_base::app);
10850 // Write the message to the file
10851 bifurcation_info << message.str();
10852 bifurcation_info.close();
10853 }
10854 }
10855
10856 // Calculate the derivatives required for the next stage of continuation
10857 // In this we pass the last value of z (i.e. approximation)
10859 {
10861 }
10862 // Or use finite differences
10863 else
10864 {
10866 }
10867
10868 // If it's the first step then the value of the next step should
10869 // be the change in parameter divided by the parameter derivative
10870 // to obtain approximately the same parameter change
10872 {
10874 }
10875
10876 // We have taken our first step
10877 Arc_length_step_taken = true;
10878 }
10879 // If there has not been a newton step then we still need to estimate
10880 // the derivatives in the arc length direction
10881 else
10882 {
10883 // Default is to calculate the continuation derivatives by solving the
10884 // linear system. We must do this to ensure that the derivatives are in
10885 // sync It could lead to problems near turning points when we should
10886 // really be solving an eigenproblem, but seems OK so far!
10887
10888 // Save the current sign of the jacobian
10890
10891 // Calculate the continuation derivatives, which includes a solve
10892 // of the linear system if not using finite differences
10894 {
10896 }
10897 // Otherwise use finite differences
10898 else
10899 {
10901 }
10902
10903 // Reset the sign of the jacobian, just in case the sign has changed when
10904 // solving the continuation derivatives. The sign change will be picked
10905 // up on the next continuation step.
10907 }
10908
10909 // Reset the is_steady status of all timesteppers that
10910 // weren't already steady when we came in here and reset their
10911 // weights
10912 for (unsigned i = 0; i < n_time_steppers; i++)
10913 {
10914 if (!was_steady[i])
10915 {
10917 }
10918 }
10919
10920 // If we are trying to find a bifurcation and the first sign change
10921 // has occured, use bisection
10924 {
10925 // If there has been a sign change we need to half the step size
10926 // and reverse the direction
10927 if (SIGN_CHANGE)
10928 {
10929 Ds_current *= -0.5;
10930 }
10931 // Otherwise
10932 else
10933 {
10934 // The size of the bracketed interval is always
10935 // 2ds - Ds_current (this will work even if the original step failed)
10936 // We want our new step size to be half this
10937 Ds_current = ds - 0.5 * Ds_current;
10938 }
10939 // Return the desired value of the step
10940 return Ds_current;
10941 }
10942
10943 // If fewer than the desired number of Newton Iterations, increase the step
10945 {
10946 return Ds_current * 1.5;
10947 }
10948 // If more than the desired number of Newton Iterations, reduce the step
10950 {
10951 return Ds_current * (2.0 / 3.0);
10952 }
10953 // Otherwise return the step just taken
10954 return Ds_current;
10955 }
10956
10957
10958 //=======================================================================
10959 /// Take an explicit timestep of size dt
10960 //======================================================================
10961 void Problem::explicit_timestep(const double& dt, const bool& shift_values)
10962 {
10963#ifdef PARANOID
10964 if (this->explicit_time_stepper_pt() == 0)
10965 {
10966 throw OomphLibError("Explicit time stepper pointer is null in problem.",
10969 }
10970#endif
10971
10972 // Firstly we shift the time values
10973 if (shift_values)
10974 {
10976 }
10977 // Set the current value of dt, if we can
10978 if (time_pt()->ndt() > 0)
10979 {
10980 time_pt()->dt() = dt;
10981 }
10982
10983 // Take the explicit step
10984 this->explicit_time_stepper_pt()->timestep(this, dt);
10985 }
10986
10987
10988 //========================================================================
10989 /// Do one timestep of size dt using Newton's method with the specified
10990 /// tolerance and linear solver defined as member data of the Problem class.
10991 /// This will be the most commonly used version
10992 /// of unsteady_newton_solve, in which the time values are always shifted
10993 /// This does not include any kind of adaptativity. If the solution fails to
10994 /// converge the program will end.
10995 //========================================================================
10996 void Problem::unsteady_newton_solve(const double& dt)
10997 {
10998 // We shift the values, so shift_values is true
10999 unsteady_newton_solve(dt, true);
11000 }
11001
11002 //========================================================================
11003 /// Do one timestep forward of size dt using Newton's method with the
11004 /// specified tolerance and linear solver defined via member data of the
11005 /// Problem class.
11006 /// The boolean flag shift_values is used to control whether the time values
11007 /// should be shifted or not.
11008 //========================================================================
11009 void Problem::unsteady_newton_solve(const double& dt,
11010 const bool& shift_values)
11011 {
11012 // Shift the time values and the dts, according to the control flag
11013 if (shift_values)
11014 {
11016 }
11017
11018 // Advance global time and set current value of dt
11019 time_pt()->time() += dt;
11020 time_pt()->dt() = dt;
11021
11022 // Find out how many timesteppers there are
11023 unsigned n_time_steppers = ntime_stepper();
11024
11025 // Loop over them all and set the weights
11026 for (unsigned i = 0; i < n_time_steppers; i++)
11027 {
11029 }
11030
11031 // Run the individual timesteppers actions before timestep. These need to
11032 // be before the problem's actions_before_implicit_timestep so that the
11033 // boundary conditions are set consistently.
11034 for (unsigned i = 0; i < n_time_steppers; i++)
11035 {
11037 }
11038
11039 // Now update anything that needs updating before the timestep
11040 // This could be time-dependent boundary conditions, for example.
11042
11043 try
11044 {
11045 // Solve the non-linear problem for this timestep with Newton's method
11046 newton_solve();
11047 }
11048 // Catch any exceptions thrown in the Newton solver
11049 catch (NewtonSolverError& error)
11050 {
11051 oomph_info << std::endl
11052 << "USER-DEFINED ERROR IN NEWTON SOLVER " << std::endl;
11053 // Check whether it's the linear solver
11054 if (error.linear_solver_error())
11055 {
11056 oomph_info << "ERROR IN THE LINEAR SOLVER" << std::endl;
11057 }
11058 // Check to see whether we have reached Max_iterations
11059 else if (error.iterations() == Max_newton_iterations)
11060 {
11061 oomph_info << "MAXIMUM NUMBER OF ITERATIONS (" << error.iterations()
11062 << ") REACHED WITHOUT CONVERGENCE " << std::endl;
11063 }
11064 // If not, it must be that we have exceeded the maximum residuals
11065 else
11066 {
11067 oomph_info << "MAXIMUM RESIDUALS: " << error.maxres()
11068 << " EXCEEDS PREDEFINED MAXIMUM " << Max_residuals
11069 << std::endl;
11070 }
11071 // Die horribly!!
11072 std::ostringstream error_stream;
11073 error_stream << "Error occured in unsteady Newton solver. " << std::endl;
11074 throw OomphLibError(
11076 }
11077
11078 // Run the individual timesteppers actions, these need to be before the
11079 // problem's actions_after_implicit_timestep so that the time step is
11080 // finished before the problem does any auxiliary calculations (e.g. in
11081 // semi-implicit micromagnetics the calculation of magnetostatic field).
11082 for (unsigned i = 0; i < n_time_steppers; i++)
11083 {
11085 }
11086
11087
11088 // Now update anything that needs updating after the timestep
11091 }
11092
11093 //=======================================================================
11094 /// Attempt to take one timestep forward using dt_desired. The error control
11095 /// parameter, epsilon, is used to specify the desired approximate value of
11096 /// the global error norm per timestep. The routine returns the value an
11097 /// estimate of the next value of dt that should be taken.
11098 //=======================================================================
11100 const double& epsilon)
11101 {
11102 // We always want to shift the time values
11103 return adaptive_unsteady_newton_solve(dt_desired, epsilon, true);
11104 }
11105
11106
11107 //=======================================================================
11108 /// Attempt to take one timestep forward using the dt_desired.
11109 /// This is the driver for a number of adaptive solvers. If the solution
11110 /// fails to converge at a given timestep, the routine will automatically
11111 /// halve the time step and try again, until the time step falls below the
11112 /// specified minimum value. The routine returns the value an estimate
11113 /// of the next value of dt that should be taken.
11114 /// Timestep is also rejected if the error estimate post-solve
11115 /// (computed by global_temporal_error_norm()) exceeds epsilon.
11116 /// This behaviour can be over-ruled by setting the protected
11117 /// boolean Problem::Keep_temporal_error_below_tolerance to false.
11118 //========================================================================
11120 const double& epsilon,
11121 const bool& shift_values)
11122 {
11123 // First, we need to backup the existing dofs, in case the timestep is
11124 // rejected
11125
11126 // Find total number of dofs on current processor
11128
11129 // Now set up a Vector to hold current values
11131
11132 // Load values into dofs_current
11133 for (unsigned i = 0; i < n_dof_local; i++) dofs_current[i] = dof(i);
11134
11135 // Store the time
11136 double time_current = time_pt()->time();
11137
11138 // Flag to detect whether the timestep has been rejected or not
11139 bool reject_timestep = 0;
11140
11141 // Flag to detect whether any of the timesteppers are adaptive
11142 unsigned adaptive_flag = 0;
11143
11144 // The value of the actual timestep, by default the same as desired timestep
11145 double dt_actual = dt_desired;
11146
11147 // Find out whether any of the timesteppers are adaptive
11148 unsigned n_time_steppers = ntime_stepper();
11149 for (unsigned i = 0; i < n_time_steppers; i++)
11150 {
11151 if (time_stepper_pt(i)->adaptive_flag())
11152 {
11153 adaptive_flag = 1;
11154 break;
11155 }
11156 }
11157
11158 // Shift the time_values according to the control flag
11159 if (shift_values)
11160 {
11162 }
11163
11164 // This loop surrounds the adaptive time-stepping and will not be broken
11165 // until a timestep is accepted
11166 do
11167 {
11168 // Initially we assume that this step will succeed and that this dt
11169 // value is ok.
11170 reject_timestep = 0;
11171 double dt_rescaling_factor = 1.0;
11172
11173 // Set the new time and value of dt
11174 time_pt()->time() += dt_actual;
11175 time_pt()->dt() = dt_actual;
11176
11177 // Loop over all timesteppers and set the weights and predictor weights
11178 for (unsigned i = 0; i < n_time_steppers; i++)
11179 {
11180 // If the time_stepper is non-adaptive, this will be zero
11183 }
11184
11185 // Now calculate the predicted values for the all data and all positions
11187
11188 // Run the individual timesteppers actions before timestep. These need to
11189 // be before the problem's actions_before_implicit_timestep so that the
11190 // boundary conditions are set consistently.
11191 for (unsigned i = 0; i < n_time_steppers; i++)
11192 {
11194 }
11195
11196 // Do any updates/boundary conditions changes here
11198
11199 // Attempt to solve the non-linear system
11200 try
11201 {
11202 // Solve the non-linear problem at this timestep
11203 newton_solve();
11204 }
11205 // Catch any exceptions thrown
11206 catch (NewtonSolverError& error)
11207 {
11208 // If it's a solver error then die
11209 if (error.linear_solver_error() ||
11211 {
11212 std::string error_message = "USER-DEFINED ERROR IN NEWTON SOLVER\n";
11213 error_message += "ERROR IN THE LINEAR SOLVER\n";
11214
11215 // Die
11216 throw OomphLibError(
11218 }
11219 else
11220 {
11221 // Reject the timestep, if we have an exception
11222 oomph_info << "TIMESTEP REJECTED DUE TO THE NEWTON SOLVER"
11223 << std::endl;
11224 reject_timestep = true;
11225
11226 // Half the time step
11228 }
11229 }
11230 catch (InvertedElementError const& error)
11231 {
11232 /// Reject the timestep, if we have an exception
11233 oomph_info << "TIMESTEP REJECTED DUE TO INVERTED ELEMENTS" << std::endl;
11234 reject_timestep = true;
11235
11236 /// Half the time step
11238 }
11239
11240 // Run the individual timesteppers actions, these need to be before the
11241 // problem's actions_after_implicit_timestep so that the time step is
11242 // finished before the problem does any auxiliary calculations (e.g. in
11243 // semi-implicit micromagnetics the calculation of magnetostatic field).
11244 for (unsigned i = 0; i < n_time_steppers; i++)
11245 {
11247 }
11248
11249 // Update anything that needs updating after the timestep
11251
11252 // If we have an adapative timestepper (and we haven't already failed)
11253 // then calculate the error estimate and rescaling factor.
11254 if (adaptive_flag && !reject_timestep)
11255 {
11256 // Once timestep has been accepted can do fancy error processing
11257 // Set the error weights
11258 for (unsigned i = 0; i < n_time_steppers; i++)
11259 {
11261 }
11262
11263 // Get a global error norm to use in adaptivity (as specified by the
11264 // problem sub-class writer). Prevent a divide by zero if the solution
11265 // gives very close to zero error. Error norm should never be negative
11266 // but use absolute value just in case.
11267 double error = std::max(std::abs(global_temporal_error_norm()), 1e-12);
11268
11269 // Target error that we wish our next timestep to approximately produce
11270 // as a factor of the maximum error tolerance
11271 double target_error = Target_error_safety_factor * epsilon;
11272
11273 // Calculate the scaling factor
11274 dt_rescaling_factor = std::pow(
11275 (target_error / error), (1.0 / (1.0 + time_stepper_pt()->order())));
11276
11278 << "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n"
11279 << "Estimated timestepping error is " << error << "\n"
11280 << "Timestep scaling factor is " << dt_rescaling_factor << "\n";
11281
11282
11283 // Do we have to do it again?
11284 if (error > epsilon)
11285 {
11286 oomph_info << "Estimated timestepping error " << error
11287 << " exceeds tolerance " << epsilon << "\n";
11289 {
11290 oomph_info << " --> rejecting timestep.\n";
11291 reject_timestep = true;
11292 }
11293 else
11294 {
11295 oomph_info << " ...but we're not rejecting the timestep\n";
11296 }
11298 << "Note: This behaviour can be adjusted by changing the\n"
11299 << "protected boolean\n"
11300 << " Problem::Keep_temporal_error_below_tolerance\n\n"
11301 << "Also, if you are noticing that many of your timesteps result\n"
11302 << "in error > tolerance, try reducing the target error with\n"
11303 << "respect to the error tolerance by reducing the value of\n"
11304 << "Target_error_safety_factor from its default value of 1.0\n"
11305 << "using the access function\n"
11306 << " target_error_safety_factor() = 0.5 (e.g.)\n"
11307 << "The default strategy (Target_error_safety_factor=1.0) tries\n"
11308 << "to suggest a timestep which will produce an error equal to\n"
11309 << "the error tolerance `epsilon` which risks error > tolerance\n"
11310 << "quite often. Setting the safety factor to too small a value\n"
11311 << "will make the timesteps unnecessarily small; too large will\n"
11312 << "not address the issue -- neither is optimal and a problem\n"
11313 << "dependent compromise is needed.\n"
11314 << "for more info see:\n"
11315 << " Mayr et al. (2018), p5,9, DOI:10.1016/j.finel.2017.12.002\n"
11316 << " Harrier et al. (1993), p168, ISBN:978-3-540-56670-0\n"
11317 << " Söderlind (2002), (2.7) on p5, DOI:10.1023/A:1021160023092\n";
11318 }
11320 << "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n"
11321 << std::endl;
11322
11323
11324 } // End of if adaptive flag
11325
11326
11327 // Calculate the next time step size and check it's ok
11328 // ============================================================
11329
11330 // Calculate the possible next time step, if no error conditions
11331 // trigger.
11333
11334 // Check that the scaling factor is within the allowed range
11336 {
11337 oomph_info << "Tried to increase dt by the ratio "
11338 << dt_rescaling_factor << " which is above the maximum ("
11340 << "). Attempting to increase by the maximum ratio instead."
11341 << std::endl;
11343 }
11344 // If we have already rejected the timestep then don't do this check
11345 // because DTSF will definitely be too small.
11347 {
11348 // Handle this special case where we want to continue anyway (usually
11349 // Minimum_dt_but_still_proceed = -1 so this has no effect).
11351 {
11353 << "Warning: Adaptation of timestep to ensure satisfaction\n"
11354 << " of error bounds during adaptive timestepping\n"
11355 << " would lower dt below \n"
11356 << " Problem::Minimum_dt_but_still_proceed="
11358 << " ---> We're continuing with present timestep.\n"
11359 << std::endl;
11360 dt_rescaling_factor = 1.0;
11361 // ??ds shouldn't we set new_dt_candidate =
11362 // Minimum_dt_but_still_proceed here, rather than not changing dt at
11363 // all?
11364 }
11365 else
11366 {
11367 // Otherwise reject
11368 oomph_info << "Timestep would decrease by " << dt_rescaling_factor
11369 << " which is less than the minimum scaling factor "
11370 << DTSF_min_decrease << std::endl;
11371 oomph_info << "TIMESTEP REJECTED" << std::endl;
11372 reject_timestep = 1;
11373 }
11374 }
11375
11376 // Now check that the new dt is within the allowed range
11378 {
11379 oomph_info << "Tried to increase dt to " << new_dt_candidate
11380 << " which is above the maximum (" << Maximum_dt
11381 << "). I increased it to the maximum value instead.";
11383 }
11384 else if (new_dt_candidate < Minimum_dt)
11385 {
11386 std::ostringstream err;
11387 err << "Tried to reduce dt to " << new_dt_candidate
11388 << " which is less than the minimum dt (" << Minimum_dt << ")."
11389 << std::endl;
11390 throw OomphLibError(
11392 }
11393 else
11394 {
11396 }
11397
11398
11400
11401
11402 // If we are rejecting this attempt then revert the dofs etc.
11403 if (reject_timestep)
11404 {
11405 // Reset the time
11406 time_pt()->time() = time_current;
11407
11408 // Reload the dofs
11409 unsigned ni = dofs_current.size();
11410 for (unsigned i = 0; i < ni; i++)
11411 {
11412 dof(i) = dofs_current[i];
11413 }
11414
11415#ifdef OOMPH_HAS_MPI
11416 // Synchronise the solution on different processors (on each submesh)
11417 this->synchronise_all_dofs();
11418#endif
11419
11420 // Call all "after" actions, e.g. to handle mesh updates
11426 }
11427
11428 }
11429 // Keep this loop going until we accept the timestep
11430 while (reject_timestep);
11431
11432 // Once the timestep has been accepted, return the time step that should be
11433 // used next time.
11434 return dt_actual;
11435 }
11436
11437
11438 //=======================================================================
11439 /// Private helper function to perform
11440 /// unsteady "doubly" adaptive Newton solve: Does temporal
11441 /// adaptation first, i.e. we try to do a timestep with an increment
11442 /// of dt, and adjusting dt until the solution on the given mesh satisfies
11443 /// the temporal error measure with tolerance epsilon. Following
11444 /// this, we do up to max_adapt spatial adaptions (without
11445 /// re-examining the temporal error). If first==true, the initial conditions
11446 /// are re-assigned after the mesh adaptations.
11447 /// Shifting of time can be suppressed by overwriting the
11448 /// default value of shift (true). [Shifting must be done
11449 /// if first_timestep==true because we're constantly re-assigning
11450 /// the initial conditions; if first_timestep==true and shift==false
11451 /// shifting is performed anyway and a warning is issued.
11452 /// Pseudo-Boolean flag suppress_resolve_after_spatial_adapt [0: false;
11453 /// 1: true] does what it says.]
11454 //========================================================================
11456 const double& dt_desired,
11457 const double& epsilon,
11458 const unsigned& max_adapt,
11460 const bool& first,
11461 const bool& shift_values)
11462 {
11463 // Store the initial time
11464 double initial_time = time_pt()->time();
11465
11466 // Take adaptive timestep, adjusting dt until tolerance is satisfied
11467 double new_dt =
11469 double dt_taken = time_pt()->dt();
11470 oomph_info << "Accepted solution taken with timestep: " << dt_taken
11471 << std::endl;
11472
11473
11474 // Bail out straightaway if no spatial adaptation allowed
11475 if (max_adapt == 0)
11476 {
11477 oomph_info << "No spatial refinement allowed; max_adapt=0\n";
11478 return new_dt;
11479 }
11480
11481 // Adapt problem/mesh
11482 unsigned n_refined = 0;
11483 unsigned n_unrefined = 0;
11485
11486 // Check if mesh has been adapted on other processors
11490
11491
11492#ifdef OOMPH_HAS_MPI
11494 {
11495 // Sum n_refine across all processors
11497 ref_count[0] = n_refined;
11500 &total_ref_count[0],
11501 2,
11502 MPI_INT,
11503 MPI_SUM,
11505 }
11506#endif
11507
11508
11509 // Re-solve the problem if the adaptation has changed anything
11510 if ((total_ref_count[0] != 0) || (total_ref_count[1] != 0))
11511 {
11513 {
11514 oomph_info << "Mesh was adapted but re-solve has been suppressed."
11515 << std::endl;
11516 }
11517 else
11518 {
11520 << "Mesh was adapted --> we'll re-solve for current timestep."
11521 << std::endl;
11522
11523 // Reset time to what it was when we entered here
11524 // because it will be incremented again by dt_taken.
11525 time_pt()->time() = initial_time;
11526
11527 // Shift the timesteps? No! They've been shifted already when we
11528 // called the solve with pure temporal adaptivity...
11529 bool shift = false;
11530
11531 // Reset the inital condition on refined meshes
11532 if (first)
11533 {
11534 // Reset default set_initial_condition has been called flag to false
11536
11537 // Reset the initial conditions
11538 oomph_info << "Re-assigning initial condition at time="
11539 << time_pt()->time() << std::endl;
11541
11542 // This is the first timestep so shifting
11543 // has to be done following the assignment of initial conditions,
11544 // providing the default set_initial_condition function has not
11545 // been called.
11546 // In fact, unsteady_newton_solve(...) does that automatically.
11547 // We're changing the flag here to avoid warning messages.
11549 {
11550 shift = true;
11551 }
11552 }
11553
11554 // Now take the step again on the refined mesh, using the same
11555 // timestep as used before.
11557 }
11558 }
11559 else
11560 {
11561 oomph_info << "Mesh wasn't adapted --> we'll accept spatial refinement."
11562 << std::endl;
11563 }
11564
11565 return new_dt;
11566 }
11567
11568
11569 //========================================================================
11570 /// Initialise the previous values of the variables for time stepping
11571 /// corresponding to an impulsive start. Previous history for all data
11572 /// is generated by the appropriate timesteppers. Previous nodal
11573 /// positions are simply copied backwards.
11574 //========================================================================
11576 {
11577 // Assign the impulsive values in the "master" mesh
11579
11580 // Loop over global data
11581 unsigned Nglobal = Global_data_pt.size();
11582 for (unsigned iglobal = 0; iglobal < Nglobal; iglobal++)
11583 {
11585 ->time_stepper_pt()
11587 }
11588 }
11589
11590
11591 //=======================================================================
11592 /// Assign the values for an impulsive start and also set the initial
11593 /// values of the previous dts to both be dt
11594 //======================================================================
11596 {
11597 // First initialise the dts and set the weights
11598 initialise_dt(dt);
11599 // Now call assign_initial_values_impulsive
11601 }
11602
11603 //=======================================================================
11604 /// Return the current value of continuous time. If not Time object
11605 /// has been assigned, then throw an error
11606 //======================================================================
11608 {
11609 if (Time_pt == 0)
11610 {
11611 throw OomphLibError("Time object has not been set",
11614 }
11615 else
11616 {
11617 return Time_pt->time();
11618 }
11619 }
11620
11621 //=======================================================================
11622 /// Return the current value of continuous time. If not Time object
11623 /// has been assigned, then throw an error. Const version.
11624 //======================================================================
11625 double Problem::time() const
11626 {
11627 if (Time_pt == 0)
11628 {
11629 throw OomphLibError("Time object has not been set",
11632 }
11633 else
11634 {
11635 return Time_pt->time();
11636 }
11637 }
11638
11639
11640 //=======================================================================
11641 /// Set all problem data to have the same timestepper (timestepper_pt).
11642 /// This is mainly used in continuation and bifurcation detection problems
11643 /// in which case the total number of unknowns may change and the changes
11644 /// to the underlying memory layout means that the Dof_pt must be
11645 /// reallocated. Thus, the function calls assign_eqn_numbers() and returns
11646 /// the number of new equation numbers.
11647 //=========================================================================
11649 TimeStepper* const& time_stepper_pt, const bool& preserve_existing_data)
11650 {
11651 // Set the timestepper for the master mesh's nodal and elemental data
11652 // to be the
11653 // continuation time stepper. This will wipe all storage other than
11654 // the 0th (present time) value at all the data objects
11657
11658 // Deal with the any additional mesh level timestepper data separately
11659 const unsigned n_sub_mesh = this->nsub_mesh();
11660 // If there is only one mesh
11661 if (n_sub_mesh == 0)
11662 {
11665 }
11666 // Otherwise loop over the sub meshes
11667 else
11668 {
11669 // Assign global equation numbers first
11670 for (unsigned i = 0; i < n_sub_mesh; i++)
11671 {
11672 this->Sub_mesh_pt[i]->set_mesh_level_time_stepper(
11673 time_stepper_pt, preserve_existing_data);
11674 }
11675 }
11676
11677 // Also set time stepper for global data
11678 const unsigned n_global = Global_data_pt.size();
11679 for (unsigned i = 0; i < n_global; ++i)
11680 {
11681 Global_data_pt[i]->set_time_stepper(time_stepper_pt,
11683 }
11684
11685 // We now need to reassign equations numbers because the Dof pointer
11686 // will be inappropriate because memory has been reallocated
11687
11688#ifdef OOMPH_HAS_MPI
11690 {
11691 std::ostringstream warning_stream;
11692 warning_stream << "This has not been comprehensively tested for "
11693 "distributed problems.\n"
11694 << "I'm sure that I need to worry about external halo and "
11695 "external elements."
11696 << std::endl;
11699 }
11700
11701#endif
11702
11703 return (this->assign_eqn_numbers());
11704 }
11705
11706
11707 //========================================================================
11708 /// Shift all time-dependent data along for next timestep.
11709 //========================================================================
11711 {
11712 // Move the values of dt in the Time object
11713 Time_pt->shift_dt();
11714
11715 // Only shift time values in the "master" mesh, otherwise things will
11716 // get shifted twice in complex problems
11718
11719 // Shift global data with their own timesteppers
11720 unsigned Nglobal = Global_data_pt.size();
11721 for (unsigned iglobal = 0; iglobal < Nglobal; iglobal++)
11722 {
11725 }
11726 }
11727
11728
11729 //========================================================================
11730 /// Calculate the predictions of all variables in problem
11731 //========================================================================
11733 {
11734// Check that if we have multiple time steppers none of them want to
11735// predict by calling an explicit timestepper (as opposed to doing
11736// something like an explicit step by combining known history values, as
11737// done in BDF).
11738#ifdef PARANOID
11739 if (Time_stepper_pt.size() != 1)
11740 {
11741 for (unsigned j = 0; j < Time_stepper_pt.size(); j++)
11742 {
11743 if (time_stepper_pt()->predict_by_explicit_step())
11744 {
11745 std::string err = "Prediction by explicit step only works for "
11746 "problems with a simple time";
11747 err += "stepper. I think implementing anything more general will";
11748 err += "require a rewrite of explicit time steppers. - David";
11749 throw OomphLibError(
11751 }
11752 }
11753 }
11754#endif
11755
11756
11757 // Predict using an explicit timestepper (don't do it if adaptive = false
11758 // because pointers probably aren't set up).
11759 if (time_stepper_pt()->predict_by_explicit_step() &&
11760 time_stepper_pt()->adaptive_flag())
11761 {
11762 // Copy the time stepper's predictor pt into problem's explicit time
11763 // stepper pt (unless problem already has its own explicit time
11764 // stepper).
11766#ifdef PARANOID
11767 if (ets_pt == 0)
11768 {
11769 std::string err = "Requested predictions by explicit step but explicit";
11770 err += " predictor pt is null.";
11771 throw OomphLibError(
11773 }
11774
11775 if ((explicit_time_stepper_pt() != ets_pt) &&
11776 (explicit_time_stepper_pt() != 0))
11777 {
11778 throw OomphLibError("Problem has explicit time stepper other than "
11779 "predictor, not sure how to handle this yet ??ds",
11782 }
11783#endif
11785
11786 // Backup dofs and time
11788
11789#ifdef PARANOID
11790 double backup_time = time();
11791#endif
11792
11793 // Move time back so that we are at the start of the timestep (as
11794 // explicit_timestep functions expect). This is needed because the
11795 // predictor calculations are done after unsteady newton solve has
11796 // started, and it has already moved time forwards.
11797 double dt = time_pt()->dt();
11798 time() -= dt;
11799
11800 // Explicit step
11801 this->explicit_timestep(dt, false);
11802
11803 // Copy predicted dofs and time to their storage slots.
11804 set_dofs(time_stepper_pt()->predictor_storage_index(), Dof_pt);
11806
11807 // Check we got the times right
11808#ifdef PARANOID
11809 if (std::abs(time() - backup_time) > 1e-12)
11810 {
11811 using namespace StringConversion;
11812 std::string err = "Predictor landed at the wrong time!";
11813 err += " Expected time " + to_string(backup_time, 14) + " but got ";
11814 err += to_string(time(), 14);
11815 throw OomphLibError(
11817 }
11818#endif
11819
11820 // Restore dofs and time
11822 }
11823
11824 // Otherwise we can do predictions in a more object oriented way using
11825 // whatever timestepper the data provides (this is the normal case).
11826 else
11827 {
11828 // Calculate all predictions in the "master" mesh
11830
11831 // Calculate predictions for global data with their own timesteppers
11832 unsigned Nglobal = Global_data_pt.size();
11833 for (unsigned iglobal = 0; iglobal < Nglobal; iglobal++)
11834 {
11837 }
11838 }
11839
11840 // If requested then copy the predicted value into the current time data
11841 // slots, ready for the newton solver to use as an initial guess.
11843 {
11844 // Not sure I know enough about distributed problems to implement
11845 // this. Probably you just need to loop over ndof_local or something,
11846 // but I can't really test it...
11847#ifdef OOMPH_HAS_MPI
11848 if (distributed())
11849 {
11850 throw OomphLibError("Not yet implemented for distributed problems",
11853 }
11854#endif
11855
11856 // With multiple time steppers this is much more complex becuase you
11857 // need to check the time stepper for each data to get the
11858 // predictor_storage_index(). Do-able if you need it though.
11859 if (Time_stepper_pt.size() != 1)
11860 {
11861 std::string err = "Not implemented for multiple time steppers";
11862 throw OomphLibError(
11864 }
11865
11866 // Get predicted values
11868 get_dofs(time_stepper_pt()->predictor_storage_index(), predicted_dofs);
11869
11870 // Update dofs at current step
11871 for (unsigned i = 0; i < ndof(); i++)
11872 {
11873 dof(i) = predicted_dofs[i];
11874 }
11875 }
11876 }
11877
11878 //======================================================================
11879 /// Enable recycling of the mass matrix in explicit timestepping
11880 /// schemes. Useful for timestepping on fixed meshes when you want
11881 /// to avoid the linear solve phase.
11882 //=====================================================================
11884 {
11887
11888 // If we have a discontinuous formulation set the elements to reuse
11889 // their own mass matrices
11891 {
11892 const unsigned n_element = Problem::mesh_pt()->nelement();
11893 // Loop over the other elements
11894 for (unsigned e = 0; e < n_element; e++)
11895 {
11896 // Cache the element
11897 DGElement* const elem_pt =
11898 dynamic_cast<DGElement*>(Problem::mesh_pt()->element_pt(e));
11899 elem_pt->enable_mass_matrix_reuse();
11900 }
11901 }
11902 }
11903
11904 //======================================================================
11905 /// Turn off the recyling of the mass matrix in explicit
11906 /// time-stepping schemes
11907 //======================================================================
11909 {
11912
11913 // If we have a discontinuous formulation set the element-level
11914 // function
11916 {
11917 const unsigned n_element = Problem::mesh_pt()->nelement();
11918 // Loop over the other elements
11919 for (unsigned e = 0; e < n_element; e++)
11920 {
11921 // Cache the element
11922 DGElement* const elem_pt =
11923 dynamic_cast<DGElement*>(Problem::mesh_pt()->element_pt(e));
11924 elem_pt->disable_mass_matrix_reuse();
11925 }
11926 }
11927 }
11928
11929
11930 //=========================================================================
11931 /// Copy Data values, nodal positions etc from specified problem.
11932 /// Note: This is not a copy constructor. We assume that the current
11933 /// and the "original" problem have both been created by calling
11934 /// the same problem constructor so that all Data objects,
11935 /// time steppers etc. in the two problems are completely independent.
11936 /// This function copies the nodal, internal and global values
11937 /// and the time parameters from the original problem into "this"
11938 /// one. This functionality is required, e.g. for
11939 /// multigrid computations.
11940 //=========================================================================
11942 {
11943 // Copy time
11944 //----------
11945
11946 // Flag to indicate that orig problem is unsteady problem
11947 bool unsteady_flag = (orig_problem_pt->time_pt() != 0);
11948
11949 // Copy current time and previous time increments for proper unsteady run
11950 if (unsteady_flag)
11951 {
11952 oomph_info << "Copying an unsteady problem." << std::endl;
11953 // Current time
11954 this->time_pt()->time() = orig_problem_pt->time_pt()->time();
11955 // Timesteps
11956 unsigned n_dt = orig_problem_pt->time_pt()->ndt();
11957 time_pt()->resize(n_dt);
11958 for (unsigned i = 0; i < n_dt; i++)
11959 {
11960 time_pt()->dt(i) = orig_problem_pt->time_pt()->dt(i);
11961 }
11962
11963 // Find out how many timesteppers there are
11964 unsigned n_time_steppers = ntime_stepper();
11965
11966 // Loop over them all and set the weights
11967 for (unsigned i = 0; i < n_time_steppers; i++)
11968 {
11970 }
11971 }
11972
11973 // Copy nodes
11974 //-----------
11975
11976 // Loop over submeshes:
11977 unsigned nmesh = nsub_mesh();
11978 if (nmesh == 0) nmesh = 1;
11979 for (unsigned m = 0; m < nmesh; m++)
11980 {
11981 // Find number of nodes in present mesh
11982 unsigned long n_node = mesh_pt(m)->nnode();
11983
11984 // Check # of nodes:
11985 unsigned long n_node_orig = orig_problem_pt->mesh_pt(m)->nnode();
11986 if (n_node != n_node_orig)
11987 {
11988 std::ostringstream error_message;
11989 error_message << "Number of nodes in copy " << n_node
11990 << " not equal to the number in the original "
11991 << n_node_orig << std::endl;
11992
11993 throw OomphLibError(error_message.str(),
11996 }
11997
11998 // Loop over the nodes
11999 for (unsigned long i = 0; i < n_node; i++)
12000 {
12001 // Try to cast to elastic node
12003 dynamic_cast<SolidNode*>(mesh_pt(m)->node_pt(i));
12004 if (el_node_pt != 0)
12005 {
12007 dynamic_cast<SolidNode*>(orig_problem_pt->mesh_pt(m)->node_pt(i));
12009 }
12010 else
12011 {
12012 mesh_pt(m)->node_pt(i)->copy(orig_problem_pt->mesh_pt(m)->node_pt(i));
12013 }
12014 }
12015 }
12016
12017
12018 // Copy global data:
12019 //------------------
12020
12021 // Number of global data
12022 unsigned n_global = Global_data_pt.size();
12023
12024 // Check # of nodes in orig problem
12025 unsigned long n_global_orig = orig_problem_pt->nglobal_data();
12026 if (n_global != n_global_orig)
12027 {
12028 std::ostringstream error_message;
12029 error_message << "Number of global data in copy " << n_global
12030 << " not equal to the number in the original "
12031 << n_global_orig << std::endl;
12032
12033 throw OomphLibError(
12034 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
12035 }
12036
12037 for (unsigned iglobal = 0; iglobal < n_global; iglobal++)
12038 {
12039 Global_data_pt[iglobal]->copy(orig_problem_pt->global_data_pt(iglobal));
12040 }
12041
12042
12043 // Copy internal data of elements:
12044 //--------------------------------
12045
12046 // Loop over submeshes:
12047 for (unsigned m = 0; m < nmesh; m++)
12048 {
12049 // Loop over elements and deal with internal data
12050 unsigned n_element = mesh_pt(m)->nelement();
12051 for (unsigned e = 0; e < n_element; e++)
12052 {
12054 unsigned n_internal = el_pt->ninternal_data();
12055 if (n_internal > 0)
12056 {
12057 // Check # of internals :
12058 unsigned long n_internal_orig =
12059 orig_problem_pt->mesh_pt(m)->element_pt(e)->ninternal_data();
12061 {
12062 std::ostringstream error_message;
12063 error_message << "Number of internal data in copy " << n_internal
12064 << " not equal to the number in the original "
12065 << n_internal_orig << std::endl;
12066
12067 throw OomphLibError(error_message.str(),
12070 }
12071 for (unsigned i = 0; i < n_internal; i++)
12072 {
12074 orig_problem_pt->mesh_pt(m)->element_pt(e)->internal_data_pt(i));
12075 }
12076 }
12077 }
12078 }
12079 }
12080
12081 //=========================================================================
12082 /// Make and return a pointer to the copy of the problem. A virtual
12083 /// function that must be filled in by the user is they wish to perform
12084 /// adaptive refinement in bifurcation tracking or in multigrid problems.
12085 /// ALH: WILL NOT BE NECESSARY IN BIFURCATION TRACKING IN LONG RUN...
12086 //=========================================================================
12088 {
12089 std::ostringstream error_stream;
12091 << "This function must be overloaded in your specific problem, and must\n"
12092 << "create an exact copy of your problem. Usually this will be achieved\n"
12093 << "by a call to the constructor with exactly the same arguments as "
12094 "used\n";
12095
12096 throw OomphLibError(
12098 }
12099
12100
12101 //=========================================================================
12102 /// Dump refinement pattern of all refineable meshes and all generic
12103 /// Problem data to file for restart.
12104 //=========================================================================
12105 void Problem::dump(std::ofstream& dump_file) const
12106 {
12107 // Number of submeshes?
12108 unsigned n_mesh = nsub_mesh();
12109
12110 dump_file << std::max(unsigned(1), n_mesh) << " # number of (sub)meshes "
12111 << std::endl;
12112
12113 // Single mesh:
12114 //------------
12115 if (n_mesh == 0)
12116 {
12117 // Dump level of refinement before pruning
12119 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(0)))
12120 {
12121 dump_file << mmesh_pt->uniform_refinement_level_when_pruned()
12122 << " # uniform refinement when pruned " << std::endl;
12123 }
12124 else
12125 {
12126 dump_file << 0 << " # (fake) uniform refinement when pruned "
12127 << std::endl;
12128 }
12129 dump_file << 9999 << " # test flag for end of sub-meshes " << std::endl;
12130 }
12131
12132 // Multiple submeshes
12133 //------------------
12134 else
12135 {
12136 // Loop over submeshes to dump level of refinement before pruning
12137 for (unsigned imesh = 0; imesh < n_mesh; imesh++)
12138 {
12140 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(imesh)))
12141 {
12142 dump_file << mmesh_pt->uniform_refinement_level_when_pruned()
12143 << " # uniform refinement when pruned " << std::endl;
12144 }
12145 else
12146 {
12147 dump_file << 0 << " # (fake) uniform refinement when pruned "
12148 << std::endl;
12149 }
12150 }
12151 dump_file << 9999 << " # test flag for end of sub-meshes " << std::endl;
12152 }
12153
12154#ifdef OOMPH_HAS_MPI
12155
12156 const int my_rank = this->communicator_pt()->my_rank();
12157
12158 // Record destination of all base elements
12159 unsigned n = Base_mesh_element_pt.size();
12162 for (unsigned e = 0; e < n; e++)
12163 {
12165 if (el_pt != 0)
12166 {
12167 if (!el_pt->is_halo())
12168 {
12170 }
12171 }
12172 }
12173
12174
12175 // Get target for all base elements by reduction
12177 {
12178 // Check that the base elements have been associated to a processor
12179 // (the Base_mesh_elemen_pt is only used for structured meshes,
12180 // therefore, if there are no ustructured meshes as part of the
12181 // problem this container will be empty)
12182 if (n > 0)
12183 {
12186 n,
12187 MPI_INT,
12188 MPI_MAX,
12189 this->communicator_pt()->mpi_comm());
12190 }
12191 }
12192 else
12193 {
12194 // All the same...
12196 }
12197
12198
12199 dump_file << n << " # Number of base elements; partitioning follows.\n";
12200 for (unsigned e = 0; e < n; e++)
12201 {
12203 }
12204 dump_file << "8888 #test flag for end of base element distribution\n";
12205
12206#endif
12207
12208 // Single mesh:
12209 //------------
12210 if (n_mesh == 0)
12211 {
12212 // Dump single mesh refinement pattern (if mesh is refineable)
12214 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(0)))
12215 {
12216 mmesh_pt->dump_refinement(dump_file);
12217 }
12218#ifdef OOMPH_HAS_TRIANGLE_LIB
12219 // Dump triangle mesh TriangulateIO which represents mesh topology
12220 TriangleMeshBase* mmesh_pt = dynamic_cast<TriangleMeshBase*>(mesh_pt(0));
12221 if (mmesh_pt != 0 && mmesh_pt->use_triangulateio_restart())
12222 {
12223#ifdef OOMPH_HAS_MPI
12224 // Check if the mesh is distributed, if that is the case then
12225 // additional info. needs to be saved
12226 if (mmesh_pt->is_mesh_distributed())
12227 {
12228 // Dump the info. related with the distribution of the mesh
12229 mmesh_pt->dump_distributed_info_for_restart(dump_file);
12230 }
12231#endif
12232 mmesh_pt->dump_triangulateio(dump_file);
12233 }
12234#endif
12235 }
12236
12237 // Multiple submeshes
12238 //------------------
12239 else
12240 {
12241 // Loop over submeshes
12242 for (unsigned imesh = 0; imesh < n_mesh; imesh++)
12243 {
12244 // Dump single mesh refinement pattern (if mesh is refineable)
12246 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(imesh)))
12247 {
12248 mmesh_pt->dump_refinement(dump_file);
12249 }
12250#ifdef OOMPH_HAS_TRIANGLE_LIB
12251 // Dump triangle mesh TriangulateIO which represents mesh topology
12253 dynamic_cast<TriangleMeshBase*>(mesh_pt(imesh));
12254 if (mmesh_pt != 0 && mmesh_pt->use_triangulateio_restart())
12255 {
12256#ifdef OOMPH_HAS_MPI
12257 // Check if the mesh is distributed, if that is the case then
12258 // additional info. needs to be saved
12259 if (mmesh_pt->is_mesh_distributed())
12260 {
12261 // Dump the info. related with the distribution of the mesh
12262 mmesh_pt->dump_distributed_info_for_restart(dump_file);
12263 }
12264#endif
12265 mmesh_pt->dump_triangulateio(dump_file);
12266 }
12267#endif
12268 } // End of loop over submeshes
12269 }
12270
12271
12272 // Dump time
12273 // ---------
12274
12275 // Flag to indicate unsteady run
12276 bool unsteady_flag = (time_pt() != 0);
12277 dump_file << unsteady_flag << " # bool flag for unsteady" << std::endl;
12278
12279 // Current time and previous time increments for proper unsteady run
12280 if (unsteady_flag)
12281 {
12282 // Current time
12283 dump_file << time_pt()->time() << " # Time " << std::endl;
12284 // Timesteps
12285 unsigned n_dt = time_pt()->ndt();
12286 dump_file << n_dt << " # Number of timesteps " << std::endl;
12287 for (unsigned i = 0; i < n_dt; i++)
12288 {
12289 dump_file << time_pt()->dt(i) << " # dt " << std::endl;
12290 }
12291 }
12292 // Dummy time and previous time increments for steady run
12293 else
12294 {
12295 // Current time
12296 dump_file << "0.0 # Dummy time from steady run " << std::endl;
12297 // Timesteps
12298 dump_file << "0 # Dummy number of timesteps from steady run" << std::endl;
12299 }
12300
12301 // Loop over submeshes and dump their data
12302 unsigned nmesh = nsub_mesh();
12303 if (nmesh == 0) nmesh = 1;
12304 for (unsigned m = 0; m < nmesh; m++)
12305 {
12307 }
12308
12309 // Dump global data
12310
12311 // Loop over global data
12312 unsigned Nglobal = Global_data_pt.size();
12313 dump_file << Nglobal << " # number of global Data items " << std::endl;
12314 for (unsigned iglobal = 0; iglobal < Nglobal; iglobal++)
12315 {
12317 dump_file << std::endl;
12318 }
12319 }
12320
12321 //=========================================================================
12322 /// Read refinement pattern of all refineable meshes and refine them
12323 /// accordingly, then read all Data and nodal position info from
12324 /// file for restart. Return flag to indicate if the restart was from
12325 /// steady or unsteady solution.
12326 //=========================================================================
12327 void Problem::read(std::ifstream& restart_file, bool& unsteady_restart)
12328 {
12329 // Check if the file is actually open as it won't be if it doesn't
12330 // exist! In that case we're almost certainly restarting the run on
12331 // a larger number of processors than the restart data was produced.
12332 // Say so and return
12333 bool restart_file_is_open = true;
12334 if (!restart_file.is_open())
12335 {
12336 std::ostringstream warn_message;
12337 warn_message << "Restart file isn't open -- I'm assuming that this is\n";
12338 warn_message << "because we're restarting on a larger number of\n";
12339 warn_message << "processor than were in use when the restart data was \n";
12340 warn_message << "dumped.\n";
12342 warn_message.str(), "Problem::read()", OOMPH_EXCEPTION_LOCATION);
12343 restart_file_is_open = false;
12344 }
12345
12346 // Number of (sub)meshes?
12347 unsigned n_mesh = std::max(unsigned(1), nsub_mesh());
12348
12349 std::string input_string;
12350
12351 // Read line up to termination sign
12352 getline(restart_file, input_string, '#');
12353
12354 // Ignore rest of line
12355 restart_file.ignore(80, '\n');
12356
12357 // Read in number of sub-meshes
12358 unsigned n_submesh_read;
12359 n_submesh_read = std::atoi(input_string.c_str());
12360
12361#ifdef PARANOID
12363 {
12364 if (n_submesh_read != n_mesh)
12365 {
12366 std::ostringstream error_message;
12367 error_message
12368 << "Number of sub-meshes specified in restart file, "
12369 << n_submesh_read << " doesn't \n match the my number of sub-meshes,"
12370 << n_mesh << std::endl
12371 << "Make sure all sub-meshes have been added to the global mesh\n"
12372 << "when calling the Problem::dump() function.\n";
12373 throw OomphLibError(error_message.str(),
12376 }
12377 }
12378#else
12379 // Suppress comiler warnings about non-used variable
12382#endif
12383
12384
12385 // Read levels of refinement before pruning
12386#ifdef OOMPH_HAS_MPI
12387 bool refine_and_prune_required = false;
12388#endif
12390 for (unsigned i = 0; i < n_mesh; i++)
12391 {
12392 // Read line up to termination sign
12393 getline(restart_file, input_string, '#');
12394
12395 // Ignore rest of line
12396 restart_file.ignore(80, '\n');
12397
12398 // Convert
12399 nrefinement_for_mesh[i] = std::atoi(input_string.c_str());
12400
12401 // Get pointer to sub-mesh in incarnation as tree-based refineable mesh
12403 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(i));
12404
12405 // If it's not a tree-based refineable mesh, ignore the following
12406 if (ref_mesh_pt == 0)
12407 {
12408 if (nrefinement_for_mesh[i] != 0)
12409 {
12410 std::ostringstream error_stream;
12411 error_stream << "Nonzero uniform-refinement-when-pruned specified\n"
12412 << "even though mesh is not tree-based. Odd. May want\n"
12413 << "to check this carefully before disabling this \n"
12414 << "warning/error -- most likely if/when we start to\n"
12415 << "prune unstructured meshes [though I can't see why\n"
12416 << "we would want to do this, given that they are \n"
12417 << "currently totally re-generated...]\n";
12418 throw OomphLibError(error_stream.str(),
12421 }
12422 }
12423 else
12424 {
12425 // Get min and max refinement level
12426 unsigned local_min_ref = 0;
12427 unsigned local_max_ref = 0;
12428 ref_mesh_pt->get_refinement_levels(local_min_ref, local_max_ref);
12429
12430 // Overall min refinement level over all meshes
12431 unsigned min_ref = local_min_ref;
12432
12433#ifdef OOMPH_HAS_MPI
12435 {
12436 // Reconcile between processors: If (e.g. following
12437 // distribution/pruning) the mesh has no elements on this
12438 // processor) then ignore its contribution to the poll of
12439 // max/min refinement levels
12441 if (ref_mesh_pt->nelement() == 0)
12442 {
12444 }
12445 int int_min_ref = 0;
12447 &int_min_ref,
12448 1,
12449 MPI_INT,
12450 MPI_MIN,
12451 Communicator_pt->mpi_comm());
12452
12453 // Overall min refinement level over all meshes
12455 }
12456#endif
12457
12458 // Need to refine less
12460 {
12462 }
12463 }
12464
12465#ifdef OOMPH_HAS_MPI
12466 if (nrefinement_for_mesh[i] > 0)
12467 {
12469 }
12470#endif
12471 }
12472
12473
12474 // Reconcile overall need to refine and prune (even empty
12475 // processors have to participate in some communication!)
12476#ifdef OOMPH_HAS_MPI
12478 {
12479 unsigned local_req_flag = 0;
12480 unsigned req_flag = 0;
12482 {
12483 local_req_flag = 1;
12484 }
12486 &req_flag,
12487 1,
12489 MPI_MAX,
12490 Communicator_pt->mpi_comm());
12492 if (req_flag == 1)
12493 {
12495 }
12496
12497 // If refine and prune is required make number of uniform
12498 // refinements for each mesh consistent otherwise code
12499 // hangs on "empty" processors for which no restart file exists
12501 {
12502 // This is what we have locally
12504 // Synchronise over all processors with max operation
12507 n_mesh,
12509 MPI_MAX,
12510 Communicator_pt->mpi_comm());
12511
12512#ifdef PARANOID
12513 // Check it: Reconciliation should only be required for
12514 // for processors on which no restart file was opened and
12515 // for which the meshes are therefore empty
12516 bool fail = false;
12517 std::ostringstream error_message;
12518 error_message << "Number of uniform refinements was not consistent \n"
12519 << "for following meshes during restart on processor \n"
12520 << "on which restart file could be opened:\n";
12521 for (unsigned i = 0; i < n_mesh; i++)
12522 {
12525 {
12526 fail = true;
12527 error_message << "Sub-mesh: " << i << "; local nrefinement: "
12528 << local_nrefinement_for_mesh[i] << " "
12529 << "; global/synced nrefinement: "
12530 << nrefinement_for_mesh[i] << "\n";
12531 }
12532 }
12533 if (fail)
12534 {
12536 error_message.str(), "Problem::read()", OOMPH_EXCEPTION_LOCATION);
12537 }
12538#endif
12539 }
12540 }
12541#endif
12542
12543 // Read line up to termination sign
12544 getline(restart_file, input_string, '#');
12545
12546 // Ignore rest of line
12547 restart_file.ignore(80, '\n');
12548
12549 // Check flag that indicates that we've read the final data
12550 unsigned tmp;
12551 tmp = std::atoi(input_string.c_str());
12552
12553#ifdef PARANOID
12555 {
12556 if (tmp != 9999)
12557 {
12558 std::ostringstream error_message;
12559 error_message
12560 << "Error in reading restart data: Uniform refinement when pruned \n"
12561 << "flags should be followed by 9999.\n";
12562 throw OomphLibError(error_message.str(),
12565 }
12566 }
12567
12568#else
12569 // Suppress comiler warnings about non-used variable
12570 tmp++;
12571 tmp--;
12572#endif
12573
12574
12575#ifdef OOMPH_HAS_MPI
12576
12577 // Refine and prune if required
12579 {
12582 }
12583
12584 // target_domain_for_local_non_halo_element[e] contains the number
12585 // of the domain [0,1,...,nproc-1] to which non-halo element e on THE
12586 // CURRENT PROCESSOR ONLY has been assigned. The order of the non-halo
12587 // elements is the same as in the Problem's mesh, with the halo
12588 // elements being skipped.
12590
12591 // If a restart file has been generated using code compiled without MPI
12592 // then it will not have any of the base element data.
12593 // If we try to read in that file with code that has been compied using
12594 // MPI, even if running only one processor, then it will fail here.
12595 // The ideal fix is to edit the restart file so that it contains the two
12596 // lines
12597 //
12598 // 0 # Number of base elements; partitioning follows.
12599 // 8888 # Test flag for end of base element distribution
12600 //
12601 // after the end of the sub-meshes, but before the number of elements
12602 // However, we can determine that this is the problem if n_base = 0,
12603 // so there is a little bit of logic below to catch this case
12604
12605 // Store current location in the file (before we are about to read
12606 // in either the base mesh or number of elements of the first mesh)
12607 std::streampos position_before_base_element = restart_file.tellg();
12608 // Boolean flag used to set whether to read in base element info
12609 bool read_in_base_element_info = true;
12610
12611 // Read line up to termination sign
12612 getline(restart_file, input_string, '#');
12613
12614 // Ignore rest of line
12615 restart_file.ignore(80, '\n');
12616
12617 // Get number of base elements as recorded
12618 unsigned n_base_element_read_in = atoi(input_string.c_str());
12619 unsigned nbase = Base_mesh_element_pt.size();
12621 {
12623 {
12624 // If we have zero base elements the problem could be that the
12625 // restart file was generated without MPI. Issue a warning
12626 // and continue anyway
12627 if (nbase == 0)
12628 {
12629 std::ostringstream warn_message;
12631 << "The number of base elements in the mesh is 0,\n"
12632 << " but the restart file indicates that there are "
12633 << n_base_element_read_in << ".\n"
12634 << "This could be because the restart file was \n"
12635 << "generated by using code without MPI.\n"
12636 << "\n"
12637 << "The best fix is to include two additional lines\n"
12638 << "in the restart file: \n\n"
12639 << "0 # Number of base elements; partitioning follows.\n"
12640 << "8888 # Test flag for end of base element distribution\n"
12641 << "\n"
12642 << "These lines go after the flag 9999 that indicates\n"
12643 << "the end of the submesh information.\n"
12644 << "\n"
12645 << "The file will now continue to be read assuming that\n"
12646 << "the base element information is not present.\n"
12647 << "If you get strange results then please look carefully\n"
12648 << "at the restart file. The safest thing to do is to \n"
12649 << "ensure that the restart file was generated by code\n"
12650 << "compiled and run with the same parallel options.\n";
12654 // Set the skip flag to true
12655 // and rewind the file pointer
12658 }
12659 // Otherwise throw a hard error
12660 else
12661 {
12662 std::ostringstream error_message;
12663 error_message << "About to read " << n_base_element_read_in
12664 << " base elements \n"
12665 << "though we only have " << nbase
12666 << " base elements in mesh.\n";
12667 throw OomphLibError(error_message.str(),
12670 }
12671 }
12672 }
12673
12674 // Read in the remaning base element information, if necessary
12675 if (read_in_base_element_info == true)
12676 {
12677 // Read in target_domain_for_base_element[e] for all base elements
12679 for (unsigned e = 0; e < nbase; e++)
12680 {
12681 // Read line
12682 getline(restart_file, input_string);
12683
12684 // Get target domain
12686 }
12687
12688 // Read line up to termination sign
12689 getline(restart_file, input_string, '#');
12690
12691 // Ignore rest of line
12692 restart_file.ignore(80, '\n');
12693
12694 // Check flag that indicates that we've read the final data
12695 tmp = std::atoi(input_string.c_str());
12696
12697
12698#ifdef PARANOID
12700 {
12701 if (tmp != 8888)
12702 {
12703 std::ostringstream error_message;
12704 error_message
12705 << "Error in reading restart data: Target proc for base elements \n"
12706 << "should be followed by 8888.\n";
12707 throw OomphLibError(error_message.str(),
12710 }
12711 }
12712#endif
12713
12714 // Loop over all elements (incl. any FaceElements) and assign
12715 // target domain for all local non-halo elements and check if
12716 // load balancing is required -- no need to do this if problem is
12717 // not distributed.
12718 unsigned load_balance_required_flag = 0;
12720 {
12721 // Working with TreeBasedRefineableMeshBase mesh
12723 if (dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(0)))
12724 {
12725 const int my_rank = this->communicator_pt()->my_rank();
12726 unsigned nel = mesh_pt()->nelement();
12727 for (unsigned e = 0; e < nel; e++)
12728 {
12730 if (!el_pt->is_halo())
12731 {
12732 // Get element number (plus one) in base element enumeration
12735
12736 // If it's zero then we haven't found it, it may be a FaceElement
12737 // (in which case we move it to the same processor as its bulk
12738 // element
12740 {
12741 FaceElement* face_el_pt = dynamic_cast<FaceElement*>(el_pt);
12742 if (face_el_pt != 0)
12743 {
12744 // Get corresponding bulk element
12745 FiniteElement* bulk_el_pt = face_el_pt->bulk_element_pt();
12746
12747 // Use its element number (plus one) in base element
12748 // enumeration
12751
12752 // If this is zero too we have a problem
12754 {
12755 throw OomphLibError(
12756 "el_number_in_base_mesh_plus_one=0 for bulk",
12757 "Problem::read()",
12759 }
12760 }
12761 }
12762
12763 // If we've made it here then we're not dealing with a
12764 // FaceElement but with an element that doesn't exist locally
12765 // --> WTF?
12767 {
12768 throw OomphLibError("el_number_in_base_mesh_plus_one=0",
12771 }
12772
12773 // Assign target domain for next local non-halo element in
12774 // the order in which it's encountered in the global mesh
12777 1]);
12778
12779 // Do elements on this processor to be moved elsewhere?
12782 {
12784 }
12785 }
12786 }
12787
12788 } // if (working with TreeBasedRefineableMeshBase mesh)
12789
12790 // Get overall need to load balance by max
12793 1,
12795 MPI_MAX,
12796 this->communicator_pt()->mpi_comm());
12797 }
12798
12799 // Do we need to load balance?
12801 {
12802 oomph_info << "Doing load balancing after pruning\n";
12803 DocInfo doc_info;
12804 doc_info.disable_doc();
12805 bool report_stats = false;
12808 oomph_info << "Done load balancing after pruning\n";
12809 }
12810 else
12811 {
12812 oomph_info << "No need for load balancing after pruning\n";
12813 }
12814 } // End of read in base element information
12815#endif
12816
12817
12818 // Boolean to record if any unstructured bulk meshes have
12819 // been read in (and therefore completely re-generated, with new
12820 // elements and nodes) from disk
12821 bool have_read_unstructured_mesh = false;
12822
12823 // Call the actions before adaptation
12825
12826 // If there are unstructured meshes in the problem we need
12827 // to strip out any face elements that are attached to them
12828 // because restart of unstructured meshes re-creates their elements
12829 // and nodes from scratch, leading to dangling pointers from the
12830 // face elements to the old elements and nodes. This function is
12831 // virtual and (practically) empty in the Problem base class
12832 // but toggles a flag to indicate that it has been called. We can then
12833 // issue a warning below, prompting the user to consider overloading it
12834 // if the problem is found to contain unstructured bulk meshes.
12835 // Warning can be ignored if the bulk mesh is not associated with any
12836 // face elements.
12839
12840 // Update number of submeshes
12841 n_mesh = nsub_mesh();
12842
12843 // Single mesh:
12844 //------------
12845 if (n_mesh == 0)
12846 {
12847 // Refine single mesh (if it's refineable)
12849 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(0)))
12850 {
12851 // When we get in here the problem has been constructed
12852 // by the constructor and the mesh is its original unrefined
12853 // form.
12854 // RefineableMeshBase::refine(...) reads the refinement pattern from the
12855 // specified file and performs refinements until the mesh has
12856 // reached the same level of refinement as the mesh that existed
12857 // when the problem was dumped to disk.
12858 mmesh_pt->refine(restart_file);
12859 }
12860#ifdef OOMPH_HAS_TRIANGLE_LIB
12861 // Regenerate mesh from triangulate IO if it's a triangular mesh
12862 TriangleMeshBase* mmesh_pt = dynamic_cast<TriangleMeshBase*>(mesh_pt(0));
12863 if (mmesh_pt != 0 && mmesh_pt->use_triangulateio_restart())
12864 {
12865#ifdef OOMPH_HAS_MPI
12866 // Check if the mesh is distributed, if that is the case then
12867 // additional info. needs to be read
12868 if (mmesh_pt->is_mesh_distributed())
12869 {
12870 // Dump the info. related with the distribution of the mesh
12871 mmesh_pt->read_distributed_info_for_restart(restart_file);
12872 }
12873#endif
12874 // The function reads the TriangulateIO data structure from the dump
12875 // file and then completely regenerates the mesh using the
12876 // data structure
12877 mmesh_pt->remesh_from_triangulateio(restart_file);
12879#ifdef OOMPH_HAS_MPI
12880 // Check if the mesh is distributed, if that is the case then we
12881 // need to re-establish the halo/haloed scheme (similar as in the
12882 // RefineableTriangleMesh::adapt() method)
12883 if (mmesh_pt->is_mesh_distributed())
12884 {
12885 mmesh_pt->reestablish_distribution_info_for_restart(
12886 this->communicator_pt(), restart_file);
12887 }
12888#endif
12889 // Still left to update the polylines representation, that is performed
12890 // later since the nodes positions may still change when reading info.
12891 // for the mesh, see below
12892 }
12893#endif
12894 }
12895
12896 // Multiple submeshes
12897 //------------------
12898 else
12899 {
12900 // Loop over submeshes
12901 for (unsigned imesh = 0; imesh < n_mesh; imesh++)
12902 {
12903 // Refine single mesh (if its refineable)
12905 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(imesh)))
12906 {
12907 // When we get in here the problem has been constructed
12908 // by the constructor and the mesh is its original unrefined
12909 // form.
12910 // RefineableMeshBase::refine(...) reads the refinement pattern from
12911 // the specified file and performs refinements until the mesh has
12912 // reached the same level of refinement as the mesh that existed
12913 // when the problem was dumped to disk.
12914 mmesh_pt->refine(restart_file);
12915 }
12916#ifdef OOMPH_HAS_TRIANGLE_LIB
12917 // Regenerate mesh from triangulate IO if it's a triangular mesh
12919 dynamic_cast<TriangleMeshBase*>(mesh_pt(imesh));
12920 if (mmesh_pt != 0 && mmesh_pt->use_triangulateio_restart())
12921 {
12922#ifdef OOMPH_HAS_MPI
12923 // Check if the mesh is distributed, if that is the case then
12924 // additional info. needs to be read
12925 if (mmesh_pt->is_mesh_distributed())
12926 {
12927 // Dump the info. related with the distribution of the mesh
12928 mmesh_pt->read_distributed_info_for_restart(restart_file);
12929 }
12930#endif
12931 // The function reads the TriangulateIO data structure from the dump
12932 // file and then completely regenerates the mesh using the
12933 // data structure
12934 mmesh_pt->remesh_from_triangulateio(restart_file);
12936
12937#ifdef OOMPH_HAS_MPI
12938 // Check if the mesh is distributed, if that is the case then we
12939 // need to re-establish the halo/haloed scheme (similar as in the
12940 // RefineableTriangleMesh::adapt() method)
12941 if (mmesh_pt->is_mesh_distributed())
12942 {
12943 mmesh_pt->reestablish_distribution_info_for_restart(
12944 this->communicator_pt(), restart_file);
12945 }
12946#endif
12947 // Still left to update the polylines representation, that is
12948 // performed later since the nodes positions may still change when
12949 // reading info. for the mesh, see below
12950 }
12951#endif
12952 } // End of loop over submeshes
12953
12954
12955 // Rebuild the global mesh
12957 }
12958
12959 // Any actions after adapt
12961
12962 // Re-attach face elements (or whatever else needs to be done
12963 // following the total re-generation of the unstructured meshes
12966
12967
12968 // Issue warning:
12970 {
12972 {
12975 {
12976 std::ostringstream warn_message;
12978 << "I've just read in some unstructured meshes and have, in\n"
12979 << "the process, totally re-generated their nodes and elements.\n"
12980 << "This may create dangling pointers that still point to the\n"
12981 << "old nodes and elements, e.g. because FaceElements were\n"
12982 << "attached to these meshes or pointers to nodes and elements\n"
12983 << "were stored somewhere. FaceElements should therefore be\n"
12984 << "removed before reading in these meshes, using an overloaded\n"
12985 << "version of the function\n\n"
12986 << " Problem::actions_before_read_unstructured_meshes()\n\n"
12987 << "and then re-attached using an overloaded version of\n\n"
12988 << " Problem::actions_after_read_unstructured_meshes().\n\n"
12989 << "The required content of these functions is likely to be "
12990 "similar\n"
12991 << "to the Problem::actions_before_adapt() and \n"
12992 << "Problem::actions_after_adapt() that would be required in\n"
12993 << "a spatially adaptive computation. If these functions already\n"
12994 << "exist and perform the required actions, the \n"
12995 << "actions_before/after_read_unstructured_meshes() functions\n"
12996 << "can remain empty because the former are called automatically.\n"
12997 << "In this case, this warning my be suppressed by setting the\n"
12998 << "public boolean\n\n"
12999 << " "
13000 "Problem::Suppress_warning_about_actions_before_read_"
13001 "unstructured_meshes\n\n"
13002 << "to true." << std::endl;
13006 }
13007 }
13008 }
13009
13010 // Setup equation numbering scheme
13011 oomph_info << "\nNumber of equations in Problem::read(): "
13012 << assign_eqn_numbers() << std::endl
13013 << std::endl;
13014 // Read time info
13015 //---------------
13016 unsigned local_unsteady_restart_flag = 0;
13017 double local_time = -DBL_MAX;
13018 unsigned local_n_dt = 0;
13019#ifdef OOMPH_HAS_MPI
13020 unsigned local_sync_needed_flag = 0;
13021#endif
13023
13024 if (restart_file.is_open())
13025 {
13026 oomph_info << "Restart file exists" << std::endl;
13027#ifdef OOMPH_HAS_MPI
13029#endif
13030 // Read line up to termination sign
13031 getline(restart_file, input_string, '#');
13032
13033 // Ignore rest of line
13034 restart_file.ignore(80, '\n');
13035
13036 // Is the restart data from an unsteady run?
13038
13039 // Read line up to termination sign
13040 getline(restart_file, input_string, '#');
13041
13042 // Ignore rest of line
13043 restart_file.ignore(80, '\n');
13044
13045 // Read in initial time and set
13046 local_time = atof(input_string.c_str());
13047
13048 // Read line up to termination sign
13049 getline(restart_file, input_string, '#');
13050
13051 // Ignore rest of line
13052 restart_file.ignore(80, '\n');
13053
13054 // Read & set number of timesteps
13055 local_n_dt = atoi(input_string.c_str());
13056 local_dt.resize(local_n_dt);
13057
13058 // Read in timesteps:
13059 for (unsigned i = 0; i < local_n_dt; i++)
13060 {
13061 // Read line up to termination sign
13062 getline(restart_file, input_string, '#');
13063
13064 // Ignore rest of line
13065 restart_file.ignore(80, '\n');
13066
13067 // Read in initial time and set
13068 double prev_dt = atof(input_string.c_str());
13069 local_dt[i] = prev_dt;
13070 }
13071 }
13072 else
13073 {
13074 oomph_info << "Restart file does not exist" << std::endl;
13075#ifdef OOMPH_HAS_MPI
13077#endif
13078 }
13079
13080
13081 // No prepare global values, possibly via sync
13082 Vector<double> dt;
13083
13084 // Do we need to sync?
13085 unsigned sync_needed_flag = 0;
13086
13087#ifdef OOMPH_HAS_MPI
13089 {
13090 // Get need to sync by max
13093 1,
13095 MPI_MAX,
13096 this->communicator_pt()->mpi_comm());
13097 }
13098#endif
13099
13100 // Synchronise
13101 if (sync_needed_flag == 1)
13102 {
13103#ifdef OOMPH_HAS_MPI
13104
13105
13106#ifdef PARANOID
13108 {
13109 std::ostringstream error_message;
13110 error_message << "Synchronisation of temporal restart data \n"
13111 << "required even though Problem hasn't been distributed "
13112 "-- very odd!\n";
13113 throw OomphLibError(error_message.str(),
13116 }
13117#endif
13118
13119 // Get unsteady restart flag by max-based reduction
13120 unsigned unsteady_restart_flag = 0;
13123 1,
13125 MPI_MAX,
13126 this->communicator_pt()->mpi_comm());
13127
13128 // So, is it an unsteady restart?
13129 unsteady_restart = false;
13130 if (unsteady_restart_flag == 1)
13131 {
13132 unsteady_restart = true;
13133
13134 // Get time by max
13135 double time = -DBL_MAX;
13137 &time,
13138 1,
13139 MPI_DOUBLE,
13140 MPI_MAX,
13141 this->communicator_pt()->mpi_comm());
13142 time_pt()->time() = time;
13143
13144 // Get number of timesteps by max-based reduction
13145 unsigned n_dt = 0;
13147 &n_dt,
13148 1,
13150 MPI_MAX,
13151 this->communicator_pt()->mpi_comm());
13152
13153 // Resize whatever needs resizing
13154 time_pt()->resize(n_dt);
13155 dt.resize(n_dt);
13156 if (local_dt.size() == 0)
13157 {
13158 local_dt.resize(n_dt, -DBL_MAX);
13159 }
13160
13161 // Get timesteps increments by max-based reduction
13163 &dt[0],
13164 n_dt,
13165 MPI_DOUBLE,
13166 MPI_MAX,
13167 this->communicator_pt()->mpi_comm());
13168 }
13169
13170#else
13171
13172 std::ostringstream error_message;
13173 error_message
13174 << "Synchronisation of temporal restart data \n"
13175 << "required even though we don't have mpi support -- very odd!\n";
13176 throw OomphLibError(
13177 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
13178
13179#endif
13180 }
13181 // No sync needed -- just copy across
13182 else
13183 {
13184 unsteady_restart = false;
13186 {
13187 unsteady_restart = true;
13188 time_pt()->time() = local_time;
13190 dt.resize(local_n_dt);
13191 for (unsigned i = 0; i < local_n_dt; i++)
13192 {
13193 dt[i] = local_dt[i];
13194 }
13195 }
13196 }
13197
13198 // Initialise timestep -- also sets the weights for all timesteppers
13199 // in the problem.
13201
13202 // Loop over submeshes:
13203 unsigned nmesh = nsub_mesh();
13204 if (nmesh == 0) nmesh = 1;
13205 for (unsigned m = 0; m < nmesh; m++)
13206 {
13207 // //---------------------------------------------------------
13208 // // Keep this commented out code around to debug restarts
13209 // //---------------------------------------------------------
13210 // std::ofstream some_file;
13211 // char filename[100];
13212 // snprintf(filename, sizeof(filename), "read_mesh%i_on_proc%i.dat",m,
13213 // this->communicator_pt()->my_rank());
13214 // some_file.open(filename);
13215 // mesh_pt(m)->output(some_file);
13216 // some_file.close();
13217
13218 // snprintf(filename, sizeof(filename),
13219 // "read_mesh%i_with_haloes_on_proc%i.dat",m,
13220 // this->communicator_pt()->my_rank());
13221 // mesh_pt(m)->enable_output_of_halo_elements();
13222 // some_file.open(filename);
13223 // mesh_pt(m)->output(some_file);
13224 // mesh_pt(m)->disable_output_of_halo_elements();
13225 // some_file.close();
13226 // oomph_info << "Doced mesh " << m << " before reading\n";
13227
13228 // snprintf(filename, sizeof(filename),
13229 // "read_nodes_mesh%i_on_proc%i.dat",m,
13230 // this->communicator_pt()->my_rank());
13231 // some_file.open(filename);
13232 // unsigned nnod=mesh_pt(m)->nnode();
13233 // for (unsigned j=0;j<nnod;j++)
13234 // {
13235 // Node* nod_pt=mesh_pt(m)->node_pt(j);
13236 // unsigned n=nod_pt->ndim();
13237 // for (unsigned i=0;i<n;i++)
13238 // {
13239 // some_file << nod_pt->x(i) << " ";
13240 // }
13241 // some_file << nod_pt->is_halo() << " "
13242 // << nod_pt->nvalue() << " "
13243 // << nod_pt->hang_code() << "\n";
13244 // }
13245 // some_file.close();
13246 // oomph_info << "Doced mesh " << m << " before reading\n";
13247 // //---------------------------------------------------------
13248 // // End keep this commented out code around to debug restarts
13249 // //---------------------------------------------------------
13250
13252
13253#ifdef OOMPH_HAS_TRIANGLE_LIB
13254 // Here update the polyline representation if working with
13255 // triangle base meshes
13257 dynamic_cast<TriangleMeshBase*>(mesh_pt(m)))
13258 {
13259 // In charge of updating the polylines representation to the
13260 // current refinement/unrefinement level after restart, it
13261 // also update the shared boundaries in case of working with a
13262 // distributed mesh
13263 mmesh_pt->update_polyline_representation_from_restart();
13264 }
13265#endif // #ifdef OOMPH_HAS_TRIANGLE_LIB
13266 }
13267
13268 // Read global data:
13269 //------------------
13270
13271 // Number of global data
13272 unsigned Nglobal = Global_data_pt.size();
13273
13274 // Read line up to termination sign
13275 getline(restart_file, input_string, '#');
13276
13277 // Ignore rest of line
13278 restart_file.ignore(80, '\n');
13279
13280 // Check # of nodes:
13281 unsigned long check_nglobal = atoi(input_string.c_str());
13282
13283
13285 {
13286 if (check_nglobal != Nglobal)
13287 {
13288 std::ostringstream error_message;
13289 error_message << "The number of global data " << Nglobal
13290 << " is not equal to that specified in the input file "
13291 << check_nglobal << std::endl;
13292
13293 throw OomphLibError(error_message.str(),
13296 }
13297 }
13298
13299 for (unsigned iglobal = 0; iglobal < Nglobal; iglobal++)
13300 {
13302 }
13303 }
13304
13305 //===================================================================
13306 /// Set all timesteps to the same value, dt, and assign
13307 /// weights for all timesteppers in the problem.
13308 //===================================================================
13309 void Problem::initialise_dt(const double& dt)
13310 {
13311 // Initialise the timesteps in the Problem's time object
13313
13314 // Find out how many timesteppers there are
13315 unsigned n_time_steppers = ntime_stepper();
13316
13317 // Loop over them all and set the weights
13318 for (unsigned i = 0; i < n_time_steppers; i++)
13319 {
13321 if (time_stepper_pt(i)->adaptive_flag())
13322 {
13324 }
13325 }
13326 }
13327
13328 //=========================================================================
13329 /// Set the value of the timesteps to be equal to the values passed in
13330 /// a vector and assign weights for all timesteppers in the problem
13331 //========================================================================
13333 {
13334 // Initialise the timesteps in the Problem's time object
13336
13337 // Find out how many timesteppers there are
13338 unsigned n_time_steppers = ntime_stepper();
13339
13340 // Loop over them all and set the weights
13341 for (unsigned i = 0; i < n_time_steppers; i++)
13342 {
13344 if (time_stepper_pt(i)->adaptive_flag())
13345 {
13347 }
13348 }
13349 }
13350
13351 //========================================================
13352 /// Self-test: Check meshes and global data. Return 0 for OK
13353 //========================================================
13355 {
13356 // Initialise
13357 bool passed = true;
13358
13359 // Are there any submeshes?
13360 unsigned Nmesh = nsub_mesh();
13361
13362 // Just one mesh: Check it
13363 if (Nmesh == 0)
13364 {
13365 if (mesh_pt()->self_test() != 0)
13366 {
13367 passed = false;
13369 << "\n ERROR: Failed Mesh::self_test() for single mesh in problem"
13370 << std::endl;
13371 }
13372 }
13373 // Loop over all submeshes and check them
13374 else
13375 {
13376 for (unsigned imesh = 0; imesh < Nmesh; imesh++)
13377 {
13378 if (mesh_pt(imesh)->self_test() != 0)
13379 {
13380 passed = false;
13381 oomph_info << "\n ERROR: Failed Mesh::self_test() for mesh imesh"
13382 << imesh << std::endl;
13383 }
13384 }
13385 }
13386
13387
13388 // Check global data
13389 unsigned Nglobal = Global_data_pt.size();
13390 for (unsigned iglobal = 0; iglobal < Nglobal; iglobal++)
13391 {
13392 if (Global_data_pt[iglobal]->self_test() != 0)
13393 {
13394 passed = false;
13396 << "\n ERROR: Failed Data::self_test() for global data iglobal"
13397 << iglobal << std::endl;
13398 }
13399 }
13400
13401
13402#ifdef OOMPH_HAS_MPI
13403
13405 {
13406 // Note: This throws an error if it fails so no return is required.
13408 tmp_doc_info.disable_doc();
13410 }
13411
13412#endif
13413
13414 // Return verdict
13415 if (passed)
13416 {
13417 return 0;
13418 }
13419 else
13420 {
13421 return 1;
13422 }
13423 }
13424
13425 //====================================================================
13426 /// A function that is used to adapt a bifurcation-tracking
13427 /// problem, which requires separate interpolation of the
13428 /// associated eigenfunction. The error measure is chosen to be
13429 /// a suitable combination of the errors in the base flow and the
13430 /// eigenfunction. The bifurcation type is passed as an argument
13431 //=====================================================================
13433 unsigned& n_unrefined,
13434 const unsigned& bifurcation_type,
13435 const bool& actually_adapt)
13436 {
13437 // Storage for eigenfunction from the problem
13439 // Get the eigenfunction from the problem
13440 this->get_bifurcation_eigenfunction(eigenfunction);
13441
13442 // Get the bifurcation parameter
13443 double* parameter_pt = this->bifurcation_parameter_pt();
13444
13445 // Get the frequency parameter if tracking a Hopf bifurcation
13446 double omega = 0.0;
13447 // If we're tracking a Hopf then also get the frequency
13448 if (bifurcation_type == 3)
13449 {
13450 omega = dynamic_cast<HopfHandler*>(assembly_handler_pt())->omega();
13451 }
13452
13453 // If we're tracking a Pitchfork get the slack parameter (Hack)
13454 double sigma = 0.0;
13455 if (bifurcation_type == 2)
13456 {
13457 sigma = this->dof(this->ndof() - 1);
13458 }
13459
13460 // We can now deactivate the bifurcation tracking in the problem
13461 // to restore the degrees of freedom to the unaugmented value
13463
13464 // Next, we create copies of the present problem
13465 // The number of copies depends on the number of eigenfunctions
13466 // One copy for each eigenfunction
13467 const unsigned n_copies = eigenfunction.size();
13469
13470 // Loop over the number of copies
13471 for (unsigned c = 0; c < n_copies; c++)
13472 {
13473 // If we don't already have a copy
13474 if (Copy_of_problem_pt[c] == 0)
13475 {
13476 // Create the copy
13477 Copy_of_problem_pt[c] = this->make_copy();
13478
13479 // Refine the copy to the same level as the current problem
13480
13481 // Find number of submeshes
13482 const unsigned N_mesh = Copy_of_problem_pt[c]->nsub_mesh();
13483 // If there is only one mesh
13484 if (N_mesh == 0)
13485 {
13486 // Can we refine the mesh
13488 dynamic_cast<TreeBasedRefineableMeshBase*>(
13490 {
13491 // Is the adapt flag set
13492 if (mmesh_pt->is_adaptation_enabled())
13493 {
13494 // Now get the original problem's mesh if it's refineable
13496 dynamic_cast<TreeBasedRefineableMeshBase*>(
13497 this->mesh_pt(0)))
13498 {
13499 mmesh_pt->refine_base_mesh_as_in_reference_mesh(
13501 }
13502 else
13503 {
13505 << "Info/Warning: Mesh in orginal problem is not refineable."
13506 << std::endl;
13507 }
13508 }
13509 else
13510 {
13511 oomph_info << "Info/Warning: Mesh adaptation is disabled in copy."
13512 << std::endl;
13513 }
13514 }
13515 else
13516 {
13517 oomph_info << "Info/Warning: Mesh cannot be adapted in copy."
13518 << std::endl;
13519 }
13520 } // End of single mesh case
13521 // Otherwise loop over the submeshes
13522 else
13523 {
13524 for (unsigned m = 0; m < N_mesh; m++)
13525 {
13526 // Can we refine the submesh
13528 dynamic_cast<TreeBasedRefineableMeshBase*>(
13530 {
13531 // Is the adapt flag set
13532 if (mmesh_pt->is_adaptation_enabled())
13533 {
13534 // Now get the original problem's mesh
13536 dynamic_cast<TreeBasedRefineableMeshBase*>(
13537 this->mesh_pt(m)))
13538 {
13539 mmesh_pt->refine_base_mesh_as_in_reference_mesh(
13541 }
13542 else
13543 {
13544 oomph_info << "Info/Warning: Mesh in orginal problem is not "
13545 "refineable."
13546 << std::endl;
13547 }
13548 }
13549 else
13550 {
13552 << "Info/Warning: Mesh adaptation is disabled in copy."
13553 << std::endl;
13554 }
13555 }
13556 else
13557 {
13558 oomph_info << "Info/Warning: Mesh cannot be adapted in copy."
13559 << std::endl;
13560 }
13561 }
13562 // rebuild the global mesh in the copy
13563 Copy_of_problem_pt[c]->rebuild_global_mesh();
13564
13565 } // End of multiple mesh case
13566
13567 // Must call actions after adapt
13568 Copy_of_problem_pt[c]->actions_after_adapt();
13569
13570 // Assign the equation numbers to the copy (quietly)
13572 }
13573 } // End of creation of copies
13574
13575
13576 // Now check some numbers
13577 for (unsigned c = 0; c < n_copies; c++)
13578 {
13579 // Check that the dofs match for each copy
13580#ifdef PARANOID
13581 // If the problems don't match then complain
13582 if (Copy_of_problem_pt[c]->ndof() != this->ndof())
13583 {
13584 std::ostringstream error_stream;
13585 error_stream << "Number of unknowns in the problem copy " << c << " "
13586 << "not equal to number in the original:\n"
13587 << this->ndof() << " (original) "
13588 << Copy_of_problem_pt[c]->ndof() << " (copy)\n";
13589
13590 throw OomphLibError(
13592 }
13593#endif
13594
13595 // Assign the eigenfunction(s) to the copied problems
13596 Copy_of_problem_pt[c]->assign_eigenvector_to_dofs(eigenfunction[c]);
13597 // Set all pinned values to zero
13598 Copy_of_problem_pt[c]->set_pinned_values_to_zero();
13599 }
13600
13601 // Symmetrise the problem if we are solving a pitchfork
13602 if (bifurcation_type == 2)
13603 {
13605 ->symmetrise_eigenfunction_for_adaptive_pitchfork_tracking();
13606 }
13607
13608 // Find error estimates based on current problem and eigenproblem
13609 // Now we need to get the error estimates for both problems.
13611 this->get_all_error_estimates(base_error);
13612 // Loop over the copies
13613 for (unsigned c = 0; c < n_copies; c++)
13614 {
13615 // Get the error estimates for the copy
13616 Copy_of_problem_pt[c]->get_all_error_estimates(eigenfunction_error);
13617
13618 // Find the number of meshes
13619 unsigned n_mesh = base_error.size();
13620
13621#ifdef PARANOID
13623 {
13624 std::ostringstream error_stream;
13625 error_stream << "Problems do not have the same number of meshes\n"
13626 << "Base : " << n_mesh
13627 << " : Eigenproblem : " << eigenfunction_error.size()
13628 << "\n";
13629 throw OomphLibError(
13631 }
13632#endif
13633
13634 for (unsigned m = 0; m < n_mesh; m++)
13635 {
13636 // Check the number of elements is the same
13637 unsigned n_element = base_error[m].size();
13638#ifdef PARANOID
13639 if (n_element != eigenfunction_error[m].size())
13640 {
13641 std::ostringstream error_stream;
13642 error_stream << "Mesh " << m
13643 << " does not have the same number of elements in the "
13644 "two problems:\n"
13645 << "Base: " << n_element
13646 << " : Eigenproblem: " << eigenfunction_error[m].size()
13647 << "\n";
13648 throw OomphLibError(error_stream.str(),
13651 }
13652#endif
13653 // Now add all the error esimates together
13654 for (unsigned e = 0; e < n_element; e++)
13655 {
13656 // Add the error estimates (lazy)
13658 }
13659 }
13660 } // End of loop over copies
13661
13662 // Then refine all problems based on the combined measure
13663 // if we are actually adapting (not just estimating the errors)
13664 if (actually_adapt)
13665 {
13667 for (unsigned c = 0; c < n_copies; c++)
13668 {
13669 Copy_of_problem_pt[c]->adapt_based_on_error_estimates(
13671 }
13672 // Symmetrise the problem (again) if we are solving for a pitchfork
13673 if (bifurcation_type == 2)
13674 {
13676 ->symmetrise_eigenfunction_for_adaptive_pitchfork_tracking();
13677 }
13678
13679 // Now get the refined guess for the eigenvector
13680 for (unsigned c = 0; c < n_copies; c++)
13681 {
13682 Copy_of_problem_pt[c]->get_dofs(eigenfunction[c]);
13683 }
13684 }
13685
13686 // Reactivate the tracking
13687 switch (bifurcation_type)
13688 {
13689 // Fold tracking
13690 case 1:
13691 this->activate_fold_tracking(parameter_pt);
13692 break;
13693
13694 // Pitchfork
13695 case 2:
13696 this->activate_pitchfork_tracking(parameter_pt, eigenfunction[0]);
13697 // reset the slack parameter
13698 this->dof(this->ndof() - 1) = sigma;
13699 break;
13700
13701 // Hopf
13702 case 3:
13704 parameter_pt, omega, eigenfunction[0], eigenfunction[1]);
13705 break;
13706
13707 default:
13708 std::ostringstream error_stream;
13709 error_stream << "Bifurcation type " << bifurcation_type
13710 << " not known\n"
13711 << "1: Fold, 2: Pitchfork, 3: Hopf\n";
13712 throw OomphLibError(
13714 }
13715 }
13716
13717
13718 //====================================================================
13719 /// A function that is used to document the errors when
13720 /// adapting a bifurcation-tracking
13721 /// problem, which requires separate interpolation of the
13722 /// associated eigenfunction. The error measure is chosen to be
13723 /// a suitable combination of the errors in the base flow and the
13724 /// eigenfunction. The bifurcation type is passed as an argument
13725 //=====================================================================
13726 void Problem::bifurcation_adapt_doc_errors(const unsigned& bifurcation_type)
13727 {
13728 // Dummy arguments
13729 unsigned n_refined, n_unrefined;
13730 // Just call the bifurcation helper without actually adapting
13731 bifurcation_adapt_helper(n_refined, n_unrefined, bifurcation_type, false);
13732 }
13733
13734
13735 //========================================================================
13736 /// Adapt problem:
13737 /// Perform mesh adaptation for (all) refineable (sub)mesh(es),
13738 /// based on their own error estimates and the target errors specified
13739 /// in the mesh(es). Following mesh adaptation,
13740 /// update global mesh, and re-assign equation numbers.
13741 /// Return # of refined/unrefined elements. On return from this
13742 /// function, Problem can immediately be solved again.
13743 //======================================================================
13744 void Problem::adapt(unsigned& n_refined, unsigned& n_unrefined)
13745 {
13746 double t_start_total = 0.0;
13748 {
13750 }
13751
13752 // Get the bifurcation type
13753 int bifurcation_type = this->Assembly_handler_pt->bifurcation_type();
13754
13755 bool continuation_problem = false;
13756
13757 // If we have continuation data then we need to project that across to the
13758 // new mesh
13760 {
13761 if (Dof_derivative.size() != 0)
13762 {
13763 continuation_problem = true;
13764 }
13765 }
13766
13767 // If we are tracking a bifurcation then call the bifurcation adapt function
13768 if (bifurcation_type != 0)
13769 {
13770 this->bifurcation_adapt_helper(n_refined, n_unrefined, bifurcation_type);
13771 // Return immediately
13772 return;
13773 }
13774
13776 {
13777 // Create a copy of the problem
13778 Copy_of_problem_pt.resize(2);
13779 // If we don't already have a copy
13780 for (unsigned c = 0; c < 2; c++)
13781 {
13782 if (Copy_of_problem_pt[c] == 0)
13783 {
13784 // Create the copy
13785 Copy_of_problem_pt[c] = this->make_copy();
13786
13787 // Refine the copy to the same level as the current problem
13788 // Must call actions before adapt
13789 Copy_of_problem_pt[c]->actions_before_adapt();
13790
13791 // Find number of submeshes
13792 const unsigned N_mesh = Copy_of_problem_pt[c]->nsub_mesh();
13793
13794 // If there is only one mesh
13795 if (N_mesh == 0)
13796 {
13797 // Can we refine the mesh
13799 dynamic_cast<TreeBasedRefineableMeshBase*>(
13801 {
13802 // Is the adapt flag set
13803 if (mmesh_pt->is_adaptation_enabled())
13804 {
13805 // Now get the original problem's mesh if it's refineable
13807 dynamic_cast<TreeBasedRefineableMeshBase*>(
13808 this->mesh_pt(0)))
13809 {
13810 if (dynamic_cast<SolidMesh*>(original_mesh_pt) != 0)
13811 {
13813 << "Info/Warning: Adaptive Continuation is broken in "
13814 << "SolidElement" << std::endl;
13815 }
13816 mmesh_pt->refine_base_mesh_as_in_reference_mesh(
13818 }
13819 else
13820 {
13821 oomph_info << "Info/Warning: Mesh in orginal problem is not "
13822 "refineable."
13823 << std::endl;
13824 }
13825 }
13826 else
13827 {
13829 << "Info/Warning: Mesh adaptation is disabled in copy."
13830 << std::endl;
13831 }
13832 }
13833 else if (TriangleMeshBase* tmesh_pt =
13834 dynamic_cast<TriangleMeshBase*>(
13836 {
13838 dynamic_cast<TriangleMeshBase*>(this->mesh_pt(0)))
13839 {
13840 if (dynamic_cast<SolidMesh*>(original_mesh_pt) != 0)
13841 {
13843 << "Info/Warning: Adaptive Continuation is broken in "
13844 << "SolidElement" << std::endl;
13845 }
13846
13847 // Remesh using the triangulateIO of the base mesh
13848 // Done via a file, so a bit hacky but this will be
13849 // superseded very soon
13850 std::ofstream tri_dump("triangle_mesh.dmp");
13851 original_mesh_pt->dump_triangulateio(tri_dump);
13852 tri_dump.close();
13853 std::ifstream tri_read("triangle_mesh.dmp");
13854 tmesh_pt->remesh_from_triangulateio(tri_read);
13855 tri_read.close();
13856
13857
13858 // Set the nodes to be at the same positions
13859 // as the original just in case the
13860 // triangulatio is out of sync with the real data
13861 const unsigned n_node = original_mesh_pt->nnode();
13862 for (unsigned n = 0; n < n_node; ++n)
13863 {
13865 Node* const new_node_pt = tmesh_pt->node_pt(n);
13866 unsigned n_dim = nod_pt->ndim();
13867 for (unsigned i = 0; i < n_dim; ++i)
13868 {
13869 new_node_pt->x(i) = nod_pt->x(i);
13870 }
13871 }
13872 }
13873 else
13874 {
13876 << "Info/warning: Original Mesh is not TriangleBased\n"
13877 << "... but the copy is!" << std::endl;
13878 }
13879 }
13880 else
13881 {
13882 oomph_info << "Info/Warning: Mesh cannot be adapted in copy."
13883 << std::endl;
13884 }
13885 } // End of single mesh case
13886 // Otherwise loop over the submeshes
13887 else
13888 {
13889 for (unsigned m = 0; m < N_mesh; m++)
13890 {
13891 // Can we refine the submesh
13893 dynamic_cast<TreeBasedRefineableMeshBase*>(
13895 {
13896 // Is the adapt flag set
13897 if (mmesh_pt->is_adaptation_enabled())
13898 {
13899 // Now get the original problem's mesh
13901 dynamic_cast<TreeBasedRefineableMeshBase*>(
13902 this->mesh_pt(m)))
13903 {
13904 if (dynamic_cast<SolidMesh*>(original_mesh_pt) != 0)
13905 {
13907 << "Info/Warning: Adaptive Continuation is broken in "
13908 << "SolidElement" << std::endl;
13909 }
13910
13911 mmesh_pt->refine_base_mesh_as_in_reference_mesh(
13913 }
13914 else
13915 {
13916 oomph_info << "Info/Warning: Mesh in orginal problem is "
13917 "not refineable."
13918 << std::endl;
13919 }
13920 }
13921 else
13922 {
13924 << "Info/Warning: Mesh adaptation is disabled in copy."
13925 << std::endl;
13926 }
13927 }
13928 else if (TriangleMeshBase* tmesh_pt =
13929 dynamic_cast<TriangleMeshBase*>(
13931 {
13933 dynamic_cast<TriangleMeshBase*>(this->mesh_pt(m)))
13934 {
13935 if (dynamic_cast<SolidMesh*>(original_mesh_pt) != 0)
13936 {
13938 << "Info/Warning: Adaptive Continuation is broken in "
13939 << "SolidElement" << std::endl;
13940 }
13941
13942 // Remesh using the triangulateIO of the base mesh
13943 // Done via a file, so a bit hacky but this will be
13944 // superseded very soon
13945 std::ofstream tri_dump("triangle_mesh.dmp");
13946 original_mesh_pt->dump_triangulateio(tri_dump);
13947 tri_dump.close();
13948 std::ifstream tri_read("triangle_mesh.dmp");
13949 tmesh_pt->remesh_from_triangulateio(tri_read);
13950 tri_read.close();
13951
13952 // Set the nodes to be at the same positions
13953 // as the original just in case the
13954 // triangulatio is out of sync with the real data
13955 const unsigned n_node = original_mesh_pt->nnode();
13956 for (unsigned n = 0; n < n_node; ++n)
13957 {
13959 Node* const new_node_pt = tmesh_pt->node_pt(n);
13960 unsigned n_dim = nod_pt->ndim();
13961 for (unsigned i = 0; i < n_dim; ++i)
13962 {
13963 new_node_pt->x(i) = nod_pt->x(i);
13964 }
13965 }
13966 }
13967 else
13968 {
13970 << "Info/warning: Original Mesh is not TriangleBased\n"
13971 << "... but the copy is!" << std::endl;
13972 }
13973 }
13974 else
13975 {
13976 oomph_info << "Info/Warning: Mesh cannot be adapted in copy."
13977 << std::endl;
13978 }
13979 }
13980
13981
13982 // Must call actions after adapt
13983 Copy_of_problem_pt[c]->actions_after_adapt();
13984
13985 // rebuild the global mesh in the copy
13986 Copy_of_problem_pt[c]->rebuild_global_mesh();
13987
13988 } // End of multiple mesh case
13989
13990 // Must call actions after adapt
13991 Copy_of_problem_pt[c]->actions_after_adapt();
13992
13993 // Assign the equation numbers to the copy (quietly)
13995 }
13996
13997 // Check that the dofs match for each copy
13998#ifdef PARANOID
13999 // If the problems don't match then complain
14000 if (Copy_of_problem_pt[c]->ndof() != this->ndof())
14001 {
14002 std::ostringstream error_stream;
14003 error_stream << "Number of unknowns in the problem copy " << c << " "
14004 << "not equal to number in the original:\n"
14005 << this->ndof() << " (original) "
14006 << Copy_of_problem_pt[c]->ndof() << " (copy)\n";
14007
14008 throw OomphLibError(error_stream.str(),
14011 }
14012#endif
14013 }
14014
14015 // Need to set the Dof derivatives to the copied problem
14016 // Assign the eigenfunction(s) to the copied problems
14018 for (unsigned i = 0; i < ndof_local; i++)
14019 {
14020 Copy_of_problem_pt[0]->dof(i) = this->dof_derivative(i);
14021 Copy_of_problem_pt[1]->dof(i) = this->dof_current(i);
14022 }
14023 // Set all pinned values to zero
14024 Copy_of_problem_pt[0]->set_pinned_values_to_zero();
14025 // Don't need to for the current dofs that are actuall the dofs
14026
14027 // Now adapt
14029 this->get_all_error_estimates(base_error);
14031 Copy_of_problem_pt[0]->adapt_based_on_error_estimates(
14033 Copy_of_problem_pt[1]->adapt_based_on_error_estimates(
14035
14036 // Now sort out the Dof pointer
14038 if (Dof_derivative.size() != ndof_local)
14039 {
14040 Dof_derivative.resize(ndof_local, 0.0);
14041 }
14042 if (Dof_current.size() != ndof_local)
14043 {
14044 Dof_current.resize(ndof_local, 0.0);
14045 }
14046 for (unsigned i = 0; i < ndof_local; i++)
14047 {
14049 Dof_current[i] = Copy_of_problem_pt[1]->dof(i);
14050 }
14051 // Return immediately
14052 return;
14053 }
14054
14055 oomph_info << std::endl << std::endl;
14056 oomph_info << "Adapting problem:" << std::endl;
14057 oomph_info << "=================" << std::endl;
14058
14059 double t_start = 0.0;
14061 {
14063 }
14064
14065 // Call the actions before adaptation
14067
14068 double t_end = 0.0;
14070 {
14072 oomph_info << "Time for actions before adapt: " << t_end - t_start
14073 << std::endl;
14075 }
14076
14077 // Initialise counters
14078 n_refined = 0;
14079 n_unrefined = 0;
14080
14081 // Number of submeshes?
14082 unsigned Nmesh = nsub_mesh();
14083
14084 // Single mesh:
14085 //------------
14086 if (Nmesh == 0)
14087 {
14088 // Refine single mesh if possible
14090 dynamic_cast<RefineableMeshBase*>(mesh_pt(0)))
14091 {
14092 if (mmesh_pt->is_adaptation_enabled())
14093 {
14094 double t_start = TimingHelpers::timer();
14095
14096 // Get pointer to error estimator
14098 mmesh_pt->spatial_error_estimator_pt();
14099
14100#ifdef PARANOID
14101 if (error_estimator_pt == 0)
14102 {
14103 throw OomphLibError("Error estimator hasn't been set yet",
14106 }
14107#endif
14108
14109 // Get error for all elements
14111
14112 if (mmesh_pt->doc_info_pt() == 0)
14113 {
14114 error_estimator_pt->get_element_errors(mesh_pt(0), elemental_error);
14115 }
14116 else
14117 {
14118 error_estimator_pt->get_element_errors(
14119 mesh_pt(0), elemental_error, *mmesh_pt->doc_info_pt());
14120 }
14121
14122 // Store max./min actual error
14123 mmesh_pt->max_error() = std::fabs(*std::max_element(
14125
14126 mmesh_pt->min_error() = std::fabs(*std::min_element(
14128
14129 oomph_info << "\n Max/min error: " << mmesh_pt->max_error() << " "
14130 << mmesh_pt->min_error() << std::endl
14131 << std::endl;
14132
14133
14135 {
14137 oomph_info << "Time for error estimation: " << t_end - t_start
14138 << std::endl;
14140 }
14141
14142 // Adapt mesh
14143 mmesh_pt->adapt(elemental_error);
14144
14145 // Add to counters
14146 n_refined += mmesh_pt->nrefined();
14147 n_unrefined += mmesh_pt->nunrefined();
14148
14150 {
14152 oomph_info << "Time for complete mesh adaptation "
14153 << "(but excluding comp of error estimate): "
14154 << t_end - t_start << std::endl;
14156 }
14157 }
14158 else
14159 {
14160 oomph_info << "Info/Warning: Mesh adaptation is disabled."
14161 << std::endl;
14162 }
14163 }
14164 else
14165 {
14166 oomph_info << "Info/Warning: Mesh cannot be adapted" << std::endl;
14167 }
14168 }
14169 // Multiple submeshes
14170 //------------------
14171 else
14172 {
14173 // Loop over submeshes
14174 for (unsigned imesh = 0; imesh < Nmesh; imesh++)
14175 {
14176 // Refine single mesh uniformly if possible
14178 dynamic_cast<RefineableMeshBase*>(mesh_pt(imesh)))
14179 {
14180 double t_start = TimingHelpers::timer();
14181
14182 // Get pointer to error estimator
14184 mmesh_pt->spatial_error_estimator_pt();
14185
14186#ifdef PARANOID
14187 if (error_estimator_pt == 0)
14188 {
14189 throw OomphLibError("Error estimator hasn't been set yet",
14192 }
14193#endif
14194
14195 if (mmesh_pt->is_adaptation_enabled())
14196 {
14197 // Get error for all elements
14199 if (mmesh_pt->doc_info_pt() == 0)
14200 {
14201 error_estimator_pt->get_element_errors(mesh_pt(imesh),
14203 }
14204 else
14205 {
14206 error_estimator_pt->get_element_errors(
14207 mesh_pt(imesh), elemental_error, *mmesh_pt->doc_info_pt());
14208 }
14209
14210 // Store max./min error if the mesh has any elements
14211 if (mesh_pt(imesh)->nelement() > 0)
14212 {
14213 mmesh_pt->max_error() =
14214 std::fabs(*std::max_element(elemental_error.begin(),
14215 elemental_error.end(),
14216 AbsCmp<double>()));
14217
14218 mmesh_pt->min_error() =
14219 std::fabs(*std::min_element(elemental_error.begin(),
14220 elemental_error.end(),
14221 AbsCmp<double>()));
14222 }
14223
14224 oomph_info << "\n Max/min error: " << mmesh_pt->max_error() << " "
14225 << mmesh_pt->min_error() << std::endl;
14226
14227
14229 {
14231 oomph_info << "Time for error estimation: " << t_end - t_start
14232 << std::endl;
14234 }
14235
14236 // Adapt mesh
14237 mmesh_pt->adapt(elemental_error);
14238
14239 // Add to counters
14240 n_refined += mmesh_pt->nrefined();
14241 n_unrefined += mmesh_pt->nunrefined();
14242
14243
14245 {
14247 oomph_info << "Time for complete mesh adaptation "
14248 << "(but excluding comp of error estimate): "
14249 << t_end - t_start << std::endl;
14251 }
14252 }
14253 else
14254 {
14255 oomph_info << "Info/Warning: Mesh adaptation is disabled."
14256 << std::endl;
14257 }
14258 }
14259 else
14260 {
14261 oomph_info << "Info/Warning: Mesh cannot be adapted." << std::endl;
14262 }
14263
14264 } // End of loop over submeshes
14265
14266 // Rebuild the global mesh
14268 }
14269
14270
14272 {
14274 oomph_info << "Total time for actual adaptation "
14275 << "(all meshes; incl error estimates): " << t_end - t_start
14276 << std::endl;
14278 }
14279
14280 // Any actions after adapt
14282
14283
14285 {
14287 oomph_info << "Time for actions after adapt: " << t_end - t_start
14288 << std::endl;
14290
14291 oomph_info << "About to start re-assigning eqn numbers "
14292 << "with Problem::assign_eqn_numbers() at end of "
14293 << "Problem::adapt().\n";
14294 }
14295
14296 // Attach the boundary conditions to the mesh
14297 oomph_info << "\nNumber of equations: " << assign_eqn_numbers() << std::endl
14298 << std::endl;
14299
14300
14302 {
14304 oomph_info << "Time for re-assigning eqn numbers with "
14305 << "Problem::assign_eqn_numbers() at end of Problem::adapt(): "
14306 << t_end - t_start << std::endl;
14307 oomph_info << "Total time for adapt: " << t_end - t_start_total
14308 << std::endl;
14309 }
14310 }
14311
14312 //========================================================================
14313 /// p-adapt problem:
14314 /// Perform mesh adaptation for (all) refineable (sub)mesh(es),
14315 /// based on their own error estimates and the target errors specified
14316 /// in the mesh(es). Following mesh adaptation,
14317 /// update global mesh, and re-assign equation numbers.
14318 /// Return # of refined/unrefined elements. On return from this
14319 /// function, Problem can immediately be solved again.
14320 //======================================================================
14321 void Problem::p_adapt(unsigned& n_refined, unsigned& n_unrefined)
14322 {
14323 double t_start_total = 0.0;
14325 {
14327 }
14328
14329 // Get the bifurcation type
14330 int bifurcation_type = this->Assembly_handler_pt->bifurcation_type();
14331
14332 // If we are tracking a bifurcation then call the bifurcation adapt function
14333 if (bifurcation_type != 0)
14334 {
14335 this->bifurcation_adapt_helper(n_refined, n_unrefined, bifurcation_type);
14336 // Return immediately
14337 return;
14338 }
14339
14340 oomph_info << std::endl << std::endl;
14341 oomph_info << "p-adapting problem:" << std::endl;
14342 oomph_info << "===================" << std::endl;
14343
14344 double t_start = 0.0;
14346 {
14348 }
14349
14350 // Call the actions before adaptation
14352
14353 double t_end = 0.0;
14355 {
14357 oomph_info << "Time for actions before adapt: " << t_end - t_start
14358 << std::endl;
14360 }
14361
14362 // Initialise counters
14363 n_refined = 0;
14364 n_unrefined = 0;
14365
14366 // Number of submeshes?
14367 unsigned Nmesh = nsub_mesh();
14368
14369 // Single mesh:
14370 //------------
14371 if (Nmesh == 0)
14372 {
14373 // Refine single mesh if possible
14375 dynamic_cast<RefineableMeshBase*>(mesh_pt(0)))
14376 {
14377 if (mmesh_pt->is_p_adaptation_enabled())
14378 {
14379 double t_start = TimingHelpers::timer();
14380
14381 // Get pointer to error estimator
14383 mmesh_pt->spatial_error_estimator_pt();
14384
14385#ifdef PARANOID
14386 if (error_estimator_pt == 0)
14387 {
14388 throw OomphLibError("Error estimator hasn't been set yet",
14391 }
14392#endif
14393
14394 // Get error for all elements
14396
14397 if (mmesh_pt->doc_info_pt() == 0)
14398 {
14399 error_estimator_pt->get_element_errors(mesh_pt(0), elemental_error);
14400 }
14401 else
14402 {
14403 error_estimator_pt->get_element_errors(
14404 mesh_pt(0), elemental_error, *mmesh_pt->doc_info_pt());
14405 }
14406
14407 // Store max./min actual error
14408 mmesh_pt->max_error() = std::fabs(*std::max_element(
14410
14411 mmesh_pt->min_error() = std::fabs(*std::min_element(
14413
14414 oomph_info << "\n Max/min error: " << mmesh_pt->max_error() << " "
14415 << mmesh_pt->min_error() << std::endl
14416 << std::endl;
14417
14418
14420 {
14422 oomph_info << "Time for error estimation: " << t_end - t_start
14423 << std::endl;
14425 }
14426
14427 // Adapt mesh
14428 mmesh_pt->p_adapt(elemental_error);
14429
14430 // Add to counters
14431 n_refined += mmesh_pt->nrefined();
14432 n_unrefined += mmesh_pt->nunrefined();
14433
14435 {
14437 oomph_info << "Time for complete mesh adaptation "
14438 << "(but excluding comp of error estimate): "
14439 << t_end - t_start << std::endl;
14441 }
14442 }
14443 else
14444 {
14445 oomph_info << "Info/Warning: Mesh adaptation is disabled."
14446 << std::endl;
14447 }
14448 }
14449 else
14450 {
14451 oomph_info << "Info/Warning: Mesh cannot be adapted" << std::endl;
14452 }
14453 }
14454 // Multiple submeshes
14455 //------------------
14456 else
14457 {
14458 // Loop over submeshes
14459 for (unsigned imesh = 0; imesh < Nmesh; imesh++)
14460 {
14461 // Refine single mesh uniformly if possible
14463 dynamic_cast<RefineableMeshBase*>(mesh_pt(imesh)))
14464 {
14465 double t_start = TimingHelpers::timer();
14466
14467 // Get pointer to error estimator
14469 mmesh_pt->spatial_error_estimator_pt();
14470
14471#ifdef PARANOID
14472 if (error_estimator_pt == 0)
14473 {
14474 throw OomphLibError("Error estimator hasn't been set yet",
14477 }
14478#endif
14479
14480 if (mmesh_pt->is_p_adaptation_enabled())
14481 {
14482 // Get error for all elements
14484 if (mmesh_pt->doc_info_pt() == 0)
14485 {
14486 error_estimator_pt->get_element_errors(mesh_pt(imesh),
14488 }
14489 else
14490 {
14491 error_estimator_pt->get_element_errors(
14492 mesh_pt(imesh), elemental_error, *mmesh_pt->doc_info_pt());
14493 }
14494
14495 // Store max./min error if the mesh has any elements
14496 if (mesh_pt(imesh)->nelement() > 0)
14497 {
14498 mmesh_pt->max_error() =
14499 std::fabs(*std::max_element(elemental_error.begin(),
14500 elemental_error.end(),
14501 AbsCmp<double>()));
14502
14503 mmesh_pt->min_error() =
14504 std::fabs(*std::min_element(elemental_error.begin(),
14505 elemental_error.end(),
14506 AbsCmp<double>()));
14507 }
14508
14509 oomph_info << "\n Max/min error: " << mmesh_pt->max_error() << " "
14510 << mmesh_pt->min_error() << std::endl;
14511
14512
14514 {
14516 oomph_info << "Time for error estimation: " << t_end - t_start
14517 << std::endl;
14519 }
14520
14521 // Adapt mesh
14522 mmesh_pt->p_adapt(elemental_error);
14523
14524 // Add to counters
14525 n_refined += mmesh_pt->nrefined();
14526 n_unrefined += mmesh_pt->nunrefined();
14527
14528
14530 {
14532 oomph_info << "Time for complete mesh adaptation "
14533 << "(but excluding comp of error estimate): "
14534 << t_end - t_start << std::endl;
14536 }
14537 }
14538 else
14539 {
14540 oomph_info << "Info/Warning: Mesh adaptation is disabled."
14541 << std::endl;
14542 }
14543 }
14544 else
14545 {
14546 oomph_info << "Info/Warning: Mesh cannot be adapted." << std::endl;
14547 }
14548
14549 } // End of loop over submeshes
14550
14551 // Rebuild the global mesh
14553 }
14554
14555
14557 {
14559 oomph_info << "Total time for actual adaptation "
14560 << "(all meshes; incl error estimates): " << t_end - t_start
14561 << std::endl;
14563 }
14564
14565 // Any actions after adapt
14567
14568
14570 {
14572 oomph_info << "Time for actions after adapt: " << t_end - t_start
14573 << std::endl;
14575
14576 oomph_info << "About to start re-assigning eqn numbers "
14577 << "with Problem::assign_eqn_numbers() at end of "
14578 << "Problem::adapt().\n";
14579 }
14580
14581 // Attach the boundary conditions to the mesh
14582 oomph_info << "\nNumber of equations: " << assign_eqn_numbers() << std::endl
14583 << std::endl;
14584
14585
14587 {
14589 oomph_info << "Time for re-assigning eqn numbers with "
14590 << "Problem::assign_eqn_numbers() at end of Problem::adapt(): "
14591 << t_end - t_start << std::endl;
14592 oomph_info << "Total time for adapt: " << t_end - t_start_total
14593 << std::endl;
14594 }
14595 }
14596
14597 //========================================================================
14598 /// Perform mesh adaptation for (all) refineable (sub)mesh(es),
14599 /// based on the error estimates in elemental_error
14600 /// and the target errors specified
14601 /// in the mesh(es). Following mesh adaptation,
14602 /// update global mesh, and re-assign equation numbers.
14603 /// Return # of refined/unrefined elements. On return from this
14604 /// function, Problem can immediately be solved again.
14605 //========================================================================
14607 unsigned& n_refined,
14608 unsigned& n_unrefined,
14610 {
14611 oomph_info << std::endl << std::endl;
14612 oomph_info << "Adapting problem:" << std::endl;
14613 oomph_info << "=================" << std::endl;
14614
14615 // Call the actions before adaptation
14617
14618 // Initialise counters
14619 n_refined = 0;
14620 n_unrefined = 0;
14621
14622 // Number of submeshes?
14623 unsigned Nmesh = nsub_mesh();
14624
14625 // Single mesh:
14626 //------------
14627 if (Nmesh == 0)
14628 {
14629 // Refine single mesh uniformly if possible
14631 dynamic_cast<RefineableMeshBase*>(Problem::mesh_pt(0)))
14632 {
14633 if (mmesh_pt->is_adaptation_enabled())
14634 {
14635 // Adapt mesh
14636 mmesh_pt->adapt(elemental_error[0]);
14637
14638 // Add to counters
14639 n_refined += mmesh_pt->nrefined();
14640 n_unrefined += mmesh_pt->nunrefined();
14641 }
14642 else
14643 {
14644 oomph_info << "Info/Warning: Mesh adaptation is disabled."
14645 << std::endl;
14646 }
14647 }
14648 else
14649 {
14650 oomph_info << "Info/Warning: Mesh cannot be adapted" << std::endl;
14651 }
14652 }
14653
14654 // Multiple submeshes
14655 //------------------
14656 else
14657 {
14658 // Loop over submeshes
14659 for (unsigned imesh = 0; imesh < Nmesh; imesh++)
14660 {
14661 // Refine single mesh uniformly if possible
14663 dynamic_cast<RefineableMeshBase*>(Problem::mesh_pt(imesh)))
14664 {
14665 if (mmesh_pt->is_adaptation_enabled())
14666 {
14667 // Adapt mesh
14669
14670 // Add to counters
14671 n_refined += mmesh_pt->nrefined();
14672 n_unrefined += mmesh_pt->nunrefined();
14673 }
14674 else
14675 {
14676 oomph_info << "Info/Warning: Mesh adaptation is disabled."
14677 << std::endl;
14678 }
14679 }
14680 else
14681 {
14682 oomph_info << "Info/Warning: Mesh cannot be adapted." << std::endl;
14683 }
14684
14685 } // End of loop over submeshes
14686
14687 // Rebuild the global mesh
14689 }
14690
14691 // Any actions after adapt
14693
14694 // Attach the boundary conditions to the mesh
14695 oomph_info << "\nNumber of equations: " << assign_eqn_numbers() << std::endl
14696 << std::endl;
14697 }
14698
14699
14700 //========================================================================
14701 /// Return the error estimates computed by (all) refineable
14702 /// (sub)mesh(es) in the elemental_error structure, which consists of
14703 /// a vector of elemental errors for each (sub)mesh.
14704 //========================================================================
14706 {
14707 // Number of submeshes?
14708 const unsigned Nmesh = nsub_mesh();
14709
14710 // Single mesh:
14711 //------------
14712 if (Nmesh == 0)
14713 {
14714 // There is only one mesh
14715 elemental_error.resize(1);
14716 // Refine single mesh uniformly if possible
14718 dynamic_cast<RefineableMeshBase*>(Problem::mesh_pt(0)))
14719 {
14720 // If we can adapt the mesh
14721 if (mmesh_pt->is_adaptation_enabled())
14722 {
14723 // Get pointer to error estimator
14725 mmesh_pt->spatial_error_estimator_pt();
14726
14727#ifdef PARANOID
14728 if (error_estimator_pt == 0)
14729 {
14730 throw OomphLibError("Error estimator hasn't been set yet",
14733 }
14734#endif
14735
14736 // Get error for all elements
14737 elemental_error[0].resize(mmesh_pt->nelement());
14738 // Are we documenting the errors or not
14739 if (mmesh_pt->doc_info_pt() == 0)
14740 {
14741 error_estimator_pt->get_element_errors(Problem::mesh_pt(0),
14742 elemental_error[0]);
14743 }
14744 else
14745 {
14746 error_estimator_pt->get_element_errors(Problem::mesh_pt(0),
14747 elemental_error[0],
14748 *mmesh_pt->doc_info_pt());
14749 }
14750
14751 // Store max./min actual error
14752 mmesh_pt->max_error() =
14753 std::fabs(*std::max_element(elemental_error[0].begin(),
14754 elemental_error[0].end(),
14755 AbsCmp<double>()));
14756
14757 mmesh_pt->min_error() =
14758 std::fabs(*std::min_element(elemental_error[0].begin(),
14759 elemental_error[0].end(),
14760 AbsCmp<double>()));
14761
14762 oomph_info << "\n Max/min error: " << mmesh_pt->max_error() << " "
14763 << mmesh_pt->min_error() << std::endl;
14764 }
14765 else
14766 {
14767 oomph_info << "Info/Warning: Mesh adaptation is disabled."
14768 << std::endl;
14769 }
14770 }
14771 else
14772 {
14773 oomph_info << "Info/Warning: Mesh cannot be adapted" << std::endl;
14774 }
14775 }
14776
14777 // Multiple submeshes
14778 //------------------
14779 else
14780 {
14781 // Resize to the number of submeshes
14782 elemental_error.resize(Nmesh);
14783
14784 // Loop over submeshes
14785 for (unsigned imesh = 0; imesh < Nmesh; imesh++)
14786 {
14787 // Refine single mesh uniformly if possible
14789 dynamic_cast<RefineableMeshBase*>(Problem::mesh_pt(imesh)))
14790 {
14791 // Get pointer to error estimator
14793 mmesh_pt->spatial_error_estimator_pt();
14794
14795#ifdef PARANOID
14796 if (error_estimator_pt == 0)
14797 {
14798 throw OomphLibError("Error estimator hasn't been set yet",
14801 }
14802#endif
14803 // If we can adapt the mesh
14804 if (mmesh_pt->is_adaptation_enabled())
14805 {
14806 // Get error for all elements
14807 elemental_error[imesh].resize(mmesh_pt->nelement());
14808 if (mmesh_pt->doc_info_pt() == 0)
14809 {
14810 error_estimator_pt->get_element_errors(Problem::mesh_pt(imesh),
14812 }
14813 else
14814 {
14815 error_estimator_pt->get_element_errors(Problem::mesh_pt(imesh),
14817 *mmesh_pt->doc_info_pt());
14818 }
14819
14820 // Store max./min error
14821 mmesh_pt->max_error() =
14822 std::fabs(*std::max_element(elemental_error[imesh].begin(),
14824 AbsCmp<double>()));
14825
14826 mmesh_pt->min_error() =
14827 std::fabs(*std::min_element(elemental_error[imesh].begin(),
14829 AbsCmp<double>()));
14830
14831 oomph_info << "\n Max/min error: " << mmesh_pt->max_error() << " "
14832 << mmesh_pt->min_error() << std::endl;
14833 }
14834 else
14835 {
14836 oomph_info << "Info/Warning: Mesh adaptation is disabled."
14837 << std::endl;
14838 }
14839 }
14840 else
14841 {
14842 oomph_info << "Info/Warning: Mesh cannot be adapted." << std::endl;
14843 }
14844
14845 } // End of loop over submeshes
14846 }
14847 }
14848
14849 //========================================================================
14850 /// Get max and min error for all elements in submeshes
14851 //========================================================================
14853 {
14854 // Get the bifurcation type
14855 int bifurcation_type = this->Assembly_handler_pt->bifurcation_type();
14856 // If we are tracking a bifurcation then call the bifurcation adapt function
14857 if (bifurcation_type != 0)
14858 {
14859 this->bifurcation_adapt_doc_errors(bifurcation_type);
14860 // Return immediately
14861 return;
14862 }
14863
14864 // Number of submeshes?
14865 unsigned Nmesh = nsub_mesh();
14866
14867 // Single mesh:
14868 //------------
14869 if (Nmesh == 0)
14870 {
14871 // Is the single mesh refineable?
14873 dynamic_cast<RefineableMeshBase*>(mesh_pt(0)))
14874 {
14875 // Get pointer to error estimator
14877 mmesh_pt->spatial_error_estimator_pt();
14878
14879#ifdef PARANOID
14880 if (error_estimator_pt == 0)
14881 {
14882 throw OomphLibError("Error estimator hasn't been set yet",
14885 }
14886#endif
14887
14888 // Get error for all elements
14890 if (!doc_info.is_doc_enabled())
14891 {
14892 error_estimator_pt->get_element_errors(mesh_pt(0), elemental_error);
14893 }
14894 else
14895 {
14896 error_estimator_pt->get_element_errors(
14897 mesh_pt(0), elemental_error, doc_info);
14898 }
14899
14900 // Store max./min actual error
14901 mmesh_pt->max_error() = std::fabs(*std::max_element(
14903
14904 mmesh_pt->min_error() = std::fabs(*std::min_element(
14906
14907 oomph_info << "\n Max/min error: " << mmesh_pt->max_error() << " "
14908 << mmesh_pt->min_error() << std::endl;
14909 }
14910 }
14911
14912 // Multiple submeshes
14913 //------------------
14914 else
14915 {
14916 // Loop over submeshes
14917 for (unsigned imesh = 0; imesh < Nmesh; imesh++)
14918 {
14919 // Is the single mesh refineable?
14921 dynamic_cast<RefineableMeshBase*>(mesh_pt(imesh)))
14922 {
14923 // Get pointer to error estimator
14925 mmesh_pt->spatial_error_estimator_pt();
14926
14927#ifdef PARANOID
14928 if (error_estimator_pt == 0)
14929 {
14930 throw OomphLibError("Error estimator hasn't been set yet",
14933 }
14934#endif
14935
14936 // Get error for all elements
14938 if (mmesh_pt->doc_info_pt() == 0)
14939 {
14940 error_estimator_pt->get_element_errors(mesh_pt(imesh),
14942 }
14943 else
14944 {
14945 error_estimator_pt->get_element_errors(
14946 mesh_pt(imesh), elemental_error, *mmesh_pt->doc_info_pt());
14947 }
14948
14949 // Store max./min error if the mesh has any elements
14950 if (mesh_pt(imesh)->nelement() > 0)
14951 {
14952 mmesh_pt->max_error() =
14953 std::fabs(*std::max_element(elemental_error.begin(),
14954 elemental_error.end(),
14955 AbsCmp<double>()));
14956
14957 mmesh_pt->min_error() =
14958 std::fabs(*std::min_element(elemental_error.begin(),
14959 elemental_error.end(),
14960 AbsCmp<double>()));
14961 }
14962
14963 oomph_info << "\n Max/min error: " << mmesh_pt->max_error() << " "
14964 << mmesh_pt->min_error() << std::endl;
14965 }
14966
14967 } // End of loop over submeshes
14968 }
14969 }
14970
14971 //========================================================================
14972 /// Refine (one and only!) mesh by splitting the elements identified
14973 /// by their numbers relative to the problems' only mesh, then rebuild
14974 /// the problem.
14975 //========================================================================
14978 {
14980
14981 // Number of submeshes?
14982 unsigned Nmesh = nsub_mesh();
14983
14984 // Single mesh:
14985 if (Nmesh == 0)
14986 {
14987 // Refine single mesh if possible
14989 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(0)))
14990 {
14991 mmesh_pt->refine_selected_elements(elements_to_be_refined);
14992 }
14993 else
14994 {
14995 oomph_info << "Info/Warning: Mesh cannot be refined " << std::endl;
14996 }
14997 }
14998 // Multiple submeshes
14999 else
15000 {
15001 std::ostringstream error_message;
15002 error_message << "Problem::refine_selected_elements(...) only works for\n"
15003 << "multiple-mesh problems if you specify the mesh\n"
15004 << "number in the function argument before the Vector,\n"
15005 << "or a Vector of Vectors for each submesh.\n"
15006 << std::endl;
15007 throw OomphLibError(
15008 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
15009 }
15010
15011 // Any actions after the adapatation phase
15013
15014 // Attach the boundary conditions to the mesh
15015 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15016 }
15017
15018 //========================================================================
15019 /// Refine (one and only!) mesh by splitting the elements identified
15020 /// by their pointers, then rebuild the problem.
15021 //========================================================================
15024 {
15026
15027 // Number of submeshes?
15028 unsigned Nmesh = nsub_mesh();
15029
15030 // Single mesh:
15031 if (Nmesh == 0)
15032 {
15033 // Refine single mesh if possible
15035 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(0)))
15036 {
15037 mmesh_pt->refine_selected_elements(elements_to_be_refined_pt);
15038 }
15039 else
15040 {
15041 oomph_info << "Info/Warning: Mesh cannot be refined " << std::endl;
15042 }
15043 }
15044 // Multiple submeshes
15045 else
15046 {
15047 std::ostringstream error_message;
15048 error_message << "Problem::refine_selected_elements(...) only works for\n"
15049 << "multiple-mesh problems if you specify the mesh\n"
15050 << "number in the function argument before the Vector,\n"
15051 << "or a Vector of Vectors for each submesh.\n"
15052 << std::endl;
15053 throw OomphLibError(
15054 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
15055 }
15056
15057 // Any actions after the adapatation phase
15059
15060 // Do equation numbering
15061 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15062 }
15063
15064 //========================================================================
15065 /// Refine specified submesh by splitting the elements identified
15066 /// by their numbers relative to the specified mesh, then rebuild the problem.
15067 //========================================================================
15069 const unsigned& i_mesh, const Vector<unsigned>& elements_to_be_refined)
15070 {
15072
15073 // Number of submeshes?
15074 unsigned n_mesh = nsub_mesh();
15075
15076 if (i_mesh >= n_mesh)
15077 {
15078 std::ostringstream error_message;
15079 error_message << "Problem only has " << n_mesh
15080 << " submeshes. Cannot refine submesh " << i_mesh
15081 << std::endl;
15082 throw OomphLibError(
15083 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
15084 }
15085
15086 // Refine single mesh if possible
15089 {
15090 mmesh_pt->refine_selected_elements(elements_to_be_refined);
15091 }
15092 else
15093 {
15094 oomph_info << "Info/Warning: Mesh cannot be refined " << std::endl;
15095 }
15096
15097 if (n_mesh > 1)
15098 {
15099 // Rebuild the global mesh
15101 }
15102
15103 // Any actions after the adapatation phase
15105
15106 // Do equation numbering
15107 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15108 }
15109
15110
15111 //========================================================================
15112 /// Refine specified submesh by splitting the elements identified
15113 /// by their pointers, then rebuild the problem.
15114 //========================================================================
15116 const unsigned& i_mesh,
15118 {
15120
15121 // Number of submeshes?
15122 unsigned n_mesh = nsub_mesh();
15123
15124 if (i_mesh >= n_mesh)
15125 {
15126 std::ostringstream error_message;
15127 error_message << "Problem only has " << n_mesh
15128 << " submeshes. Cannot refine submesh " << i_mesh
15129 << std::endl;
15130 throw OomphLibError(
15131 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
15132 }
15133
15134 // Refine single mesh if possible
15137 {
15138 mmesh_pt->refine_selected_elements(elements_to_be_refined_pt);
15139 }
15140 else
15141 {
15142 oomph_info << "Info/Warning: Mesh cannot be refined " << std::endl;
15143 }
15144
15145 if (n_mesh > 1)
15146 {
15147 // Rebuild the global mesh
15149 }
15150
15151 // Any actions after the adapatation phase
15153
15154 // Do equation numbering
15155 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15156 }
15157
15158 //========================================================================
15159 /// Refine all submeshes by splitting the elements identified by their
15160 /// numbers relative to each submesh in a Vector of Vectors, then
15161 /// rebuild the problem.
15162 //========================================================================
15165 {
15167
15168 // Number of submeshes?
15169 unsigned n_mesh = nsub_mesh();
15170
15171 // Refine all submeshes if possible
15172 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
15173 {
15176 {
15177 mmesh_pt->refine_selected_elements(elements_to_be_refined[i_mesh]);
15178 }
15179 else
15180 {
15181 oomph_info << "Info/Warning: Mesh cannot be refined " << std::endl;
15182 }
15183 }
15184
15185 // Rebuild the global mesh
15187
15188 // Any actions after the adapatation phase
15190
15191 // Do equation numbering
15192 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15193 }
15194
15195 //========================================================================
15196 /// Refine all submeshes by splitting the elements identified by their
15197 /// pointers within each submesh in a Vector of Vectors, then
15198 /// rebuild the problem.
15199 //========================================================================
15202 {
15204
15205 // Number of submeshes?
15206 unsigned n_mesh = nsub_mesh();
15207
15208 // Refine all submeshes if possible
15209 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
15210 {
15213 {
15214 mmesh_pt->refine_selected_elements(elements_to_be_refined_pt[i_mesh]);
15215 }
15216 else
15217 {
15218 oomph_info << "Info/Warning: Mesh cannot be refined " << std::endl;
15219 }
15220 }
15221
15222 // Rebuild the global mesh
15224
15225 // Any actions after the adapatation phase
15227
15228 // Do equation numbering
15229 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15230 }
15231
15232 //========================================================================
15233 /// p-refine (one and only!) mesh by refining the elements identified
15234 /// by their numbers relative to the problems' only mesh, then rebuild
15235 /// the problem.
15236 //========================================================================
15239 {
15241
15242 // Number of submeshes?
15243 unsigned Nmesh = nsub_mesh();
15244
15245 // Single mesh:
15246 if (Nmesh == 0)
15247 {
15248 // Refine single mesh if possible
15250 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(0)))
15251 {
15252 mmesh_pt->p_refine_selected_elements(elements_to_be_refined);
15253 }
15254 else
15255 {
15256 oomph_info << "Info/Warning: Mesh cannot be refined " << std::endl;
15257 }
15258 }
15259 // Multiple submeshes
15260 else
15261 {
15262 std::ostringstream error_message;
15263 error_message
15264 << "Problem::p_refine_selected_elements(...) only works for\n"
15265 << "multiple-mesh problems if you specify the mesh\n"
15266 << "number in the function argument before the Vector,\n"
15267 << "or a Vector of Vectors for each submesh.\n"
15268 << std::endl;
15269 throw OomphLibError(
15270 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
15271 }
15272
15273 // Any actions after the adapatation phase
15275
15276 // Attach the boundary conditions to the mesh
15277 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15278 }
15279
15280 //========================================================================
15281 /// p-refine (one and only!) mesh by refining the elements identified
15282 /// by their pointers, then rebuild the problem.
15283 //========================================================================
15286 {
15288
15289 // Number of submeshes?
15290 unsigned Nmesh = nsub_mesh();
15291
15292 // Single mesh:
15293 if (Nmesh == 0)
15294 {
15295 // Refine single mesh if possible
15297 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt(0)))
15298 {
15299 mmesh_pt->p_refine_selected_elements(elements_to_be_refined_pt);
15300 }
15301 else
15302 {
15303 oomph_info << "Info/Warning: Mesh cannot be refined " << std::endl;
15304 }
15305 }
15306 // Multiple submeshes
15307 else
15308 {
15309 std::ostringstream error_message;
15310 error_message
15311 << "Problem::p_refine_selected_elements(...) only works for\n"
15312 << "multiple-mesh problems if you specify the mesh\n"
15313 << "number in the function argument before the Vector,\n"
15314 << "or a Vector of Vectors for each submesh.\n"
15315 << std::endl;
15316 throw OomphLibError(
15317 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
15318 }
15319
15320 // Any actions after the adapatation phase
15322
15323 // Do equation numbering
15324 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15325 }
15326
15327 //========================================================================
15328 /// p-refine specified submesh by refining the elements identified
15329 /// by their numbers relative to the specified mesh, then rebuild the problem.
15330 //========================================================================
15332 const unsigned& i_mesh, const Vector<unsigned>& elements_to_be_refined)
15333 {
15335 "p-refinement for multiple submeshes has not yet been tested.",
15336 "Problem::p_refine_selected_elements()",
15338
15340
15341 // Number of submeshes?
15342 unsigned n_mesh = nsub_mesh();
15343
15344 if (i_mesh >= n_mesh)
15345 {
15346 std::ostringstream error_message;
15347 error_message << "Problem only has " << n_mesh
15348 << " submeshes. Cannot p-refine submesh " << i_mesh
15349 << std::endl;
15350 throw OomphLibError(
15351 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
15352 }
15353
15354 // Refine single mesh if possible
15357 {
15358 mmesh_pt->p_refine_selected_elements(elements_to_be_refined);
15359 }
15360 else
15361 {
15362 oomph_info << "Info/Warning: Mesh cannot be refined " << std::endl;
15363 }
15364
15365 if (n_mesh > 1)
15366 {
15367 // Rebuild the global mesh
15369 }
15370
15371 // Any actions after the adapatation phase
15373
15374 // Do equation numbering
15375 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15376 }
15377
15378
15379 //========================================================================
15380 /// p-refine specified submesh by refining the elements identified
15381 /// by their pointers, then rebuild the problem.
15382 //========================================================================
15384 const unsigned& i_mesh,
15386 {
15388 "p-refinement for multiple submeshes has not yet been tested.",
15389 "Problem::p_refine_selected_elements()",
15391
15393
15394 // Number of submeshes?
15395 unsigned n_mesh = nsub_mesh();
15396
15397 if (i_mesh >= n_mesh)
15398 {
15399 std::ostringstream error_message;
15400 error_message << "Problem only has " << n_mesh
15401 << " submeshes. Cannot p-refine submesh " << i_mesh
15402 << std::endl;
15403 throw OomphLibError(
15404 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
15405 }
15406
15407 // Refine single mesh if possible
15410 {
15411 mmesh_pt->p_refine_selected_elements(elements_to_be_refined_pt);
15412 }
15413 else
15414 {
15415 oomph_info << "Info/Warning: Mesh cannot be refined " << std::endl;
15416 }
15417
15418 if (n_mesh > 1)
15419 {
15420 // Rebuild the global mesh
15422 }
15423
15424 // Any actions after the adapatation phase
15426
15427 // Do equation numbering
15428 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15429 }
15430
15431 //========================================================================
15432 /// p-refine all submeshes by refining the elements identified by their
15433 /// numbers relative to each submesh in a Vector of Vectors, then
15434 /// rebuild the problem.
15435 //========================================================================
15438 {
15440 "p-refinement for multiple submeshes has not yet been tested.",
15441 "Problem::p_refine_selected_elements()",
15443
15445
15446 // Number of submeshes?
15447 unsigned n_mesh = nsub_mesh();
15448
15449 // Refine all submeshes if possible
15450 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
15451 {
15454 {
15455 mmesh_pt->p_refine_selected_elements(elements_to_be_refined[i_mesh]);
15456 }
15457 else
15458 {
15459 oomph_info << "Info/Warning: Mesh cannot be refined " << std::endl;
15460 }
15461 }
15462
15463 // Rebuild the global mesh
15465
15466 // Any actions after the adapatation phase
15468
15469 // Do equation numbering
15470 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15471 }
15472
15473 //========================================================================
15474 /// p-refine all submeshes by refining the elements identified by their
15475 /// pointers within each submesh in a Vector of Vectors, then
15476 /// rebuild the problem.
15477 //========================================================================
15480 {
15482 "p-refinement for multiple submeshes has not yet been tested.",
15483 "Problem::p_refine_selected_elements()",
15485
15487
15488 // Number of submeshes?
15489 unsigned n_mesh = nsub_mesh();
15490
15491 // Refine all submeshes if possible
15492 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
15493 {
15496 {
15497 mmesh_pt->p_refine_selected_elements(elements_to_be_refined_pt[i_mesh]);
15498 }
15499 else
15500 {
15501 oomph_info << "Info/Warning: Mesh cannot be refined " << std::endl;
15502 }
15503 }
15504
15505 // Rebuild the global mesh
15507
15508 // Any actions after the adapatation phase
15510
15511 // Do equation numbering
15512 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15513 }
15514
15515
15516 //========================================================================
15517 /// Helper function to do compund refinement of (all) refineable
15518 /// (sub)mesh(es) uniformly as many times as specified in vector and
15519 /// rebuild problem; doc refinement process. Set boolean argument
15520 /// to true if you want to prune immediately after refining the meshes
15521 /// individually.
15522 //========================================================================
15524 DocInfo& doc_info,
15525 const bool& prune)
15526 {
15527 double t_start = 0.0;
15529 {
15531 }
15532
15534
15535 double t_end = 0.0;
15537 {
15540 << "Time for actions before adapt in Problem::refine_uniformly_aux(): "
15541 << t_end - t_start << std::endl;
15543 }
15544
15545 // Number of submeshes?
15546 unsigned n_mesh = nsub_mesh();
15547
15548 // Single mesh:
15549 if (n_mesh == 0)
15550 {
15551 // Refine single mesh uniformly if possible
15553 dynamic_cast<RefineableMeshBase*>(mesh_pt(0)))
15554 {
15555 unsigned nref = nrefine_for_mesh[0];
15556 for (unsigned i = 0; i < nref; i++)
15557 {
15558 mmesh_pt->refine_uniformly(doc_info);
15559 }
15560 }
15561 else
15562 {
15563 oomph_info << "Info/Warning: Mesh cannot be refined uniformly "
15564 << std::endl;
15565 }
15566 }
15567 // Multiple submeshes
15568 else
15569 {
15570 // Loop over submeshes
15571 for (unsigned imesh = 0; imesh < n_mesh; imesh++)
15572 {
15573 // Refine i-th submesh uniformly if possible
15575 dynamic_cast<RefineableMeshBase*>(mesh_pt(imesh)))
15576 {
15577 unsigned nref = nrefine_for_mesh[imesh];
15578 for (unsigned i = 0; i < nref; i++)
15579 {
15580 mmesh_pt->refine_uniformly(doc_info);
15581 }
15582 }
15583 else
15584 {
15585 oomph_info << "Info/Warning: Cannot refine mesh " << imesh
15586 << std::endl;
15587 }
15588 }
15589 // Rebuild the global mesh
15591 }
15592
15594 {
15596 oomph_info << "Time for mesh-level mesh refinement in "
15597 << "Problem::refine_uniformly_aux(): " << t_end - t_start
15598 << std::endl;
15600 }
15601
15602 // Any actions after the adaptation phase
15604
15605
15607 {
15610 << "Time for actions after adapt Problem::refine_uniformly_aux(): "
15611 << t_end - t_start << std::endl;
15613 }
15614
15615
15616#ifdef OOMPH_HAS_MPI
15617
15618 // Prune it?
15619 if (prune)
15620 {
15621 // Note: This calls assign eqn numbers already...
15625
15627 {
15629 oomph_info << "Time for Problem::prune_halo_elements_and_nodes() in "
15630 << "Problem::refine_uniformly_aux(): " << t_end - t_start
15631 << std::endl;
15632 }
15633 }
15634 else
15635#else
15636 if (prune)
15637 {
15638 std::ostringstream error_message;
15639 error_message
15640 << "Requested pruning in serial build. Ignoring the request.\n";
15641 OomphLibWarning(error_message.str(),
15642 "Problem::refine_uniformly_aux()",
15644 }
15645#endif
15646 {
15647 // Do equation numbering
15649 << "Number of equations after Problem::refine_uniformly_aux(): "
15650 << assign_eqn_numbers() << std::endl;
15651
15653 {
15655 oomph_info << "Time for Problem::assign_eqn_numbers() in "
15656 << "Problem::refine_uniformly_aux(): " << t_end - t_start
15657 << std::endl;
15658 }
15659 }
15660 }
15661
15662
15663 //========================================================================
15664 /// Helper function to do compund p-refinement of (all) p-refineable
15665 /// (sub)mesh(es) uniformly as many times as specified in vector and
15666 /// rebuild problem; doc refinement process. Set boolean argument
15667 /// to true if you want to prune immediately after refining the meshes
15668 /// individually.
15669 //========================================================================
15671 DocInfo& doc_info,
15672 const bool& prune)
15673 {
15674 double t_start = 0.0;
15676 {
15678 }
15679
15681
15682 double t_end = 0.0;
15684 {
15686 oomph_info << "Time for actions before adapt in "
15687 "Problem::p_refine_uniformly_aux(): "
15688 << t_end - t_start << std::endl;
15690 }
15691
15692 // Number of submeshes?
15693 unsigned n_mesh = nsub_mesh();
15694
15695 // Single mesh:
15696 if (n_mesh == 0)
15697 {
15698 // Refine single mesh uniformly if possible
15700 dynamic_cast<RefineableMeshBase*>(mesh_pt(0)))
15701 {
15702 unsigned nref = nrefine_for_mesh[0];
15703 for (unsigned i = 0; i < nref; i++)
15704 {
15705 mmesh_pt->p_refine_uniformly(doc_info);
15706 }
15707 }
15708 else
15709 {
15710 oomph_info << "Info/Warning: Mesh cannot be p-refined uniformly "
15711 << std::endl;
15712 }
15713 }
15714 // Multiple submeshes
15715 else
15716 {
15718 "p-refinement for multiple submeshes has not yet been tested.",
15719 "Problem::p_refine_uniformly_aux()",
15721
15722 // Loop over submeshes
15723 for (unsigned imesh = 0; imesh < n_mesh; imesh++)
15724 {
15725 // Refine i-th submesh uniformly if possible
15727 dynamic_cast<RefineableMeshBase*>(mesh_pt(imesh)))
15728 {
15729 unsigned nref = nrefine_for_mesh[imesh];
15730 for (unsigned i = 0; i < nref; i++)
15731 {
15732 mmesh_pt->p_refine_uniformly(doc_info);
15733 }
15734 }
15735 else
15736 {
15737 oomph_info << "Info/Warning: Cannot p-refine mesh " << imesh
15738 << std::endl;
15739 }
15740 }
15741 // Rebuild the global mesh
15743 }
15744
15746 {
15748 oomph_info << "Time for mesh-level mesh refinement in "
15749 << "Problem::p_refine_uniformly_aux(): " << t_end - t_start
15750 << std::endl;
15752 }
15753
15754 // Any actions after the adaptation phase
15756
15757
15759 {
15762 << "Time for actions after adapt Problem::p_refine_uniformly_aux(): "
15763 << t_end - t_start << std::endl;
15765 }
15766
15767
15768#ifdef OOMPH_HAS_MPI
15769
15770 // Prune it?
15771 if (prune)
15772 {
15773 // Note: This calls assign eqn numbers already...
15777
15779 {
15781 oomph_info << "Time for Problem::prune_halo_elements_and_nodes() in "
15782 << "Problem::p_refine_uniformly_aux(): " << t_end - t_start
15783 << std::endl;
15784 }
15785 }
15786 else
15787#else
15788 if (prune)
15789 {
15790 std::ostringstream error_message;
15791 error_message
15792 << "Requested pruning in serial build. Ignoring the request.\n";
15793 OomphLibWarning(error_message.str(),
15794 "Problem::p_refine_uniformly_aux()",
15796 }
15797#endif
15798 {
15799 // Do equation numbering
15801 << "Number of equations after Problem::p_refine_uniformly_aux(): "
15802 << assign_eqn_numbers() << std::endl;
15803
15805 {
15807 oomph_info << "Time for Problem::assign_eqn_numbers() in "
15808 << "Problem::p_refine_uniformly_aux(): " << t_end - t_start
15809 << std::endl;
15810 }
15811 }
15812 }
15813
15814 //========================================================================
15815 /// Refine submesh i_mesh uniformly and rebuild problem;
15816 /// doc refinement process.
15817 //========================================================================
15818 void Problem::refine_uniformly(const unsigned& i_mesh, DocInfo& doc_info)
15819 {
15821
15822#ifdef PARANOID
15823 // Number of submeshes?
15824 if (i_mesh >= nsub_mesh())
15825 {
15826 std::ostringstream error_message;
15827 error_message << "imesh " << i_mesh
15828 << " is greater than the number of sub meshes "
15829 << nsub_mesh() << std::endl;
15830
15831 throw OomphLibError(
15832 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
15833 }
15834#endif
15835
15836 // Refine single mesh uniformly if possible
15838 dynamic_cast<RefineableMeshBase*>(mesh_pt(i_mesh)))
15839 {
15840 mmesh_pt->refine_uniformly(doc_info);
15841 }
15842 else
15843 {
15844 oomph_info << "Info/Warning: Mesh cannot be refined uniformly "
15845 << std::endl;
15846 }
15847
15848 // Rebuild the global mesh
15850
15851 // Any actions after the adaptation phase
15853
15854 // Do equation numbering
15855 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15856 }
15857
15858 //========================================================================
15859 /// p-refine submesh i_mesh uniformly and rebuild problem;
15860 /// doc refinement process.
15861 //========================================================================
15862 void Problem::p_refine_uniformly(const unsigned& i_mesh, DocInfo& doc_info)
15863 {
15865
15866#ifdef PARANOID
15867 // Number of submeshes?
15868 if (i_mesh >= nsub_mesh())
15869 {
15870 std::ostringstream error_message;
15871 error_message << "imesh " << i_mesh
15872 << " is greater than the number of sub meshes "
15873 << nsub_mesh() << std::endl;
15874
15875 throw OomphLibError(
15876 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
15877 }
15878#endif
15879
15880 // Refine single mesh uniformly if possible
15882 dynamic_cast<RefineableMeshBase*>(mesh_pt(i_mesh)))
15883 {
15884 mmesh_pt->p_refine_uniformly(doc_info);
15885 }
15886 else
15887 {
15888 oomph_info << "Info/Warning: Mesh cannot be refined uniformly "
15889 << std::endl;
15890 }
15891
15892 // Rebuild the global mesh
15894
15895 // Any actions after the adaptation phase
15897
15898 // Do equation numbering
15899 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
15900 }
15901
15902
15903 //========================================================================
15904 /// Unrefine (all) refineable (sub)mesh(es) uniformly and rebuild problem.
15905 /// Return 0 for success,
15906 /// 1 for failure (if unrefinement has reached the coarsest permitted
15907 /// level)
15908 //========================================================================
15910 {
15911 // Call actions_before_adapt()
15913
15914 // Has unrefinement been successful?
15915 unsigned success_flag = 0;
15916
15917 // Number of submeshes?
15918 unsigned n_mesh = nsub_mesh();
15919
15920 // Single mesh:
15921 if (n_mesh == 0)
15922 {
15923 // Unrefine single mesh uniformly if possible
15925 dynamic_cast<RefineableMeshBase*>(mesh_pt(0)))
15926 {
15927 success_flag += mmesh_pt->unrefine_uniformly();
15928 }
15929 else
15930 {
15931 oomph_info << "Info/Warning: Mesh cannot be unrefined uniformly "
15932 << std::endl;
15933 }
15934 }
15935 // Multiple submeshes
15936 else
15937 {
15938 // Loop over submeshes
15939 for (unsigned imesh = 0; imesh < n_mesh; imesh++)
15940 {
15941 // Unrefine i-th submesh uniformly if possible
15943 dynamic_cast<RefineableMeshBase*>(mesh_pt(imesh)))
15944 {
15945 success_flag += mmesh_pt->unrefine_uniformly();
15946 }
15947 else
15948 {
15949 oomph_info << "Info/Warning: Cannot unrefine mesh " << imesh
15950 << std::endl;
15951 }
15952 }
15953 // Rebuild the global mesh
15955 }
15956
15957 // Any actions after the adaptation phase
15959
15960 // Do equation numbering
15961 oomph_info << " Number of equations: " << assign_eqn_numbers() << std::endl;
15962
15963 // Judge success
15964 if (success_flag > 0)
15965 {
15966 return 1;
15967 }
15968 else
15969 {
15970 return 0;
15971 }
15972 }
15973
15974 //========================================================================
15975 /// Unrefine submesh i_mesh uniformly and rebuild problem.
15976 /// Return 0 for success,
15977 /// 1 for failure (if unrefinement has reached the coarsest permitted
15978 /// level)
15979 //========================================================================
15980 unsigned Problem::unrefine_uniformly(const unsigned& i_mesh)
15981 {
15983
15984 // Has unrefinement been successful?
15985 unsigned success_flag = 0;
15986
15987#ifdef PARANOID
15988 // Number of submeshes?
15989 if (i_mesh >= nsub_mesh())
15990 {
15991 std::ostringstream error_message;
15992 error_message << "imesh " << i_mesh
15993 << " is greater than the number of sub meshes "
15994 << nsub_mesh() << std::endl;
15995
15996 throw OomphLibError(
15997 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
15998 }
15999#endif
16000
16001 // Unrefine single mesh uniformly if possible
16003 dynamic_cast<RefineableMeshBase*>(mesh_pt(i_mesh)))
16004 {
16005 success_flag += mmesh_pt->unrefine_uniformly();
16006 }
16007 else
16008 {
16009 oomph_info << "Info/Warning: Mesh cannot be unrefined uniformly "
16010 << std::endl;
16011 }
16012
16013 // Rebuild the global mesh
16015
16016 // Any actions after the adaptation phase
16018
16019 // Do equation numbering
16020 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
16021
16022 // Judge success
16023 if (success_flag > 0)
16024 {
16025 return 1;
16026 }
16027 else
16028 {
16029 return 0;
16030 }
16031 }
16032
16033
16034 //========================================================================
16035 /// p-unrefine (all) p-refineable (sub)mesh(es) uniformly and rebuild problem;
16036 /// doc refinement process.
16037 //========================================================================
16039 {
16041
16042 // Number of submeshes?
16043 unsigned n_mesh = nsub_mesh();
16044
16045 // Single mesh:
16046 if (n_mesh == 0)
16047 {
16048 // Unrefine single mesh uniformly if possible
16050 dynamic_cast<RefineableMeshBase*>(mesh_pt(0)))
16051 {
16052 mmesh_pt->p_unrefine_uniformly(doc_info);
16053 }
16054 else
16055 {
16056 oomph_info << "Info/Warning: Mesh cannot be p-unrefined uniformly "
16057 << std::endl;
16058 }
16059 }
16060 // Multiple submeshes
16061 else
16062 {
16063 // Not tested:
16064 throw OomphLibError("This functionality has not yet been tested.",
16067 // Loop over submeshes
16068 for (unsigned imesh = 0; imesh < n_mesh; imesh++)
16069 {
16070 // Unrefine i-th submesh uniformly if possible
16072 dynamic_cast<RefineableMeshBase*>(mesh_pt(imesh)))
16073 {
16074 mmesh_pt->p_unrefine_uniformly(doc_info);
16075 }
16076 else
16077 {
16078 oomph_info << "Info/Warning: Cannot p-unrefine mesh " << imesh
16079 << std::endl;
16080 }
16081 }
16082 // Rebuild the global mesh
16084 }
16085
16086 // Any actions after the adaptation phase
16088
16089 // Do equation numbering
16090 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
16091 }
16092
16093 //========================================================================
16094 /// p-unrefine submesh i_mesh uniformly and rebuild problem;
16095 /// doc refinement process.
16096 //========================================================================
16097 void Problem::p_unrefine_uniformly(const unsigned& i_mesh, DocInfo& doc_info)
16098 {
16100
16101#ifdef PARANOID
16102 // Number of submeshes?
16103 if (i_mesh >= nsub_mesh())
16104 {
16105 std::ostringstream error_message;
16106 error_message << "imesh " << i_mesh
16107 << " is greater than the number of sub meshes "
16108 << nsub_mesh() << std::endl;
16109
16110 throw OomphLibError(
16111 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
16112 }
16113#endif
16114
16115 // Refine single mesh uniformly if possible
16117 dynamic_cast<RefineableMeshBase*>(mesh_pt(i_mesh)))
16118 {
16119 mmesh_pt->p_unrefine_uniformly(doc_info);
16120 }
16121 else
16122 {
16123 oomph_info << "Info/Warning: Mesh cannot be p-unrefined uniformly "
16124 << std::endl;
16125 }
16126
16127 // Rebuild the global mesh
16129
16130 // Any actions after the adaptation phase
16132
16133 // Do equation numbering
16134 oomph_info << "Number of equations: " << assign_eqn_numbers() << std::endl;
16135 }
16136
16137
16138 //========================================================================
16139 /// Do one timestep, dt, forward using Newton's method with specified
16140 /// tolerance and linear solver specified via member data.
16141 /// Keep adapting on all meshes to criteria specified in
16142 /// these meshes (up to max_adapt adaptations are performed).
16143 /// If first_timestep==true, re-set initial conditions after mesh adaptation.
16144 /// Shifting of time can be suppressed by overwriting the
16145 /// default value of shift (true). [Shifting must be done
16146 /// if first_timestep==true because we're constantly re-assigning
16147 /// the initial conditions; if first_timestep==true and shift==false
16148 /// shifting is performed anyway and a warning is issued.
16149 //========================================================================
16150 void Problem::unsteady_newton_solve(const double& dt,
16151 const unsigned& max_adapt,
16152 const bool& first_timestep,
16153 const bool& shift)
16154 {
16155 // Do shifting or not?
16156 bool shift_it = shift;
16157
16158 // Warning:
16160 {
16161 shift_it = true;
16163 << "\n\n===========================================================\n";
16164 oomph_info << " ******** WARNING *********** \n";
16166 << "===========================================================\n";
16167 oomph_info << "Problem::unsteady_newton_solve() called with "
16168 << std::endl;
16169 oomph_info << "first_timestep: " << first_timestep << std::endl;
16170 oomph_info << "shift: " << shift << std::endl;
16171 oomph_info << "This doesn't make sense (shifting does have to be done"
16172 << std::endl;
16173 oomph_info << "since we're constantly re-assigning the initial conditions"
16174 << std::endl;
16176 << "\n===========================================================\n\n";
16177 }
16178
16179
16180 // Find the initial time
16181 double initial_time = time_pt()->time();
16182
16183 // Max number of solves
16184 unsigned max_solve = max_adapt + 1;
16185
16186 // Adaptation loop
16187 //----------------
16188 for (unsigned isolve = 0; isolve < max_solve; isolve++)
16189 {
16190 // Only adapt after the first solve has been done!
16191 if (isolve > 0)
16192 {
16193 unsigned n_refined;
16194 unsigned n_unrefined;
16195
16196 // Adapt problem
16198
16199#ifdef OOMPH_HAS_MPI
16200 // Adaptation only converges if ALL the processes have no
16201 // refinement or unrefinement to perform
16202 unsigned total_refined = 0;
16203 unsigned total_unrefined = 0;
16205 {
16208 1,
16210 MPI_SUM,
16211 this->communicator_pt()->mpi_comm());
16215 1,
16217 MPI_SUM,
16218 this->communicator_pt()->mpi_comm());
16220 }
16221#endif
16222
16223 oomph_info << "---> " << n_refined << " elements were refined, and "
16224 << n_unrefined << " were unrefined, in total." << std::endl;
16225
16226 // Check convergence of adaptation cycle
16227 if ((n_refined == 0) && (n_unrefined == 0))
16228 {
16229 oomph_info << "\n \n Solution is fully converged in "
16230 << "Problem::unsteady_newton_solver() \n \n ";
16231 break;
16232 }
16233
16234 // Reset the time
16235 time_pt()->time() = initial_time;
16236
16237 // Reset the inital condition on refined meshes. Note that because we
16238 // have reset the global time to the initial time, the initial
16239 // conditions are reset at time t=0 rather than at time t=dt
16240 if (first_timestep)
16241 {
16242 // Reset default set_initial_condition has been called flag to false
16244
16245 oomph_info << "Re-setting initial condition " << std::endl;
16247
16248 // If the default set_initial_condition function has been called,
16249 // we must not shift the timevalues on the first timestep, as we
16250 // will NOT be constantly re-assigning the initial condition
16252 {
16253 shift_it = false;
16254 }
16255 }
16256 }
16257
16258 // Now do the actual unsteady timestep
16259 // If it's the first time around the loop, or the first timestep
16260 // shift the timevalues, otherwise don't
16261 // Note: we need to shift if it's the first timestep because
16262 // we're constantly re-assigning the initial condition above!
16263 // The only exception to this is if the default set_initial_condition
16264 // function has been called, in which case we must NOT shift!
16265 if ((isolve == 0) || (first_timestep))
16266 {
16268 }
16269 // Subsequent solve: Have shifted already -- don't do it again.
16270 else
16271 {
16272 shift_it = false;
16274 }
16275
16276 if (isolve == max_solve - 1)
16277 {
16279 << std::endl
16280 << "----------------------------------------------------------"
16281 << std::endl
16282 << "Reached max. number of adaptations in \n"
16283 << "Problem::unsteady_newton_solver().\n"
16284 << "----------------------------------------------------------"
16285 << std::endl
16286 << std::endl;
16287 }
16288
16289 } // End of adaptation loop
16290 }
16291
16292
16293 //========================================================================
16294 /// Adaptive Newton solver.
16295 /// The linear solver takes a pointer to the problem (which defines
16296 /// the Jacobian \b J and the residual Vector \b r) and returns
16297 /// the solution \b x of the system
16298 /// \f[ {\bf J} {\bf x} = - \bf{r} \f].
16299 /// Performs at most max_adapt adaptations on all meshes.
16300 //========================================================================
16301 void Problem::newton_solve(const unsigned& max_adapt)
16302 {
16303 // Max number of solves
16304 unsigned max_solve = max_adapt + 1;
16305
16306 // Adaptation loop
16307 //----------------
16308 for (unsigned isolve = 0; isolve < max_solve; isolve++)
16309 {
16310 // Only adapt after the first solve has been done!
16311 if (isolve > 0)
16312 {
16313 unsigned n_refined;
16314 unsigned n_unrefined;
16315
16316 // Adapt problem
16318
16319#ifdef OOMPH_HAS_MPI
16320 // Adaptation only converges if ALL the processes have no
16321 // refinement or unrefinement to perform
16322 unsigned total_refined = 0;
16323 unsigned total_unrefined = 0;
16325 {
16328 1,
16330 MPI_SUM,
16331 this->communicator_pt()->mpi_comm());
16335 1,
16337 MPI_SUM,
16338 this->communicator_pt()->mpi_comm());
16340 }
16341#endif
16342
16343 oomph_info << "---> " << n_refined << " elements were refined, and "
16344 << n_unrefined << " were unrefined"
16345#ifdef OOMPH_HAS_MPI
16346 << ", in total (over all processors).\n";
16347#else
16348 << ".\n";
16349#endif
16350
16351
16352 // Check convergence of adaptation cycle
16353 if ((n_refined == 0) && (n_unrefined == 0))
16354 {
16355 oomph_info << "\n \n Solution is fully converged in "
16356 << "Problem::newton_solver(). \n \n ";
16357 break;
16358 }
16359 }
16360
16361
16362 // Do actual solve
16363 //----------------
16364 {
16365 // Now update anything that needs updating
16366 // NOT NEEDED -- IS CALLED IN newton_solve BELOW! #
16367 // actions_before_newton_solve();
16368
16369 try
16370 {
16371 // Solve the non-linear problem for this timestep with Newton's method
16372 newton_solve();
16373 }
16374 // Catch any exceptions thrown in the Newton solver
16375 catch (NewtonSolverError& error)
16376 {
16377 oomph_info << std::endl
16378 << "USER-DEFINED ERROR IN NEWTON SOLVER " << std::endl;
16379 // Check to see whether we have reached Max_iterations
16380 if (error.iterations() == Max_newton_iterations)
16381 {
16382 oomph_info << "MAXIMUM NUMBER OF ITERATIONS (" << error.iterations()
16383 << ") REACHED WITHOUT CONVERGENCE " << std::endl;
16384 }
16385 // If not, it must be that we have exceeded the maximum residuals
16386 else
16387 {
16388 oomph_info << "MAXIMUM RESIDUALS: " << error.maxres()
16389 << "EXCEEDS PREDEFINED MAXIMUM " << Max_residuals
16390 << std::endl;
16391 }
16392
16393 // Die horribly!!
16394 std::ostringstream error_stream;
16395 error_stream << "Error occured in adaptive Newton solver. "
16396 << std::endl;
16397 throw OomphLibError(error_stream.str(),
16400 }
16401
16402 // Now update anything that needs updating
16403 // NOT NEEDED -- WAS CALLED IN newton_solve ABOVE
16404 // !actions_after_newton_solve();
16405
16406 } // End of solve block
16407
16408
16409 if (isolve == max_solve - 1)
16410 {
16412 << std::endl
16413 << "----------------------------------------------------------"
16414 << std::endl
16415 << "Reached max. number of adaptations in \n"
16416 << "Problem::newton_solver().\n"
16417 << "----------------------------------------------------------"
16418 << std::endl
16419 << std::endl;
16420 }
16421
16422 } // End of adaptation loop
16423 }
16424
16425 //========================================================================
16426 /// Delete any external storage for any submeshes
16427 /// NB this would ordinarily take place within the adaptation procedure
16428 /// for each submesh (See RefineableMesh::adapt_mesh(...)), but there
16429 /// are instances where the actions_before/after_adapt routines are used
16430 /// and no adaptive routines are called in between (e.g. when doc-ing
16431 /// errors at the end of an adaptive newton solver)
16432 //========================================================================
16434 {
16435 // Number of submeshes
16436 unsigned n_mesh = nsub_mesh();
16437
16438 // External storage will only exist if there is more than one (sub)mesh
16439 if (n_mesh > 1)
16440 {
16441 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
16442 {
16444 }
16445 }
16446 }
16447
16448
16449#ifdef OOMPH_HAS_MPI
16450
16451 //====================================================================
16452 /// Get all the halo data stored on this processor and store pointers
16453 /// to the data in a map, indexed by the gobal eqn number
16454 //====================================================================
16455 void Problem::get_all_halo_data(std::map<unsigned, double*>& map_of_halo_data)
16456 {
16457 // Halo data is stored in the meshes, so kick the problem down to that
16458 // level
16459
16460 // Find the number of meshes
16461 unsigned n_mesh = this->nsub_mesh();
16462 // If there are no submeshes it's only the main mesh
16463 if (n_mesh == 0)
16464 {
16466 }
16467 // Otherwise loop over all the submeshes
16468 else
16469 {
16470 for (unsigned imesh = 0; imesh < n_mesh; ++imesh)
16471 {
16473 }
16474 }
16475 }
16476
16477
16478 //========================================================================
16479 /// Check the halo/haloed/shared node/element schemes.
16480 //========================================================================
16482 {
16483 // The bulk of the stuff that was in this routine is mesh-based, and
16484 // should therefore drop into the Mesh base class. All that needs to remain
16485 // here is a "wrapper" which calls the function dependent upon the number
16486 // of (sub)meshes that may have been distributed.
16487
16488 unsigned n_mesh = nsub_mesh();
16489
16490 if (n_mesh == 0)
16491 {
16492 oomph_info << "Checking halo schemes on single mesh" << std::endl;
16493 doc_info.label() = "_one_and_only_mesh_";
16494 mesh_pt()->check_halo_schemes(doc_info,
16496 }
16497 else // there are submeshes
16498 {
16499 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
16500 {
16501 oomph_info << "Checking halo schemes on submesh " << i_mesh
16502 << std::endl;
16503 std::stringstream tmp;
16504 tmp << "_mesh" << i_mesh << "_";
16505 doc_info.label() = tmp.str();
16508 }
16509 }
16510 }
16511
16512
16513 //========================================================================
16514 /// Synchronise all dofs by calling the appropriate synchronisation
16515 /// routines for all meshes and the assembly handler
16516 //========================================================================
16518 {
16519 // Synchronise dofs themselves
16520 bool do_halos = true;
16521 bool do_external_halos = false;
16522 this->synchronise_dofs(do_halos, do_external_halos);
16523
16524
16525 do_halos = false;
16526 do_external_halos = true;
16527 this->synchronise_dofs(do_halos, do_external_halos);
16528
16529 // Now perform any synchronisation required by the assembly handler
16531 }
16532
16533
16534 //========================================================================
16535 /// Synchronise the degrees of freedom by overwriting
16536 /// the haloed values with their non-halo counterparts held
16537 /// on other processors. Bools control if we deal with data associated with
16538 /// external halo/ed elements/nodes or the "normal" halo/ed ones.
16539 //========================================================================
16541 const bool& do_external_halos)
16542 {
16543 // Do we have submeshes?
16544 unsigned n_mesh_loop = 1;
16545 unsigned nmesh = nsub_mesh();
16546 if (nmesh > 0)
16547 {
16548 n_mesh_loop = nmesh;
16549 }
16550
16551 // Local storage for number of processors and current processor
16552 const int n_proc = this->communicator_pt()->nproc();
16553
16554 // If only one processor then return
16555 if (n_proc == 1)
16556 {
16557 return;
16558 }
16559
16560 const int my_rank = this->communicator_pt()->my_rank();
16561
16562 // Storage for number of data to be sent to each processor
16564
16565 // Storage for all values to be sent to all processors
16567
16568 // Start location within send_data for data to be sent to each processor
16570
16571 // Loop over all processors
16572 for (int rank = 0; rank < n_proc; rank++)
16573 {
16574 // Set the offset for the current processor
16576
16577 // Don't bother to do anything if the processor in the loop is the
16578 // current processor
16579 if (rank != my_rank)
16580 {
16581 // Deal with sub-meshes one-by-one if required
16582 Mesh* my_mesh_pt = 0;
16583
16584 // Loop over submeshes
16585 for (unsigned imesh = 0; imesh < n_mesh_loop; imesh++)
16586 {
16587 if (nmesh == 0)
16588 {
16589 my_mesh_pt = mesh_pt();
16590 }
16591 else
16592 {
16594 }
16595
16596 if (do_halos)
16597 {
16598 // How many of my nodes are haloed by the processor whose values
16599 // are updated?
16600 unsigned n_nod = my_mesh_pt->nhaloed_node(rank);
16601 for (unsigned n = 0; n < n_nod; n++)
16602 {
16603 // Add the data for each haloed node to the vector
16604 my_mesh_pt->haloed_node_pt(rank, n)->add_values_to_vector(
16605 send_data);
16606 }
16607
16608 // Now loop over haloed elements and prepare to add their
16609 // internal data to the big vector to be sent
16611 my_mesh_pt->haloed_element_pt(rank);
16612 unsigned nelem_haloed = haloed_elem_pt.size();
16613 for (unsigned e = 0; e < nelem_haloed; e++)
16614 {
16616 }
16617 }
16618
16620 {
16621 // How many of my nodes are externally haloed by the processor whose
16622 // values are updated? NB these nodes are on the external mesh.
16623 unsigned n_ext_nod = my_mesh_pt->nexternal_haloed_node(rank);
16624 for (unsigned n = 0; n < n_ext_nod; n++)
16625 {
16626 // Add data from each external haloed node to the vector
16627 my_mesh_pt->external_haloed_node_pt(rank, n)
16628 ->add_values_to_vector(send_data);
16629 }
16630
16631 // Now loop over haloed elements and prepare to send internal data
16632 unsigned next_elem_haloed =
16633 my_mesh_pt->nexternal_haloed_element(rank);
16634 for (unsigned e = 0; e < next_elem_haloed; e++)
16635 {
16636 my_mesh_pt->external_haloed_element_pt(rank, e)
16638 }
16639 }
16640 } // end of loop over meshes
16641 }
16642
16643 // Find the number of data added to the vector
16645 }
16646
16647
16648 // Storage for the number of data to be received from each processor
16650
16651 // Now send numbers of data to be sent between all processors
16652 MPI_Alltoall(&send_n[0],
16653 1,
16654 MPI_INT,
16655 &receive_n[0],
16656 1,
16657 MPI_INT,
16658 this->communicator_pt()->mpi_comm());
16659
16660 // We now prepare the data to be received
16661 // by working out the displacements from the received data
16663 int receive_data_count = 0;
16664 for (int rank = 0; rank < n_proc; ++rank)
16665 {
16666 // Displacement is number of data received so far
16669 }
16670
16671 // Now resize the receive buffer for all data from all processors
16672 // Make sure that it has a size of at least one
16673 if (receive_data_count == 0)
16674 {
16676 }
16678
16679 // Make sure that the send buffer has size at least one
16680 // so that we don't get a segmentation fault
16681 if (send_data.size() == 0)
16682 {
16683 send_data.resize(1);
16684 }
16685
16686 // Now send the data between all the processors
16688 &send_n[0],
16690 MPI_DOUBLE,
16691 &receive_data[0],
16692 &receive_n[0],
16694 MPI_DOUBLE,
16695 this->communicator_pt()->mpi_comm());
16696
16697 // Now use the received data to update the halo nodes
16698 for (int send_rank = 0; send_rank < n_proc; send_rank++)
16699 {
16700 // Don't bother to do anything for the processor corresponding to the
16701 // current processor or if no data were received from this processor
16702 if ((send_rank != my_rank) && (receive_n[send_rank] != 0))
16703 {
16704 // Counter for the data within the large array
16706
16707 // Deal with sub-meshes one-by-one if required
16708 Mesh* my_mesh_pt = 0;
16709
16710 // Loop over submeshes
16711 for (unsigned imesh = 0; imesh < n_mesh_loop; imesh++)
16712 {
16713 if (nmesh == 0)
16714 {
16715 my_mesh_pt = mesh_pt();
16716 }
16717 else
16718 {
16720 }
16721
16722 if (do_halos)
16723 {
16724 // How many of my nodes are halos whose non-halo counter
16725 // parts live on processor send_rank?
16726 unsigned n_nod = my_mesh_pt->nhalo_node(send_rank);
16727 for (unsigned n = 0; n < n_nod; n++)
16728 {
16729 // Read in values for each halo node
16730 my_mesh_pt->halo_node_pt(send_rank, n)
16731 ->read_values_from_vector(receive_data, count);
16732 }
16733
16734 // Get number of halo elements whose non-halo is
16735 // on process send_rank
16737 my_mesh_pt->halo_element_pt(send_rank);
16738
16739 unsigned nelem_halo = halo_elem_pt.size();
16740 for (unsigned e = 0; e < nelem_halo; e++)
16741 {
16744 }
16745 }
16746
16748 {
16749 // How many of my nodes are external halos whose external non-halo
16750 // counterparts live on processor send_rank?
16751 unsigned n_ext_nod = my_mesh_pt->nexternal_halo_node(send_rank);
16752
16753 // Copy into the values of the external halo nodes
16754 // on the present processors
16755 for (unsigned n = 0; n < n_ext_nod; n++)
16756 {
16757 // Read the data from the array into each halo node
16758 my_mesh_pt->external_halo_node_pt(send_rank, n)
16759 ->read_values_from_vector(receive_data, count);
16760 }
16761
16762 // Get number of halo elements whose non-halo is
16763 // on process send_rank
16764 unsigned next_elem_halo =
16765 my_mesh_pt->nexternal_halo_element(send_rank);
16766 for (unsigned e = 0; e < next_elem_halo; e++)
16767 {
16768 my_mesh_pt->external_halo_element_pt(send_rank, e)
16770 }
16771 }
16772
16773 } // end of loop over meshes
16774 }
16775 } // End of data is received
16776 } // End of synchronise
16777
16778
16779 //========================================================================
16780 /// Synchronise equation numbers and return the total
16781 /// number of degrees of freedom in the overall problem
16782 //========================================================================
16783 long Problem::synchronise_eqn_numbers(const bool& assign_local_eqn_numbers)
16784 {
16785 // number of equations on this processor, which at this stage is only known
16786 // by counting the number of dofs that have been added to the problem
16787 unsigned my_n_eqn = Dof_pt.size();
16788
16789 // my rank
16790 unsigned my_rank = Communicator_pt->my_rank();
16791
16792 // number of processors
16793 unsigned nproc = Communicator_pt->nproc();
16794
16795 // // Time alternative communication
16796 // Vector<unsigned> n_eqn(nproc);
16797 // {
16798 // double t_start = TimingHelpers::timer();
16799
16800 // // Gather numbers of equations (enumerated independently on all procs)
16801 // MPI_Allgather(&my_n_eqn,1,MPI_UNSIGNED,&n_eqn[0],
16802 // 1,MPI_INT,Communicator_pt->mpi_comm());
16803
16804 // double t_end = TimingHelpers::timer();
16805 // oomph_info << "Time for allgather-based exchange of eqn numbers: "
16806 // << t_end-t_start << std::endl;
16807 // }
16808
16809 double t_start = TimingHelpers::timer();
16810
16811 // send my_n_eqn to with rank greater than my_rank
16812 unsigned n_send = nproc - my_rank - 1;
16814 for (unsigned p = my_rank + 1; p < nproc; p++)
16815 {
16817 1,
16819 p,
16820 0,
16821 Communicator_pt->mpi_comm(),
16822 &send_req[p - my_rank - 1]);
16823 }
16824
16825 // recv n_eqn from processors with rank less than my_rank
16827 for (unsigned p = 0; p < my_rank; p++)
16828 {
16830 1,
16832 p,
16833 0,
16834 Communicator_pt->mpi_comm(),
16836 }
16837
16838 double t_end = 0.0;
16840 {
16842 oomph_info << "Time for send and receive stuff: " << t_end - t_start
16843 << std::endl;
16845 }
16846
16847 // determine the number of equation on processors with rank
16848 // less than my_rank
16849 unsigned my_eqn_num_base = 0;
16850 for (unsigned p = 0; p < my_rank; p++)
16851 {
16853 // if (n_eqn_on_proc[p]!=n_eqn[p])
16854 // {
16855 // std::cout << "proc " << my_rank << "clash in eqn numbers: "
16856 // << p << " " << n_eqn_on_proc[p] << " " << n_eqn[p]
16857 // << std::endl;
16858 // }
16859 }
16860
16861 // Loop over all internal data (on elements) and bump up their
16862 // equation numbers if they exist
16863 unsigned nelem = mesh_pt()->nelement();
16864 for (unsigned e = 0; e < nelem; e++)
16865 {
16867
16868 unsigned nintern_data = el_pt->ninternal_data();
16869 for (unsigned iintern = 0; iintern < nintern_data; iintern++)
16870 {
16872 unsigned nval = int_data_pt->nvalue();
16873 for (unsigned ival = 0; ival < nval; ival++)
16874 {
16876 if (old_eqn_number >= 0) // i.e. it's being used
16877 {
16878 // Bump up eqn number
16881 }
16882 }
16883 }
16884 }
16885
16886 // Loop over all nodes on current processor and bump up their
16887 // equation numbers if they're not pinned!
16888 unsigned nnod = mesh_pt()->nnode();
16889 for (unsigned j = 0; j < nnod; j++)
16890 {
16891 Node* nod_pt = mesh_pt()->node_pt(j);
16892
16893 // loop over ALL eqn numbers - variable number of values
16894 unsigned nval = nod_pt->nvalue();
16895
16896 for (unsigned ival = 0; ival < nval; ival++)
16897 {
16899 // Include all eqn numbers
16900 if (old_eqn_number >= 0)
16901 {
16902 // Bump up eqn number
16905 }
16906 }
16907
16908 // Is this a solid node? If so, need to bump up its equation number(s)
16909 SolidNode* solid_nod_pt = dynamic_cast<SolidNode*>(nod_pt);
16910
16911 if (solid_nod_pt != 0)
16912 {
16913 // Find equation numbers
16914 unsigned nval = solid_nod_pt->variable_position_pt()->nvalue();
16915 for (unsigned ival = 0; ival < nval; ival++)
16916 {
16917 int old_eqn_number =
16918 solid_nod_pt->variable_position_pt()->eqn_number(ival);
16919 // include all eqn numbers
16920
16921 if (old_eqn_number >= 0)
16922 {
16923 // Bump up eqn number
16925 solid_nod_pt->variable_position_pt()->eqn_number(ival) =
16927 }
16928 }
16929 }
16930 }
16931
16933 {
16935 oomph_info << "Time for bumping: " << t_end - t_start << std::endl;
16937 }
16938
16939
16940 // Now copy the haloed eqn numbers across
16941 // This has to include the internal data equation numbers as well
16942 // as the solid node equation numbers
16943 bool do_halos = true;
16944 bool do_external_halos = false;
16946
16948 {
16950 oomph_info << "Time for copy_haloed_eqn_numbers_helper for halos: "
16951 << t_end - t_start << std::endl;
16953 }
16954
16955 // Now do external halo stuff
16956 do_halos = false;
16957 do_external_halos = true;
16959
16961 {
16964 << "Time for copy_haloed_eqn_numbers_helper for external halos: "
16965 << t_end - t_start << std::endl;
16967 }
16968
16969 // Now the global equation numbers have been updated.
16970 //---------------------------------------------------
16971 // Setup the local equation numbers again.
16972 //----------------------------------------
16973 if (assign_local_eqn_numbers)
16974 {
16975 // Loop over the submeshes: Note we need to call the submeshes' own
16976 // assign_*_eqn_number() otherwise we miss additional functionality
16977 // that is implemented (e.g.) in SolidMeshes!
16978 unsigned n_sub_mesh = nsub_mesh();
16979 if (n_sub_mesh == 0)
16980 {
16982 }
16983 else
16984 {
16985 for (unsigned i = 0; i < n_sub_mesh; i++)
16986 {
16988 }
16989 }
16990 }
16991
16993 {
16995 oomph_info << "Time for assign_local_eqn_numbers in sync: "
16996 << t_end - t_start << std::endl;
16998 }
16999
17000 // wait for the sends to complete
17001 if (n_send > 0)
17002 {
17005 }
17006
17008 {
17010 oomph_info << "Time for waitall: " << t_end - t_start << std::endl;
17012 }
17013
17014 // build the Dof distribution pt
17016
17017 // and return the total number of equations in the problem
17018 return (long)Dof_distribution_pt->nrow();
17019 }
17020
17021
17022 //=======================================================================
17023 /// A private helper function to
17024 /// copy the haloed equation numbers into the halo equation numbers,
17025 /// either for the problem's one and only mesh or for all of its
17026 /// submeshes. Bools control if we deal with data associated with
17027 /// external halo/ed elements/nodes or the "normal" halo/ed ones.
17028 //===================================================================
17030 const bool& do_external_halos)
17031 {
17032 // Do we have submeshes?
17033 unsigned n_mesh_loop = 1;
17034 unsigned nmesh = nsub_mesh();
17035 if (nmesh > 0)
17036 {
17037 n_mesh_loop = nmesh;
17038 }
17039
17040 // Storage for number of processors and current processor
17041 int n_proc = this->communicator_pt()->nproc();
17042
17043 // If only one processor then return
17044 if (n_proc == 1)
17045 {
17046 return;
17047 }
17048 int my_rank = this->communicator_pt()->my_rank();
17049
17050 // Storage for number of data to be sent to each processor
17052 // Storage for all equation numbers to be sent to all processors
17054 // Start location within send_data for data to be sent to each processor
17056
17057
17058 // Loop over all processors whose eqn numbers are to be updated
17059 for (int rank = 0; rank < n_proc; rank++)
17060 {
17061 // Set the displacement of the current processor in the loop
17063
17064 // If I'm not the processor whose halo eqn numbers are updated,
17065 // some of my nodes may be haloed: Stick their
17066 // eqn numbers into the vector
17067 if (rank != my_rank)
17068 {
17069 // Deal with sub-meshes one-by-one if required
17070 Mesh* my_mesh_pt = 0;
17071
17072 // Loop over submeshes
17073 for (unsigned imesh = 0; imesh < n_mesh_loop; imesh++)
17074 {
17075 if (nmesh == 0)
17076 {
17077 my_mesh_pt = mesh_pt();
17078 }
17079 else
17080 {
17082 }
17083
17084 if (do_halos)
17085 {
17086 // Add equation numbers for each haloed node
17087 unsigned n_nod = my_mesh_pt->nhaloed_node(rank);
17088 for (unsigned n = 0; n < n_nod; n++)
17089 {
17090 my_mesh_pt->haloed_node_pt(rank, n)->add_eqn_numbers_to_vector(
17091 send_data);
17092 }
17093
17094 // Add the equation numbers associated with internal data
17095 // in the haloed elements
17097 my_mesh_pt->haloed_element_pt(rank);
17098 unsigned nelem_haloed = haloed_elem_pt.size();
17099 for (unsigned e = 0; e < nelem_haloed; e++)
17100 {
17102 }
17103 }
17104
17106 {
17107 // Add equation numbers associated with external haloed nodes
17108 unsigned n_ext_nod = my_mesh_pt->nexternal_haloed_node(rank);
17109 for (unsigned n = 0; n < n_ext_nod; n++)
17110 {
17111 my_mesh_pt->external_haloed_node_pt(rank, n)
17112 ->add_eqn_numbers_to_vector(send_data);
17113 }
17114
17115 // Add the equation numbers associated with internal data in
17116 // each external haloed element
17117 unsigned next_elem_haloed =
17118 my_mesh_pt->nexternal_haloed_element(rank);
17119 for (unsigned e = 0; e < next_elem_haloed; e++)
17120 {
17121 // how many internal data values for this element?
17122 my_mesh_pt->external_haloed_element_pt(rank, e)
17124 }
17125 }
17126
17127 } // end of loop over meshes
17128 }
17129
17130 // Find the number of data added to the vector by this processor
17132 }
17133
17134 // Storage for the number of data to be received from each processor
17136
17137 // Communicate all numbers of data to be sent between all processors
17138 MPI_Alltoall(&send_n[0],
17139 1,
17140 MPI_INT,
17141 &receive_n[0],
17142 1,
17143 MPI_INT,
17144 this->communicator_pt()->mpi_comm());
17145
17146 // We now prepare the data to be received
17147 // by working out the displacements from the received data
17149 int receive_data_count = 0;
17150 for (int rank = 0; rank < n_proc; ++rank)
17151 {
17152 // Displacement is number of data received so far
17155 }
17156
17157 // Now resize the receive buffer
17158 // Make sure that it has a size of at least one
17159 if (receive_data_count == 0)
17160 {
17162 }
17164
17165 // Make sure that the send buffer has size at least one
17166 // so that we don't get a segmentation fault
17167 if (send_data.size() == 0)
17168 {
17169 send_data.resize(1);
17170 }
17171
17172 // Now send the data between all the processors
17174 &send_n[0],
17176 MPI_LONG,
17177 &receive_data[0],
17178 &receive_n[0],
17180 MPI_LONG,
17181 this->communicator_pt()->mpi_comm());
17182
17183
17184 // Loop over all other processors to receive their
17185 // eqn numbers
17186 for (int send_rank = 0; send_rank < n_proc; send_rank++)
17187 {
17188 // Don't do anything for the processor corresponding to the
17189 // current processor or if no data were received from this processor
17190 if ((send_rank != my_rank) && (receive_n[send_rank] != 0))
17191 {
17192 // Counter for the data within the large array
17194
17195 // Deal with sub-meshes one-by-one if required
17196 Mesh* my_mesh_pt = 0;
17197
17198 // Loop over submeshes
17199 for (unsigned imesh = 0; imesh < n_mesh_loop; imesh++)
17200 {
17201 if (nmesh == 0)
17202 {
17203 my_mesh_pt = mesh_pt();
17204 }
17205 else
17206 {
17208 }
17209
17210 if (do_halos)
17211 {
17212 // How many of my nodes are halos whose non-halo counter
17213 // parts live on processor send_rank?
17214 unsigned n_nod = my_mesh_pt->nhalo_node(send_rank);
17215 for (unsigned n = 0; n < n_nod; n++)
17216 {
17217 // Generalise to variable number of values per node
17218 my_mesh_pt->halo_node_pt(send_rank, n)
17219 ->read_eqn_numbers_from_vector(receive_data, count);
17220 }
17221
17222 // Get number of halo elements whose non-halo is on
17223 // process send_rank
17225 my_mesh_pt->halo_element_pt(send_rank);
17226 unsigned nelem_halo = halo_elem_pt.size();
17227 for (unsigned e = 0; e < nelem_halo; e++)
17228 {
17231 }
17232 }
17233
17235 {
17236 // How many of my nodes are external halos whose external non-halo
17237 // counterparts live on processor send_rank?
17238 unsigned n_ext_nod = my_mesh_pt->nexternal_halo_node(send_rank);
17239 for (unsigned n = 0; n < n_ext_nod; n++)
17240 {
17241 my_mesh_pt->external_halo_node_pt(send_rank, n)
17242 ->read_eqn_numbers_from_vector(receive_data, count);
17243 }
17244
17245 // Get number of external halo elements whose external haloed
17246 // counterpart is on process send_rank
17247 unsigned next_elem_halo =
17248 my_mesh_pt->nexternal_halo_element(send_rank);
17249 for (unsigned e = 0; e < next_elem_halo; e++)
17250 {
17251 my_mesh_pt->external_halo_element_pt(send_rank, e)
17253 }
17254 }
17255
17256 } // end of loop over meshes
17257 }
17258 } // End of loop over processors
17259 }
17260
17261 //==========================================================================
17262 /// Balance the load of a (possibly non-uniformly refined) problem that has
17263 /// already been distributed, by re-distributing elements over the processors.
17264 /// Produce explicit stats of load balancing process if boolean, report_stats,
17265 /// is set to true and doc various bits of data (mainly for debugging)
17266 /// in directory specified by DocInfo object.
17267 //==========================================================================
17269 DocInfo& doc_info,
17270 const bool& report_stats,
17272 {
17273 double start_t = TimingHelpers::timer();
17274
17275 // Number of processes
17276 const unsigned n_proc = this->communicator_pt()->nproc();
17277
17278 // Don't do anything if this is a single-process job
17279 if (n_proc == 1)
17280 {
17281 if (report_stats)
17282 {
17283 std::ostringstream warn_message;
17284 warn_message << "WARNING: You've tried to load balance a problem over\n"
17285 << "only one processor: ignoring your request.\n";
17287 "Problem::load_balance()",
17289 }
17290 }
17291 // Multiple processors
17292 else
17293 {
17294 // This will only work if the problem has already been distributed
17296 {
17297 // Throw an error
17298 std::ostringstream error_stream;
17299 error_stream << "You have called Problem::load_balance()\n"
17300 << "on a non-distributed problem. This doesn't\n"
17301 << "make sense -- go distribute your problem first."
17302 << std::endl;
17303 throw OomphLibError(
17305 }
17306
17307 // Timings
17308 double t_start = 0.0;
17309 double t_metis = 0.0;
17310 double t_partition = 0.0;
17311 double t_distribute = 0.0;
17312 double t_refine = 0.0;
17313 double t_copy_solution = 0.0;
17314
17315 if (report_stats)
17316 {
17318 }
17319
17320
17321#ifdef PARANOID
17322 unsigned old_ndof = ndof();
17323#endif
17324
17325 // Store pointers to the old mesh(es) so we retain a handle
17326 //---------------------------------------------------------
17327 // to them for deletion
17328 //---------------------
17330 unsigned n_mesh = nsub_mesh();
17331 if (n_mesh == 0)
17332 {
17333 // Resize the container
17334 old_mesh_pt.resize(1);
17335 old_mesh_pt[0] = mesh_pt();
17336 }
17337 else
17338 {
17339 // Resize the container
17340 old_mesh_pt.resize(n_mesh);
17341 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
17342 {
17344 }
17345 }
17346
17347
17348 // Partition the global mesh in its current state
17349 //-----------------------------------------------
17350
17351 // target_domain_for_local_non_halo_element[e] contains the number
17352 // of the domain [0,1,...,nproc-1] to which non-halo element e on THE
17353 // CURRENT PROCESSOR ONLY has been assigned. The order of the non-halo
17354 // elements is the same as in the Problem's mesh, with the halo
17355 // elements being skipped.
17357
17358 // Do any of the processors want to go through externally imposed
17359 // partitioning? If so, we'd better do it here too (even if the processor
17360 // is empty, e.g. following a restart on a larger number of procs) or
17361 // we hang.
17362 unsigned local_ntarget =
17364 unsigned global_ntarget = 0;
17367 1,
17369 MPI_MAX,
17370 Communicator_pt->mpi_comm());
17371
17372 // External prescribed partitioning
17373 if (global_ntarget > 0)
17374 {
17377 }
17378 else
17379 {
17380 // Metis does not always produce repeatable results which is
17381 // a disaster for validation runs -- this bypasses metis and
17382 // comes up with a stupid but repeatable partioning.
17384 {
17385 // Bypass METIS to perform the partitioning
17386 unsigned objective = 0;
17387 bool bypass_metis = true;
17389 this,
17390 objective,
17392 bypass_metis);
17393 }
17394 else
17395 {
17396 // Use METIS to perform the partitioning
17397 unsigned objective = 0;
17400 }
17401 }
17402
17403 if (report_stats)
17404 {
17406 }
17407
17408 // Setup map linking element with target domain
17409 std::map<GeneralisedElement*, unsigned>
17411 unsigned n_elem = mesh_pt()->nelement();
17412 unsigned count_non_halo_el = 0;
17413 for (unsigned e = 0; e < n_elem; e++)
17414 {
17416 if (!el_pt->is_halo())
17417 {
17421 }
17422 }
17423
17424 // Load balancing is equivalent to distribution so call the
17425 // appropriate "actions before". NOTE: This acts on the
17426 // current, refined, distributed, etc. problem object
17427 // before it's being wiped. This step is therefore not
17428 // a duplicate of the call below, which acts on the
17429 // new, not-yet refined, distributed etc. problem!
17431
17432 // Re-setup target domains for remaining elements (FaceElements
17433 // are likely to have been stripped out in actions_before_distribute()
17434 n_elem = mesh_pt()->nelement();
17438 for (unsigned e = 0; e < n_elem; e++)
17439 {
17441 if (!el_pt->is_halo())
17442 {
17445 }
17446 } // for (e < n_elem)
17447
17448 // Re-setup the number of sub-meshes since some of them may have
17449 // been stripped out in actions_before_distribute(), but save the
17450 // number of old sub-meshes
17451 const unsigned n_old_sub_meshes = n_mesh;
17452 n_mesh = nsub_mesh();
17453
17454 // Now get the target domains for each of the submeshes, we only
17455 // get the target domains for the nonhalo elements
17457 n_mesh);
17458 // If we have no sub-meshes then we do not need to copy the target areas
17459 // of the submeshes
17460 if (n_mesh != 0)
17461 {
17462 // Counter to copy the target domains from the global vector
17463 unsigned count_td = 0;
17464 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
17465 {
17466 // Get the number of elements (considering halo elements)
17467 const unsigned nsub_ele = mesh_pt(i_mesh)->nelement();
17468 // Now copy that number of data from the global target domains
17469 for (unsigned i = 0; i < nsub_ele; i++)
17470 {
17471 // Get the element
17473 // ... and check if it is a nonhalo element
17474 if (!ele_pt->is_halo())
17475 {
17476 // Get the target domain for the current element
17477 const unsigned target_domain =
17479 // Add the target domain for the nonhalo element in the
17480 // submesh
17482 .push_back(target_domain);
17483 } // if (!ele_pt->is_halo())
17484 } // for (i < nsub_ele)
17485 } // for (imesh < n_mesh)
17486
17487#ifdef PARANOID
17488 // Check that the total number of copied data be the same as the
17489 // total number of nonhalo elements in the (sub)-mesh(es)
17490 const unsigned ntarget_domain =
17492 if (count_td != ntarget_domain)
17493 {
17494 std::ostringstream error_stream;
17496 << "The number of nonhalo elements (" << count_td
17497 << ") found in (all)\n"
17498 << "the (sub)-mesh(es) is different from the number of target "
17499 "domains\n"
17500 << "(" << ntarget_domain << ") for the nonhalo elements.\n"
17501 << "Please ensure that you called the rebuild_global_mesh() method "
17502 << "after the\npossible deletion of FaceElements in "
17503 << "actions_before_distribute()!!!\n\n";
17504 throw OomphLibError(error_stream.str(),
17505 "Problem::load_balance()",
17507 } // if (count_td != ntarget_domain)
17508#endif
17509
17510 } // if (n_mesh != 0)
17511
17512 // Check if we have different type of submeshes (unstructured
17513 // and/or structured). Identify to which type each submesh belongs.
17514 // If we have only one mesh then identify to which type that mesh
17515 // belongs.
17516
17517 // The load balancing strategy acts in the structured meshes and
17518 // then acts in the unstructured meshes
17519
17520 // Vector to temporaly store pointers to unstructured meshes
17521 // (TriangleMeshBase)
17523 std::vector<bool> is_unstructured_mesh;
17524
17525 // Flag to indicate that there are unstructured meshes as part of
17526 // the problem
17527 bool are_there_unstructured_meshes = false;
17528
17529 // We have only one mesh
17530 if (n_mesh == 0)
17531 {
17532 // Check if it is a TriangleMeshBase mesh
17534 dynamic_cast<TriangleMeshBase*>(old_mesh_pt[0]))
17535 {
17536 // Add the pointer to the unstructured meshes container
17538 // Indicate that it is an unstructured mesh
17539 is_unstructured_mesh.push_back(true);
17540 // Indicate that there are unstructured meshes as part of the
17541 // problem
17543 }
17544 else
17545 {
17546 // Add the pointer to the unstructured meshes container (null
17547 // pointer)
17549 // Indicate that it is not an unstructured mesh
17550 is_unstructured_mesh.push_back(false);
17551 }
17552 } // if (n_mesh == 0)
17553 else // We have sub-meshes
17554 {
17555 // Check which sub-meshes are unstructured meshes (work with the
17556 // old sub-meshes number)
17557 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
17558 {
17559 // Is it a TriangleMeshBase mesh
17561 dynamic_cast<TriangleMeshBase*>(old_mesh_pt[i_mesh]))
17562 {
17563 // Add the pointer to the unstructured meshes container
17565 // Indicate that it is an unstructured mesh
17566 is_unstructured_mesh.push_back(true);
17567 // Indicate that there are unstructured meshes as part of the
17568 // problem
17570 }
17571 else
17572 {
17573 // Add the pointer to the unstructured meshes container (null
17574 // pointer)
17576 // Indicate that it is not an unstructured mesh
17577 is_unstructured_mesh.push_back(false);
17578 }
17579 } // for (i_mesh < n_mesh)
17580 } // else if (n_mesh == 0) // We have sub-meshes
17581
17582 // Extract data to be sent to various processors after the
17583 //--------------------------------------------------------
17584 // problem has been rebuilt/re-distributed
17585 //----------------------------------------
17586
17587 // Storage for number of data to be sent to each processor
17589
17590 // Storage for all values to be sent to all processors
17592
17593 // Start location within send_data for data to be sent to each processor
17595
17596 // Old and new domains for each base element (available for all, for
17597 // convenience)
17600
17601 // Flat-packed refinement info, labeled by id of locally
17602 // available root elements
17603 std::map<unsigned, Vector<unsigned>> flat_packed_refinement_info_for_root;
17604
17605 // Max. level of refinement
17606 unsigned max_refinement_level_overall = 0;
17607
17608 // Prepare the input for the get_data...() method, only copy the
17609 // data from the structured meshes, TreeBaseMesh meshes
17612 if (n_mesh == 0)
17613 {
17614 // Check if the mesh is an structured mesh
17615 if (!is_unstructured_mesh[0])
17616 {
17617 const unsigned nele_mesh =
17619 for (unsigned e = 0; e < nele_mesh; e++)
17620 {
17621 const unsigned target_domain =
17624 .push_back(target_domain);
17625 } // for (e < nele_mesh)
17626 } // if (!is_unstructured_mesh[0])
17627 } // if (n_mesh == 0)
17628 else
17629 {
17630 // Copy the target domains from the structured meshes only
17631 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
17632 {
17633 // Check if the mesh is an structured mesh
17635 {
17636 const unsigned nele_sub_mesh =
17638 for (unsigned e = 0; e < nele_sub_mesh; e++)
17639 {
17640 const unsigned target_domain =
17643 .push_back(target_domain);
17644 } // for (e < nele_sub_mesh)
17645 } // if (!is_triangle_mesh_base[i_mesh])
17646 } // for (i_mesh < n_mesh)
17647 } // else if (n_mesh == 0)
17648
17649 // Extract data from current problem
17650 // sorted into data to be sent to various processors after
17651 // rebuilding the meshes in a load-balanced form
17654 send_n,
17655 send_data,
17660
17661 // Extract flat-packed refinement pattern
17667
17668 if (report_stats)
17669 {
17671 oomph_info << "CPU for partition calculation for roots: "
17672 << t_partition - t_metis << std::endl;
17673 }
17674
17675
17676 // Flush and delete old submeshes and null the global mesh
17677 //--------------------------------------------------------
17678 // and rebuild the new (not yet distributed, refined etc.) mesh
17679 //-------------------------------------------------------------
17680 // that will be distributed in the new, improved way determined
17681 //-------------------------------------------------------------
17682 // by METIS
17683 //---------
17685 std::max(int(n_old_sub_meshes), 1));
17686 if (n_mesh == 0)
17687 {
17690 dynamic_cast<TreeBasedRefineableMeshBase*>(old_mesh_pt[0]);
17691 if (ref_mesh_pt != 0)
17692 {
17694 ref_mesh_pt->uniform_refinement_level_when_pruned();
17695 }
17696
17697 // If the mesh is an unstructured mesh (TriangleMeshBase mesh)
17698 // then we should not delete it since the load balance strategy
17699 // requires the mesh
17700
17701 // Delete the mesh if it is not an unstructured mesh
17702 if (!is_unstructured_mesh[0])
17703 {
17704 delete old_mesh_pt[0];
17705 old_mesh_pt[0] = 0;
17706 } // if (!is_unstructured_mesh[0])
17707 } // if (n_mesh==0)
17708 else
17709 {
17710 // Loop over the number of old meshes (required to delete the
17711 // pointers of structured meshes in the old_mesh_pt structure)
17712 for (unsigned i_mesh = 0; i_mesh < n_old_sub_meshes; i_mesh++)
17713 {
17717 if (ref_mesh_pt != 0)
17718 {
17720 ref_mesh_pt->uniform_refinement_level_when_pruned();
17721 }
17722
17723 // If the mesh is an unstructured mesh (TriangleMeshBase mesh)
17724 // then we should NOT delete it since the load balance strategy
17725 // requires the mesh
17726
17727 // Delete the mesh if it is not an unstructured mesh
17729 {
17730 delete old_mesh_pt[i_mesh];
17731 old_mesh_pt[i_mesh] = 0;
17732 } // if (!is_unstructured_mesh[i_mesh])
17733
17734 } // for (i_mesh<n_mesh)
17735
17736 // Empty storage for sub-meshes
17738
17739 // Flush the storage for nodes and elements in compound mesh
17740 // (they've already been deleted in the sub-meshes)
17742
17743 // Kill
17744 delete mesh_pt();
17745 mesh_pt() = 0;
17746 } // else if (n_mesh==0)
17747
17748 bool some_mesh_has_been_pruned = false;
17749 unsigned n = pruned_refinement_level.size();
17750 for (unsigned i = 0; i < n; i++)
17751 {
17753 }
17754
17755 // (Re-)build the new mesh(es) -- this must get the problem into the
17756 // state it was in when it was first distributed!
17757 build_mesh();
17758
17759 // Has one of the meshes been pruned; if so refine to the
17760 // common refinement level
17762 {
17763 // Do actions before adapt
17765
17766 // Re-assign number of submeshes -- when this was first
17767 // set, the problem may have had face meshes that have now
17768 // disappeared.
17769 n_mesh = nsub_mesh();
17770
17771 // Now adapt meshes manually
17772 if (n_mesh == 0)
17773 {
17775 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt());
17776 if (ref_mesh_pt != 0)
17777 {
17778 // Get min and max refinement level
17779 unsigned local_min_ref = 0;
17780 unsigned local_max_ref = 0;
17781 ref_mesh_pt->get_refinement_levels(local_min_ref, local_max_ref);
17782
17783 // Reconcile between processors: If (e.g. following
17784 // distribution/pruning) the mesh has no elements on this
17785 // processor) then ignore its contribution to the poll of
17786 // max/min refinement levels
17788 if (ref_mesh_pt->nelement() == 0)
17789 {
17791 }
17792 int int_min_ref = 0;
17794 &int_min_ref,
17795 1,
17796 MPI_INT,
17797 MPI_MIN,
17798 Communicator_pt->mpi_comm());
17799
17800 // Overall min refinement level over all meshes
17801 unsigned min_ref = unsigned(int_min_ref);
17802
17803 // Refine as many times as required to get refinement up to
17804 // uniform refinement level after last prune
17805 unsigned nref = pruned_refinement_level[0] - min_ref;
17806 oomph_info << "Refining one-and-only mesh uniformly " << nref
17807 << " times\n";
17808 for (unsigned i = 0; i < nref; i++)
17809 {
17810 ref_mesh_pt->refine_uniformly();
17811 }
17812 }
17813 }
17814 else
17815 {
17816 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
17817 {
17820 if (ref_mesh_pt != 0)
17821 {
17822 // Get min and max refinement level
17823 unsigned local_min_ref = 0;
17824 unsigned local_max_ref = 0;
17825 ref_mesh_pt->get_refinement_levels(local_min_ref, local_max_ref);
17826
17827 // Reconcile between processors: If (e.g. following
17828 // distribution/pruning) the mesh has no elements on this
17829 // processor) then ignore its contribution to the poll of
17830 // max/min refinement levels
17832 if (ref_mesh_pt->nelement() == 0)
17833 {
17835 }
17836 int int_min_ref = 0;
17838 &int_min_ref,
17839 1,
17840 MPI_INT,
17841 MPI_MIN,
17842 Communicator_pt->mpi_comm());
17843
17844 // Overall min refinement level over all meshes
17845 unsigned min_ref = unsigned(int_min_ref);
17846
17847 // Refine as many times as required to get refinement up to
17848 // uniform refinement level after last prune
17850 oomph_info << "Refining sub-mesh " << i_mesh << " uniformly "
17851 << nref << " times\n";
17852 for (unsigned i = 0; i < nref; i++)
17853 {
17854 ref_mesh_pt->refine_uniformly();
17855 }
17856 }
17857 }
17858 // Rebuild the global mesh
17860 }
17861
17862 // Do actions after adapt
17864
17865 // Re-assign number of submeshes -- when this was first
17866 // set, the problem may have had face meshes that have now
17867 // disappeared.
17868 n_mesh = nsub_mesh();
17869 } // if (some_mesh_has_been_pruned)
17870
17871
17872 // Perform any actions before distribution but now for the new mesh
17873 // NOTE: This does NOT replicate the actions_before_distribute()
17874 // call made above for the previous mesh!
17876
17877 // Do some book-keeping
17878 //---------------------
17879
17880 // Re-assign number of submeshes -- when this was first
17881 // set, the problem may have had face meshes that have now
17882 // disappeared.
17883 n_mesh = nsub_mesh();
17884
17885 // The submeshes, if they exist, need to know their own element
17886 // domains.
17887 // NOTE: This vector only stores the target domains or the
17888 // element partition for structured meshes
17890 if (n_mesh != 0)
17891 {
17892 unsigned count = 0;
17893 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
17894 {
17895 // Only work with structured meshes
17897 {
17898 // Get the number of element in the mesh
17899 const unsigned nsub_ele = mesh_pt(i_mesh)->nelement();
17901 for (unsigned e = 0; e < nsub_ele; e++)
17902 {
17905 } // for (e<nsub_elem)
17906 } // if (sub_mesh_pt!=0)
17907 } // for (i_mesh<n_mesh)
17908
17909#ifdef PARANOID
17910 const unsigned nnew_domain_for_base_element =
17913 {
17914 std::ostringstream error_stream;
17916 << "The number of READ target domains for nonhalo elements\n"
17917 << " is (" << count << "), but the number of target domains for\n"
17918 << "nonhalo elements is (" << nnew_domain_for_base_element
17919 << ")!\n";
17920 throw OomphLibError(error_stream.str(),
17921 "Problem::load_balance()",
17923 }
17924#endif
17925
17926 } // if (n_mesh!=0)
17927
17928 // Setup the map between "root" element and number in global mesh
17929 // again
17930
17931 // This map is only established for structured meshes, then we
17932 // need to check here the type of mesh
17933 if (n_mesh == 0)
17934 {
17935 // Check if the only one mesh is an stuctured mesh
17936 if (!is_unstructured_mesh[0])
17937 {
17938 const unsigned n_ele = mesh_pt()->nelement();
17941 for (unsigned e = 0; e < n_ele; e++)
17942 {
17946 } // for (e<n_ele)
17947 } // if (!is_triangle_mesh_base[0])
17948 } // if (n_mesh==0)
17949 else
17950 {
17951 // If we have submeshes then we only add those elements that
17952 // belong to structured meshes, but first compute the number of
17953 // total elements in the structured sub-meshes
17954 unsigned nglobal_element = 0;
17955 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
17956 {
17957 // Check if mesh is an structured mesh
17959 {
17961 } // if (!is_triangle_mesh_base[i_mesh])
17962 } // for (i_mesh<n_mesh)
17963
17964 // Once computed the number of elements, then resize the
17965 // structure
17968 unsigned counter_base = 0;
17969 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
17970 {
17971 // Check if mesh is a structured mesh
17973 {
17974 const unsigned n_ele = mesh_pt(i_mesh)->nelement();
17975 for (unsigned e = 0; e < n_ele; e++)
17976 {
17980 // Inrease the global element number
17981 counter_base++;
17982 } // for (e<n_ele)
17983 } // if (!is_triangle_mesh_base[i_mesh])
17984 } // for (i_mesh<n_mesh)
17985
17986#ifdef PARANOID
17988 {
17989 std::ostringstream error_stream;
17990 error_stream << "The number of global elements (" << nglobal_element
17991 << ") is not the same as the number of\nadded elements ("
17992 << counter_base << ") to the Base_mesh_element_pt data "
17993 << "structure!!!\n\n";
17994 throw OomphLibError(error_stream.str(),
17995 "Problem::load_balance()",
17997 } // if (counter_base != nglobal_element)
17998#endif // #ifdef PARANOID
17999
18000 } // else if (n_mesh==0)
18001
18002 // Storage for the number of face elements in the base mesh --
18003 // element is identified by number of bulk element and face index
18004 // so we can reconstruct it if and when the FaceElements have been wiped
18005 // in actions_before_distribute().
18006 // NOTE: Not really clear (any more) why this is required. Typically
18007 // FaceElements get wiped in actions_before_distribute() so
18008 // at this point there shouldn't be any of them left.
18009 // This is certainly the case in all our currently existing
18010 // test codes. However, I'm too scared to take this out
18011 // in case it does matter (we're not insisting that FaceElements
18012 // are always removed in actions_before_distribute()...).
18013 std::map<unsigned, std::map<int, unsigned>> face_element_number;
18014 unsigned n_element = mesh_pt()->nelement();
18015 for (unsigned e = 0; e < n_element; e++)
18016 {
18018 dynamic_cast<FaceElement*>(mesh_pt()->finite_element_pt(e));
18019 if (face_el_pt != 0)
18020 {
18021#ifdef PARANOID
18022 std::stringstream info;
18023 info << "================================================\n";
18024 info << "INFO: I've come across a FaceElement while \n";
18025 info << " load-balancing a problem. \n";
18026 info << "================================================\n";
18027 oomph_info << info.str() << std::endl;
18028#endif
18029 FiniteElement* bulk_elem_pt = face_el_pt->bulk_element_pt();
18031#ifdef PARANOID
18032 if (e_bulk == 0)
18033 {
18034 throw OomphLibError("Base_mesh_element_number_plus_one[...]=0",
18037 }
18038#endif
18039 e_bulk -= 1;
18040 int face_index = face_el_pt->face_index();
18041 face_element_number[e_bulk][face_index] = e;
18042 }
18043 }
18044
18045 // Distribute the (sub)meshes
18046 //---------------------------
18048 if (n_mesh == 0)
18049 {
18050 // Only distribute (load balance strategy) if this is an
18051 // structured mesh
18052 if (!is_unstructured_mesh[0])
18053 {
18054#ifdef PARANOID
18055 if (mesh_pt()->nelement() != new_domain_for_base_element.size())
18056 {
18057 std::ostringstream error_stream;
18058 error_stream << "Distributing one-and-only mesh containing "
18059 << mesh_pt()->nelement() << " elements with info for "
18060 << new_domain_for_base_element.size() << std::endl;
18061 throw OomphLibError(error_stream.str(),
18064 }
18065#endif
18066
18067 if (report_stats)
18068 {
18069 oomph_info << "Distributing one and only mesh\n"
18070 << "------------------------------" << std::endl;
18071 }
18072
18073 // No pre-set distribution from restart that may leave some
18074 // processors empty so no need to overrule deletion of elements
18076
18078 new_domain_for_base_element,
18080 doc_info,
18083
18084 } // if (!is_unstructured_mesh[0])
18085
18086 } // if (n_mesh==0)
18087 else // There are submeshes, "distribute" each one separately
18088 {
18089 // Rebuild the mesh only if one of the meshes was modified
18090 bool need_to_rebuild_mesh = false;
18091 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
18092 {
18093 // Perform the load balancing based on distribution in the
18094 // structured meshes only
18096 {
18097 if (report_stats)
18098 {
18099 oomph_info << "Distributing submesh " << i_mesh << " of "
18100 << n_mesh << " in total\n"
18101 << "---------------------------------------------"
18102 << std::endl;
18103 }
18104
18105 // Set the doc_info number to reflect the submesh
18106 doc_info.number() = i_mesh;
18107
18108 // No pre-set distribution from restart that may leave some
18109 // processors empty so no need to overrule deletion of elements
18112 submesh_element_partition[i_mesh],
18114 doc_info,
18117
18118 // Set the flag to rebuild the global mesh
18119 need_to_rebuild_mesh = true;
18120
18121 } // if (!is_unstructured_mesh[i_mesh])
18122
18123 } // for (i_mesh<n_mesh)
18124
18126 {
18127 // Rebuild the global mesh
18129 } // if (need_to_rebuild_mesh)
18130
18131 } // else if (n_mesh==0)
18132
18133 // Null out information associated with deleted elements
18134 unsigned n_del = deleted_element_pt.size();
18135 for (unsigned e = 0; e < n_del; e++)
18136 {
18141 }
18142
18143 // Has one of the meshes been pruned before distribution? If so
18144 // then prune here now
18146 {
18148 if (n_mesh == 0)
18149 {
18151 dynamic_cast<TreeBasedRefineableMeshBase*>(mesh_pt());
18152 if (ref_mesh_pt != 0)
18153 {
18154 ref_mesh_pt->prune_halo_elements_and_nodes(
18155 deleted_element_pt, doc_info, report_stats);
18156 }
18157 }
18158 else
18159 {
18160 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
18161 {
18164 if (ref_mesh_pt != 0)
18165 {
18166 ref_mesh_pt->prune_halo_elements_and_nodes(
18167 deleted_element_pt, doc_info, report_stats);
18168 }
18169 }
18170 // Rebuild the global mesh
18172 }
18173
18174 // Null out information associated with deleted elements
18175 unsigned n_del = deleted_element_pt.size();
18176 for (unsigned e = 0; e < n_del; e++)
18177 {
18182 }
18183
18184 // Setup the map between "root" element and number in global mesh again
18186 }
18187
18188 if (report_stats)
18189 {
18191 oomph_info << "CPU for build and distribution of new mesh(es): "
18192 << t_distribute - t_partition << std::endl;
18193 }
18194
18195
18196 // Send refinement info to other processors
18197 //-----------------------------------------
18198
18199 // Storage for refinement pattern: Given ID of root element,
18200 // root_element_id, and current refinement level, level, the e-th entry in
18201 // refinement_info_for_root_elements[root_element_id][level][e] is equal
18202
18203 // to 2 if the e-th element (using the enumeration when the mesh has been
18204 // refined to the level-th level) is to be refined during the next
18205 // refinement; it's 1 if it's not to be refined.
18207
18208
18209 // Send refinement information between processors, using flat-packed
18210 // information accumulated earlier
18216
18217 // Refine each mesh based upon refinement information stored for each root
18218 //------------------------------------------------------------------------
18221
18222 if (report_stats)
18223 {
18225 oomph_info << "CPU for refinement of base mesh: "
18226 << t_refine - t_distribute << std::endl;
18227 }
18228
18229 // NOTE: The following two calls are important e.g. when
18230 // FaceElements that resize nodes are attached/detached
18231 // after/before adaptation. If we don't attach them
18232 // on the newly built/refined mesh, there isn't enough
18233 // storage for the nodal values that are sent around
18234 // (in a flat-packed format) resulting in total disaster.
18235 // So we attach them first, but then immediatly strip
18236 // them out again because the FaceElements themselves
18237 // will have been stripped out before distribution/adaptation.
18238
18239 // Do actions after adapt because we have just adapted the mesh.
18241
18242 // Now strip it back out to get problem into the same state
18243 // it was in when data to be sent was recorded.
18245
18246 // Send the stored values in each root from the old mesh into the new mesh
18247 //------------------------------------------------------------------------
18250
18251 // If there are unstructured meshes here we perform the load
18252 // balancing of those meshes
18254 {
18255 // Delete any storage of external elements and nodes
18257
18258 if (n_mesh == 0)
18259 {
18260 // Before doing the load balancing delete the mesh created at
18261 // calling build_mesh(), and restore the pointer to the old
18262 // mesh
18263
18264 // It MUST be an unstructured mesh, otherwise we should not be
18265 // here
18266 if (is_unstructured_mesh[0])
18267 {
18268 // Delete the new created mesh
18269 delete mesh_pt();
18270 // Re-assign the pointer to the old mesh
18271 this->mesh_pt() = old_mesh_pt[0];
18272 } // if (is_unstructured_mesh[0])
18273#ifdef PARANOID
18274 else
18275 {
18276 std::ostringstream error_stream;
18277 error_stream << "The only one mesh in the problem is not an "
18278 "unstructured mesh,\n"
18279 << "but the flag 'are_there_unstructures_meshes' ("
18281 << ") was turned on,\n"
18282 << "this is weird. Please check for any condition "
18283 "that may have\n"
18284 << "turned on this flag!!!!\n\n";
18285 throw OomphLibError(error_stream.str(),
18286 "Problem::load_balance()",
18288 }
18289#endif
18290
18291 unstructured_mesh_pt[0]->load_balance(
18293 } // if (n_mesh == 0)
18294 else
18295 {
18296 // Before doing the load balancing delete the meshes created
18297 // at calling build_mesh(), and restore the pointer to the
18298 // old meshes
18299 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
18300 {
18302 {
18303 // Delete the new created mesh
18304 delete mesh_pt(i_mesh);
18305 // Now point it to nothing
18306 mesh_pt(i_mesh) = 0;
18307 // ... and re-assign the pointer to the old mesh
18308 this->mesh_pt(i_mesh) = old_mesh_pt[i_mesh];
18309 } // if (is_unstructured_mesh[i_mesh])
18310
18311 } // for (i_mesh<n_mesh)
18312
18313 // Empty storage for sub-meshes
18314 // flush_sub_meshes();
18315
18316 // Flush the storage for nodes and elements in compound mesh
18317 // (they've already been deleted in the sub-meshes)
18319
18320 // Now we can procede with the load balancing thing
18321 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
18322 {
18324 {
18325 // Get the number of elements in the "i_mesh" (the old one)
18326 const unsigned n_element = old_mesh_pt[i_mesh]->nelement();
18327
18328 // Perform the load balancing if there are elements in the
18329 // mesh. We check for this case because the meshes created
18330 // from face elements have been cleaned in
18331 // "actions_before_distribute()"
18333 {
18334 unstructured_mesh_pt[i_mesh]->load_balance(
18336 } // if (n_element > 0)
18337 } // if (is_unstructured_mesh[i_mesh)]
18338 } // for (i_mesh < n_mesh)
18339
18340 // Rebuild the global mesh
18342
18343 } // else if (n_mesh == 0)
18344
18345 } // if (are_there_unstructured_meshes)
18346
18347 if (report_stats)
18348 {
18350 oomph_info << "CPU for transferring solution to new mesh(es): "
18351 << t_copy_solution - t_refine << std::endl;
18352 oomph_info << "CPU for load balancing: " << t_copy_solution - t_start
18353 << std::endl;
18354 }
18355
18356 // Do actions after distribution
18358
18359 // Re-assign equation numbers
18360#ifdef PARANOID
18361 unsigned n_dof = assign_eqn_numbers();
18362#else
18364#endif
18365
18366 if (report_stats)
18367 {
18369 << "Total number of elements on this processor after load balance: "
18370 << mesh_pt()->nelement() << std::endl;
18371
18372 oomph_info << "Number of non-halo elements on this processor after "
18373 "load balance: "
18374 << mesh_pt()->nnon_halo_element() << std::endl;
18375 }
18376
18377#ifdef PARANOID
18378 if (n_dof != old_ndof)
18379 {
18380 std::ostringstream error_stream;
18382 << "Number of dofs in load_balance() has changed from " << old_ndof
18383 << " to " << n_dof << "\n"
18384 << "Check that you've implemented any necessary "
18385 "actions_before/after\n"
18386 << "adapt/distribute functions, e.g. to pin redundant pressure dofs"
18387 << " etc.\n";
18388 throw OomphLibError(
18390 }
18391#endif
18392 }
18393
18394 // Finally synchronise all dofs to allow halo check to pass
18396
18397 double end_t = TimingHelpers::timer();
18398 oomph_info << "Time for load_balance() [sec] : " << end_t - start_t
18399 << std::endl;
18400 }
18401
18402
18403 //==========================================================================
18404 /// Send refinement information between processors
18405 //==========================================================================
18409 const unsigned& max_refinement_level_overall,
18412 {
18413 // Number of processes etc.
18414 const int n_proc = this->communicator_pt()->nproc();
18415 const int my_rank = this->communicator_pt()->my_rank();
18416
18417 // Make space
18420
18421 // Make space for list of domains that the refinement info
18422 // is to be forwarded to
18423 std::map<unsigned, Vector<unsigned>> halo_domain_of_haloed_base_element;
18424
18425 // Find out haloed elements in new, redistributed problem
18426 //-------------------------------------------------------
18427
18428 // halo_domains[e][j] = j-th halo domain associated with (haloed) element e
18429 std::map<unsigned, Vector<unsigned>> halo_domains;
18430
18431 // Loop over sub meshes
18432 unsigned n_sub_mesh = nsub_mesh();
18433 unsigned max_mesh = std::max(n_sub_mesh, unsigned(1));
18434 for (unsigned i_mesh = 0; i_mesh < max_mesh; i_mesh++)
18435 {
18436 // Choose the right mesh
18437 Mesh* my_mesh_pt = 0;
18438 if (n_sub_mesh == 0)
18439 {
18440 my_mesh_pt = mesh_pt();
18441 }
18442 else
18443 {
18445 }
18446
18447 // Only work with structured meshes
18449 dynamic_cast<TriangleMeshBase*>(mesh_pt(i_mesh));
18450 if (!(sub_mesh_pt != 0))
18451 {
18452 // Loop over processors to find haloed elements -- need to
18453 // send their refinement patterns processors that hold their
18454 // halo counterparts!
18455 for (int p = 0; p < n_proc; p++)
18456 {
18458 my_mesh_pt->haloed_element_pt(p);
18459 unsigned nhaloed = haloed_elem_pt.size();
18460 for (unsigned h = 0; h < nhaloed; h++)
18461 {
18462 // This element must send its refinement information to processor p
18464#ifdef PARANOID
18465 if (e == 0)
18466 {
18467 throw OomphLibError("Base_mesh_element_number_plus_one[...]=0",
18470 }
18471#endif
18472 e -= 1;
18473 halo_domains[e].push_back(p);
18474 }
18475 }
18476 } // if (!(sub_mesh_pt!=0))
18477 } // for (i_mesh<max_mesh)
18478
18479 // Accumulate relevant flat-packed refinement data to be sent to
18480 //--------------------------------------------------------------
18481 // various processors
18482 //-------------------
18483
18484 // Map to accumulate unsigned data to be sent to each processor
18485 // (map for sparsity)
18486 std::map<unsigned, Vector<unsigned>> data_for_proc;
18487
18488 // Number of base elements to be sent to specified domain
18490
18491 // Total number of entries in send vector
18492 unsigned count = 0;
18493
18494 // Loop over all base elements
18495 //----------------------------
18496 for (unsigned e = 0; e < n_base_element; e++)
18497 {
18498 // Is it one of mine (i.e. was it a non-halo element on this
18499 //----------------------------------------------------------
18500 // processor before re-distribution, and do I therefore hold
18501 //----------------------------------------------------------
18502 // refinement information for it)?
18503 //--------------------------------
18505 {
18506 // Where does it go?
18508
18509 // Keep counting
18511
18512 // If it stays local, deal with it here
18513 if (int(new_domain) == my_rank)
18514 {
18515 // Record on which other procs/domains the refinement info for
18516 // this element is required because it's haloed.
18517 unsigned nhalo = halo_domains[e].size();
18519 for (unsigned j = 0; j < nhalo; j++)
18520 {
18522 }
18523
18524 // Provide storage for refinement pattern
18527
18528#ifdef PARANOID
18529 // Get number of additional data sent for check
18530 unsigned n_additional_data =
18532#endif
18533
18534 // Get number of tree nodes
18536
18537 // Counter for entries to be processed locally
18538 unsigned local_count = 1; // (have already processed zero-th entry)
18539
18540 // Loop over levels and number of nodes in tree
18541 for (unsigned level = 0; level < max_refinement_level_overall;
18542 level++)
18543 {
18544 for (unsigned ee = 0; ee < n_tree_nodes; ee++)
18545 {
18546 // Element exists at this level
18548 {
18549 local_count++;
18550
18551 // Element should be refined
18553 {
18554 refinement_info_for_root_elements[e][level].push_back(2);
18555 local_count++;
18556 }
18557 // Element should not be refined
18558 else
18559 {
18560 refinement_info_for_root_elements[e][level].push_back(1);
18561 local_count++;
18562 }
18563 }
18564 // Element does not exist at this level
18565 else
18566 {
18567 refinement_info_for_root_elements[e][level].push_back(0);
18568 local_count++;
18569 }
18570 }
18571 }
18572
18573#ifdef PARANOID
18575 {
18576 std::stringstream error_message;
18577 error_message << "Number of additional data: " << n_additional_data
18578 << " doesn't match that actually send: "
18579 << local_count << std::endl;
18580 throw OomphLibError(error_message.str(),
18583 }
18584#endif
18585 }
18586 // Element in question is not one of mine so prepare for sending
18587 //--------------------------------------------------------------
18588 else
18589 {
18590 // Make space
18592 unsigned n_additional_data =
18595 2);
18596
18597 // Keep counting
18598 count += n_additional_data + 2;
18599
18600 // Add base element number
18601 data_for_proc[new_domain].push_back(e);
18602
18603#ifdef PARANOID
18604 // Add number of flat-packed instructions to follow
18606#endif
18607
18608 // Add flat packed refinement data
18609 for (unsigned j = 0; j < n_additional_data; j++)
18610 {
18611 data_for_proc[new_domain].push_back(
18613 }
18614 }
18615 }
18616 }
18617
18618
18619 // Now do the actual send/receive
18620 //-------------------------------
18621
18622 // Storage for number of data to be sent to each processor
18624
18625 // Storage for all values to be sent to all processors
18627 send_data.reserve(count);
18628
18629 // Start location within send_data for data to be sent to each processor
18631
18632 // Loop over all processors
18633 for (int rank = 0; rank < n_proc; rank++)
18634 {
18635 // Set the offset for the current processor
18637
18638 // Don't bother to do anything if the processor in the loop is the
18639 // current processor
18640 if (rank != my_rank)
18641 {
18642 // Record how many base elements are to be sent
18644
18645 // Add data
18646 unsigned n_data = data_for_proc[rank].size();
18647 for (unsigned j = 0; j < n_data; j++)
18648 {
18649 send_data.push_back(data_for_proc[rank][j]);
18650 }
18651 }
18652
18653 // Find the number of data added to the vector
18655 }
18656
18657 // Storage for the number of data to be received from each processor
18659
18660 // Now send numbers of data to be sent between all processors
18661 MPI_Alltoall(&send_n[0],
18662 1,
18663 MPI_INT,
18664 &receive_n[0],
18665 1,
18666 MPI_INT,
18667 this->communicator_pt()->mpi_comm());
18668
18669 // We now prepare the data to be received
18670 // by working out the displacements from the received data
18672 int receive_data_count = 0;
18673 for (int rank = 0; rank < n_proc; ++rank)
18674 {
18675 // Displacement is number of data received so far
18678 }
18679
18680 // Now resize the receive buffer for all data from all processors
18681 // Make sure that it has a size of at least one
18682 if (receive_data_count == 0)
18683 {
18685 }
18687
18688 // Make sure that the send buffer has size at least one
18689 // so that we don't get a segmentation fault
18690 if (send_data.size() == 0)
18691 {
18692 send_data.resize(1);
18693 }
18694
18695 // Now send the data between all the processors
18697 &send_n[0],
18700 &receive_data[0],
18701 &receive_n[0],
18704 this->communicator_pt()->mpi_comm());
18705
18706
18707 // Now use the received data to update
18708 //-----------------------------------
18709 for (int send_rank = 0; send_rank < n_proc; send_rank++)
18710 {
18711 // Don't bother to do anything for the processor corresponding to the
18712 // current processor or if no data were received from this processor
18713 if ((send_rank != my_rank) && (receive_n[send_rank] != 0))
18714 {
18715 // Counter for the data within the large array
18717
18718 // Loop over base elements
18719 unsigned nbase_element = receive_data[count];
18720 count++;
18721 for (unsigned b = 0; b < nbase_element; b++)
18722 {
18723 // Get base element number
18725 count++;
18726
18727 // Record on which other procs/domains the refinement info for
18728 // this element is required because it's haloed.
18731 for (unsigned j = 0; j < nhalo; j++)
18732 {
18735 }
18736
18737 // Provide storage for refinement pattern
18740
18741 // Get number of flat-packed instructions to follow
18742 // (only used for check)
18743#ifdef PARANOID
18745 count++;
18746
18747 // Counter for number of additional data (validation only)
18748 unsigned check_count = 0;
18749#endif
18750
18751 // Get number of tree nodes
18752 unsigned n_tree_nodes = receive_data[count];
18753 count++;
18754
18755#ifdef PARANOID
18756 check_count++;
18757#endif
18758
18759 // Loop over levels and number of nodes in tree
18760 for (unsigned level = 0; level < max_refinement_level_overall;
18761 level++)
18762 {
18763 for (unsigned e = 0; e < n_tree_nodes; e++)
18764 {
18765 // Element exists at this level
18766 if (receive_data[count] == 1)
18767 {
18768 count++;
18769
18770#ifdef PARANOID
18771 check_count++;
18772#endif
18773
18774 // Element should be refined
18775 if (receive_data[count] == 1)
18776 {
18778 .push_back(2);
18779 count++;
18780
18781#ifdef PARANOID
18782 check_count++;
18783#endif
18784 }
18785 // Element should not be refined
18786 else
18787 {
18789 .push_back(1);
18790 count++;
18791
18792#ifdef PARANOID
18793 check_count++;
18794#endif
18795 }
18796 }
18797 // Element does not exist at this level
18798 else
18799 {
18801 .push_back(0);
18802 count++;
18803
18804#ifdef PARANOID
18805 check_count++;
18806#endif
18807 }
18808 }
18809 }
18810
18811#ifdef PARANOID
18813 {
18814 std::stringstream error_message;
18815 error_message << "Number of additional data: " << n_additional_data
18816 << " doesn't match that actually send: "
18817 << check_count << std::endl;
18818 throw OomphLibError(error_message.str(),
18821 }
18822#endif
18823 }
18824 }
18825 }
18826
18827
18828 // Now send the fully assembled refinement info to halo elements
18829 //---------------------------------------------------------------
18830 {
18831 // Accumulate data to be sent
18832 //---------------------------
18833
18834 // Map to accumulate data to be sent to other procs
18835 // (map for sparsity)
18836 std::map<unsigned, Vector<unsigned>> data_for_proc;
18837
18838 // Number of base elements to be sent to specified domain
18840
18841 // Loop over all haloed root elements and find out which
18842 // processors they have haloes on
18843 for (std::map<unsigned, Vector<unsigned>>::iterator it =
18846 it++)
18847 {
18848 // Get base element number
18849 unsigned base_element_number = (*it).first;
18850
18851 // Loop over target domains
18852 Vector<unsigned> domains = (*it).second;
18853 unsigned nd = domains.size();
18854 for (unsigned jd = 0; jd < nd; jd++)
18855 {
18856 // Actual number of domain
18857 unsigned d = domains[jd];
18858
18859 // Keep counting number of base elemements for domain
18861
18862 // Write base element number
18863 data_for_proc[d].push_back(base_element_number);
18864
18865 // Write refinement info in flat-packed form
18866 for (unsigned level = 0; level < max_refinement_level_overall;
18867 level++)
18868 {
18869 // Number of entries at each level
18870 unsigned n =
18872 .size();
18873 data_for_proc[d].push_back(n);
18874 for (unsigned j = 0; j < n; j++)
18875 {
18876 data_for_proc[d].push_back(
18878 [j]);
18879 }
18880 }
18881 }
18882 }
18883
18884
18885 // Do the actual send
18886 //-------------------
18887
18888 // Storage for number of data to be sent to each processor
18890
18891 // Storage for all values to be sent to all processors
18893 send_data.reserve(count);
18894
18895 // Start location within send_data for data to be sent to each processor
18897
18898 // Loop over all processors
18899 for (int rank = 0; rank < n_proc; rank++)
18900 {
18901 // Set the offset for the current processor
18903
18904 // Don't bother to do anything if the processor in the loop is the
18905 // current processor
18906 if (rank != my_rank)
18907 {
18908 // Record how many base elements are to be sent
18910
18911 // Add data
18912 unsigned n_data = data_for_proc[rank].size();
18913 for (unsigned j = 0; j < n_data; j++)
18914 {
18915 send_data.push_back(data_for_proc[rank][j]);
18916 }
18917 }
18918 // Find the number of data added to the vector
18920 }
18921
18922 // Storage for the number of data to be received from each processor
18924
18925 // Now send numbers of data to be sent between all processors
18926 MPI_Alltoall(&send_n[0],
18927 1,
18928 MPI_INT,
18929 &receive_n[0],
18930 1,
18931 MPI_INT,
18932 this->communicator_pt()->mpi_comm());
18933
18934 // We now prepare the data to be received
18935 // by working out the displacements from the received data
18937 int receive_data_count = 0;
18938 for (int rank = 0; rank < n_proc; ++rank)
18939 {
18940 // Displacement is number of data received so far
18943 }
18944
18945 // Now resize the receive buffer for all data from all processors
18946 // Make sure that it has a size of at least one
18947 if (receive_data_count == 0)
18948 {
18950 }
18952
18953 // Make sure that the send buffer has size at least one
18954 // so that we don't get a segmentation fault
18955 if (send_data.size() == 0)
18956 {
18957 send_data.resize(1);
18958 }
18959
18960 // Now send the data between all the processors
18962 &send_n[0],
18965 &receive_data[0],
18966 &receive_n[0],
18969 this->communicator_pt()->mpi_comm());
18970
18971
18972 // Now use the received data
18973 //------------------------
18974 for (int send_rank = 0; send_rank < n_proc; send_rank++)
18975 {
18976 // Don't bother to do anything for the processor corresponding to the
18977 // current processor or if no data were received from this processor
18978 if ((send_rank != my_rank) && (receive_n[send_rank] != 0))
18979 {
18980 // Counter for the data within the large array
18982
18983 // Read number of base elements
18984 unsigned nbase_element = receive_data[count];
18985 count++;
18986
18987 for (unsigned e = 0; e < nbase_element; e++)
18988 {
18989 // Read base element number
18991 count++;
18992
18993 // Provide storage for refinement pattern
18996
18997 // Read refinement info in flat-packed form
18998 for (unsigned level = 0; level < max_refinement_level_overall;
18999 level++)
19000 {
19001 // Read number of entries at each level
19002 unsigned n = receive_data[count];
19003 count++;
19004
19005 // Read entries
19006 for (unsigned j = 0; j < n; j++)
19007 {
19009 .push_back(receive_data[count]);
19010 count++;
19011 }
19012 }
19013 }
19014 }
19015 }
19016 }
19017 }
19018
19019 //==========================================================================
19020 /// Load balance helper routine: Send data to other
19021 /// processors during load balancing.
19022 /// - send_n: Input, number of data to be sent to each processor
19023 /// - send_data: Input, storage for all values to be sent to all processors
19024 /// - send_displacement: Input, start location within send_data for data to
19025 /// be sent to each processor
19026 //==========================================================================
19031 {
19032 // Communicator info
19033 OomphCommunicator* comm_pt = this->communicator_pt();
19034 const int n_proc = comm_pt->nproc();
19035
19036 // Storage for the number of data to be received from each processor
19038
19039 // Now send numbers of data to be sent between all processors
19040 MPI_Alltoall(&send_n[0],
19041 1,
19042 MPI_INT,
19043 &receive_n[0],
19044 1,
19045 MPI_INT,
19046 this->communicator_pt()->mpi_comm());
19047
19048 // We now prepare the data to be received
19049 // by working out the displacements from the received data
19051 int receive_data_count = 0;
19052 for (int rank = 0; rank < n_proc; ++rank)
19053 {
19054 // Displacement is number of data received so far
19057 }
19058
19059 // Now resize the receive buffer for all data from all processors
19060 // Make sure that it has a size of at least one
19061 if (receive_data_count == 0)
19062 {
19064 }
19066
19067 // Make sure that the send buffer has size at least one
19068 // so that we don't get a segmentation fault
19069 if (send_data.size() == 0)
19070 {
19071 send_data.resize(1);
19072 }
19073
19074 // Now send the data between all the processors
19076 &send_n[0],
19078 MPI_DOUBLE,
19079 &receive_data[0],
19080 &receive_n[0],
19082 MPI_DOUBLE,
19083 this->communicator_pt()->mpi_comm());
19084
19085 unsigned el_count = 0;
19086
19087 // Only do each node once
19089
19090 // Now use the received data to update the halo nodes
19091 for (int send_rank = 0; send_rank < n_proc; send_rank++)
19092 {
19093 // Don't bother to do anything if no data were received from this
19094 // processor
19095 // NOTE: We do have to loop over our own processor number to process
19096 // the data locally.
19097 if (receive_n[send_rank] != 0)
19098 {
19099 // Counter for the data within the large array
19101
19102 // How many batches are there for current rank
19103 unsigned nbatch = unsigned(receive_data[count]);
19104 count++;
19105
19106 // Loop over batches (containing leaves associated with root elements)
19107 for (unsigned b = 0; b < nbatch; b++)
19108 {
19109 // How many elements were received for this batch?
19110 unsigned nel = unsigned(receive_data[count]);
19111 count++;
19112
19113 // Get the unique base/root element number of this batch
19114 // in unrefined mesh
19116 count++;
19117
19118 // Get pointer to base/root element from reverse lookup scheme
19120
19121 // Vector for pointers to associated elements in batch
19123
19124 // Is it a refineable element?
19126 dynamic_cast<RefineableElement*>(root_el_pt);
19127 if (ref_root_el_pt != 0)
19128 {
19129 // Get all leaves associated with this base/root element
19131 ref_root_el_pt->tree_pt()->stick_leaves_into_vector(
19133
19134 // How many leaves are there?
19135 unsigned n_leaf = all_leaf_nodes_pt.size();
19136
19137#ifdef PARANOID
19138 if (n_leaf != nel)
19139 {
19140 std::ostringstream error_message;
19141 error_message
19142 << "Number of leaves: " << n_leaf << " "
19143 << " doesn't match number of elements sent in batch: " << nel
19144 << "\n";
19145 throw OomphLibError(error_message.str(),
19148 }
19149#endif
19150
19151 // Loop over batch of elements associated with this base/root
19152 // element
19153 batch_el_pt.resize(n_leaf);
19154 for (unsigned e = 0; e < n_leaf; e++)
19155 {
19156 batch_el_pt[e] = all_leaf_nodes_pt[e]->object_pt();
19157 }
19158 }
19159 // Not refineable -- the batch contains just the root element itself
19160 else
19161 {
19162#ifdef PARANOID
19163 if (1 != nel)
19164 {
19165 std::ostringstream error_message;
19166 error_message
19167 << "Non-refineable root element should only be associated with"
19168 << " one element but nel=" << nel << "\n";
19169 throw OomphLibError(error_message.str(),
19172 }
19173#endif
19174 batch_el_pt.push_back(root_el_pt);
19175 }
19176
19177 // Now loop over all elements in batch
19178 for (unsigned e = 0; e < nel; e++)
19179 {
19181 el_count++;
19182
19183 // FE?
19184 FiniteElement* fe_pt = dynamic_cast<FiniteElement*>(el_pt);
19185 if (fe_pt != 0)
19186 {
19187 // Loop over nodes
19188 unsigned nnod = fe_pt->nnode();
19189 for (unsigned j = 0; j < nnod; j++)
19190 {
19191 Node* nod_pt = fe_pt->node_pt(j);
19192 if (!node_done[send_rank][nod_pt])
19193 {
19194 node_done[send_rank][nod_pt] = true;
19195
19196
19197 // Read number of values (as double) to allow for resizing
19198 // before read (req'd in case we store data that
19199 // got introduced by attaching FaceElements to bulk)
19200 unsigned nval = unsigned(receive_data[count]);
19201 count++;
19202
19203#ifdef PARANOID
19204 // Does the size match?
19205 if (nval < nod_pt->nvalue())
19206 {
19207 std::ostringstream error_message;
19208 error_message
19209 << "Node has more values, namely " << nod_pt->nvalue()
19210 << ", than we're about to receive, namely " << nval
19211 << ". Something's wrong!\n";
19212 throw OomphLibError(error_message.str(),
19215 }
19216#endif
19217
19218
19219#ifdef PARANOID
19220 // Check if it's been sent as a boundary node
19222 count++;
19223#endif
19224
19225 // Check if it's actually a boundary node
19227 dynamic_cast<BoundaryNodeBase*>(nod_pt);
19228 if (bnod_pt != 0)
19229 {
19230#ifdef PARANOID
19231 // Check if local and received status are consistent
19232 if (is_boundary_node != 1)
19233 {
19234 std::ostringstream error_message;
19235 error_message << "Local node is boundary node but "
19236 "information sent is\n"
19237 << "for non-boundary node\n";
19238 throw OomphLibError(error_message.str(),
19241 }
19242#endif
19243
19244 // Do we have entries in the map?
19245 unsigned n_entry = unsigned(receive_data[count]);
19246 count++;
19247 if (n_entry > 0)
19248 {
19249 // Create storage, if it doesn't already exist, for the
19250 // map that will contain the position of the first entry
19251 // of this face element's additional values,
19252 if (
19253 bnod_pt
19254 ->index_of_first_value_assigned_by_face_element_pt() ==
19255 0)
19256 {
19257 bnod_pt
19258 ->index_of_first_value_assigned_by_face_element_pt() =
19259 new std::map<unsigned, unsigned>;
19260 }
19261
19262 // Get pointer to the map of indices associated with
19263 // additional values created by face elements
19264 std::map<unsigned, unsigned>* map_pt =
19265 bnod_pt
19266 ->index_of_first_value_assigned_by_face_element_pt();
19267
19268 // Loop over number of entries in map
19269 for (unsigned i = 0; i < n_entry; i++)
19270 {
19271 // Read out pairs...
19272 unsigned first = unsigned(receive_data[count]);
19273 count++;
19274 unsigned second = unsigned(receive_data[count]);
19275 count++;
19276
19277 // ...and assign
19278 (*map_pt)[first] = second;
19279 }
19280 }
19281 }
19282#ifdef PARANOID
19283 // Not a boundary node
19284 else
19285 {
19286 // Check if local and received status are consistent
19287 if (is_boundary_node != 0)
19288 {
19289 std::ostringstream error_message;
19290 error_message << "Local node is not a boundary node but "
19291 "information \n"
19292 << "sent is for boundary node.\n";
19293 throw OomphLibError(error_message.str(),
19296 }
19297 }
19298#endif
19299
19300 // Do we have to resize? This can happen if node was
19301 // resized (due to a FaceElement that hasn't been attached
19302 // yet here) when the send data was written. If so make space
19303 // for the data here
19304 if (nval > nod_pt->nvalue())
19305 {
19306 nod_pt->resize(nval);
19307 }
19308
19309 // Now read the actual values
19310 nod_pt->read_values_from_vector(receive_data, count);
19311 }
19312 }
19313 }
19314
19315 // Now add internal data
19317 }
19318 }
19319 }
19320 }
19321
19322 // Now that this is done, we need to synchronise dofs to get
19323 // the halo element and node values correct
19324 bool do_halos = true;
19325 bool do_external_halos = false;
19326 this->synchronise_dofs(do_halos, do_external_halos);
19327
19328 // Now rebuild global mesh if required
19329 unsigned n_mesh = nsub_mesh();
19330 if (n_mesh != 0)
19331 {
19332 bool do_halos = false;
19333 bool do_external_halos = true;
19334 this->synchronise_dofs(do_halos, do_external_halos);
19336 }
19337 }
19338
19339
19340 //==========================================================================
19341 /// Load balance helper routine: Get data to be sent to other
19342 /// processors during load balancing and other information about
19343 /// re-distribution.
19344 /// - target_domain_for_local_non_halo_element: Input, generated by METIS.
19345 /// target_domain_for_local_non_halo_element[e] contains the number
19346 /// of the domain [0,1,...,nproc-1] to which non-halo element e on THE
19347 /// CURRENT PROCESSOR ONLY has been assigned. The order of the non-halo
19348 /// elements is the same as in the Problem's mesh, with the halo
19349 /// elements being skipped.
19350 /// - send_n: Output, number of data to be sent to each processor
19351 /// - send_data: Output, storage for all values to be sent to all processors
19352 /// - send_displacement: Output, start location within send_data for data to
19353 /// be sent to each processor
19354 /// - max_refinement_level_overall: Output, max. refinement level of any
19355 /// element
19356 //==========================================================================
19365 {
19366 // Communicator info
19367 OomphCommunicator* comm_pt = this->communicator_pt();
19368 const int n_proc = comm_pt->nproc();
19369 const int my_rank = this->communicator_pt()->my_rank();
19370
19371 //------------------------------------------------------------------------
19372 // Overall strategy: Loop over all elements (in structured meshes),
19373 // identify their corresponding root elements and move all associated
19374 // leaves together, collecting the leaves in batches.
19375 // ------------------------------------------------------------------------
19376
19377 // Map to store whether the root element has been visited yet
19378 std::map<RefineableElement*, bool> root_el_done;
19379
19380#ifdef PARANOID
19381
19382 // Map for checking if all elements associated with same root
19383 // have the same target processor
19384 std::map<RefineableElement*, unsigned> target_plus_one_for_root;
19385
19386#endif
19387
19388 // Storage for maximum refinement level
19389 unsigned max_refinement_level = 0;
19390
19391 // Storage for (vector of) elements associated with target domain
19392 // (stored in map for sparsity): element_for_processor[d][e] is pointer
19393 // to e-th element that's supposed to move onto processor (domain) d.
19394 std::map<unsigned, Vector<GeneralisedElement*>> element_for_processor;
19395
19396 // Storage for the number of elements in a specified batch of leaf
19397 // elements, all of which are associated with the same root/base element:
19398 // nelement_batch_for_processor[d][j] is the number of (leaf)
19399 // elements (all associated with the same root) to be moved together to
19400 // domain/processor d, in the j-th batch of elements.
19401 std::map<unsigned, Vector<unsigned>> nelement_batch_for_processor;
19402
19403 // Storage for the unique number of the root element (in the unrefined
19404 // base mesh) whose leaves are moved together in a batch:
19405 // base_element_for_element_batch_for_processo[d][j] is the number of
19406 // unique number of the root element (in the unrefined
19407 // base mesh) of all leaf elements (associated with that root),
19408 // to be moved together to domain/processor d, in the j-th batch of
19409 // elements.
19410 std::map<unsigned, Vector<unsigned>>
19412
19413 // Record old and new domains for non-halo root elements (will be
19414 // communicated globally). Initialise to -1 so we can use max
19415 // to extract the right one via MPI_Allreduce.
19416 // NOTE: We communicate these globally to facilitate distribution
19417 // of refinement pattern. While the data itself can be
19418 // sent point-to-point for non-halo elements,
19419 // mesh refinement information also needs to be sent for
19420 // halo elements which aren't known yet.
19424
19425 // Loop over all non-halo elements on current processor and identify roots
19426 // -------------------------------------------------------------------
19427 // All leaf elements in associated tree (must!) get moved together
19428 //----------------------------------------------------------------
19429 unsigned count_non_halo_el = 0;
19430 // Get the number of submeshs, if there are no submeshes, then
19431 // increase the counter so that the loop below also work for the only
19432 // one mesh in the problem
19433 unsigned n_mesh = nsub_mesh();
19434 if (n_mesh == 0)
19435 {
19436 n_mesh = 1;
19437 }
19438 // We need to know if there are structure meshes (with elements) as
19439 // part of the problem in order to perform (or not) the proper
19440 // communications
19441 bool are_there_structured_meshes = false;
19442 // Go for the nonhalo elements only in the TreeBaseMeshes
19443 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
19444 {
19445 // Only work with structured meshes
19447 dynamic_cast<TriangleMeshBase*>(mesh_pt(i_mesh));
19448 if (!(sub_mesh_pt != 0))
19449 {
19450 const unsigned nele = mesh_pt(i_mesh)->nelement();
19451 if (nele > 0)
19452 {
19453 // Change the flag to indicate that there are structured meshes
19454 // (with elements, because we may have meshes with face
19455 // elements and therefore zero elements at this point)
19457 }
19458
19459 for (unsigned e = 0; e < nele; e++)
19460 {
19462 if (!el_pt->is_halo())
19463 {
19464 // New non-halo: Where is this element supposed to go to?
19465 //-------------------------------------------------------
19466 unsigned target_domain =
19468
19469 // Bump up counter for non-halo elements
19471
19472 // Is it a root element? (It is, trivially, if it's not refineable)
19473 //------------------------------------------------------------------
19475 dynamic_cast<RefineableElement*>(el_pt);
19476 if (ref_el_pt == 0)
19477 {
19478 // Not refineable so add element itself
19480
19481 // Number of elements associated with this root/base
19482 // element (just the element itself)
19484
19485 // This is the unique base/root element number in unrefined mesh
19488#ifdef PARANOID
19490 {
19491 throw OomphLibError("Base_mesh_element_number_plus_one[...]=0",
19494 }
19495#endif
19498 .push_back(element_number_in_base_mesh);
19499
19500 /// Where do I come from, where do I go to?
19502 my_rank;
19505 } // if (ref_el_pt==0)
19506 // It's not a root element so we package its leaves into a batch
19507 //--------------------------------------------------------------
19508 // of elements
19509 //------------
19510 else
19511 {
19512 // Get the root element
19513 RefineableElement* root_el_pt = ref_el_pt->root_element_pt();
19514
19515 // Has this root been visited yet?
19517 {
19518 // Now we've done it
19519 root_el_done[root_el_pt] = true;
19520
19521 // Unique number of root element in base mesh
19524#ifdef PARANOID
19526 {
19527 throw OomphLibError(
19528 "Base_mesh_element_number_plus_one[...]=0",
19531 }
19532#endif
19534
19535 /// Where do I come from, where do I go to?
19537 my_rank;
19540
19541#ifdef PARANOID
19542 // Store target domain associated with this root element
19543 // (offset by one) to allow checking that all elements
19544 // with the same root move to the same processor
19546#endif
19547
19548 // Package all leaves into batch of elements
19550 root_el_pt->tree_pt()->stick_leaves_into_vector(
19552
19553 // Number of leaves
19554 unsigned n_leaf = all_leaf_nodes_pt.size();
19555
19556 // Number of elements associated with this root/base element
19557 // (all the leaves)
19559
19560 // Store the unique base/root element number in unrefined mesh
19562 .push_back(element_number_in_base_mesh);
19563
19564 // Loop over leaves
19565 for (unsigned i_leaf = 0; i_leaf < n_leaf; i_leaf++)
19566 {
19567 // Add element object at leaf
19569 all_leaf_nodes_pt[i_leaf]->object_pt();
19571
19572 // Monitor/update maximum refinement level
19573 unsigned level = all_leaf_nodes_pt[i_leaf]->level();
19574 if (level > max_refinement_level)
19575 {
19576 max_refinement_level = level;
19577 }
19578 }
19579 }
19580
19581#ifdef PARANOID
19582 // Root element has already been visited
19583 else
19584 {
19585 // We don't have to do anything with this element since it's
19586 // already been processed earlier, but check that it's scheduled
19587 // to go onto the same processor as its root.
19589 {
19590 std::ostringstream error_message;
19591 error_message
19592 << "All elements associated with same root must have "
19593 << "same target. during load balancing\n";
19594 throw OomphLibError(error_message.str(),
19597 }
19598 }
19599#endif
19600 } // else if (ref_el_pt==0)
19601 } // if (!ele_pt->is_halo())
19602 } // for (e < nele)
19603 } // if (!(sub_mesh_pt!=0))
19604 } // for (i_mesh < n_mesh)
19605
19606#ifdef PARANOID
19607 // Have we processed all target domains?
19609 {
19610 std::ostringstream error_message;
19611 error_message
19612 << "Have processed " << count_non_halo_el << " of "
19614 << " target domains for local non-halo elelemts. \n "
19615 << "Very Odd -- we do (now) strip out the information for elements\n"
19616 << "that are removed in actions_before_distribute()...\n";
19617 throw OomphLibError(
19618 error_message.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
19619 }
19620#endif
19621
19622 // Determine max. refinement level and origin/destination scheme
19623 // -------------------------------------------------------------
19624 // for all root/base elements
19625 // --------------------------
19626
19627 // Allreduce to work out max max refinement level across all processors
19629
19630 // Only perform this communications if necessary (it means if there
19631 // are structured meshes as part of the problem)
19633 {
19634 MPI_Allreduce(&max_refinement_level,
19636 1,
19638 MPI_MAX,
19639 comm_pt->mpi_comm());
19640 } // if (are_there_structured_meshes)
19641
19642 // Allreduce to tell everybody about the original and new domains
19643 // for root elements
19645
19646 // Only perform this communications if necessary (it means if there
19647 // are structured meshes as part of the problem)
19649 {
19653 MPI_INT,
19654 MPI_MAX,
19655 comm_pt->mpi_comm());
19656 } // if (are_there_structured_meshes)
19657
19659 // Only perform this communications if necessary (it means if there
19660 // are structured meshes as part of the problem)
19662 {
19666 MPI_INT,
19667 MPI_MAX,
19668 comm_pt->mpi_comm());
19669 } // if (are_there_structured_meshes)
19670
19671 // Copy across (after optional sanity check)
19674 for (unsigned j = 0; j < n_base_element; j++)
19675 {
19676#ifdef PARANOID
19678 {
19679 std::ostringstream error_message;
19680 error_message << "Old domain for base element " << j << ": "
19682 << "or its incarnation as refineable el: "
19683 << dynamic_cast<RefineableElement*>(
19685 << " which is of type "
19686 << typeid(*Base_mesh_element_pt[j]).name()
19687 << " does not\n"
19688 << "appear to have been assigned by any processor\n";
19689 throw OomphLibError(error_message.str(),
19692 }
19693#endif
19695#ifdef PARANOID
19697 {
19698 std::ostringstream error_message;
19699 error_message << "New domain for base element " << j
19700 << "which is of type "
19701 << typeid(*Base_mesh_element_pt[j]).name()
19702 << " does not\n"
19703 << "appear to have been assigned by any processor\n";
19704 throw OomphLibError(error_message.str(),
19707 }
19708#endif
19710 }
19711
19712
19713 // Loop over all processors and accumulate data to be sent
19714 //--------------------------------------------------------
19715 send_data.clear();
19716
19717 // Only do each node once (per processor!)
19719
19720 // Loop over all processors. NOTE: We include current processor
19721 // since we have to refine local elements too -- store their data
19722 // in same data structure as the one used for off-processor elements.
19723 for (int rank = 0; rank < n_proc; rank++)
19724 {
19725 // Set the offset for the current processor
19727
19728#ifdef PARANOID
19729 // Check that total number of elements processed matches those
19730 // in individual batches
19732#endif
19733
19734 // Counter for number of elements
19735 unsigned el_count = 0;
19736
19737 // How many baches are there for current rank?
19739
19740 // Add to vector of doubles to save on number of comms
19741 send_data.push_back(double(nbatch));
19742
19743 // Loop over batches of elemnts associated with same root
19744 for (unsigned b = 0; b < nbatch; b++)
19745 {
19746 // How many elements are to be sent in this batch?
19747 unsigned nel = nelement_batch_for_processor[rank][b];
19748
19749 // Get the unique number of the root element in unrefined mesh for
19750 // all the elements in this batch
19751 unsigned base_el_no =
19753
19754 // Add unsigneds to send data to minimise number of
19755 // communications
19756 send_data.push_back(double(nel));
19757 send_data.push_back(double(base_el_no));
19758
19759 // Loop over batch of elements
19760 for (unsigned e = 0; e < nel; e++)
19761 {
19762 // Get element
19764
19765 // FE?
19766 FiniteElement* fe_pt = dynamic_cast<FiniteElement*>(el_pt);
19767 if (fe_pt != 0)
19768 {
19769 // Loop over nodes
19770 unsigned nnod = fe_pt->nnode();
19771 for (unsigned j = 0; j < nnod; j++)
19772 {
19773 Node* nod_pt = fe_pt->node_pt(j);
19774
19775 // Reconstruct the nodal values/position from the node's
19776 // possible hanging node representation to be on the safe side
19777 unsigned n_value = nod_pt->nvalue();
19778 unsigned nt = nod_pt->ntstorage();
19779 Vector<double> values(n_value);
19780 unsigned n_dim = nod_pt->ndim();
19781 Vector<double> position(n_dim);
19782
19783 // Loop over all history values
19784 for (unsigned t = 0; t < nt; t++)
19785 {
19786 nod_pt->value(t, values);
19787 for (unsigned i = 0; i < n_value; i++)
19788 {
19789 nod_pt->set_value(t, i, values[i]);
19790 }
19791 nod_pt->position(t, position);
19792 for (unsigned i = 0; i < n_dim; i++)
19793 {
19794 nod_pt->x(t, i) = position[i];
19795 }
19796 }
19797
19798
19799 // Has the node already been done for current rank?
19800 if (!node_done[rank][nod_pt])
19801 {
19802 // Now it has been done
19803 node_done[rank][nod_pt] = true;
19804
19805 // Store number of values (as double) to allow for resizing
19806 // before read (req'd in case we store data that
19807 // got introduced by attaching FaceElements to bulk)
19808 send_data.push_back(double(n_value));
19809
19810 // Check if it's a boundary node
19812 dynamic_cast<BoundaryNodeBase*>(nod_pt);
19813
19814 // Not a boundary node
19815 if (bnod_pt == 0)
19816 {
19817#ifdef PARANOID
19818 // Record status for checking
19819 send_data.push_back(double(0));
19820#endif
19821 }
19822 // Yes it's a boundary node
19823 else
19824 {
19825#ifdef PARANOID
19826 // Record status for checking
19827 send_data.push_back(double(1));
19828#endif
19829 // Get pointer to the map of indices associated with
19830 // additional values created by face elements
19831 std::map<unsigned, unsigned>* map_pt =
19832 bnod_pt->index_of_first_value_assigned_by_face_element_pt();
19833
19834 // No additional values created
19835 if (map_pt == 0)
19836 {
19837 send_data.push_back(double(0));
19838 }
19839 // Created additional values
19840 else
19841 {
19842 // How many?
19843 send_data.push_back(double(map_pt->size()));
19844
19845 // Loop over entries in map and add to send data
19846 for (std::map<unsigned, unsigned>::iterator p =
19847 map_pt->begin();
19848 p != map_pt->end();
19849 p++)
19850 {
19851 send_data.push_back(double((*p).first));
19852 send_data.push_back(double((*p).second));
19853 }
19854 }
19855 }
19856
19857 // Add the actual values
19858 nod_pt->add_values_to_vector(send_data);
19859 }
19860 }
19861 }
19862
19863 // Now add internal data
19865
19866 // Bump up counter in long vector of elements
19867 el_count++;
19868 }
19869 }
19870
19871
19872#ifdef PARANOID
19873 // Check that total number of elements matches the total of those
19874 // in batches
19875 if (total_nel != el_count)
19876 {
19877 std::ostringstream error_message;
19878 error_message
19879 << "total_nel: " << total_nel << " "
19880 << " doesn't match total number of elements sent in batch: "
19881 << el_count << "\n";
19882 throw OomphLibError(error_message.str(),
19885 }
19886#endif
19887
19888 // Find the number of data added to the vector
19890 }
19891 }
19892
19893
19894 //==========================================================================
19895 /// Get flat-packed refinement pattern for each root element in current
19896 /// mesh (labeled by unique number of root element in unrefined base mesh).
19897 /// The vector stored for each root element contains the following
19898 /// information:
19899 /// - First entry: Number of tree nodes (not just leaves!) in refinement
19900 /// tree emanating from this root [Zero if root element is not refineable]
19901 /// - Loop over all refinement levels
19902 /// - Loop over all tree nodes (not just leaves!)
19903 /// - If associated element exists when the mesh has been refined to
19904 /// this level (either because it has been refined to this level or
19905 /// because it's less refined): 1
19906 /// - If the element is to be refined: 1; else: 0
19907 /// - else (element doesn't exist when mesh is refined to this level
19908 /// (because it's more refined): 0
19909 /// .
19910 /// .
19911 /// .
19912 //==========================================================================
19916 const unsigned& max_refinement_level_overall,
19918 {
19919 // Map to store whether the root element has been visited yet
19920 std::map<RefineableElement*, bool> root_el_done;
19921
19922 // Get the number of submeshs, if there are no submeshes, then
19923 // increase the counter so that the loop below also work for the only
19924 // one mesh in the problem
19925 unsigned n_mesh = nsub_mesh();
19926 if (n_mesh == 0)
19927 {
19928 n_mesh = 1;
19929 }
19930 // Go for the nonhalo elements only in the TreeBaseMeshes
19931 for (unsigned i_mesh = 0; i_mesh < n_mesh; i_mesh++)
19932 {
19933 // Only work with structured
19935 dynamic_cast<TriangleMeshBase*>(mesh_pt(i_mesh));
19936 if (!(sub_mesh_pt != 0))
19937 {
19938 const unsigned nele_submesh = mesh_pt(i_mesh)->nelement();
19939 for (unsigned e = 0; e < nele_submesh; e++)
19940 {
19941 // Get pointer to element
19943
19944 // Ignore halos
19945 if (!el_pt->is_halo())
19946 {
19947 // Is it refineable? No!
19949 dynamic_cast<RefineableElement*>(el_pt);
19950 if (ref_el_pt == 0)
19951 {
19952 // The element is not refineable - stick a zero in refinement_info
19953 // indicating that there are no tree nodes following
19955#ifdef PARANOID
19956 if (e == 0)
19957 {
19958 throw OomphLibError("Base_mesh_element_number_plus_one[...]=0",
19961 }
19962#endif
19963 e -= 1;
19965 }
19966 // Refineable
19967 else
19968 {
19969 // Get the root element
19970 RefineableElement* root_el_pt = ref_el_pt->root_element_pt();
19971
19972 // Has this root been visited yet?
19974 {
19975 // Get unique number of root element in base mesh
19976 unsigned root_element_number =
19978
19979#ifdef PARANOID
19980 if (root_element_number == 0)
19981 {
19982 throw OomphLibError(
19983 "Base_mesh_element_number_plus_one[...]=0",
19986 }
19987#endif
19989
19990 // Get all the nodes associated with this root element
19992 root_el_pt->tree_pt()->stick_all_tree_nodes_into_vector(
19994
19995 // How many tree nodes are there?
19996 unsigned n_tree_nodes = all_tree_nodes_pt.size();
19998 .push_back(n_tree_nodes);
19999
20000 // Loop over all levels
20001 for (unsigned current_level = 0;
20003 current_level++)
20004 {
20005 // Loop over all tree nodes
20006 for (unsigned e = 0; e < n_tree_nodes; e++)
20007 {
20008 // What's the level of this tree node?
20009 unsigned level = all_tree_nodes_pt[e]->level();
20010
20011 // Element exists at this refinement level of the mesh
20012 // if it's at this level or it's at a lower level and a leaf
20013 if ((level == current_level) ||
20014 ((level < current_level) &&
20015 (all_tree_nodes_pt[e]->is_leaf())))
20016 {
20018 .push_back(1);
20019
20020 // If it's at this level, and not a leaf, then it will
20021 // need to be refined in the new mesh
20022 if ((level == current_level) &&
20023 (!all_tree_nodes_pt[e]->is_leaf()))
20024 {
20027 .push_back(1);
20028 }
20029 // Element exists at this level and is a leaf so it
20030 // doesn't have to be refined
20031 else
20032 {
20035 .push_back(0);
20036 }
20037 }
20038 // Element does not exist at this level so it doesn't have
20039 // to be refined
20040 else
20041 {
20043 .push_back(0);
20044 }
20045 }
20046 }
20047 // Now we've done it
20048 root_el_done[root_el_pt] = true;
20049 }
20050 }
20051
20052 } // if (!el_pt->is_halo())
20053 } // for (e < nele_submesh)
20054 } // if (!(sub_mesh_pt!=0))
20055 } // for (i_mesh < n_mesh)
20056 }
20057
20058 //==========================================================================
20059 /// Load balance helper routine: Function performs max_level_overall
20060 /// successive refinements of the problem's mesh(es) using the following
20061 /// procdure: Given ID of root element, root_element_id, and current
20062 /// refinement level, level, the e-th entry in
20063 /// refinement_info_for_root_elements[root_element_id][level][e] is equal
20064 /// to 2 if the e-th element (using the enumeration when the mesh has been
20065 /// refined to the level-th level) is to be refined during the next
20066 /// refinement; it's 1 if it's not to be refined.
20067 //==========================================================================
20070 const unsigned& max_level_overall)
20071 {
20072 // Loop over sub meshes
20073 unsigned n_sub_mesh = nsub_mesh();
20074 unsigned max_mesh = std::max(n_sub_mesh, unsigned(1));
20075 for (unsigned i_mesh = 0; i_mesh < max_mesh; i_mesh++)
20076 {
20077 // Choose the right mesh
20078 Mesh* my_mesh_pt = 0;
20079 if (n_sub_mesh == 0)
20080 {
20081 my_mesh_pt = mesh_pt();
20082 }
20083 else
20084 {
20086 }
20087
20088 // Number of elements on this processor -- currently all elements
20089 // are "base" elements since the mesh hasn't been refined.
20090 unsigned n_el_on_this_proc = my_mesh_pt->nelement();
20091
20092 // Storage for actual refinement pattern:
20093 // to_be_refined_on_this_proc[level][e] contains the element number
20094 // of the e-th element that is to refined at the level-th refinement level
20096
20097 // Count, at each level, the total number of elements in the mesh
20098 // (we can accumulate this because we know that elements are
20099 // enumerated tree by tree).
20101
20102 // Loop over levels where refinement is taking place
20103 for (unsigned level = 0; level < max_level_overall; level++)
20104 {
20105 // Loop over roots = unrefined elements on this processor in order.
20106 // Note that this loops over the trees in unique order
20107 for (unsigned e = 0; e < n_el_on_this_proc; e++)
20108 {
20109 // Get the (root) element
20110 FiniteElement* el_pt = my_mesh_pt->finite_element_pt(e);
20111
20112 // What is its unique number in the base mesh
20114#ifdef PARANOID
20115 if (root_el_no == 0)
20116 {
20117 throw OomphLibError("Base_mesh_element_number_plus_one[...]=0",
20120 }
20121#endif
20122 root_el_no -= 1;
20123
20124 // Number of refinements to be performed starting from current
20125 // root element
20126 unsigned n_refinements =
20128
20129 // Perform refinement?
20130 if (level < n_refinements)
20131 {
20132 // Loop over elements at this level
20133 unsigned n_el =
20135 for (unsigned ee = 0; ee < n_el; ee++)
20136 {
20137 // Refinement code 2: Element is to be refined at this
20138 // level
20140 {
20141 to_be_refined_on_this_proc[level].push_back(
20142 el_count_on_this_proc[level]);
20143 el_count_on_this_proc[level]++;
20144 }
20145 // Refinement code 1: Element should not be refined at this
20146 // level -- keep going
20148 [ee] == 1)
20149 {
20150 el_count_on_this_proc[level]++;
20151 }
20152 }
20153 }
20154
20155 } // end of loop over elements on proc; all of which should be root
20156 }
20157
20158 // Now do the actual refinement
20161 if (ref_mesh_pt != 0)
20162 {
20163 ref_mesh_pt->refine_base_mesh(to_be_refined_on_this_proc);
20164 }
20165 }
20166
20167 // Rebuild global mesh after refinement
20168 if (n_sub_mesh != 0)
20169 {
20170 // Rebuild the global mesh
20172 }
20173 }
20174
20175
20176 //====================================================================
20177 /// Helper function to re-setup the Base_mesh enumeration
20178 /// (used during load balancing) after pruning.
20179 //====================================================================
20181 {
20182 // Storage for number of processors and current processor
20183 int n_proc = this->communicator_pt()->nproc();
20184 int my_rank = this->communicator_pt()->my_rank();
20185
20186 // Loop over sub meshes
20187 unsigned n_sub_mesh = nsub_mesh();
20188 unsigned max_mesh = std::max(n_sub_mesh, unsigned(1));
20189 for (unsigned i_mesh = 0; i_mesh < max_mesh; i_mesh++)
20190 {
20191 // Choose the right mesh
20192 Mesh* my_mesh_pt = 0;
20193 if (n_sub_mesh == 0)
20194 {
20195 my_mesh_pt = mesh_pt();
20196 }
20197 else
20198 {
20200 }
20201
20202 // Only work with structured meshes
20204 dynamic_cast<TriangleMeshBase*>(my_mesh_pt);
20205 if (!(sub_mesh_pt != 0))
20206 {
20207 // Storage for number of data to be sent to each processor
20209
20210 // Storage for all values to be sent to all processors
20212
20213 // Start location within send_data for data to be sent to each processor
20215
20216 // Loop over all processors
20217 for (int rank = 0; rank < n_proc; rank++)
20218 {
20219 // Set the offset for the current processor
20221
20222 // Don't bother to do anything if the processor in the loop is the
20223 // current processor
20224 if (rank != my_rank)
20225 {
20226 // Get root haloed elements with that processor
20228 my_mesh_pt->root_haloed_element_pt(rank);
20229 unsigned nel = root_haloed_elements_pt.size();
20230
20231 // Store element numbers for send
20232 for (unsigned e = 0; e < nel; e++)
20233 {
20236 }
20237 }
20238
20239 // Find the number of data added to the vector
20241 }
20242
20243 // Storage for the number of data to be received from each processor
20245
20246 // Now send numbers of data to be sent between all processors
20247 MPI_Alltoall(&send_n[0],
20248 1,
20249 MPI_INT,
20250 &receive_n[0],
20251 1,
20252 MPI_INT,
20253 this->communicator_pt()->mpi_comm());
20254
20255 // We now prepare the data to be received
20256 // by working out the displacements from the received data
20258 int receive_data_count = 0;
20259 for (int rank = 0; rank < n_proc; ++rank)
20260 {
20261 // Displacement is number of data received so far
20264 }
20265
20266 // Now resize the receive buffer for all data from all processors
20267 // Make sure that it has a size of at least one
20268 if (receive_data_count == 0)
20269 {
20271 }
20273
20274 // Make sure that the send buffer has size at least one
20275 // so that we don't get a segmentation fault
20276 if (send_data.size() == 0)
20277 {
20278 send_data.resize(1);
20279 }
20280
20281 // Now send the data between all the processors
20283 &send_n[0],
20286 &receive_data[0],
20287 &receive_n[0],
20290 this->communicator_pt()->mpi_comm());
20291
20292 // Now use the received data to update the halo element numbers in
20293 // base mesh
20294 for (int send_rank = 0; send_rank < n_proc; send_rank++)
20295 {
20296 // Don't bother to do anything for the processor corresponding to the
20297 // current processor or if no data were received from this processor
20298 if ((send_rank != my_rank) && (receive_n[send_rank] != 0))
20299 {
20300 // Counter for the data within the large array
20302
20303 // Get root halo elements with that processor
20305 my_mesh_pt->root_halo_element_pt(send_rank);
20306 unsigned nel = root_halo_elements_pt.size();
20307
20308 // Read in element numbers
20309 for (unsigned e = 0; e < nel; e++)
20310 {
20315 }
20316 }
20317
20318 } // End of data is received
20319
20320 } // if (!(sub_mesh_pt!=0))
20321
20322 } // for (i_mesh<max_mesh)
20323 }
20324
20325#endif
20326
20327 /// Instantiation of public flag to allow suppression of warning
20328 /// messages re reading in unstructured meshes during restart.
20330 false;
20331
20332
20333} // namespace oomph
e
Definition cfortran.h:571
cstr elem_len * i
Definition cfortran.h:603
char t
Definition cfortran.h:568
A class that is used to define the functions used to assemble the elemental contributions to the resi...
virtual unsigned ndof(GeneralisedElement *const &elem_pt)
Return the number of degrees of freedom in the element elem_pt.
virtual void synchronise()
Function that is used to perform any synchronisation required during the solution.
virtual int bifurcation_type() const
Return an unsigned integer to indicate whether the handler is a bifurcation tracking handler....
virtual void get_hessian_vector_products(GeneralisedElement *const &elem_pt, Vector< double > const &Y, DenseMatrix< double > const &C, DenseMatrix< double > &product)
Calculate the product of the Hessian (derivative of Jacobian with respect to all variables) an eigenv...
virtual double * bifurcation_parameter_pt() const
Return a pointer to the bifurcation parameter in bifurcation tracking problems.
virtual void get_eigenfunction(Vector< DoubleVector > &eigenfunction)
Return the eigenfunction(s) associated with the bifurcation that has been detected in bifurcation tra...
virtual void get_residuals(GeneralisedElement *const &elem_pt, Vector< double > &residuals)
Return the contribution to the residuals of the element elem_pt.
virtual unsigned long eqn_number(GeneralisedElement *const &elem_pt, const unsigned &ieqn_local)
Return the global equation number of the local unknown ieqn_local in elem_pt.
virtual void get_all_vectors_and_matrices(GeneralisedElement *const &elem_pt, Vector< Vector< double > > &vec, Vector< DenseMatrix< double > > &matrix)
Calculate all desired vectors and matrices provided by the element elem_pt.
virtual void get_jacobian(GeneralisedElement *const &elem_pt, Vector< double > &residuals, DenseMatrix< double > &jacobian)
Calculate the elemental Jacobian matrix "d equation / d variable" for elem_pt.
A custom linear solver class that is used to solve a block-factorised version of the Hopf bifurcation...
A class that contains the information required by Nodes that are located on Mesh boundaries....
Definition nodes.h:1996
A class for compressed column matrices that store doubles.
Definition matrices.h:2791
void build_without_copy(T *value, int *row_index, int *column_start, const unsigned long &nnz, const unsigned long &n, const unsigned long &m)
Function to build matrix from pointers to arrays which hold the column starts, row indices and non-ze...
Definition matrices.h:3199
A class for compressed row matrices. This is a distributable object.
Definition matrices.h:888
void redistribute(const LinearAlgebraDistribution *const &dist_pt)
The contents of the matrix are redistributed to match the new distribution. In a non-MPI build this m...
Definition matrices.cc:2575
void build_without_copy(const unsigned &ncol, const unsigned &nnz, double *value, int *column_index, int *row_start)
keeps the existing distribution and just matrix that is stored without copying the matrix data
Definition matrices.cc:1710
void build(const LinearAlgebraDistribution *distribution_pt, const unsigned &ncol, const Vector< double > &value, const Vector< int > &column_index, const Vector< int > &row_start)
build method: vector of values, vector of column indices, vector of row starts and number of rows and...
Definition matrices.cc:1672
A Base class for DGElements.
A class that represents a collection of data; each Data object may contain many different individual ...
Definition nodes.h:86
void copy(Data *orig_data_pt)
Copy Data values from specified Data object.
Definition nodes.cc:601
void set_value(const unsigned &i, const double &value_)
Set the i-th stored data value to specified value. The only reason that we require an explicit set fu...
Definition nodes.h:271
unsigned nvalue() const
Return number of values stored in data object (incl pinned ones).
Definition nodes.h:483
double value(const unsigned &i) const
Return i-th stored value. This function is not virtual so that it can be inlined. This means that if ...
Definition nodes.h:293
long & eqn_number(const unsigned &i)
Return the equation number of the i-th stored variable.
Definition nodes.h:367
Class of matrices containing doubles, and stored as a DenseMatrix<double>, but with solving functiona...
Definition matrices.h:1271
void initialise(const T &val)
Initialize all values in the matrix to val.
Definition matrices.h:514
void resize(const unsigned long &n)
Resize to a square nxn matrix; any values already present will be transfered.
Definition matrices.h:498
bool distribution_built() const
if the communicator_pt is null then the distribution is not setup then false is returned,...
LinearAlgebraDistribution * distribution_pt() const
access to the LinearAlgebraDistribution
Information for documentation of results: Directory and file number to enable output in the form RESL...
std::string & label()
String used (e.g.) for labeling output files.
bool is_doc_enabled() const
Are we documenting?
void disable_doc()
Disable documentation.
std::string directory() const
Output directory.
unsigned & number()
Number used (e.g.) for labeling output files.
A class that stores the halo/haloed entries required when using a DoubleVectorWithHaloEntries....
void setup_halo_dofs(const std::map< unsigned, double * > &halo_data_pt, Vector< double * > &halo_dof_pt)
Function that sets up a vector of pointers to halo data, index using the scheme in Local_index.
===================================================================== An extension of DoubleVector th...
void build_halo_scheme(DoubleVectorHaloScheme *const &halo_scheme_pt)
Construct the halo scheme and storage for the halo data.
void sum_all_halo_and_haloed_values()
Sum all the data, store in the master (haloed) data and then synchronise.
double & global_value(const unsigned &i)
Direct access to global entry.
A vector in the mathematical sense, initially developed for linear algebra type applications....
double max() const
returns the maximum coefficient
void build(const DoubleVector &old_vector)
Just copys the argument DoubleVector.
void redistribute(const LinearAlgebraDistribution *const &dist_pt)
The contents of the vector are redistributed to match the new distribution. In a non-MPI rebuild this...
double * values_pt()
access function to the underlying values
void clear()
wipes the DoubleVector
A class that is used to define the functions used to assemble the elemental contributions to the mass...
virtual void solve_eigenproblem(Problem *const &problem_pt, const int &n_eval, Vector< std::complex< double > > &eigenvalue, Vector< DoubleVector > &eigenvector_real, Vector< DoubleVector > &eigenvector_imag, const bool &do_adjoint_problem=false)
Solve the real eigenproblem that is assembled by elements in a mesh in a Problem object....
Base class for spatial error estimators.
A class that is used to define the functions used to assemble and invert the mass matrix when taking ...
A Base class for explicit timesteppers.
virtual void timestep(ExplicitTimeSteppableObject *const &object_pt, const double &dt)=0
Pure virtual function that is used to advance time in the object.
FaceElements are elements that coincide with the faces of higher-dimensional "bulk" elements....
Definition elements.h:4342
A general Finite Element class.
Definition elements.h:1317
void position(const Vector< double > &zeta, Vector< double > &r) const
Return the parametrised position of the FiniteElement in its incarnation as a GeomObject,...
Definition elements.h:2680
double size() const
Calculate the size of the element (length, area, volume,...) in Eulerian computational coordinates....
Definition elements.cc:4320
unsigned nnode() const
Return the number of nodes.
Definition elements.h:2214
Node *& node_pt(const unsigned &n)
Return a pointer to the local node n.
Definition elements.h:2179
virtual void describe_local_dofs(std::ostream &out, const std::string &current_string) const
Function to describe the local dofs of the element[s]. The ostream specifies the output stream to whi...
Definition elements.cc:1737
A Generalised Element class.
Definition elements.h:73
bool is_halo() const
Is this element a halo?
Definition elements.h:1150
void read_internal_eqn_numbers_from_vector(const Vector< long > &vector_of_eqn_numbers, unsigned &index)
Read all equation numbers associated with internal data from the vector starting from index....
Definition elements.cc:675
unsigned ndof() const
Return the number of equations/dofs in the element.
Definition elements.h:822
unsigned long eqn_number(const unsigned &ieqn_local) const
Return the global equation number corresponding to the ieqn_local-th local equation number.
Definition elements.h:691
Data *& internal_data_pt(const unsigned &i)
Return a pointer to i-th internal data object.
Definition elements.h:605
void add_internal_data_values_to_vector(Vector< double > &vector_of_values)
Add all internal data and time history values to the vector in the internal storage order.
Definition elements.cc:633
unsigned ninternal_data() const
Return the number of internal data objects.
Definition elements.h:810
void add_internal_eqn_numbers_to_vector(Vector< long > &vector_of_eqn_numbers)
Add all equation numbers associated with internal data to the vector in the internal storage order.
Definition elements.cc:660
virtual void assign_local_eqn_numbers(const bool &store_local_dof_pt)
Setup the arrays of local equation numbers for the element. If the optional boolean argument is true,...
Definition elements.cc:696
void describe_dofs(std::ostream &out, const std::string &current_string) const
Function to describe the dofs of the element. The ostream specifies the output stream to which the de...
Definition elements.cc:556
virtual void complete_setup_of_dependencies()
Complete the setup of any additional dependencies that the element may have. Empty virtual function t...
Definition elements.h:961
void read_internal_data_values_from_vector(const Vector< double > &vector_of_values, unsigned &index)
Read all internal data and time history values from the vector starting from index....
Definition elements.cc:647
unsigned ndim() const
Access function to # of Eulerian coordinates.
TimeStepper *& time_stepper_pt()
Access function for pointer to time stepper: Null if object is not time-dependent.
Class that contains data for hanging nodes.
Definition nodes.h:742
Node *const & master_node_pt(const unsigned &i) const
Return a pointer to the i-th master node.
Definition nodes.h:791
unsigned nmaster() const
Return the number of master nodes.
Definition nodes.h:785
void set_master_node_pt(const unsigned &i, Node *const &master_node_pt, const double &weight)
Set the pointer to the i-th master node and its weight.
Definition nodes.cc:1474
A class that is used to assemble the augmented system that defines a Hopf bifurcation....
A class to specify when the error is caused by an inverted element.
Definition elements.h:5270
Class for the LAPACK QZ eigensolver.
Describes the distribution of a distributable linear algebra type object. Typically this is a contain...
bool distributed() const
access function to the distributed - indicates whether the distribution is serial or distributed
OomphCommunicator * communicator_pt() const
const access to the communicator pointer
void build(const OomphCommunicator *const comm_pt, const unsigned &first_row, const unsigned &nrow_local, const unsigned &nrow=0)
Sets the distribution. Takes first_row, nrow_local and nrow as arguments. If nrow is not provided or ...
unsigned nrow() const
access function to the number of global rows.
unsigned nrow_local() const
access function for the num of local rows on this processor. If no MPI then Nrow is returned.
virtual void solve(Problem *const &problem_pt, DoubleVector &result)=0
Solver: Takes pointer to problem and returns the results vector which contains the solution of the li...
virtual void enable_resolve()
Enable resolve (i.e. store matrix and/or LU decomposition, say) Virtual so it can be overloaded to pe...
virtual void enable_computation_of_gradient()
function to enable the computation of the gradient required for the globally convergent Newton method
virtual void resolve(const DoubleVector &rhs, DoubleVector &result)
Resolve the system defined by the last assembled jacobian and the rhs vector. Solution is returned in...
void get_gradient(DoubleVector &gradient)
function to access the gradient, provided it has been computed
void reset_gradient()
function to reset the size of the gradient before each Newton solve
virtual void disable_resolve()
Disable resolve (i.e. store matrix and/or LU decomposition, say) This function simply resets an inter...
bool is_resolve_enabled() const
Boolean flag indicating if resolves are enabled.
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 does_pointer_correspond_to_mesh_data(double *const &parameter_pt)
Does the double pointer correspond to any mesh data.
Definition mesh.cc:2471
void remove_boundary_node(const unsigned &b, Node *const &node_pt)
Remove a node from the boundary b.
Definition mesh.cc:221
GeneralisedElement *& external_halo_element_pt(const unsigned &p, const unsigned &e)
Access fct to the e-th external halo element in this Mesh whose non-halo counterpart is held on proce...
Definition mesh.h:2259
void flush_element_and_node_storage()
Flush storage for elements and nodes by emptying the vectors that store the pointers to them....
Definition mesh.h:411
FiniteElement * finite_element_pt(const unsigned &e) const
Upcast (downcast?) to FiniteElement (needed to access FiniteElement member functions).
Definition mesh.h:477
void check_halo_schemes(DocInfo &doc_info, double &max_permitted_error_for_halo_check)
Check halo and shared schemes on the mesh.
Definition mesh.cc:6881
virtual void set_mesh_level_time_stepper(TimeStepper *const &time_stepper_pt, const bool &preserve_existing_data)
Function that can be used to set any additional timestepper data stored at the Mesh (as opposed to no...
Definition mesh.cc:2402
void describe_local_dofs(std::ostream &out, const std::string &current_string) const
Function to describe the local dofs of the elements. The ostream specifies the output stream to which...
Definition mesh.cc:746
Node *& node_pt(const unsigned long &n)
Return pointer to global node n.
Definition mesh.h:440
void set_nodal_and_elemental_time_stepper(TimeStepper *const &time_stepper_pt, const bool &preserve_existing_data)
Set the timestepper associated with all nodal and elemental data stored in the mesh.
Definition mesh.h:1040
void shift_time_values()
Shift time-dependent data along for next timestep: Deal with nodal Data/positions and the element's i...
Definition mesh.cc:2326
void prune_halo_elements_and_nodes(Vector< GeneralisedElement * > &deleted_element_pt, const bool &report_stats=false)
(Irreversibly) prune halo(ed) elements and nodes, usually after another round of refinement,...
Definition mesh.h:1675
void calculate_predictions()
Calculate predictions for all Data and positions associated with the mesh, usually used in adaptive t...
Definition mesh.cc:2366
void describe_dofs(std::ostream &out, const std::string &current_string) const
Function to describe the dofs of the Mesh. The ostream specifies the output stream to which the descr...
Definition mesh.cc:711
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
void delete_all_external_storage()
Wipe the storage for all externally-based elements.
Definition mesh.cc:9190
unsigned nnon_halo_element()
Total number of non-halo elements in this mesh (Costly call computes result on the fly)
Definition mesh.h:1825
void get_all_halo_data(std::map< unsigned, double * > &map_of_halo_data)
Get all the halo data stored in the mesh and add pointers to the data to the map, indexed by global e...
Definition mesh.cc:4749
void assign_initial_values_impulsive()
Assign initial values for an impulsive start.
Definition mesh.cc:2288
void assign_local_eqn_numbers(const bool &store_local_dof_pt)
Assign the local equation numbers in all elements If the boolean argument is true then also store poi...
Definition mesh.cc:765
virtual void read(std::ifstream &restart_file)
Read solution from restart file.
Definition mesh.cc:1130
void set_consistent_pinned_values_for_continuation(ContinuationStorageScheme *const &continuation_stepper_pt)
Set consistent values for pinned data in continuation.
Definition mesh.cc:2436
unsigned long assign_global_eqn_numbers(Vector< double * > &Dof_pt)
Assign the global equation numbers in the Data stored at the nodes and also internal element Data....
Definition mesh.cc:677
virtual void distribute(OomphCommunicator *comm_pt, const Vector< unsigned > &element_domain, Vector< GeneralisedElement * > &deleted_element_pt, DocInfo &doc_info, const bool &report_stats, const bool &overrule_keep_as_halo_element_status)
Distribute the problem and doc; make this virtual to allow overloading for particular meshes where fu...
Definition mesh.cc:4959
void null_external_halo_node(const unsigned &p, Node *nod_pt)
Null out specified external halo node (used when deleting duplicates)
Definition mesh.cc:8569
unsigned long nelement() const
Return number of elements in the mesh.
Definition mesh.h:598
void merge_meshes(const Vector< Mesh * > &sub_mesh_pt)
Merge meshes. Note: This simply merges the meshes' elements and nodes (ignoring duplicates; no bounda...
Definition mesh.cc:65
unsigned nexternal_halo_element()
Total number of external halo elements in this Mesh.
Definition mesh.h:2230
virtual void dump(std::ofstream &dump_file, const bool &use_old_ordering=true) const
Dump the data in the mesh into a file for restart.
Definition mesh.cc:1088
Wrapper to Mumps solver.
A class to handle errors in the Newton solver.
Definition problem.h:3057
Nodes are derived from Data, but, in addition, have a definite (Eulerian) position in a space of a gi...
Definition nodes.h:906
void copy(Node *orig_node_pt)
Copy all nodal data from specified Node object.
Definition nodes.cc:1916
unsigned ndim() const
Return (Eulerian) spatial dimension of the node.
Definition nodes.h:1054
double & x(const unsigned &i)
Return the i-th nodal coordinate.
Definition nodes.h:1060
bool is_hanging() const
Test whether the node is geometrically hanging.
Definition nodes.h:1285
double value(const unsigned &i) const
Return i-th value (dofs or pinned) at this node either directly or via hanging node representation....
Definition nodes.cc:2408
HangInfo *const & hanging_pt() const
Return pointer to hanging node data (this refers to the geometric hanging node status) (const version...
Definition nodes.h:1228
An oomph-lib wrapper to the MPI_Comm communicator object. Just contains an MPI_Comm object (which is ...
std::ostream *& stream_pt()
Access function for the stream pointer.
An OomphLibError object which should be thrown when an run-time error is encountered....
An OomphLibWarning object which should be created as a temporary object to issue a warning....
A class that is used to assemble the residuals in parallel by overloading the get_all_vectors_and_mat...
A class that is used to define the functions used when assembling the derivatives of the residuals wi...
////////////////////////////////////////////////////////////////// //////////////////////////////////...
Definition problem.h:154
virtual void actions_after_implicit_timestep()
Actions that should be performed after each implicit time step. This is needed when one wants to solv...
Definition problem.h:1090
bool Always_take_one_newton_step
Boolean to indicate whether a Newton step should be taken even if the initial residuals are below the...
Definition problem.h:2302
bool Jacobian_reuse_is_enabled
Is re-use of Jacobian in Newton iteration enabled? Default: false.
Definition problem.h:621
virtual void actions_after_newton_solve()
Any actions that are to be performed after a complete Newton solve, e.g. post processing....
Definition problem.h:1058
void parallel_sparse_assemble(const LinearAlgebraDistribution *const &dist_pt, Vector< int * > &column_or_row_index, Vector< int * > &row_or_column_start, Vector< double * > &value, Vector< unsigned > &nnz, Vector< double * > &residuals)
Helper method to assemble CRDoubleMatrices from distributed on multiple processors.
Definition problem.cc:6547
void describe_dofs(std::ostream &out= *(oomph_info.stream_pt())) const
Function to describe the dofs in terms of the global equation number, i.e. what type of value (nodal ...
Definition problem.cc:2455
virtual void sparse_assemble_row_or_column_compressed(Vector< int * > &column_or_row_index, Vector< int * > &row_or_column_start, Vector< double * > &value, Vector< unsigned > &nnz, Vector< double * > &residual, bool compressed_row_flag)
Protected helper function that is used to assemble the Jacobian matrix in the case when the storage i...
Definition problem.cc:4475
double * global_dof_pt(const unsigned &i)
Return a pointer to the dof, indexed by global equation number which may be haloed or stored locally....
Definition problem.h:1794
bool Store_local_dof_pt_in_elements
Boolean to indicate whether local dof pointers should be stored in the elements.
Definition problem.h:229
virtual void actions_before_newton_step()
Any actions that are to be performed before each individual Newton step. Most likely to be used for d...
Definition problem.h:1073
void remove_duplicate_data(Mesh *const &mesh_pt, bool &actually_removed_some_data)
Private helper function to remove repeated data in external haloed elements in specified mesh....
Definition problem.cc:2664
bool Bifurcation_detection
Boolean to control bifurcation detection via determinant of Jacobian.
Definition problem.h:810
void get_flat_packed_refinement_pattern_for_load_balancing(const Vector< unsigned > &old_domain_for_base_element, const Vector< unsigned > &new_domain_for_base_element, const unsigned &max_refinement_level_overall, std::map< unsigned, Vector< unsigned > > &flat_packed_refinement_info_for_root)
Get flat-packed refinement pattern for each root element in current mesh (labeled by unique number of...
Definition problem.cc:19913
void adapt()
Adapt problem: Perform mesh adaptation for (all) refineable (sub)mesh(es), based on their own error e...
Definition problem.h:2964
virtual void actions_before_newton_solve()
Any actions that are to be performed before a complete Newton solve (e.g. adjust boundary conditions)...
Definition problem.h:1052
Vector< unsigned > First_el_for_assembly
First element to be assembled by given processor for non-distributed problem (only kept up to date wh...
Definition problem.h:522
unsigned long assign_eqn_numbers(const bool &assign_local_eqn_numbers=true)
Assign all equation numbers for problem: Deals with global data (= data that isn't attached to any el...
Definition problem.cc:2085
void refine_uniformly_aux(const Vector< unsigned > &nrefine_for_mesh, DocInfo &doc_info, const bool &prune)
Helper function to do compund refinement of (all) refineable (sub)mesh(es) uniformly as many times as...
Definition problem.cc:15523
void build_global_mesh()
Build the global mesh by combining the all the submeshes. Note: The nodes boundary information refers...
Definition problem.cc:1589
unsigned unrefine_uniformly()
Refine (all) refineable (sub)mesh(es) uniformly and rebuild problem. Return 0 for success,...
Definition problem.cc:15909
void assign_initial_values_impulsive()
Initialise data and nodal positions to simulate impulsive start from initial configuration/solution.
Definition problem.cc:11575
double Theta_squared
Value of the scaling parameter required so that the parameter occupies the desired proportion of the ...
Definition problem.h:762
virtual void get_eigenproblem_matrices(CRDoubleMatrix &mass_matrix, CRDoubleMatrix &main_matrix, const double &shift=0.0)
Get the matrices required by a eigensolver. If the shift parameter is non-zero the second matrix will...
Definition problem.cc:8442
Vector< double > Dof_derivative
Storage for the derivative of the problem variables wrt arc-length.
Definition problem.h:794
double & dof_current(const unsigned &i)
Access function to the current value of the i-th (local) dof at the start of a continuation step.
Definition problem.h:1202
virtual void sparse_assemble_row_or_column_compressed_with_lists(Vector< int * > &column_or_row_index, Vector< int * > &row_or_column_start, Vector< double * > &value, Vector< unsigned > &nnz, Vector< double * > &residual, bool compressed_row_flag)
Private helper function that is used to assemble the Jacobian matrix in the case when the storage is ...
Definition problem.cc:4921
virtual void sparse_assemble_row_or_column_compressed_with_two_arrays(Vector< int * > &column_or_row_index, Vector< int * > &row_or_column_start, Vector< double * > &value, Vector< unsigned > &nnz, Vector< double * > &residual, bool compressed_row_flag)
Private helper function that is used to assemble the Jacobian matrix in the case when the storage is ...
Definition problem.cc:6052
friend class BlockHopfLinearSolver
Definition problem.h:165
void synchronise_dofs(const bool &do_halos, const bool &do_external_halos)
Synchronise the degrees of freedom by overwriting the haloed values with their non-halo counterparts ...
Definition problem.cc:16540
virtual void actions_before_distribute()
Actions to be performed before a (mesh) distribution.
Definition problem.h:1136
friend class FoldHandler
Definition problem.h:156
Distributed_problem_matrix_distribution Dist_problem_matrix_distribution
The distributed matrix distribution method 1 - Automatic - the Problem distribution is employed,...
Definition problem.h:976
DoubleVectorHaloScheme * Halo_scheme_pt
Pointer to the halo scheme for any global vectors that have the Dof_distribution.
Definition problem.h:580
virtual void actions_after_change_in_global_parameter(double *const &parameter_pt)
Actions that are to be performed when the global parameter addressed by parameter_pt has been changed...
Definition problem.h:1153
void p_refine_selected_elements(const Vector< unsigned > &elements_to_be_refined)
p-refine (one and only!) mesh by refining the elements identified by their numbers relative to the pr...
Definition problem.cc:15237
void setup_dof_halo_scheme()
Function that is used to setup the halo scheme.
Definition problem.cc:396
unsigned long ndof() const
Return the number of dofs.
Definition problem.h:1704
void p_unrefine_uniformly(DocInfo &doc_info)
p-unrefine (all) p-refineable (sub)mesh(es) uniformly and rebuild problem.
Definition problem.cc:16038
virtual void build_mesh()
Function to build the Problem's base mesh; this must be supplied by the user if they wish to use the ...
Definition problem.h:1385
static bool Suppress_warning_about_actions_before_read_unstructured_meshes
Flag to allow suppression of warning messages re reading in unstructured meshes during restart.
Definition problem.h:320
OomphCommunicator * communicator_pt()
access function to the oomph-lib communicator
Definition problem.h:1266
bool Use_default_partition_in_load_balance
Flag to use "default partition" during load balance. Should only be set to true when run in validatio...
Definition problem.h:517
void bifurcation_adapt_helper(unsigned &n_refined, unsigned &n_unrefined, const unsigned &bifurcation_type, const bool &actually_adapt=true)
A function that is used to adapt a bifurcation-tracking problem, which requires separate interpolatio...
Definition problem.cc:13432
void adapt_based_on_error_estimates(unsigned &n_refined, unsigned &n_unrefined, Vector< Vector< double > > &elemental_error)
Adapt problem: Perform mesh adaptation for (all) refineable (sub)mesh(es), based on the error estimat...
Definition problem.cc:14606
friend class AugmentedBlockFoldLinearSolver
Definition problem.h:163
Vector< double > Elemental_assembly_time
Storage for assembly times (used for load balancing)
Definition problem.h:576
double & dof(const unsigned &i)
i-th dof in the problem
Definition problem.h:1843
bool Jacobian_has_been_computed
Has a Jacobian been computed (and can therefore be re-used if required)? Default: false.
Definition problem.h:625
bool Bypass_increase_in_dof_check_during_pruning
Boolean to bypass check of increase in dofs during pruning.
Definition problem.h:987
virtual void get_dvaluesdt(DoubleVector &f)
Get the time derivative of all values (using get_inverse_mass_matrix_times_residuals(....
Definition problem.cc:3780
void initialise_dt(const double &dt)
Set all timesteps to the same value, dt, and assign weights for all timesteppers in the problem.
Definition problem.cc:13309
void add_to_dofs(const double &lambda, const DoubleVector &increment_dofs)
Add lambda x incremenet_dofs[l] to the l-th dof.
Definition problem.cc:3660
double Timestep_reduction_factor_after_nonconvergence
What it says: If temporally adaptive Newton solver fails to to converge, reduce timestep by this fact...
Definition problem.h:2307
Vector< double > Dof_current
Storage for the present values of the variables.
Definition problem.h:797
double Desired_proportion_of_arc_length
Proportion of the arc-length to taken by the parameter.
Definition problem.h:754
virtual void actions_after_implicit_timestep_and_error_estimation()
Actions that should be performed after each implicit time step. This is needed if your actions_after_...
Definition problem.h:1095
void disable_mass_matrix_reuse()
Turn off recyling of the mass matrix in explicit timestepping schemes.
Definition problem.cc:11908
EigenSolver * Default_eigen_solver_pt
Pointer to the default eigensolver.
Definition problem.h:194
unsigned Nnewton_iter_taken
Actual number of Newton iterations taken during the most recent iteration.
Definition problem.h:606
double * bifurcation_parameter_pt() const
Return pointer to the parameter that is used in the bifurcation detection. If we are not tracking a b...
Definition problem.cc:10114
void copy_haloed_eqn_numbers_helper(const bool &do_halos, const bool &do_external_halos)
A private helper function to copy the haloed equation numbers into the halo equation numbers,...
Definition problem.cc:17029
virtual void sparse_assemble_row_or_column_compressed_with_vectors_of_pairs(Vector< int * > &column_or_row_index, Vector< int * > &row_or_column_start, Vector< double * > &value, Vector< unsigned > &nnz, Vector< double * > &residual, bool compressed_row_flag)
Private helper function that is used to assemble the Jacobian matrix in the case when the storage is ...
Definition problem.cc:5336
void send_data_to_be_sent_during_load_balancing(Vector< int > &send_n, Vector< double > &send_data, Vector< int > &send_displacement)
Load balance helper routine: Send data to other processors during load balancing.
Definition problem.cc:19027
bool Time_adaptive_newton_crash_on_solve_fail
Bool to specify what to do if a Newton solve fails within a time adaptive solve. Default (false) is t...
Definition problem.h:618
Problem()
Constructor: Allocate space for one time stepper and set all pointers to NULL and set defaults for al...
Definition problem.cc:71
bool is_dparameter_calculated_analytically(double *const &parameter_pt)
Function to determine whether the parameter derivatives are calculated analytically.
Definition problem.h:280
void flush_sub_meshes()
Flush the problem's collection of sub-meshes. Must be followed by call to rebuild_global_mesh().
Definition problem.h:1359
unsigned Sparse_assembly_method
Flag to determine which sparse assembly method to use By default we use assembly by vectors of pairs.
Definition problem.h:644
bool & use_predictor_values_as_initial_guess()
Definition problem.h:2099
void calculate_predictions()
Calculate predictions.
Definition problem.cc:11732
Vector< unsigned > Last_el_plus_one_for_assembly
Last element (plus one) to be assembled by given processor for non-distributed problem (only kept up ...
Definition problem.h:527
virtual void actions_after_read_unstructured_meshes()
Actions that are to be performed before reading in restart data for problems involving unstructured b...
Definition problem.h:1129
void copy(Problem *orig_problem_pt)
Copy Data values, nodal positions etc from specified problem. Note: This is not a copy constructor....
Definition problem.cc:11941
virtual void get_jacobian(DoubleVector &residuals, DenseDoubleMatrix &jacobian)
Return the fully-assembled Jacobian and residuals for the problem Interface for the case when the Jac...
Definition problem.cc:3935
void set_explicit_time_stepper_pt(ExplicitTimeStepper *const &explicit_time_stepper_pt)
Set the explicit timestepper for the problem. The function will automatically create or resize the Ti...
Definition problem.cc:1682
virtual void actions_after_distribute()
Actions to be performed after a (mesh) distribution.
Definition problem.h:1139
virtual void actions_before_implicit_timestep()
Actions that should be performed before each implicit time step. This is needed when one wants to sol...
Definition problem.h:1084
LinearSolver * Linear_solver_pt
Pointer to the linear solver for the problem.
Definition problem.h:176
bool Doc_time_in_distribute
Protected boolean flag to provide comprehensive timimings during problem distribution....
Definition problem.h:640
unsigned Max_newton_iterations
Maximum number of Newton iterations.
Definition problem.h:602
Vector< Vector< unsigned > > Sparse_assemble_with_arrays_previous_allocation
the number of elements in each row of a compressed matrix in the previous matrix assembly.
Definition problem.h:670
virtual void actions_after_parameter_increase(double *const &parameter_pt)
Empty virtual function; provides hook to perform actions after the increase in the arclength paramete...
Definition problem.h:1181
friend class HopfHandler
Definition problem.h:158
friend class PitchForkHandler
Definition problem.h:157
void calculate_continuation_derivatives(double *const &parameter_pt)
A function to calculate the derivatives wrt the arc-length. This version of the function actually doe...
Definition problem.cc:9748
void store_current_dof_values()
Store the current values of the degrees of freedom.
Definition problem.cc:8627
double & dof_derivative(const unsigned &i)
Access function to the derivative of the i-th (local) dof with respect to the arc length,...
Definition problem.h:1188
bool Problem_has_been_distributed
Has the problem been distributed amongst multiple processors?
Definition problem.h:984
void synchronise_all_dofs()
Perform all required synchronisation in solvers.
Definition problem.cc:16517
void get_all_error_estimates(Vector< Vector< double > > &elemental_error)
Return the error estimates computed by (all) refineable (sub)mesh(es) in the elemental_error structur...
Definition problem.cc:14705
bool Empty_actions_before_read_unstructured_meshes_has_been_called
Boolean to indicate that empty actions_before_read_unstructured_meshes() function has been called.
Definition problem.h:221
bool Mass_matrix_has_been_computed
Has the mass matrix been computed (and can therefore be reused) Default: false.
Definition problem.h:698
unsigned nglobal_data() const
Return the number of global data values.
Definition problem.h:1716
virtual void actions_before_adapt()
Actions that are to be performed before a mesh adaptation. These might include removing any additiona...
Definition problem.h:1042
void newton_solve()
Use Newton method to solve the problem.
Definition problem.cc:8816
bool First_jacobian_sign_change
Boolean to indicate whether a sign change has occured in the Jacobian.
Definition problem.h:816
void calculate_continuation_derivatives_fd_helper(double *const &parameter_pt)
A function that performs the guts of the continuation derivative calculation in arc-length continuati...
Definition problem.cc:10037
double Continuation_direction
The direction of the change in parameter that will ensure that a branch is followed in one direction ...
Definition problem.h:769
unsigned Parallel_sparse_assemble_previous_allocation
The amount of data allocated during the previous parallel sparse assemble. Used to optimise the next ...
Definition problem.h:980
void enable_mass_matrix_reuse()
Enable recycling of the mass matrix in explicit timestepping schemes. Useful for timestepping on fixe...
Definition problem.cc:11883
void steady_newton_solve(unsigned const &max_adapt=0)
Solve a steady problem using adaptive Newton's method, but in the context of an overall unstady probl...
Definition problem.cc:9325
bool Scale_arc_length
Boolean to control whether arc-length should be scaled.
Definition problem.h:751
double doubly_adaptive_unsteady_newton_solve_helper(const double &dt, const double &epsilon, const unsigned &max_adapt, const unsigned &suppress_resolve_after_spatial_adapt, const bool &first, const bool &shift=true)
Private helper function that actually performs the unsteady "doubly" adaptive Newton solve....
Definition problem.cc:11455
bool Empty_actions_after_read_unstructured_meshes_has_been_called
Boolean to indicate that empty actions_after_read_unstructured_meshes() function has been called.
Definition problem.h:225
virtual void get_residuals(DoubleVector &residuals)
Return the fully-assembled residuals Vector for the problem: Virtual so it can be overloaded in for m...
Definition problem.cc:3810
Vector< GeneralisedElement * > Base_mesh_element_pt
Vector to store the correspondence between a root element and its element number within the global me...
Definition problem.h:551
void get_fd_jacobian(DoubleVector &residuals, DenseMatrix< double > &jacobian)
Return the fully-assembled Jacobian and residuals, generated by finite differences.
Definition problem.cc:7814
virtual void sparse_assemble_row_or_column_compressed_with_two_vectors(Vector< int * > &column_or_row_index, Vector< int * > &row_or_column_start, Vector< double * > &value, Vector< unsigned > &nnz, Vector< double * > &residual, bool compressed_row_flag)
Private helper function that is used to assemble the Jacobian matrix in the case when the storage is ...
Definition problem.cc:5689
virtual Problem * make_copy()
Make and return a pointer to the copy of the problem. A virtual function that must be filled in by th...
Definition problem.cc:12087
double Maximum_dt
Maximum desired dt.
Definition problem.h:712
unsigned long set_timestepper_for_all_data(TimeStepper *const &time_stepper_pt, const bool &preserve_existing_data=false)
Set all problem data to have the same timestepper (timestepper_pt) Return the new number of dofs in t...
Definition problem.cc:11648
void activate_pitchfork_tracking(double *const &parameter_pt, const DoubleVector &symmetry_vector, const bool &block_solve=true)
Turn on pitchfork tracking using the augmented system specified in the PitchForkHandler class....
Definition problem.cc:10221
virtual void dump(std::ofstream &dump_file) const
Dump refinement pattern of all refineable meshes and all generic Problem data to file for restart.
Definition problem.cc:12105
bool Use_finite_differences_for_continuation_derivatives
Boolean to specify which scheme to use to calculate the continuation derivatievs.
Definition problem.h:823
bool Arc_length_step_taken
Boolean to indicate whether an arc-length step has been taken.
Definition problem.h:819
void set_pinned_values_to_zero()
Set all pinned values to zero. Used to set boundary conditions to be homogeneous in the copy of the p...
Definition problem.cc:4295
bool Default_set_initial_condition_called
Has default set_initial_condition function been called? Default: false.
Definition problem.h:214
void get_bifurcation_eigenfunction(Vector< DoubleVector > &eigenfunction)
Return the eigenfunction calculated as part of a bifurcation tracking process. If we are not tracking...
Definition problem.cc:10124
double Relaxation_factor
Relaxation fator for Newton method (only a fractional Newton correction is applied if this is less th...
Definition problem.h:595
void add_time_stepper_pt(TimeStepper *const &time_stepper_pt)
Add a timestepper to the problem. The function will automatically create or resize the Time object so...
Definition problem.cc:1641
void refine_selected_elements(const Vector< unsigned > &elements_to_be_refined)
Refine (one and only!) mesh by splitting the elements identified by their numbers relative to the pro...
Definition problem.cc:14976
LinearAlgebraDistribution *const & dof_distribution_pt() const
Return the pointer to the dof distribution (read-only)
Definition problem.h:1698
void prune_halo_elements_and_nodes(DocInfo &doc_info, const bool &report_stats)
(Irreversibly) prune halo(ed) elements and nodes, usually after another round of refinement,...
Definition problem.cc:1149
virtual void get_inverse_mass_matrix_times_residuals(DoubleVector &Mres)
Return the residual vector multiplied by the inverse mass matrix Virtual so that it can be overloaded...
Definition problem.cc:3675
void check_halo_schemes()
Check the halo/haloed node/element schemes.
Definition problem.h:2221
double Parameter_current
Storage for the present value of the global parameter.
Definition problem.h:775
double *& dof_pt(const unsigned &i)
Pointer to i-th dof in the problem.
Definition problem.h:1855
void assign_eigenvector_to_dofs(DoubleVector &eigenvector)
Assign the eigenvector passed to the function to the dofs in the problem so that it can be output by ...
Definition problem.cc:8736
@ Uniform_matrix_distribution
Definition problem.h:851
@ Default_matrix_distribution
Definition problem.h:849
@ Problem_matrix_distribution
Definition problem.h:850
void send_refinement_info_helper(Vector< unsigned > &old_domain_for_base_element, Vector< unsigned > &new_domain_for_base_element, const unsigned &max_refinement_level_overall, std::map< unsigned, Vector< unsigned > > &refinement_info_for_root_local, Vector< Vector< Vector< unsigned > > > &refinement_info_for_root_elements)
Send refinement information between processors.
Definition problem.cc:18406
unsigned setup_element_count_per_dof()
Function that populates the Element_counter_per_dof vector with the number of elements that contribut...
Definition problem.cc:240
OomphCommunicator * Communicator_pt
The communicator for this problem.
Definition problem.h:1262
double Newton_solver_tolerance
The Tolerance below which the Newton Method is deemed to have converged.
Definition problem.h:599
void set_consistent_pinned_values_for_continuation()
Private helper function that is used to set the appropriate pinned values for continuation.
Definition problem.cc:10499
void activate_bifurcation_tracking(double *const &parameter_pt, const DoubleVector &eigenvector, const bool &block_solve=true)
Activate generic bifurcation tracking for a single (real) eigenvalue where the initial guess for the ...
Definition problem.cc:10162
LinearSolver *& mass_matrix_solver_for_explicit_timestepper_pt()
Return a pointer to the linear solver object used for explicit time stepping.
Definition problem.h:1499
bool Discontinuous_element_formulation
Is the problem a discontinuous one, i.e. can the elemental contributions be treated independently....
Definition problem.h:703
bool Doc_imbalance_in_parallel_assembly
Boolean to switch on assessment of load imbalance in parallel assembly of distributed problem.
Definition problem.h:513
long synchronise_eqn_numbers(const bool &assign_local_eqn_numbers=true)
Classify any non-classified nodes into halo/haloed and synchronise equation numbers....
Definition problem.cc:16783
void create_new_linear_algebra_distribution(LinearAlgebraDistribution *&dist_pt)
Get new linear algebra distribution (you're in charge of deleting it!)
Definition problem.cc:309
void p_refine_uniformly_aux(const Vector< unsigned > &nrefine_for_mesh, DocInfo &doc_info, const bool &prune)
Helper function to do compund p-refinement of (all) p-refineable (sub)mesh(es) uniformly as many time...
Definition problem.cc:15670
void calculate_continuation_derivatives_helper(const DoubleVector &z)
A function that performs the guts of the continuation derivative calculation in arc length continuati...
Definition problem.cc:9945
Vector< Problem * > Copy_of_problem_pt
Vector of pointers to copies of the problem used in adaptive bifurcation tracking problems (ALH: TEMP...
Definition problem.h:237
Vector< unsigned > distribute(const Vector< unsigned > &element_partition, DocInfo &doc_info, const bool &report_stats=false)
Distribute the problem and doc, using the specified partition; returns a vector which details the par...
Definition problem.cc:500
Mesh * Mesh_pt
The mesh pointer.
Definition problem.h:170
double Parameter_derivative
Storage for the derivative of the global parameter wrt arc-length.
Definition problem.h:772
TimeStepper *& time_stepper_pt()
Access function for the pointer to the first (presumably only) timestepper.
Definition problem.h:1544
void add_eigenvector_to_dofs(const double &epsilon, const DoubleVector &eigenvector)
Add the eigenvector passed to the function scaled by the constat epsilon to the dofs in the problem s...
Definition problem.cc:8773
Vector< Data * > Global_data_pt
Vector of global data: "Nobody" (i.e. none of the elements etc.) is "in charge" of this Data so it wo...
Definition problem.h:428
void recompute_load_balanced_assembly()
Helper function to re-assign the first and last elements to be assembled by each processor during par...
Definition problem.cc:1785
Vector< double * > Dof_pt
Vector of pointers to dofs.
Definition problem.h:557
void p_adapt()
p-adapt problem: Perform mesh adaptation for (all) refineable (sub)mesh(es), based on their own error...
Definition problem.h:2986
double DTSF_max_increase
Maximum possible increase of dt between time-steps in adaptive schemes.
Definition problem.h:716
bool Must_recompute_load_balance_for_assembly
Boolean indicating that the division of elements over processors during the assembly process must be ...
Definition problem.h:532
virtual void shift_time_values()
Shift all values along to prepare for next timestep.
Definition problem.cc:11710
double Target_error_safety_factor
Safety factor to ensure we are aiming for a target error, TARGET, that is below our tolerance: TARGET...
Definition problem.h:738
void set_default_first_and_last_element_for_assembly()
Set default first and last elements for parallel assembly of non-distributed problem.
Definition problem.cc:1709
bool Keep_temporal_error_below_tolerance
Boolean to decide if a timestep is to be rejected if the error estimate post-solve (computed by globa...
Definition problem.h:2314
bool Shut_up_in_newton_solve
Boolean to indicate if all output is suppressed in Problem::newton_solve(). Defaults to false.
Definition problem.h:2296
void set_dofs(const DoubleVector &dofs)
Set the values of the dofs.
Definition problem.cc:3507
void setup_base_mesh_info_after_pruning()
Helper function to re-setup the Base_mesh enumeration (used during load balancing) after pruning.
Definition problem.cc:20180
Vector< Mesh * > Sub_mesh_pt
Vector of pointers to submeshes.
Definition problem.h:173
ExplicitTimeStepper *& explicit_time_stepper_pt()
Return a pointer to the explicit timestepper.
Definition problem.h:1575
Vector< double > Max_res
Maximum residuals at start and after each newton iteration.
Definition problem.h:609
EigenSolver * Eigen_solver_pt
Pointer to the eigen solver for the problem.
Definition problem.h:185
bool Bisect_to_find_bifurcation
Boolean to control wheter bisection is used to located bifurcation.
Definition problem.h:813
void explicit_timestep(const double &dt, const bool &shift_values=true)
Take an explicit timestep of size dt and optionally shift any stored values of the time history.
Definition problem.cc:10961
void delete_all_external_storage()
Wrapper function to delete external storage for each submesh of the problem.
Definition problem.cc:16433
friend class BlockPitchForkLinearSolver
Definition problem.h:162
double DTSF_min_decrease
Minimum allowed decrease of dt between time-steps in adaptive schemes. Lower scaling values will reje...
Definition problem.h:721
virtual void symmetrise_eigenfunction_for_adaptive_pitchfork_tracking()
Virtual function that is used to symmetrise the problem so that the current solution exactly satisfie...
Definition problem.cc:10092
double Minimum_dt_but_still_proceed
If Minimum_dt_but_still_proceed positive, then dt will not be reduced below this value during adaptiv...
Definition problem.h:745
double adaptive_unsteady_newton_solve(const double &dt_desired, const double &epsilon)
Attempt to advance timestep by dt_desired. If the solution fails the timestep will be halved until co...
Definition problem.cc:11099
unsigned Desired_newton_iterations_ds
The desired number of Newton Steps to reach convergence at each step along the arc.
Definition problem.h:804
void rebuild_global_mesh()
If one of the submeshes has changed (e.g. by mesh adaptation) we need to update the global mesh....
Definition problem.cc:1629
void solve_adjoint_eigenproblem(const unsigned &n_eval, Vector< std::complex< double > > &eigenvalue, Vector< DoubleVector > &eigenvector_real, Vector< DoubleVector > &eigenvector_imag, const bool &steady=true)
Solve an adjoint eigenvalue problem using the same procedure as solve_eigenproblem....
Definition problem.cc:8376
void activate_hopf_tracking(double *const &parameter_pt, const bool &block_solve=true)
Turn on Hopf bifurcation tracking using the augmented system specified in the HopfHandler class....
Definition problem.cc:10251
Vector< double * > Halo_dof_pt
Storage for the halo degrees of freedom (only required) when accessing via the global equation number...
Definition problem.h:584
void deactivate_bifurcation_tracking()
Deactivate all bifuraction tracking, by reseting the assembly handler to the default.
Definition problem.h:2458
void globally_convergent_line_search(const Vector< double > &x_old, const double &half_residual_squared_old, DoubleVector &gradient, DoubleVector &newton_dir, double &half_residual_squared, const double &stpmax)
Line search helper for globally convergent Newton method.
Definition problem.cc:9179
void get_hessian_vector_products(DoubleVectorWithHaloEntries const &Y, Vector< DoubleVectorWithHaloEntries > const &C, Vector< DoubleVectorWithHaloEntries > &product)
Return the product of the global hessian (derivative of Jacobian matrix with respect to all variables...
Definition problem.cc:7976
virtual double global_temporal_error_norm()
Function to calculate a global error norm, used in adaptive timestepping to control the change in tim...
Definition problem.h:1250
@ Perform_assembly_using_two_arrays
Definition problem.h:653
@ Perform_assembly_using_maps
Definition problem.h:651
@ Perform_assembly_using_two_vectors
Definition problem.h:650
@ Perform_assembly_using_vectors_of_pairs
Definition problem.h:649
@ Perform_assembly_using_lists
Definition problem.h:652
void refine_distributed_base_mesh(Vector< Vector< Vector< unsigned > > > &to_be_refined_on_each_root, const unsigned &max_level_overall)
Load balance helper routine: refine each new base (sub)mesh based upon the elements to be refined wit...
Definition problem.cc:20068
int Sign_of_jacobian
Storage for the sign of the global Jacobian.
Definition problem.h:765
std::map< GeneralisedElement *, unsigned > Base_mesh_element_number_plus_one
Map which stores the correspondence between a root element and its element number (plus one) within t...
Definition problem.h:541
double Max_residuals
Maximum desired residual: if the maximum residual exceeds this value, the program will exit.
Definition problem.h:613
unsigned nsub_mesh() const
Return number of submeshes.
Definition problem.h:1343
double & time()
Return the current value of continuous time.
Definition problem.cc:11607
unsigned self_test()
Self-test: Check meshes and global data. Return 0 for OK.
Definition problem.cc:13354
unsigned ntime_stepper() const
Return the number of time steppers.
Definition problem.h:1710
void activate_fold_tracking(double *const &parameter_pt, const bool &block_solve=true)
Turn on fold tracking using the augmented system specified in the FoldHandler class....
Definition problem.cc:10136
bool Use_globally_convergent_newton_method
Use the globally convergent newton method.
Definition problem.h:217
LinearSolver * Default_linear_solver_pt
Pointer to the default linear solver.
Definition problem.h:191
double Minimum_ds
Minimum desired value of arc-length.
Definition problem.h:807
void get_data_to_be_sent_during_load_balancing(const Vector< unsigned > &element_domain_on_this_proc, Vector< int > &send_n, Vector< double > &send_data, Vector< int > &send_displacement, Vector< unsigned > &old_domain_for_base_element, Vector< unsigned > &new_domain_for_base_element, unsigned &max_refinement_level_overall)
Load balance helper routine: Get data to be sent to other processors during load balancing and other ...
Definition problem.cc:19357
void restore_dof_values()
Restore the stored values of the degrees of freedom.
Definition problem.cc:8673
double Numerical_zero_for_sparse_assembly
A tolerance used to determine whether the entry in a sparse matrix is zero. If it is then storage nee...
Definition problem.h:674
double FD_step_used_in_get_hessian_vector_products
Definition problem.h:688
virtual void partition_global_mesh(Mesh *&global_mesh_pt, DocInfo &doc_info, Vector< unsigned > &element_domain, const bool &report_stats=false)
Partition the global mesh, return vector specifying the processor number for each element....
Definition problem.cc:974
unsigned Sparse_assemble_with_arrays_initial_allocation
the number of elements to initially allocate for a matrix row within the sparse_assembly_with_two_arr...
Definition problem.h:661
void bifurcation_adapt_doc_errors(const unsigned &bifurcation_type)
A function that is used to document the errors used in the adaptive solution of bifurcation problems.
Definition problem.cc:13726
double Ds_current
Storage for the current step value.
Definition problem.h:800
virtual void set_initial_condition()
Set initial condition (incl previous timesteps). We need to establish this interface because I....
Definition problem.h:1218
LinearSolver * Mass_matrix_solver_for_explicit_timestepper_pt
Pointer to the linear solver used for explicit time steps (this is likely to be different to the line...
Definition problem.h:182
void get_all_halo_data(std::map< unsigned, double * > &map_of_halo_data)
Get pointers to all possible halo data indexed by global equation number in a map.
Definition problem.cc:16455
void load_balance()
Balance the load of a (possibly non-uniformly refined) problem that has already been distributed,...
Definition problem.h:1402
double Minimum_dt
Minimum desired dt: if dt falls below this value, exit.
Definition problem.h:709
double arc_length_step_solve(double *const &parameter_pt, const double &ds, const unsigned &max_adapt=0)
Solve a steady problem using arc-length continuation, when the parameter that becomes a variable corr...
Definition problem.cc:10327
virtual void actions_after_adapt()
Actions that are to be performed after a mesh adaptation.
Definition problem.h:1045
void p_refine_uniformly()
p-refine (all) p-refineable (sub)mesh(es) uniformly and rebuild problem
Definition problem.h:2842
bool Use_continuation_timestepper
Boolean to control original or new storage of dof stuff.
Definition problem.h:778
void calculate_continuation_derivatives_fd(double *const &parameter_pt)
A function to calculate the derivatives with respect to the arc-length required for continuation by f...
Definition problem.cc:9851
LinearAlgebraDistribution * Dof_distribution_pt
The distribution of the DOFs in this problem. This object is created in the Problem constructor and s...
Definition problem.h:463
void refine_uniformly()
Refine (all) refineable (sub)mesh(es) uniformly and rebuild problem.
Definition problem.h:2745
static ContinuationStorageScheme Continuation_time_stepper
Storage for the single static continuation timestorage object.
Definition problem.h:781
bool Problem_is_nonlinear
Boolean flag indicating if we're dealing with a linear or nonlinear Problem – if set to false the New...
Definition problem.h:631
virtual void sparse_assemble_row_or_column_compressed_with_maps(Vector< int * > &column_or_row_index, Vector< int * > &row_or_column_start, Vector< double * > &value, Vector< unsigned > &nnz, Vector< double * > &residual, bool compressed_row_flag)
Private helper function that is used to assemble the Jacobian matrix in the case when the storage is ...
Definition problem.cc:4575
AssemblyHandler * Default_assembly_handler_pt
Pointer to the default assembly handler.
Definition problem.h:197
void get_my_eqns(AssemblyHandler *const &assembly_handler_pt, const unsigned &el_lo, const unsigned &el_hi, Vector< unsigned > &my_eqns)
Helper method that returns the (unique) global equations to which the elements in the range el_lo to ...
Definition problem.cc:6500
virtual void read(std::ifstream &restart_file, bool &unsteady_restart)
Read refinement pattern of all refineable meshes and refine them accordingly, then read all Data and ...
Definition problem.cc:12327
virtual ~Problem()
Virtual destructor to clean up memory.
Definition problem.cc:191
Mesh *& mesh_pt()
Return a pointer to the global mesh.
Definition problem.h:1300
void get_dofs(DoubleVector &dofs) const
Return the vector of dofs, i.e. a vector containing the current values of all unknowns.
Definition problem.cc:2575
Time * Time_pt
Pointer to global time for the problem.
Definition problem.h:200
DoubleVectorWithHaloEntries Element_count_per_dof
Counter that records how many elements contribute to each dof. Used to determine the (discrete) arc-l...
Definition problem.h:563
virtual void actions_before_newton_convergence_check()
Any actions that are to be performed before the residual is checked in the Newton method,...
Definition problem.h:1068
Time *& time_pt()
Return a pointer to the global time object.
Definition problem.h:1524
AssemblyHandler *& assembly_handler_pt()
Return a pointer to the assembly handler object.
Definition problem.h:1590
unsigned newton_solve_continuation(double *const &parameter_pt)
Perform a basic arc-length continuation step using Newton's method. Returns number of Newton steps ta...
Definition problem.cc:9409
double Max_permitted_error_for_halo_check
Threshold for error throwing in Problem::check_halo_schemes()
Definition problem.h:2280
unsigned Sparse_assemble_with_arrays_allocation_increment
the number of elements to add to a matrix row when the initial allocation is exceeded within the spar...
Definition problem.h:666
void reset_assembly_handler_to_default()
Reset the system to the standard non-augemented state.
Definition problem.cc:10308
void solve_eigenproblem(const unsigned &n_eval, Vector< std::complex< double > > &alpha, Vector< double > &beta, Vector< DoubleVector > &eigenvector_real, Vector< DoubleVector > &eigenvector_imag, const bool &steady=true)
Solve an eigenproblem as assembled by the Problem's constituent elements. Calculate (at least) n_eval...
Definition problem.cc:8245
virtual void actions_after_newton_step()
Any actions that are to be performed after each individual Newton step. Most likely to be used for di...
Definition problem.h:1078
bool Pause_at_end_of_sparse_assembly
Protected boolean flag to halt program execution during sparse assemble process to assess peak memory...
Definition problem.h:636
void remove_null_pointers_from_external_halo_node_storage()
Consolidate external halo node storage by removing nulled out pointers in external halo and haloed sc...
Definition problem.cc:3274
Vector< double > * Saved_dof_pt
Pointer to vector for backup of dofs.
Definition problem.h:210
void unsteady_newton_solve(const double &dt)
Advance time by dt and solve by Newton's method. This version always shifts time values.
Definition problem.cc:10996
AssemblyHandler * Assembly_handler_pt
Definition problem.h:188
virtual void actions_before_read_unstructured_meshes()
Actions that are to be performed before reading in restart data for problems involving unstructured b...
Definition problem.h:1115
Vector< TimeStepper * > Time_stepper_pt
The Vector of time steppers (there could be many different ones in multiphysics problems)
Definition problem.h:204
void doc_errors()
Get max and min error for all elements in submeshes.
Definition problem.h:3031
bool Mass_matrix_reuse_is_enabled
Is re-use of the mass matrix in explicit timestepping enabled Default:false.
Definition problem.h:694
void get_derivative_wrt_global_parameter(double *const &parameter_pt, DoubleVector &result)
Get the derivative of the entire residuals vector wrt a global parameter, used in continuation proble...
Definition problem.cc:7878
bool distributed() const
If we have MPI return the "problem has been distributed" flag, otherwise it can't be distributed so r...
Definition problem.h:828
bool are_hessian_products_calculated_analytically()
Function to determine whether the hessian products are calculated analytically.
Definition problem.h:306
bool does_pointer_correspond_to_problem_data(double *const &parameter_pt)
Return a boolean flag to indicate whether the pointer parameter_pt refers to values stored in a Data ...
Definition problem.cc:9879
ExplicitTimeStepper * Explicit_time_stepper_pt
Pointer to a single explicit timestepper.
Definition problem.h:207
double arc_length_step_solve_helper(double *const &parameter_pt, const double &ds, const unsigned &max_adapt)
Private helper function that actually contains the guts of the arc-length stepping,...
Definition problem.cc:10548
bool Use_predictor_values_as_initial_guess
Use values from the time stepper predictor as an initial guess.
Definition problem.h:232
RefineableElements are FiniteElements that may be subdivided into children to provide a better local ...
Base class for refineable meshes. Provides standardised interfaces for the following standard mesh ad...
General SolidMesh class.
Definition mesh.h:2570
A Class for nodes that deform elastically (i.e. position is an unknown in the problem)....
Definition nodes.h:1686
General SpineMesh class.
Definition spines.h:613
SuperLU Project Solver class. This is a combined wrapper for both SuperLU and SuperLU Dist....
TAdvectionDiffusionReactionElement<NREAGENT,DIM,NNODE_1D> elements are isoparametric triangular DIM-d...
void output(std::ostream &outfile)
Output function: x,y,u or x,y,z,u.
Base class for time-stepping schemes. Timestepper provides an approximation of the temporal derivativ...
virtual unsigned ndt() const =0
Number of timestep increments that are required by the scheme.
virtual void shift_time_values(Data *const &data_pt)=0
This function advances the Data's time history so that we can move on to the next timestep.
virtual void set_weights()=0
Function to set the weights for present timestep (don't need to pass present timestep or previous tim...
ExplicitTimeStepper * explicit_predictor_pt()
Get the pointer to the explicit timestepper to use as a predictor in adaptivity if Predict_by_explici...
virtual void calculate_predicted_values(Data *const &data_pt)
Do the predictor step for data stored in a Data object (currently empty – overwrite for specific sche...
virtual void set_predictor_weights()
Set the weights for the predictor previous timestep (currently empty – overwrite for specific scheme)
virtual void actions_before_timestep(Problem *problem_pt)
Interface for any actions that need to be performed before a time step.
virtual void actions_after_timestep(Problem *problem_pt)
Interface for any actions that need to be performed after a time step.
virtual void assign_initial_values_impulsive(Data *const &data_pt)=0
Initialise the time-history for the Data values corresponding to an impulsive start.
void make_steady()
Function to make the time stepper temporarily steady. This is trivially achieved by setting all the w...
virtual void undo_make_steady()
Reset the is_steady status of a specific TimeStepper to its default and re-assign the weights.
void update_predicted_time(const double &new_time)
Set the time that the current predictions apply for, only needed for paranoid checks when doing Predi...
Time *const & time_pt() const
Access function for the pointer to time (const version)
bool is_steady() const
Flag to indicate if a timestepper has been made steady (possibly temporarily to switch off time-depen...
virtual void set_error_weights()
Set the weights for the error computation, (currently empty – overwrite for specific scheme)
Class to keep track of discrete/continous time. It is essential to have a single Time object when usi...
double & time()
Return the current value of the continuous time.
void shift_dt()
Update all stored values of dt by shifting each value along the array. This function must be called b...
void initialise_dt(const double &dt_)
Set all timesteps to the same value, dt.
double & dt(const unsigned &t=0)
Return the value of the t-th stored timestep (t=0: present; t>0: previous).
unsigned ndt() const
Return the number of timesteps stored.
void resize(const unsigned &n_dt)
Resize the vector holding the number of previous timesteps and initialise the new values to zero.
Base class for tree-based refineable meshes.
TreeRoot is a Tree that forms the root of a (recursive) tree. The "root node" is special as it holds ...
Definition tree.h:324
Base class for triangle meshes (meshes made of 2D triangle elements). Note: we choose to template Tri...
A slight extension to the standard template vector class so that we can include "graceful" array rang...
Definition Vector.h:58
bool Doc_comprehensive_timings
Global boolean to switch on comprehensive timing – can probably be declared const false when developm...
void partition_distributed_mesh(Problem *problem_pt, const unsigned &objective, Vector< unsigned > &element_domain_on_this_proc, const bool &bypass_metis=false)
Use METIS to assign each element in an already-distributed mesh to a domain. On return,...
void partition_mesh(Problem *problem_pt, const unsigned &ndomain, const unsigned &objective, Vector< unsigned > &element_domain)
Use METIS to assign each element to a domain. On return, element_domain[ielem] contains the number of...
std::string to_string(T object, unsigned float_precision=8)
Conversion function that should work for anything with operator<< defined (at least all basic types).
void clean_up_memory()
Clean up function that deletes anything dynamically allocated in this namespace.
void setup()
Setup terminate helper.
double timer()
returns the time in seconds after some point in past
std::string convert_secs_to_formatted_string(const double &time_in_sec)
Returns a nicely formatted string from an input time in seconds; the format depends on the size of ti...
DRAIG: Change all instances of (SPATIAL_DIM) to (DIM-1).
void pause(std::string message)
Pause and display message.
OomphInfo oomph_info
Single (global) instantiation of the OomphInfo object – this is used throughout the library as a "rep...