navier_stokes_preconditioners.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//====================================================================
27
28namespace oomph
29{
30 ///////////////////////////////////////////////////////////////////////
31 ///////////////////////////////////////////////////////////////////////
32 ///////////////////////////////////////////////////////////////////////
33
34
35 //======start_of_namespace============================================
36 /// Namespace for exact solution for pressure advection diffusion
37 /// problem
38 //====================================================================
39 namespace PressureAdvectionDiffusionValidation
40 {
41 /// Flag for solution
42 unsigned Flag = 0;
43
44 /// Peclet number -- overwrite with actual Reynolds number
45 double Peclet = 0.0;
46
47 /// Wind
49 {
50 if (Flag == 0)
51 {
52 wind[0] = sin(6.0 * x[1]);
53 wind[1] = cos(6.0 * x[0]);
54 }
55 else
56 {
57 wind[0] = 1.0;
58 wind[1] = 0.0;
59 }
60 }
61
62 /// Exact solution as a Vector
64 {
65 u.resize(3);
66 wind_function(x, u);
67 if (Flag == 0)
68 {
69 u[2] = x[0] * x[0] * pow(1.0 - x[0], 2.0) * x[1] * x[1] *
70 pow(1.0 - x[1], 2.0);
71 }
72 else
73 {
74 u[2] = 0.1E1 - Peclet * x[0] * (0.1E1 - 0.5 * x[0]);
75 }
76 }
77
78 /// Exact solution as a scalar
79 void get_exact_u(const Vector<double>& x, double& u)
80 {
81 if (Flag == 0)
82 {
83 u = x[0] * x[0] * pow(1.0 - x[0], 2.0) * x[1] * x[1] *
84 pow(1.0 - x[1], 2.0);
85 }
86 else
87 {
88 u = 0.1E1 - Peclet * x[0] * (0.1E1 - 0.5 * x[0]);
89 }
90 }
91
92 /// Source function required to make the solution above an exact solution
94 {
95 double x[2];
96 x[0] = x_vect[0];
97 x[1] = x_vect[1];
98
99
100 double source = 0.0;
101
102 if (Flag == 0)
103 {
104 source =
105 Peclet *
106 (sin(0.6E1 * x[1]) * (2.0 * x[0] * pow(1.0 - x[0], 2.0) * x[1] *
107 x[1] * pow(1.0 - x[1], 2.0) -
108 2.0 * x[0] * x[0] * (1.0 - x[0]) * x[1] *
109 x[1] * pow(1.0 - x[1], 2.0)) +
110 cos(0.6E1 * x[0]) * (2.0 * x[0] * x[0] * pow(1.0 - x[0], 2.0) *
111 x[1] * pow(1.0 - x[1], 2.0) -
112 2.0 * x[0] * x[0] * pow(1.0 - x[0], 2.0) *
113 x[1] * x[1] * (1.0 - x[1]))) -
114 2.0 * pow(1.0 - x[0], 2.0) * x[1] * x[1] * pow(1.0 - x[1], 2.0) +
115 8.0 * x[0] * (1.0 - x[0]) * x[1] * x[1] * pow(1.0 - x[1], 2.0) -
116 2.0 * x[0] * x[0] * x[1] * x[1] * pow(1.0 - x[1], 2.0) -
117 2.0 * x[0] * x[0] * pow(1.0 - x[0], 2.0) * pow(1.0 - x[1], 2.0) +
118 8.0 * x[0] * x[0] * pow(1.0 - x[0], 2.0) * x[1] * (1.0 - x[1]) -
119 2.0 * x[0] * x[0] * pow(1.0 - x[0], 2.0) * x[1] * x[1];
120 }
121 else
122 {
123 source = Peclet * (-0.1E1 * Peclet * (0.1E1 - 0.5 * x[0]) +
124 0.5 * Peclet * x[0]) -
125 0.1E1 * Peclet;
126 }
127
128 return source;
129 }
130
131
132 } // namespace PressureAdvectionDiffusionValidation
133
134
135 ////////////////////////////////////////////////////////////////
136 ////////////////////////////////////////////////////////////////
137 ////////////////////////////////////////////////////////////////
138
139
140 //===========================================================================
141 /// Setup the least-squares commutator Navier Stokes preconditioner. This
142 /// extracts blocks corresponding to the velocity and pressure unknowns,
143 /// creates the matrices actually needed in the application of the
144 /// preconditioner and deletes what can be deleted... Note that
145 /// this preconditioner needs a CRDoubleMatrix.
146 //============================================================================
148 {
149 // For debugging...
150 bool doc_block_matrices = false;
151
152 // For output timing results - to be removed soon. Ray
153 bool raytime_flag = false;
154
155 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
156 // NOTE: In the interest of minimising memory usage, several containers
157 // are recycled, therefore their content/meaning changes
158 // throughout this function. The code is carefully annotated
159 // but you'll have to read it line by line!
160 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
162 // make sure any old data is deleted
165 double clean_up_memory_time =
167 if (raytime_flag)
168 {
169 oomph_info << "LSC: clean_up_memory_time: " << clean_up_memory_time
170 << std::endl;
171 }
172
173
174#ifdef PARANOID
175 // paranoid check that the navier stokes mesh pt has been set
176 if (Navier_stokes_mesh_pt == 0)
177 {
178 std::ostringstream error_message;
179 error_message << "The navier stokes elements mesh pointer must be set.\n"
180 << "Use method set_navier_stokes_mesh(...)";
181 throw OomphLibError(
183 }
184#endif
185
186
187 // Set the mesh
188 this->set_nmesh(1);
189 this->set_mesh(0,
192
193 // Get blocks
194 // ----------
195
196 // In comes the current Jacobian. Recast it to a CR double matrix;
197 // shout if that can't be done.
199
200
201#ifdef PARANOID
202 if (cr_matrix_pt == 0)
203 {
204 std::ostringstream error_message;
205 error_message
206 << "NavierStokesSchurComplementPreconditioner only works with "
207 << "CRDoubleMatrix matrices" << std::endl;
208 throw OomphLibError(
210 }
211#endif
212
213
215 {
216 std::stringstream junk;
217 junk << "j_matrix" << comm_pt()->my_rank() << ".dat";
218 oomph_info << "About to output " << junk.str() << std::endl;
219 cr_matrix_pt->sparse_indexed_output_with_offset(junk.str());
220 oomph_info << "Done output of " << junk.str() << std::endl;
221 }
222
223
224 // Set up block look up schemes (done automatically in the
225 // BlockPreconditioner base class, based on the information
226 // provided in the block-preconditionable elements in the problem)
227
228 // this preconditioner has two types of block:
229 // type 0: velocity - corresponding to DOFs 0 to n-2
230 // type 1: pressure - corresponding to DOF n-1
232 unsigned ndof_types = 0;
233
235 {
236 ndof_types = this->ndof_types();
237 }
238 else
239 {
240 // This is the upper-most master block preconditioner, the Navier-Stokes
241 // mesh is in position 0
242 ndof_types = this->ndof_types_in_mesh(0);
243 }
244
247
248 this->block_setup(dof_to_block_map);
249
252 if (Doc_time)
253 {
254 oomph_info << "Time for block_setup(...) [sec]: " << block_setup_time
255 << "\n";
256 }
257
258 if (raytime_flag)
259 {
260 oomph_info << "LSC: block_setup: " << block_setup_time << std::endl;
261 }
262
263
264 // determine whether the F preconditioner is a block preconditioner (and
265 // therefore a subsidiary preconditioner)
270 {
272 }
273
274 // Get B (the divergence block)
277 this->get_block(1, 0, *b_pt);
278
281 if (Doc_time)
282 {
283 oomph_info << "Time to get B [sec]: " << get_B_time << "\n";
284 }
285
286 if (raytime_flag)
287 {
288 oomph_info << "LSC: get block B get_B_time: " << get_B_time << std::endl;
289 }
290
292 {
293 std::stringstream junk;
294 junk << "b_matrix" << comm_pt()->my_rank() << ".dat";
295 b_pt->sparse_indexed_output_with_offset(junk.str());
296 oomph_info << "Done output of " << junk.str() << std::endl;
297 }
298
299
300 // get the inverse velocity and pressure mass matrices
303
305 if (Use_LSC)
306 {
307 // We only need the velocity mass matrix
310 }
311 else
312 {
313 // We need both mass matrices
316 }
317
319
321 if (Doc_time)
322 {
323 oomph_info << "Time to assemble inverse diagonal velocity and pressure"
324 << "mass matrices) [sec]: " << ivmm_assembly_time << "\n";
325 }
326 if (raytime_flag)
327 {
328 oomph_info << "LSC: ivmm_assembly_time: " << ivmm_assembly_time
329 << std::endl;
330 }
331
332
334 {
335 std::stringstream junk;
336 junk << "inv_v_mass_matrix" << comm_pt()->my_rank() << ".dat";
337 inv_v_mass_pt->sparse_indexed_output_with_offset(junk.str());
338 oomph_info << "Done output of " << junk.str() << std::endl;
339 }
340
341
342 // Get gradient matrix Bt
345 this->get_block(0, 1, *bt_pt);
347
349 if (Doc_time)
350 {
351 oomph_info << "Time to get Bt [sec]: " << t_get_Bt_time << std::endl;
352 }
353 if (raytime_flag)
354 {
355 oomph_info << "LSC: get block Bt: " << t_get_Bt_time << std::endl;
356 }
357
359 {
360 std::stringstream junk;
361 junk << "bt_matrix" << comm_pt()->my_rank() << ".dat";
362 bt_pt->sparse_indexed_output_with_offset(junk.str());
363 oomph_info << "Done output of " << junk.str() << std::endl;
364 }
365
366
367 // Build pressure Poisson matrix
369
370 // Multiply inverse velocity mass matrix by gradient matrix B^T
373 inv_v_mass_pt->multiply(*bt_pt, *qbt_pt);
374 delete bt_pt;
375 bt_pt = 0;
376
377 // Store product in bt_pt
378 bt_pt = qbt_pt;
380
382 if (Doc_time)
383 {
384 oomph_info << "Time to generate QBt [sec]: " << t_QBt_time << std::endl;
385 }
386 delete inv_v_mass_pt;
387 inv_v_mass_pt = 0;
388 if (raytime_flag)
389 {
390 oomph_info << "LSC: t_QBt_time (matrix multiplicaton): " << t_QBt_time
391 << std::endl;
392 }
393
394 // Multiply B from left by divergence matrix B and store result in
395 // pressure Poisson matrix.
397 b_pt->multiply(*bt_pt, *p_matrix_pt);
399
401 if (Doc_time)
402 {
403 oomph_info << "Time to generate P [sec]: " << t_p_time << std::endl;
404 }
405 // Kill divergence matrix because we don't need it any more
406 delete b_pt;
407 b_pt = 0;
408
409 if (raytime_flag)
410 {
411 oomph_info << "LSC: t_p_time (matrix multiplication): " << t_p_time
412 << std::endl;
413 }
414
415
416 // Build the matvec operator for QBt
421
423 if (Doc_time)
424 {
425 oomph_info << "Time to build QBt matrix vector operator [sec]: "
426 << t_p_time2 << std::endl;
427 }
428
429 // Kill gradient matrix B^T (it's been overwritten anyway and
430 // needs to be recomputed afresh below)
431 delete bt_pt;
432 bt_pt = 0;
433
434 if (raytime_flag)
435 {
436 oomph_info << "LSC: QBt (setup MV product): " << t_p_time2 << std::endl;
437 }
438
439 // Do we need the Fp stuff?
440 if (!Use_LSC)
441 {
442 // Get pressure advection diffusion matrix Fp and store in
443 // a "big" matrix (same size as the problem's Jacobian)
447
448 // Now extract the pressure pressure block
450 this->get_block_other_matrix(1, 1, &full_fp_matrix, *fp_matrix_pt);
452 if (Doc_time)
453 {
455 oomph_info << "Time to get Fp [sec]: " << t_get_Fp_time << std::endl;
456 }
457
458 // Build vector product of pressure advection diffusion matrix with
459 // inverse pressure mass matrix
462
463 // Build the matvec operator for E = F_p Q_p^{-1}
466 this->setup_matrix_vector_product(E_mat_vec_pt, fp_qp_inv_pt, 1);
468 if (Doc_time)
469 {
471 oomph_info << "Time to build Fp Qp^{-1} matrix vector operator [sec]: "
472 << t_p_time << std::endl;
473 }
474 // Kill pressure advection diffusion and inverse pressure mass matrices
475 delete inv_p_mass_pt;
476 inv_p_mass_pt = 0;
477 delete fp_qp_inv_pt;
478 fp_qp_inv_pt = 0;
479 }
480
481
482 // Get momentum block F
485 this->get_block(0, 0, *f_pt);
487
489 if (Doc_time)
490 {
491 oomph_info << "Time to get F [sec]: " << t_get_F_time << std::endl;
492 }
493 if (raytime_flag)
494 {
495 oomph_info << "LSC: get_block t_get_F_time: " << t_get_F_time
496 << std::endl;
497 }
498
499 // form the matrix vector product helper
504
506 if (Doc_time)
507 {
508 oomph_info << "Time to build F Matrix Vector Operator [sec]: "
509 << t_F_MV_time << std::endl;
510 }
511 if (raytime_flag)
512 {
513 oomph_info << "LSC: MV product setup t_F_MV_time: " << t_F_MV_time
514 << std::endl;
515 }
516
517
518 // if F is a block preconditioner then we can delete the F matrix
520 {
521 delete f_pt;
522 f_pt = 0;
523 }
524
525 // Rebuild Bt (remember that we temporarily overwrote
526 // it by its product with the inverse velocity mass matrix)
528 bt_pt = new CRDoubleMatrix;
529 this->get_block(0, 1, *bt_pt);
532 if (Doc_time)
533 {
534 oomph_info << "Time to get Bt [sec]: " << t_get_Bt_time2 << std::endl;
535 }
536 if (raytime_flag)
537 {
538 oomph_info << "LSC: get_block t_get_Bt_time2: " << t_get_Bt_time2
539 << std::endl;
540 }
541
542
543 // form the matrix vector operator for Bt
547
548 // if(Doc_time)
549 // {
550 // oomph_info << "Time to build Bt Matrix Vector Operator [sec]: "
551 // << t_Bt_MV_time << std::endl;
552 // }
553
554 delete bt_pt;
555 bt_pt = 0;
556
558
560 if (raytime_flag)
561 {
562 oomph_info << "LSC: MV product setup t_Bt_MV_time: " << t_Bt_MV_time
563 << std::endl;
564 }
565
566 // if the P preconditioner has not been setup
567 if (P_preconditioner_pt == 0)
568 {
572 }
573
574 // Setup the preconditioner for the Pressure matrix
576
578 {
579 std::stringstream junk;
580 junk << "p_matrix" << comm_pt()->my_rank() << ".dat";
581 p_matrix_pt->sparse_indexed_output_with_offset(junk.str());
582 oomph_info << "Done output of " << junk.str() << std::endl;
583 }
584
586 delete p_matrix_pt;
587 p_matrix_pt = 0;
589
591 if (Doc_time)
592 {
593 oomph_info << "P sub-preconditioner setup time [sec]: " << t_p_prec_time
594 << "\n";
595 }
596 if (raytime_flag)
597 {
598 oomph_info << "LSC: p_prec setup time: " << t_p_prec_time << std::endl;
599 }
600
601
602 // Set up solver for solution of system with momentum matrix
603 // ----------------------------------------------------------
604
605 // if the F preconditioner has not been setup
606 if (F_preconditioner_pt == 0)
607 {
611 }
612
613 // if F is a block preconditioner
616 {
617 unsigned nvelocity_dof_types =
619
621 for (unsigned i = 0; i < nvelocity_dof_types; i++)
622 {
623 dof_map[i] = i;
624 }
625
626 F_block_preconditioner_pt->turn_into_subsidiary_block_preconditioner(
627 this, dof_map);
628
630 }
631 // otherwise F is not a block preconditioner
632 else
633 {
635 delete f_pt;
636 f_pt = 0;
637 }
640 if (Doc_time)
641 {
642 oomph_info << "F sub-preconditioner setup time [sec]: " << t_f_prec_time
643 << "\n";
644 }
645 if (raytime_flag)
646 {
647 oomph_info << "LSC: f_prec setup time: " << t_f_prec_time << std::endl;
648 }
649
650 // Remember that the preconditioner has been setup so
651 // the stored information can be wiped when we
652 // come here next...
654 }
655
656
657 //=======================================================================
658 /// Apply preconditioner to r.
659 //=======================================================================
661 const DoubleVector& r, DoubleVector& z)
662 {
663#ifdef PARANOID
665 {
666 std::ostringstream error_message;
667 error_message << "setup must be called before using preconditioner_solve";
668 throw OomphLibError(
670 }
671 if (z.built())
672 {
673 if (z.nrow() != r.nrow())
674 {
675 std::ostringstream error_message;
676 error_message << "The vectors z and r must have the same number of "
677 << "of global rows";
678 throw OomphLibError(error_message.str(),
681 }
682 }
683#endif
684
685 // if z is not setup then give it the same distribution
686 if (!z.distribution_pt()->built())
687 {
688 z.build(r.distribution_pt(), 0.0);
689 }
690
691 // Step 1 - apply approximate Schur inverse to pressure unknowns (block 1)
692 // -----------------------------------------------------------------------
693
694 // Working vectors
698
699 // Copy pressure values from residual vector to temp_vec:
700 // Loop over all entries in the global vector (this one
701 // includes velocity and pressure dofs in some random fashion)
702 this->get_block_vector(1, r, temp_vec);
703
704 // NOTE: The vector temp_vec now contains the vector r_p.
705
706 // LSC version
707 if (Use_LSC)
708 {
709 // Solve first pressure Poisson system
710#ifdef PARANOID
711 // check a solver has been set
712 if (P_preconditioner_pt == 0)
713 {
714 std::ostringstream error_message;
715 error_message << "P_preconditioner_pt has not been set.";
716 throw OomphLibError(error_message.str(),
719 }
720#endif
721
722 // use some Preconditioner's preconditioner_solve function
724
725 // NOTE: The vector another_temp_vec now contains the vector P^{-1} r_p
726
727 // Multiply another_temp_vec by matrix E and stick the result into
728 // temp_vec
729 temp_vec.clear();
731 another_temp_vec.clear();
733 temp_vec.clear();
735
736
737 // NOTE: The vector temp_vec now contains E P^{-1} r_p
738
739 // Solve second pressure Poisson system using preconditioner_solve
740 another_temp_vec.clear();
742
743 // NOTE: The vector another_temp_vec now contains z_p = P^{-1} E P^{-1}
744 // r_p
745 // as required (apart from the sign which we'll fix in the
746 // next step.
747 }
748 // Fp version
749 else
750 {
751 // Multiply temp_vec by matrix E and stick the result into
752 // yet_another_temp_vec
754
755 // NOTE: The vector yet_another_temp_vec now contains Fp Qp^{-1} r_p
756
757 // Solve pressure Poisson system
758#ifdef PARANOID
759 // check a solver has been set
760 if (P_preconditioner_pt == 0)
761 {
762 std::ostringstream error_message;
763 error_message << "P_preconditioner_pt has not been set.";
764 throw OomphLibError(error_message.str(),
767 }
768#endif
769
770 // Solve second pressure Poisson system using preconditioner_solve
771 another_temp_vec.clear();
774
775 // NOTE: The vector another_temp_vec now contains
776 // z_p = P^{-1} Fp Qp^{-1} r_p
777 // as required (apart from the sign which we'll fix in the
778 // next step.
779 }
780
781 // Now copy another_temp_vec (i.e. z_p) back into the global vector z.
782 // Loop over all entries in the global results vector z:
783 temp_vec.build(another_temp_vec.distribution_pt(), 0.0);
786
787
788 // Step 2 - apply preconditioner to velocity unknowns (block 0)
789 // ------------------------------------------------------------
790
791 // Recall that another_temp_vec (computed above) contains the
792 // negative of the solution of the Schur complement systen, -z_p.
793 // Multiply by G (stored in Block_matrix_pt(0,1) and store
794 // result in temp_vec (vector resizes itself).
795 temp_vec.clear();
797
798 // NOTE: temp_vec now contains -G z_p
799
800 // The vector another_temp_vec is no longer needed -- re-use it to store
801 // velocity quantities:
802 another_temp_vec.clear();
803
804 // Loop over all enries in the global vector and find the
805 // entries associated with the velocities:
808
809 // NOTE: The vector another_temp_vec now contains r_u - G z_p
810
811 // Solve momentum system
812#ifdef PARANOID
813 // check a solver has been set
814 if (F_preconditioner_pt == 0)
815 {
816 std::ostringstream error_message;
817 error_message << "F_preconditioner_pt has not been set.";
818 throw OomphLibError(
820 }
821#endif
822
823 // use some Preconditioner's preconditioner solve
824 // and return
826 {
829 }
830 else
831 {
834 }
835 }
836
837
838 //========================================================================
839 /// Helper function to assemble the inverse diagonals of the pressure and
840 /// velocity mass matrix from the elemental contributions defined in
841 /// NavierStokesElementWithDiagonalMassMatrices::
842 /// get_pressure_and_velocity_mass_matrix_diagonal(...)
843 /// If do_both=true, both are computed, otherwise only the velocity
844 /// mass matrix (the LSC version of the preconditioner only needs
845 /// that one)
846 //========================================================================
851 const bool& do_both)
852 {
853 // determine the velocity rows required by this processor
854 unsigned v_first_row = this->block_distribution_pt(0)->first_row();
855 unsigned v_nrow_local = this->block_distribution_pt(0)->nrow_local();
856 unsigned v_nrow = this->block_distribution_pt(0)->nrow();
857
858 // create storage for the diagonals
859 double* v_values = new double[v_nrow_local];
860 for (unsigned i = 0; i < v_nrow_local; i++)
861 {
862 v_values[i] = 0.0;
863 }
864
865 // Equivalent information for pressure mass matrix (only needed for
866 // Fp version)
867 unsigned p_first_row = 0;
868 unsigned p_nrow_local = 0;
869 unsigned p_nrow = 0;
870 double* p_values = 0;
871 if (!Use_LSC)
872 {
873 // determine the pressure rows required by this processor
874 p_first_row = this->block_distribution_pt(1)->first_row();
875 p_nrow_local = this->block_distribution_pt(1)->nrow_local();
876 p_nrow = this->block_distribution_pt(1)->nrow();
877
878 // create storage for the diagonals
879 p_values = new double[p_nrow_local];
880 for (unsigned i = 0; i < p_nrow_local; i++)
881 {
882 p_values[i] = 0.0;
883 }
884 }
885
886 // if the problem is distributed
887 bool distributed = false;
888#ifdef OOMPH_HAS_MPI
889 if (problem_pt()->distributed() ||
891 {
892 distributed = true;
893 }
894#endif
895
896 // next we get the diagonal velocity mass matrix data
897 if (distributed)
898 {
899#ifdef OOMPH_HAS_MPI
900
901 // the number of processors
902 unsigned nproc = comm_pt()->nproc();
903
904 // and my rank
905 unsigned my_rank = comm_pt()->my_rank();
906
907 // determine the rows for which we have lookup rows
908
909 // if the problem is NOT distributed then we only classify global
910 // equations on this processor to avoid duplication (as every processor
911 // holds every element)
912 unsigned first_lookup_row = 0;
913 unsigned last_lookup_row = 0;
914 first_lookup_row = this->master_distribution_pt()->first_row();
915 last_lookup_row =
916 first_lookup_row + this->master_distribution_pt()->nrow_local() - 1;
917
918 // find number of local elements
919 unsigned n_el = Navier_stokes_mesh_pt->nelement();
920
921 // get the master distribution pt
924
925 // Do the two blocks (0: veloc; 1: press)
926 unsigned max_block = 0;
927 if (!Use_LSC) max_block = 1;
928 for (unsigned block_index = 0; block_index <= max_block; block_index++)
929 {
930 // Local working variables: Default to velocity
931 unsigned v_or_p_first_row = v_first_row;
932 double* v_or_p_values = v_values;
933 // Switch to pressure
934 if (block_index == 1)
935 {
938 }
939
940
941 // the diagonal mass matrix contributions that have been
942 // classified and should be sent to another processor
945
946 // the corresponding block indices
948
949 // the matrix contributions that cannot be classified by this processor
950 // and therefore must be sent to another for classification
953
954 // the corresponding global indices that require classification
957
958 // get the velocity or pressure distribution pt
961
962 // get the contribution for each element
963 for (unsigned e = 0; e < n_el; e++)
964 {
965 // Get element
967
968 // check that the element is not halo
969 if (!el_pt->is_halo())
970 {
971 // find number of degrees of freedom in the element
972 // (this is slightly too big because it includes the
973 // pressure dofs but this doesn't matter)
974 unsigned el_dof = el_pt->ndof();
975
976 // Allocate local storage for the element's contribution to the
977 // mass matrix diagonal
980
981 unsigned which_one = 2;
982 if (block_index == 1) which_one = 1;
983
985 cast_el_pt =
987 if (cast_el_pt != 0)
988 {
989 cast_el_pt->get_pressure_and_velocity_mass_matrix_diagonal(
991 }
992
993 // get the contribution for each dof
994 for (unsigned i = 0; i < el_dof; i++)
995 {
996 // Get the equation number
997 unsigned eqn_number = el_pt->eqn_number(i);
998
999 // if I have lookup information on this processor
1000 if ((eqn_number >= first_lookup_row) &&
1001 (eqn_number <= last_lookup_row))
1002 {
1003 // Only use the dofs that we're dealing with here
1004 if (this->block_number(eqn_number) == int(block_index))
1005 {
1006 // get the index in the block
1007 unsigned index = this->index_in_block(eqn_number);
1008
1009 // determine which processor requires the block index
1010 for (unsigned p = 0; p < nproc; p++)
1011 {
1012 if ((index >= velocity_or_press_dist_pt->first_row(p)) &&
1013 (index < (velocity_or_press_dist_pt->first_row(p) +
1014 velocity_or_press_dist_pt->nrow_local(p))))
1015 {
1016 // if it is required by this processor then add the
1017 // contribution
1018 if (p == my_rank)
1019 {
1020 if (block_index == 0)
1021 {
1024 }
1025 else if (block_index == 1)
1026 {
1029 }
1030 }
1031 // otherwise store it for communication
1032 else
1033 {
1034 if (block_index == 0)
1035 {
1038 classified_indices_send[p].push_back(index);
1039 }
1040 else if (block_index == 1)
1041 {
1044 classified_indices_send[p].push_back(index);
1045 }
1046 }
1047 }
1048 }
1049 }
1050 }
1051 // if we do not have the lookup information on this processor
1052 // then we send the mass matrix contribution to a processor
1053 // which we know to have the lookup information
1054 // the assumption: the processor for which the master block
1055 // preconditioner distribution will definitely hold the lookup
1056 // data for eqn_number (although others may)
1057 else if (problem_pt()->distributed())
1058 {
1059 // determine which processor requires the block index
1060 unsigned p = 0;
1061 while (
1062 !(eqn_number >= master_distribution_pt->first_row(p) &&
1063 (eqn_number < (master_distribution_pt->first_row(p) +
1064 master_distribution_pt->nrow_local(p)))))
1065 {
1066 p++;
1067 }
1068
1069 // store the data
1070 if (block_index == 0)
1071 {
1074 unclassified_indices_send[p].push_back(eqn_number);
1075 }
1076 else if (block_index == 1)
1077 {
1080 unclassified_indices_send[p].push_back(eqn_number);
1081 }
1082 }
1083 }
1084 }
1085 }
1086
1087 // next the unclassified contributions are communicated to
1088 // processors that can classify them
1089
1090 // first determine how many unclassified rows are to be sent to
1091 // each processor
1092 unsigned* n_unclassified_send = new unsigned[nproc];
1093 for (unsigned p = 0; p < nproc; p++)
1094 {
1095 if (p == my_rank)
1096 {
1098 }
1099 else
1100 {
1102 }
1103 }
1104
1105 // then all-to-all com number of unclassified to be sent / recv
1106 unsigned* n_unclassified_recv = new unsigned[nproc];
1108 1,
1111 1,
1113 comm_pt()->mpi_comm());
1114
1115 // the base displacement for the sends
1118
1119 // allocate storage for the data to be received
1120 // and post the sends and recvs
1126 for (unsigned p = 0; p < nproc; p++)
1127 {
1128 if (p != my_rank)
1129 {
1130 // recv
1131 if (n_unclassified_recv[p] > 0)
1132 {
1134 new double[n_unclassified_recv[p]];
1136 new unsigned[n_unclassified_recv[p]];
1137
1138 // data for the struct data type
1141 int recv_sz[2];
1142
1143 // contributions
1148 &recv_displacements[0]);
1150 recv_sz[0] = 1;
1151
1152 // indices
1157 &recv_displacements[1]);
1159 recv_sz[1] = 1;
1160
1161 // build the final recv type
1166
1167 // and recv
1170 1,
1172 p,
1173 0,
1174 comm_pt()->mpi_comm(),
1175 &req);
1177 unclassified_recv_proc.push_back(p);
1181 }
1182
1183 // send
1184 if (n_unclassified_send[p] > 0)
1185 {
1186 // data for the struct data type
1189 int send_sz[2];
1190
1191 // contributions
1196 &send_displacements[0]);
1198 send_sz[0] = 1;
1199
1200 // indices
1205 &send_displacements[1]);
1207 send_sz[1] = 1;
1208
1209 // build the final send type
1214
1215 // and send
1218 1,
1220 p,
1221 0,
1222 comm_pt()->mpi_comm(),
1223 &req);
1228 }
1229 }
1230 }
1231
1232 // next classify the data as it is received
1234 while (n_unclassified_recv_req > 0)
1235 {
1236 // get the processor number and remove the completed request
1237 // for the vector of requests
1238 int req_num;
1241 &req_num,
1243 unsigned p = unclassified_recv_proc[req_num];
1245 req_num);
1247 req_num);
1249
1250 // next classify the dofs
1251 // and store them for sending to other processors if required
1252 unsigned n_recv = n_unclassified_recv[p];
1253 for (unsigned i = 0; i < n_recv; i++)
1254 {
1255 unsigned eqn_number = unclassified_indices_recv[p][i];
1256 // Only deal with our block unknowns
1257 if (this->block_number(eqn_number) == int(block_index))
1258 {
1259 // get the index in the block
1260 unsigned index = this->index_in_block(eqn_number);
1261
1262 // determine which processor requires the block index
1263 for (unsigned pp = 0; pp < nproc; pp++)
1264 {
1265 if ((index >= velocity_or_press_dist_pt->first_row(pp)) &&
1266 (index < (velocity_or_press_dist_pt->first_row(pp) +
1267 velocity_or_press_dist_pt->nrow_local(pp))))
1268 {
1269 // if it is required by this processor then add the
1270 // contribution
1271 if (pp == my_rank)
1272 {
1275 }
1276 // otherwise store it for communication
1277 else
1278 {
1281 classified_indices_send[pp].push_back(index);
1282 }
1283 }
1284 }
1285 }
1286 }
1287
1288 // clean up
1290 delete[] unclassified_indices_recv[p];
1291 }
1292 delete[] n_unclassified_recv;
1293
1294 // now all indices have been classified
1295
1296 // next the classified contributions are communicated to
1297 // processors that require them
1298
1299 // first determine how many classified rows are to be sent to
1300 // each processor
1301 unsigned* n_classified_send = new unsigned[nproc];
1302 for (unsigned p = 0; p < nproc; p++)
1303 {
1304 if (p == my_rank)
1305 {
1306 n_classified_send[p] = 0;
1307 }
1308 else
1309 {
1311 }
1312 }
1313
1314 // then all-to-all number of classified to be sent / recv
1315 unsigned* n_classified_recv = new unsigned[nproc];
1317 1,
1320 1,
1322 comm_pt()->mpi_comm());
1323
1324 // allocate storage for the data to be received
1325 // and post the sends and recvs
1331 for (unsigned p = 0; p < nproc; p++)
1332 {
1333 if (p != my_rank)
1334 {
1335 // recv
1336 if (n_classified_recv[p] > 0)
1337 {
1339 new double[n_classified_recv[p]];
1340 classified_indices_recv[p] = new unsigned[n_classified_recv[p]];
1341
1342 // data for the struct data type
1345 int recv_sz[2];
1346
1347 // contributions
1352 &recv_displacements[0]);
1354 recv_sz[0] = 1;
1355
1356 // indices
1361 &recv_displacements[1]);
1363 recv_sz[1] = 1;
1364
1365 // build the final recv type
1370
1371 // and recv
1374 1,
1376 p,
1377 0,
1378 comm_pt()->mpi_comm(),
1379 &req);
1380 classified_recv_requests.push_back(req);
1381 classified_recv_proc.push_back(p);
1385 }
1386
1387 // send
1388 if (n_classified_send[p] > 0)
1389 {
1390 // data for the struct data type
1393 int send_sz[2];
1394
1395 // contributions
1400 &send_displacements[0]);
1402 send_sz[0] = 1;
1403
1404 // indices
1409 &send_displacements[1]);
1411 send_sz[1] = 1;
1412
1413 // build the final send type
1418
1419 // and send
1422 1,
1424 p,
1425 0,
1426 comm_pt()->mpi_comm(),
1427 &req);
1428 classified_send_requests.push_back(req);
1432 }
1433 }
1434 }
1435
1436 // next classify the data as it is received
1438 while (n_classified_recv_req > 0)
1439 {
1440 // get the processor number and remove the completed request
1441 // for the vector of requests
1442 int req_num;
1445 &req_num,
1447 unsigned p = classified_recv_proc[req_num];
1449 req_num);
1452
1453 // next classify the dofs
1454 // and store them for sending to other processors if required
1455 unsigned n_recv = n_classified_recv[p];
1456 for (unsigned i = 0; i < n_recv; i++)
1457 {
1460 }
1461
1462 // clean up
1464 delete[] classified_indices_recv[p];
1465 }
1466
1467 // wait for the unclassified sends to complete
1470 {
1474 }
1477 delete[] n_unclassified_send;
1478
1479 // wait for the classified sends to complete
1481 if (n_classified_send_req > 0)
1482 {
1486 }
1487 delete[] classified_indices_send;
1489 delete[] n_classified_recv;
1490 delete[] n_classified_send;
1491
1492 // Copy the values back where they belong
1493 if (block_index == 0)
1494 {
1496 }
1497 else if (block_index == 1)
1498 {
1500 }
1501 }
1502
1503#endif
1504 }
1505 // or if the problem is not distributed
1506 else
1507 {
1508 // find number of elements
1509 unsigned n_el = Navier_stokes_mesh_pt->nelement();
1510
1511 // Fp needs pressure and velocity mass matrices
1512 unsigned which_one = 0;
1513 if (Use_LSC) which_one = 2;
1514
1515 // get the contribution for each element
1516 for (unsigned e = 0; e < n_el; e++)
1517 {
1518 // Get element
1520
1521 // find number of degrees of freedom in the element
1522 // (this is slightly too big because it includes the
1523 // pressure dofs but this doesn't matter)
1524 unsigned el_dof = el_pt->ndof();
1525
1526 // allocate local storage for the element's contribution to the
1527 // pressure and velocity mass matrix diagonal
1530
1532 cast_el_pt =
1534 if (cast_el_pt != 0)
1535 {
1536 cast_el_pt->get_pressure_and_velocity_mass_matrix_diagonal(
1538 }
1539 else
1540 {
1541#ifdef PARANOID
1542 std::ostringstream error_message;
1543 error_message
1544 << "Navier-Stokes mesh contains element that is not of type \n"
1545 << "NavierStokesElementWithDiagonalMassMatrices. \n"
1546 << "The element is in fact of type " << typeid(*el_pt).name()
1547 << "\nWe'll assume that it does not make a used contribution \n"
1548 << "to the inverse diagonal mass matrix used in the "
1549 "preconditioner\n"
1550 << "and (to avoid divisions by zero) fill in dummy unit entries.\n"
1551 << "[This case currently arises with flux control problems\n"
1552 << "where for simplicity the NetFluxControlElement has been added "
1553 "\n"
1554 << "to the Navier Stokes mesh -- this should probably be changed "
1555 "at\n"
1556 << "some point -- if you get this warning in any other context\n"
1557 << "you should check your code very carefully]\n";
1558 OomphLibWarning(error_message.str(),
1559 "NavierStokesSchurComplementPreconditioner::assemble_"
1560 "inv_press_and_veloc_mass_matrix_diagonal()",
1562#endif
1563
1564 // Fill in dummy entries to stop division by zero below
1565 for (unsigned j = 0; j < el_dof; j++)
1566 {
1567 el_vmm_diagonal[j] = 1.0;
1568 el_pmm_diagonal[j] = 1.0;
1569 }
1570 }
1571
1572 // Get the contribution for each dof
1573 for (unsigned i = 0; i < el_dof; i++)
1574 {
1575 // Get the equation number
1576 unsigned eqn_number = el_pt->eqn_number(i);
1577
1578 // Get the velocity dofs
1579 if (this->block_number(eqn_number) == 0)
1580 {
1581 // get the index in the block
1582 unsigned index = this->index_in_block(eqn_number);
1583
1584 // if it is required on this processor
1585 if ((index >= v_first_row) &&
1586 (index < (v_first_row + v_nrow_local)))
1587 {
1589 }
1590 }
1591 // Get the pressure dofs
1592 else if (this->block_number(eqn_number) == 1)
1593 {
1594 if (!Use_LSC)
1595 {
1596 // get the index in the block
1597 unsigned index = this->index_in_block(eqn_number);
1598
1599 // if it is required on this processor
1600 if ((index >= p_first_row) &&
1601 (index < (p_first_row + p_nrow_local)))
1602 {
1604 }
1605 }
1606 }
1607 }
1608 }
1609 }
1610
1611 // Create column index and row start for velocity mass matrix
1612 int* v_column_index = new int[v_nrow_local];
1613 int* v_row_start = new int[v_nrow_local + 1];
1614 for (unsigned i = 0; i < v_nrow_local; i++)
1615 {
1616#ifdef PARANOID
1617 if (v_values[i] == 0.0)
1618 {
1619 std::ostringstream error_message;
1620 error_message << "Zero entry in diagonal of velocity mass matrix\n"
1621 << "Index: " << i << std::endl;
1622 throw OomphLibError(error_message.str(),
1625 }
1626#endif
1627 v_values[i] = 1.0 / v_values[i];
1629 v_row_start[i] = i;
1630 }
1632
1633 // Build the velocity mass matrix
1635 inv_v_mass_pt->build_without_copy(
1637
1638 // Create pressure mass matrix
1639 if (!Use_LSC)
1640 {
1641 // Create column index and row start for pressure mass matrix
1642 int* p_column_index = new int[p_nrow_local];
1643 int* p_row_start = new int[p_nrow_local + 1];
1644 for (unsigned i = 0; i < p_nrow_local; i++)
1645 {
1646#ifdef PARANOID
1647 if (p_values[i] == 0.0)
1648 {
1649 std::ostringstream error_message;
1650 error_message << "Zero entry in diagonal of pressure mass matrix\n"
1651 << "Index: " << i << std::endl;
1652 throw OomphLibError(error_message.str(),
1655 }
1656#endif
1657 p_values[i] = 1.0 / p_values[i];
1658
1660 p_row_start[i] = i;
1661 }
1663
1664 // Build the pressure mass matrix
1666 inv_p_mass_pt->build_without_copy(
1668 }
1669 }
1670
1671 //=========================================================================
1672 /// Helper function to delete preconditioner data.
1673 //=========================================================================
1675 {
1677 {
1678 // delete matvecs
1679 delete Bt_mat_vec_pt;
1680 Bt_mat_vec_pt = 0;
1681
1682 delete F_mat_vec_pt;
1683 F_mat_vec_pt = 0;
1684
1685 delete QBt_mat_vec_pt;
1686 QBt_mat_vec_pt = 0;
1687
1688 delete E_mat_vec_pt;
1689 E_mat_vec_pt = 0;
1690
1691 // delete stuff from velocity solve
1693 {
1694 delete F_preconditioner_pt;
1696 }
1697
1698 // delete stuff from Schur complement approx
1700 {
1701 delete P_preconditioner_pt;
1703 }
1704 }
1705 }
1706
1707
1708} // namespace oomph
e
Definition cfortran.h:571
cstr elem_len * i
Definition cfortran.h:603
void return_block_vector(const unsigned &n, const DoubleVector &b, DoubleVector &v) const
Takes the n-th block ordered vector, b, and copies its entries to the appropriate entries in the natu...
unsigned ndof_types_in_mesh(const unsigned &i) const
Return the number of DOF types in mesh i. WARNING: This should only be used by the upper-most master ...
const LinearAlgebraDistribution * master_distribution_pt() const
Access function to the distribution of the master preconditioner. If this preconditioner does not hav...
void get_block(const unsigned &i, const unsigned &j, CRDoubleMatrix &output_matrix, const bool &ignore_replacement_block=false) const
Put block (i,j) into output_matrix. This block accounts for any coarsening of dof types and any repla...
int index_in_block(const unsigned &i_dof) const
Given a global dof number, returns the index in the block it belongs to. This is the overall index,...
void get_block_vector(const unsigned &n, const DoubleVector &v, DoubleVector &b) const
Takes the naturally ordered vector, v and returns the n-th block vector, b. Here n is the block numbe...
int block_number(const unsigned &i_dof) const
Return the block number corresponding to a global index i_dof.
bool is_subsidiary_block_preconditioner() const
Return true if this preconditioner is a subsidiary preconditioner.
void get_block_other_matrix(const unsigned &i, const unsigned &j, CRDoubleMatrix *in_matrix_pt, CRDoubleMatrix &output_matrix)
Get a block from a different matrix using the blocking scheme that has already been set up.
unsigned ndof_types() const
Return the total number of DOF types.
void setup_matrix_vector_product(MatrixVectorProduct *matvec_prod_pt, CRDoubleMatrix *block_pt, const Vector< unsigned > &block_col_indices)
Setup a matrix vector product. matvec_prod_pt is a pointer to the MatrixVectorProduct,...
void set_nmesh(const unsigned &n)
Specify the number of meshes required by this block preconditioner. Note: elements in different meshe...
CRDoubleMatrix * matrix_pt() const
Access function to matrix_pt. If this is the master then cast the matrix pointer to MATRIX*,...
const LinearAlgebraDistribution * block_distribution_pt(const unsigned &b) const
Access function to the block distributions (const version).
virtual void block_setup()
Determine the size of the matrix blocks and setup the lookup schemes relating the global degrees of f...
void set_mesh(const unsigned &i, const Mesh *const mesh_pt, const bool &allow_multiple_element_type_in_mesh=false)
Set the i-th mesh for this block preconditioner. Note: The method set_nmesh(...) must be called befor...
A class for compressed row matrices. This is a distributable object.
Definition matrices.h:888
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
bool distributed() const
distribution is serial or distributed
unsigned nrow() const
access function to the number of global rows.
LinearAlgebraDistribution * distribution_pt() const
access to the LinearAlgebraDistribution
A vector in the mathematical sense, initially developed for linear algebra type applications....
void build(const DoubleVector &old_vector)
Just copys the argument DoubleVector.
double size() const
Calculate the size of the element (length, area, volume,...) in Eulerian computational coordinates....
Definition elements.cc:4320
unsigned dim() const
Return the spatial dimension of the element, i.e. the number of local coordinates required to paramet...
Definition elements.h:2615
A Generalised Element class.
Definition elements.h:73
bool is_halo() const
Is this element a halo?
Definition elements.h:1150
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
Describes the distribution of a distributable linear algebra type object. Typically this is a contain...
bool built() const
if the communicator_pt is null then the distribution is not setup then false is returned,...
Matrix vector product helper class - primarily a wrapper to Trilinos's Epetra matrix vector product m...
void multiply_transpose(const DoubleVector &x, DoubleVector &y) const
Apply the transpose of the operator to the vector x and return the result in the vector y.
void multiply(const DoubleVector &x, DoubleVector &y) const
Apply the operator to the vector x and return the result in the vector y.
FiniteElement * finite_element_pt(const unsigned &e) const
Upcast (downcast?) to FiniteElement (needed to access FiniteElement member functions).
Definition mesh.h:477
GeneralisedElement *& element_pt(const unsigned long &e)
Return pointer to element e.
Definition mesh.h:452
unsigned long nelement() const
Return number of elements in the mesh.
Definition mesh.h:598
Pure virtual base class for elements that can be used with Navier-Stokes Schur complement preconditio...
Definition elements.h:5235
bool Doc_time
Set Doc_time to true for outputting results of timings.
Preconditioner * F_preconditioner_pt
Pointer to the 'preconditioner' for the F matrix.
bool Preconditioner_has_been_setup
Control flag is true if the preconditioner has been setup (used so we can wipe the data when the prec...
void clean_up_memory()
Helper function to delete preconditioner data.
Mesh * Navier_stokes_mesh_pt
the pointer to the mesh of block preconditionable Navier Stokes elements.
MatrixVectorProduct * F_mat_vec_pt
MatrixVectorProduct operator for F.
MatrixVectorProduct * E_mat_vec_pt
MatrixVectorProduct operator for E = Fp Qp^{-1} (only for Fp variant)
bool Use_LSC
Boolean to indicate use of LSC (true) or Fp (false) variant.
bool Using_default_p_preconditioner
flag indicating whether the default P preconditioner is used
MatrixVectorProduct * QBt_mat_vec_pt
MatrixVectorProduct operator for Qv^{-1} Bt.
bool Using_default_f_preconditioner
flag indicating whether the default F preconditioner is used
bool Allow_multiple_element_type_in_navier_stokes_mesh
Flag to indicate if there are multiple element types in the Navier-Stokes mesh.
Preconditioner * P_preconditioner_pt
Pointer to the 'preconditioner' for the pressure matrix.
void assemble_inv_press_and_veloc_mass_matrix_diagonal(CRDoubleMatrix *&inv_p_mass_pt, CRDoubleMatrix *&inv_v_mass_pt, const bool &do_both)
Helper function to assemble the inverse diagonals of the pressure and velocity mass matrices from the...
void get_pressure_advection_diffusion_matrix(CRDoubleMatrix &fp_matrix)
Get the pressure advection diffusion matrix.
bool F_preconditioner_is_block_preconditioner
Boolean indicating whether the momentum system preconditioner is a block preconditioner.
MatrixVectorProduct * Bt_mat_vec_pt
MatrixVectorProduct operator for Bt.
void preconditioner_solve(const DoubleVector &r, DoubleVector &z)
Apply preconditioner to Vector r.
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....
virtual const OomphCommunicator * comm_pt() const
Get function for comm pointer.
virtual void setup(DoubleMatrixBase *matrix_pt)
Setup the preconditioner: store the matrix pointer and the communicator pointer then call preconditio...
virtual void preconditioner_solve(const DoubleVector &r, DoubleVector &z)=0
Apply the preconditioner. Pure virtual generic interface function. This method should apply the preco...
TAdvectionDiffusionReactionElement<NREAGENT,DIM,NNODE_1D> elements are isoparametric triangular DIM-d...
Preconditioner * create_exact_preconditioner()
Factory function to create suitable exact preconditioner.
void get_exact_u(const Vector< double > &x, Vector< double > &u)
Exact solution as a Vector.
double source_function(const Vector< double > &x_vect)
Source function required to make the solution above an exact solution.
void wind_function(const Vector< double > &x, Vector< double > &wind)
Wind.
double Peclet
Peclet number – overwrite with actual Reynolds number.
double timer()
returns the time in seconds after some point in past
DRAIG: Change all instances of (SPATIAL_DIM) to (DIM-1).
OomphInfo oomph_info
Single (global) instantiation of the OomphInfo object – this is used throughout the library as a "rep...