fsi_pseudo_solid_collapsible_channel.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-2023 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 // Generic oomph-lib includes
28 #include "generic.h"
29 #include "navier_stokes.h"
30 #include "beam.h"
31 #include "solid.h"
32 #include "constitutive.h"
33 
34 // The wall mesh
35 #include "meshes/one_d_lagrangian_mesh.h"
36 
37 //Include the fluid mesh
38 #include "meshes/collapsible_channel_mesh.h"
39 
40 using namespace std;
41 
42 using namespace oomph;
43 
44 
45 //======================================================================
46 /// Upgrade mesh to solid mesh
47 //======================================================================
48 template <class ELEMENT>
50  public virtual CollapsibleChannelMesh<ELEMENT>,
51  public virtual SolidMesh
52 {
53 
54 
55 public:
56 
57  /// Constructor: Build mesh and copy Eulerian coords to Lagrangian
58  /// ones so that the initial configuration is the stress-free one.
59  ElasticCollapsibleChannelMesh<ELEMENT>(const unsigned& nup,
60  const unsigned& ncollapsible,
61  const unsigned& ndown,
62  const unsigned& ny,
63  const double& lup,
64  const double& lcollapsible,
65  const double& ldown,
66  const double& ly,
67  GeomObject* wall_pt,
68  TimeStepper* time_stepper_pt=
69  &Mesh::Default_TimeStepper) :
70  CollapsibleChannelMesh<ELEMENT>(nup, ncollapsible, ndown, ny,
71  lup, lcollapsible, ldown, ly,
72  wall_pt,
73  time_stepper_pt)
74  {
75  /// Make the current configuration the undeformed one by
76  /// setting the nodal Lagrangian coordinates to their current
77  /// Eulerian ones
78  set_lagrangian_nodal_coordinates();
79  }
80 
81 };
82 
83 
84 
85 
86 //==========start_of_BL_Squash =========================================
87 /// Namespace to define the mapping [0,1] -> [0,1] that re-distributes
88 /// nodal points across the channel width.
89 //======================================================================
90 namespace BL_Squash
91 {
92 
93  /// Boundary layer width
94  double Delta=0.1;
95 
96  /// Fraction of points in boundary layer
97  double Fract_in_BL=0.5;
98 
99  /// Mapping [0,1] -> [0,1] that re-distributes
100  /// nodal points across the channel width
101  double squash_fct(const double& s)
102  {
103  // Default return
104  double y=s;
105  if (s<0.5*Fract_in_BL)
106  {
107  y=Delta*2.0*s/Fract_in_BL;
108  }
109  else if (s>1.0-0.5*Fract_in_BL)
110  {
111  y=2.0*Delta/Fract_in_BL*s+1.0-2.0*Delta/Fract_in_BL;
112  }
113  else
114  {
115  y=(1.0-2.0*Delta)/(1.0-Fract_in_BL)*s+
116  (Delta-0.5*Fract_in_BL)/(1.0-Fract_in_BL);
117  }
118 
119  return y;
120  }
121 }// end of BL_Squash
122 
123 
124 
125 /// ///////////////////////////////////////////////////////////////////////
126 /// ///////////////////////////////////////////////////////////////////////
127 /// ///////////////////////////////////////////////////////////////////////
128 
129 
130 //====start_of_underformed_wall============================================
131 /// Undeformed wall is a steady, straight 1D line in 2D space
132 /// \f[ x = X_0 + \zeta \f]
133 /// \f[ y = H \f]
134 //=========================================================================
135 class UndeformedWall : public GeomObject
136 {
137 
138 public:
139 
140  /// Constructor: arguments are the starting point and the height
141  /// above y=0.
142  UndeformedWall(const double& x0, const double& h): GeomObject(1,2)
143  {
144  X0=x0;
145  H=h;
146  }
147 
148 
149  /// Position vector at Lagrangian coordinate zeta
150  void position(const Vector<double>& zeta, Vector<double>& r) const
151  {
152  // Position Vector
153  r[0] = zeta[0]+X0;
154  r[1] = H;
155  }
156 
157 
158  /// Parametrised position on object: r(zeta). Evaluated at
159  /// previous timestep. t=0: current time; t>0: previous
160  /// timestep. Calls steady version.
161  void position(const unsigned& t, const Vector<double>& zeta,
162  Vector<double>& r) const
163  {
164  // Use the steady version
165  position(zeta,r);
166 
167  } // end of position
168 
169 
170  /// Posn vector and its 1st & 2nd derivatives
171  /// w.r.t. to coordinates:
172  /// \f$ \frac{dR_i}{d \zeta_\alpha}\f$ = drdzeta(alpha,i).
173  /// \f$ \frac{d^2R_i}{d \zeta_\alpha d \zeta_\beta}\f$ =
174  /// ddrdzeta(alpha,beta,i). Evaluated at current time.
175  virtual void d2position(const Vector<double>& zeta,
176  Vector<double>& r,
177  DenseMatrix<double> &drdzeta,
178  RankThreeTensor<double> &ddrdzeta) const
179  {
180  // Position vector
181  r[0] = zeta[0]+X0;
182  r[1] = H;
183 
184  // Tangent vector
185  drdzeta(0,0)=1.0;
186  drdzeta(0,1)=0.0;
187 
188  // Derivative of tangent vector
189  ddrdzeta(0,0,0)=0.0;
190  ddrdzeta(0,0,1)=0.0;
191 
192  } // end of d2position
193 
194  private :
195 
196  /// x position of the undeformed beam's left end.
197  double X0;
198 
199  /// Height of the undeformed wall above y=0.
200  double H;
201 
202 }; //end_of_undeformed_wall
203 
204 
205 
206 
207 
208 
209 
210 //====start_of_physical_parameters=====================
211 /// Namespace for phyical parameters
212 //======================================================
214 {
215  /// Reynolds number
216  double Re=50.0;
217 
218  /// Womersley = Reynolds times Strouhal
219  double ReSt=50.0;
220 
221  /// Default pressure on the left boundary
222  double P_up=0.0;
223 
224  /// Pseudo-solid mass density
225  double Lambda_sq=0.0;
226 
227  /// Pseudo-solid Poisson ratio
228  double Nu=0.1;
229 
230  /// Traction applied on the fluid at the left (inflow) boundary
231  void prescribed_traction(const double& t,
232  const Vector<double>& x,
233  const Vector<double>& n,
234  Vector<double>& traction)
235  {
236  traction.resize(2);
237  traction[0]=P_up;
238  traction[1]=0.0;
239 
240  } //end traction
241 
242  /// Non-dimensional wall thickness. As in Jensen & Heil (2003) paper.
243  double H=1.0e-2;
244 
245  /// 2nd Piola Kirchhoff pre-stress. As in Jensen & Heil (2003) paper.
246  double Sigma0=1.0e3;
247 
248  /// External pressure
249  double P_ext=0.0;
250 
251  /// Load function: Apply a constant external pressure to the wall.
252  /// Note: This is the load without the fluid contribution!
253  /// Fluid load gets added on by FSIWallElement.
254  void load(const Vector<double>& xi, const Vector<double>& x,
255  const Vector<double>& N, Vector<double>& load)
256  {
257  for(unsigned i=0;i<2;i++)
258  {
259  load[i] = -P_ext*N[i];
260  }
261  } //end of load
262 
263 
264  /// Fluid structure interaction parameter: Ratio of stresses used for
265  /// non-dimensionalisation of fluid to solid stresses.
266  double Q=1.0e-5;
267 
268 
269 } // end of namespace
270 
271 
272 
273 
274 //====start_of_problem_class==========================================
275 /// Problem class
276 //====================================================================
277 template <class ELEMENT>
278 class FSICollapsibleChannelProblem : public Problem
279 {
280 
281  public :
282 
283 /// Constructor: The arguments are the number of elements and
284 /// the lengths of the domain.
285  FSICollapsibleChannelProblem(const unsigned& nup,
286  const unsigned& ncollapsible,
287  const unsigned& ndown,
288  const unsigned& ny,
289  const double& lup,
290  const double& lcollapsible,
291  const double& ldown,
292  const double& ly);
293 
294  /// Destructor (empty)
296 
297  /// Access function for the specific bulk (fluid) mesh
299  {
300  // Upcast from pointer to the Mesh base class to the specific
301  // element type that we're using here.
302  return dynamic_cast<
304  (Bulk_mesh_pt);
305  }
306 
307 
308  /// Access function for the wall mesh
309  OneDLagrangianMesh<FSIHermiteBeamElement>* wall_mesh_pt()
310  {
311  return Wall_mesh_pt;
312 
313  } // end of access to wall mesh
314 
315 
316  /// Update the problem specs before solve (empty)
318 
319  /// Update the problem after solve (empty)
321 
322  /// Update no slip before Newton convergence check
324  {
325  // Moving wall: No slip; this implies that the velocity needs
326  // to be updated in response to wall motion
327  unsigned ibound=3;
328  unsigned num_nod=bulk_mesh_pt()->nboundary_node(ibound);
329  for (unsigned inod=0;inod<num_nod;inod++)
330  {
331  // Which node are we dealing with?
332  Node* node_pt=bulk_mesh_pt()->boundary_node_pt(ibound,inod);
333 
334  // Apply no slip
335  FSI_functions::apply_no_slip_on_moving_wall(node_pt);
336  }
337  }
338 
339 
340  /// Doc the solution
341  void doc_solution(DocInfo& doc_info,ofstream& trace_file);
342 
343  /// Apply initial conditions
345 
346 private :
347 
348  /// Create the prescribed traction elements on boundary b
349  void create_traction_elements(const unsigned &b,
350  Mesh* const &bulk_mesh_pt,
351  Mesh* const &traction_mesh_pt);
352 
353  /// Delete prescribed traction elements from the surface mesh
354  void delete_traction_elements(Mesh* const &traction_mesh_pt);
355 
356  /// Create elements that enforce prescribed boundary motion
357  /// by Lagrange multipliers
358  void create_lagrange_multiplier_elements();
359 
360  /// Delete elements that enforce prescribed boundary motion
361  /// by Lagrange multipliers
362  void delete_lagrange_multiplier_elements();
363 
364  /// Number of elements in the x direction in the upstream part of the channel
365  unsigned Nup;
366 
367  /// Number of elements in the x direction in the collapsible part of
368  /// the channel
369  unsigned Ncollapsible;
370 
371  /// Number of elements in the x direction in the downstream part of the channel
372  unsigned Ndown;
373 
374  /// Number of elements across the channel
375  unsigned Ny;
376 
377  /// x-length in the upstream part of the channel
378  double Lup;
379 
380  /// x-length in the collapsible part of the channel
381  double Lcollapsible;
382 
383  /// x-length in the downstream part of the channel
384  double Ldown;
385 
386  /// Transverse length
387  double Ly;
388 
389  /// Pointer to the "bulk" mesh
391 
392  /// Pointer to the "surface" mesh that applies the traction at the
393  /// inflow
394  Mesh* Applied_fluid_traction_mesh_pt;
395 
396  /// Pointers to mesh of Lagrange multiplier elements
398 
399  /// Pointer to the "wall" mesh
400  OneDLagrangianMesh<FSIHermiteBeamElement>* Wall_mesh_pt;
401 
402  /// Geometric object incarnation of the wall mesh
403  MeshAsGeomObject* Wall_geom_object_pt;
404 
405  /// Pointer to the left control node
406  Node* Left_node_pt;
407 
408  /// Pointer to right control node
409  Node* Right_node_pt;
410 
411  /// Pointer to control node on the wall
412  Node* Wall_node_pt;
413 
414  /// Constitutive law used to determine the mesh deformation
415  ConstitutiveLaw *Constitutive_law_pt;
416 
417 
418 };//end of problem class
419 
420 
421 
422 
423 //=====start_of_constructor======================================
424 /// Constructor for the collapsible channel problem
425 //===============================================================
426 template <class ELEMENT>
428  const unsigned& nup,
429  const unsigned& ncollapsible,
430  const unsigned& ndown,
431  const unsigned& ny,
432  const double& lup,
433  const double& lcollapsible,
434  const double& ldown,
435  const double& ly)
436 {
437  // Store problem parameters
438  Nup=nup;
439  Ncollapsible=ncollapsible;
440  Ndown=ndown;
441  Ny=ny;
442  Lup=lup;
443  Lcollapsible=lcollapsible;
444  Ldown=ldown;
445  Ly=ly;
446 
447 
448  // Overwrite maximum allowed residual to accomodate bad initial guesses
449  Problem::Max_residuals=1000.0;
450 
451  // Allocate the timestepper for the Navier-Stokes equations
452  BDF<2>* fluid_time_stepper_pt=new BDF<2>;
453 
454  // Add the fluid timestepper to the Problem's collection of timesteppers.
455  add_time_stepper_pt(fluid_time_stepper_pt);
456 
457  // Create a dummy Steady timestepper that stores two history values
458  Steady<2>* wall_time_stepper_pt = new Steady<2>;
459 
460  // Add the wall timestepper to the Problem's collection of timesteppers.
461  add_time_stepper_pt(wall_time_stepper_pt);
462 
463  // Geometric object that represents the undeformed wall:
464  // A straight line at height y=ly; starting at x=lup.
465  UndeformedWall* undeformed_wall_pt=new UndeformedWall(lup,ly);
466 
467  //Create the "wall" mesh with FSI Hermite beam elements, passing the
468  //dummy wall timestepper to the constructor
469  Wall_mesh_pt = new OneDLagrangianMesh<FSIHermiteBeamElement>
470  (Ncollapsible,Lcollapsible,undeformed_wall_pt,wall_time_stepper_pt);
471 
472  // Build a geometric object (one Lagrangian, two Eulerian coordinates)
473  // from the wall mesh
474  Wall_geom_object_pt=
475  new MeshAsGeomObject(Wall_mesh_pt);
476 
477 
478 
479  //Build bulk (fluid) mesh
480  Bulk_mesh_pt =
482  (nup, ncollapsible, ndown, ny,
483  lup, lcollapsible, ldown, ly,
484  Wall_geom_object_pt,
485  fluid_time_stepper_pt);
486 
487  // Set a non-trivial boundary-layer-squash function...
488  Bulk_mesh_pt->bl_squash_fct_pt() = &BL_Squash::squash_fct;
489 
490  // ... and update the nodal positions accordingly
491  bool update_all_solid_nodes=true;
492  Bulk_mesh_pt->node_update(update_all_solid_nodes);
493  Bulk_mesh_pt->set_lagrangian_nodal_coordinates();
494 
495  // Create "surface mesh" that will contain only the prescribed-traction
496  // elements. The constructor just creates the mesh without
497  // giving it any elements, nodes, etc.
498  Applied_fluid_traction_mesh_pt = new Mesh;
499 
500  // Create prescribed-traction elements from all elements that are
501  // adjacent to boundary 5 (left boundary), but add them to a separate mesh.
502  create_traction_elements(5,Bulk_mesh_pt,Applied_fluid_traction_mesh_pt);
503 
504  // Construct the mesh of elements that enforce prescribed boundary motion
505  // by Lagrange multipliers
506  Lagrange_multiplier_mesh_pt=new SolidMesh;
507  create_lagrange_multiplier_elements();
508 
509  // Add the sub meshes to the problem
510  add_sub_mesh(Bulk_mesh_pt);
511  add_sub_mesh(Applied_fluid_traction_mesh_pt);
512  add_sub_mesh(Wall_mesh_pt);
513  add_sub_mesh(Lagrange_multiplier_mesh_pt);
514 
515  // Combine all submeshes into a single Mesh
516  build_global_mesh();
517 
518  //Set the constitutive law
519  Constitutive_law_pt = new GeneralisedHookean(&Global_Physical_Variables::Nu);
520 
521  // Complete build of fluid mesh
522  //-----------------------------
523 
524  // Loop over the elements to set up element-specific
525  // things that cannot be handled by constructor
526  unsigned n_element=Bulk_mesh_pt->nelement();
527  for(unsigned e=0;e<n_element;e++)
528  {
529 
530  // Upcast from GeneralisedElement to the present element
531  ELEMENT* el_pt = dynamic_cast<ELEMENT*>(Bulk_mesh_pt->element_pt(e));
532 
533  //Set the Reynolds number
534  el_pt->re_pt() = &Global_Physical_Variables::Re;
535 
536  // Set the Womersley number
537  el_pt->re_st_pt() = &Global_Physical_Variables::ReSt;
538 
539  //Set the constitutive law
540  el_pt->constitutive_law_pt() = Constitutive_law_pt;
541 
542  // Density of pseudo-solid
543  el_pt->lambda_sq_pt()=&Global_Physical_Variables::Lambda_sq;
544 
545  } // end loop over elements
546 
547 
548  // Apply boundary conditions for fluid
549  //------------------------------------
550 
551 
552  //Pin the velocity on the boundaries
553  //x and y-velocities pinned along boundary 0 (bottom boundary) :
554  unsigned ibound=0;
555  unsigned num_nod= bulk_mesh_pt()->nboundary_node(ibound);
556  for (unsigned inod=0;inod<num_nod;inod++)
557  {
558  for(unsigned i=0;i<2;i++)
559  {
560  bulk_mesh_pt()->boundary_node_pt(ibound, inod)->pin(i);
561  }
562  }
563 
564  //x and y-velocities pinned along boundaries 2, 3, 4 (top boundaries) :
565  for(ibound=2;ibound<5;ibound++)
566  {
567  num_nod= bulk_mesh_pt()->nboundary_node(ibound);
568  for (unsigned inod=0;inod<num_nod;inod++)
569  {
570  for(unsigned i=0;i<2;i++)
571  {
572  bulk_mesh_pt()->boundary_node_pt(ibound, inod)->pin(i);
573  }
574  }
575  }
576 
577  //y-velocity pinned along boundary 1 (right boundary):
578  ibound=1;
579  num_nod= bulk_mesh_pt()->nboundary_node(ibound);
580  for (unsigned inod=0;inod<num_nod;inod++)
581  {
582  bulk_mesh_pt()->boundary_node_pt(ibound, inod)->pin(1);
583  }
584 
585 
586  //y-velocity pinned along boundary 5 (left boundary):
587  ibound=5;
588  num_nod= bulk_mesh_pt()->nboundary_node(ibound);
589  for (unsigned inod=0;inod<num_nod;inod++)
590  {
591 
592  bulk_mesh_pt()->boundary_node_pt(ibound, inod)->pin(1);
593 
594  }//end of pin_velocity
595 
596 
597  //Pin the nodal position on all boundaries apart from 3 (the moving wall)
598  for (unsigned ibound=0;ibound<6;ibound++)
599  {
600  if (ibound!=3)
601  {
602  unsigned num_nod= bulk_mesh_pt()->nboundary_node(ibound);
603  for (unsigned inod=0;inod<num_nod;inod++)
604  {
605  for(unsigned i=0;i<2;i++)
606  {
607  dynamic_cast<SolidNode*>(bulk_mesh_pt()->
608  boundary_node_pt(ibound, inod))
609  ->pin_position(i);
610  }
611  }
612  }
613  }
614 
615  // Now pin all nodal positions in the rigid bits
616  unsigned nnod=bulk_mesh_pt()->nnode();
617  for (unsigned j=0;j<nnod;j++)
618  {
619  SolidNode* nod_pt=dynamic_cast<SolidNode*>(bulk_mesh_pt()->node_pt(j));
620  if ((nod_pt->x(0)<=lup)||(nod_pt->x(0)>=(lup+lcollapsible)))
621  {
622  for(unsigned i=0;i<2;i++)
623  {
624  nod_pt->pin_position(i);
625  }
626  }
627  }
628 
629 
630  // Complete build of applied traction elements
631  //--------------------------------------------
632 
633  // Loop over the traction elements to pass pointer to prescribed
634  // traction function
635  unsigned n_el=Applied_fluid_traction_mesh_pt->nelement();
636  for(unsigned e=0;e<n_el;e++)
637  {
638  // Upcast from GeneralisedElement to NavierStokes traction element
639  NavierStokesTractionElement<ELEMENT> *el_pt =
640  dynamic_cast< NavierStokesTractionElement<ELEMENT>*>(
641  Applied_fluid_traction_mesh_pt->element_pt(e));
642 
643  // Set the pointer to the prescribed traction function
644  el_pt->traction_fct_pt() = &Global_Physical_Variables::prescribed_traction;
645 
646  }
647 
648 
649 
650 
651  // Complete build of wall elements
652  //--------------------------------
653 
654  //Loop over the elements to set physical parameters etc.
655  n_element = wall_mesh_pt()->nelement();
656  for(unsigned e=0;e<n_element;e++)
657  {
658  // Upcast to the specific element type
659  FSIHermiteBeamElement *elem_pt =
660  dynamic_cast<FSIHermiteBeamElement*>(wall_mesh_pt()->element_pt(e));
661 
662  // Set physical parameters for each element:
663  elem_pt->sigma0_pt() = &Global_Physical_Variables::Sigma0;
664  elem_pt->h_pt() = &Global_Physical_Variables::H;
665 
666  // Set the load vector for each element
667  elem_pt->load_vector_fct_pt() = &Global_Physical_Variables::load;
668 
669  // Function that specifies the load ratios
670  elem_pt->q_pt() = &Global_Physical_Variables::Q;
671 
672  // Set the undeformed shape for each element
673  elem_pt->undeformed_beam_pt() = undeformed_wall_pt;
674 
675  // The normal on the wall elements as computed by the FSIHermiteElements
676  // points away from the fluid rather than into the fluid (as assumed
677  // by default)
678  elem_pt->set_normal_pointing_out_of_fluid();
679 
680  } // end of loop over elements
681 
682 
683 
684  // Boundary conditions for wall mesh
685  //----------------------------------
686 
687  // Set the boundary conditions: Each end of the beam is fixed in space
688  // Loop over the boundaries (ends of the beam)
689  for(unsigned b=0;b<2;b++)
690  {
691  // Pin displacements in both x and y directions
692  wall_mesh_pt()->boundary_node_pt(b,0)->pin_position(0);
693  wall_mesh_pt()->boundary_node_pt(b,0)->pin_position(1);
694  }
695 
696 
697 
698  //Choose control nodes
699  //---------------------
700 
701  // Left boundary
702  ibound=5;
703  num_nod= bulk_mesh_pt()->nboundary_node(ibound);
704  unsigned control_nod=num_nod/2;
705  Left_node_pt= bulk_mesh_pt()->boundary_node_pt(ibound, control_nod);
706 
707  // Right boundary
708  ibound=1;
709  num_nod= bulk_mesh_pt()->nboundary_node(ibound);
710  control_nod=num_nod/2;
711  Right_node_pt= bulk_mesh_pt()->boundary_node_pt(ibound, control_nod);
712 
713 
714  // Set the pointer to the control node on the wall
715  num_nod= wall_mesh_pt()->nnode();
716  Wall_node_pt=wall_mesh_pt()->node_pt(Ncollapsible/2);
717 
718 
719 
720  // Setup FSI
721  //----------
722 
723  // The velocity of the fluid nodes on the wall (fluid mesh boundary 3)
724  // is set by the wall motion -- hence the no-slip condition needs to be
725  // re-applied whenever a node update is performed for these nodes.
726  // Such tasks may be performed automatically by the auxiliary node update
727  // function specified by a function pointer:
728  ibound=3;
729  num_nod= bulk_mesh_pt()->nboundary_node(ibound);
730  for (unsigned inod=0;inod<num_nod;inod++)
731  {
732  bulk_mesh_pt()->boundary_node_pt(ibound, inod)->
733  set_auxiliary_node_update_fct_pt(
734  FSI_functions::apply_no_slip_on_moving_wall);
735  }
736 
737  // Work out which fluid dofs affect the residuals of the wall elements:
738  // We pass the boundary between the fluid and solid meshes and
739  // pointers to the meshes. The interaction boundary is boundary 3 of the
740  // 2D fluid mesh.
741  FSI_functions::setup_fluid_load_info_for_solid_elements<ELEMENT,2>
742  (this,3,Bulk_mesh_pt,Wall_mesh_pt);
743 
744  // Setup equation numbering scheme
745  cout <<"Number of equations: " << assign_eqn_numbers() << std::endl;
746 
747 
748 }//end of constructor
749 
750 
751 
752 
753 //====start_of_doc_solution===================================================
754 /// Doc the solution
755 //============================================================================
756 template <class ELEMENT>
758  ofstream& trace_file)
759 {
760  ofstream some_file;
761  char filename[100];
762 
763  // Number of plot points
764  unsigned npts;
765  npts=5;
766 
767  // Output fluid solution
768  sprintf(filename,"%s/soln%i.dat",doc_info.directory().c_str(),
769  doc_info.number());
770  some_file.open(filename);
771  bulk_mesh_pt()->output(some_file,npts);
772  some_file.close();
773 
774  // Document the wall shape
775  sprintf(filename,"%s/beam%i.dat",doc_info.directory().c_str(),
776  doc_info.number());
777  some_file.open(filename);
778  wall_mesh_pt()->output(some_file,npts);
779  some_file.close();
780 
781  // Loop over all elements do dump out previous solutions
782  // (get the number of previous timesteps available from the wall
783  // timestepper)
784  unsigned nsteps=time_stepper_pt(1)->nprev_values();
785  for (unsigned t=0;t<=nsteps;t++)
786  {
787  sprintf(filename,"%s/wall%i-%i.dat",doc_info.directory().c_str(),
788  doc_info.number(),t);
789  some_file.open(filename);
790  unsigned n_elem=wall_mesh_pt()->nelement();
791  for (unsigned ielem=0;ielem<n_elem;ielem++)
792  {
793  dynamic_cast<FSIHermiteBeamElement*>(wall_mesh_pt()->element_pt(ielem))->
794  output(t,some_file,npts);
795  }
796  some_file.close();
797  } // end of output of previous solutions
798 
799 
800 
801  // Write trace file
802  trace_file << time_pt()->time() << " "
803  << Wall_node_pt->x(1) << " "
804  << Left_node_pt->value(0) << " "
805  << Right_node_pt->value(0) << " "
807  << std::endl;
808 
809 } // end_of_doc_solution
810 
811 
812 
813 
814 
815 //=====start_of_create_traction_elements======================================
816 /// Create the traction elements
817 //============================================================================
818 template <class ELEMENT>
820  const unsigned &b, Mesh* const &bulk_mesh_pt, Mesh* const &traction_mesh_pt)
821 {
822 
823  // How many bulk elements are adjacent to boundary b?
824  unsigned n_element = bulk_mesh_pt->nboundary_element(b);
825 
826  // Loop over the bulk elements adjacent to boundary b?
827  for(unsigned e=0;e<n_element;e++)
828  {
829  // Get pointer to the bulk element that is adjacent to boundary b
830  ELEMENT* bulk_elem_pt = dynamic_cast<ELEMENT*>
831  (bulk_mesh_pt->boundary_element_pt(b,e));
832 
833  //What is the index of the face of element e along boundary
834  int face_index = bulk_mesh_pt->face_index_at_boundary(b,e);
835 
836  // Build the corresponding prescribed-traction element
837  NavierStokesTractionElement<ELEMENT>* flux_element_pt =
838  new NavierStokesTractionElement<ELEMENT>(bulk_elem_pt,face_index);
839 
840  //Add the prescribed-traction element to the surface mesh
841  traction_mesh_pt->add_element_pt(flux_element_pt);
842 
843  } //end of loop over bulk elements adjacent to boundary b
844 
845 } // end of create_traction_elements
846 
847 
848 
849 //============start_of_delete_traction_elements==============================
850 /// Delete traction elements and wipe the surface mesh
851 //=======================================================================
852 template<class ELEMENT>
854 delete_traction_elements(Mesh* const &surface_mesh_pt)
855 {
856  // How many surface elements are in the surface mesh
857  unsigned n_element = surface_mesh_pt->nelement();
858 
859  // Loop over the surface elements
860  for(unsigned e=0;e<n_element;e++)
861  {
862  // Kill surface element
863  delete surface_mesh_pt->element_pt(e);
864  }
865 
866  // Wipe the mesh
867  surface_mesh_pt->flush_element_and_node_storage();
868 
869 } // end of delete_traction_elements
870 
871 
872 //============start_of_create_lagrange_multiplier_elements===============
873 /// Create elements that impose the prescribed boundary displacement
874 //=======================================================================
875 template<class ELEMENT>
878 {
879  // Lagrange multiplier elements are located on boundary 3:
880  unsigned b=3;
881 
882  // How many bulk elements are adjacent to boundary b?
883  unsigned n_element = bulk_mesh_pt()->nboundary_element(b);
884 
885  // Loop over the bulk elements adjacent to boundary b?
886  for(unsigned e=0;e<n_element;e++)
887  {
888  // Get pointer to the bulk element that is adjacent to boundary b
889  ELEMENT* bulk_elem_pt = dynamic_cast<ELEMENT*>(
890  bulk_mesh_pt()->boundary_element_pt(b,e));
891 
892  //Find the index of the face of element e along boundary b
893  int face_index = bulk_mesh_pt()->face_index_at_boundary(b,e);
894 
895  // Create new element and add to mesh
896  Lagrange_multiplier_mesh_pt->add_element_pt(
897  new ImposeDisplacementByLagrangeMultiplierElement<ELEMENT>(
898  bulk_elem_pt,face_index));
899  }
900 
901 
902  // Loop over the elements in the Lagrange multiplier element mesh
903  // for elements on the top boundary (boundary 3)
904  n_element=Lagrange_multiplier_mesh_pt->nelement();
905  for(unsigned i=0;i<n_element;i++)
906  {
907  //Cast to a Lagrange multiplier element
908  ImposeDisplacementByLagrangeMultiplierElement<ELEMENT> *el_pt =
909  dynamic_cast<ImposeDisplacementByLagrangeMultiplierElement<ELEMENT>*>
910  (Lagrange_multiplier_mesh_pt->element_pt(i));
911 
912  // Set the GeomObject that defines the boundary shape and set
913  // which bulk boundary we are attached to(needed to extract
914  // the boundary coordinate from the bulk nodes)
915  el_pt->set_boundary_shape_geom_object_pt(Wall_geom_object_pt,b);
916 
917  // Loop over the nodes
918  unsigned nnod=el_pt->nnode();
919  for (unsigned j=0;j<nnod;j++)
920  {
921  Node* nod_pt = el_pt->node_pt(j);
922 
923  // Is the node also on boundary 2 or 4?
924  if ((nod_pt->is_on_boundary(2))||(nod_pt->is_on_boundary(4)))
925  {
926  // How many nodal values were used by the "bulk" element
927  // that originally created this node?
928  unsigned n_bulk_value=el_pt->nbulk_value(j);
929 
930  // The remaining ones are Lagrange multipliers and we pin them.
931  unsigned nval=nod_pt->nvalue();
932  for (unsigned j=n_bulk_value;j<nval;j++)
933  {
934  nod_pt->pin(j);
935  }
936  }
937  }
938  }
939 
940 } // end of create_lagrange_multiplier_elements
941 
942 
943 
944 
945 //====start_of_delete_lagrange_multiplier_elements=======================
946 /// Delete elements that impose the prescribed boundary displacement
947 /// and wipe the associated mesh
948 //=======================================================================
949 template<class ELEMENT>
952 {
953  // How many surface elements are in the surface mesh
954  unsigned n_element = Lagrange_multiplier_mesh_pt->nelement();
955 
956  // Loop over the surface elements
957  for(unsigned e=0;e<n_element;e++)
958  {
959  // Kill surface element
960  delete Lagrange_multiplier_mesh_pt->element_pt(e);
961  }
962 
963  // Wipe the mesh
964  Lagrange_multiplier_mesh_pt->flush_element_and_node_storage();
965 
966 } // end of delete_lagrange_multiplier_elements
967 
968 
969 
970 //====start_of_apply_initial_condition========================================
971 /// Apply initial conditions
972 //============================================================================
973 template <class ELEMENT>
975 {
976  // Check that timestepper is from the BDF family
977  if (time_stepper_pt()->type()!="BDF")
978  {
979  std::ostringstream error_stream;
980  error_stream << "Timestepper has to be from the BDF family!\n"
981  << "You have specified a timestepper from the "
982  << time_stepper_pt()->type() << " family" << std::endl;
983 
984  throw OomphLibError(error_stream.str(),
985  OOMPH_CURRENT_FUNCTION,
986  OOMPH_EXCEPTION_LOCATION);
987  }
988 
989  // Loop over the nodes to set initial guess everywhere
990  unsigned num_nod = bulk_mesh_pt()->nnode();
991  for (unsigned n=0;n<num_nod;n++)
992  {
993  // Get nodal coordinates
994  Vector<double> x(2);
995  x[0]=bulk_mesh_pt()->node_pt(n)->x(0);
996  x[1]=bulk_mesh_pt()->node_pt(n)->x(1);
997 
998  // Assign initial condition: Steady Poiseuille flow
999  bulk_mesh_pt()->node_pt(n)->set_value(0,6.0*(x[1]/Ly)*(1.0-(x[1]/Ly)));
1000  bulk_mesh_pt()->node_pt(n)->set_value(1,0.0);
1001  }
1002 
1003  // Assign initial values for an impulsive start
1004  bulk_mesh_pt()->assign_initial_values_impulsive();
1005  wall_mesh_pt()->assign_initial_values_impulsive();
1006 
1007 } // end of set_initial_condition
1008 
1009 
1010 
1011 
1012 //============start_of_main====================================================
1013 /// Driver code for a collapsible channel problem with FSI.
1014 /// Presence of command line arguments indicates validation run with
1015 /// coarse resolution and small number of timesteps.
1016 //=============================================================================
1017 int main(int argc, char* argv[])
1018 {
1019 
1020  // Store command line arguments
1021  CommandLineArgs::setup(argc,argv);
1022 
1023  // Reduction in resolution for validation run?
1024  unsigned coarsening_factor=4;
1025  if (CommandLineArgs::Argc>1)
1026  {
1027  coarsening_factor=4;
1028  }
1029 
1030  // Number of elements in the domain
1031  unsigned nup=20/coarsening_factor;
1032  unsigned ncollapsible=40/coarsening_factor;
1033  unsigned ndown=40/coarsening_factor;
1034  unsigned ny=16/coarsening_factor;
1035 
1036  // Length of the domain
1037  double lup=5.0;
1038  double lcollapsible=10.0;
1039  double ldown=10.0;
1040  double ly=1.0;
1041 
1042  // Set external pressure (on the wall stiffness scale).
1044 
1045  // Pressure on the left boundary: This is consistent with steady
1046  // Poiseuille flow
1047  Global_Physical_Variables::P_up=12.0*(lup+lcollapsible+ldown);
1048 
1049 
1050 #ifdef TAYLOR_HOOD
1051 
1052  // Build the problem with QtaylorHoodElements
1054  <PseudoSolidNodeUpdateElement<
1055  QTaylorHoodElement<2>,
1056  QPVDElement<2,3> > >
1057  problem(nup, ncollapsible, ndown, ny,
1058  lup, lcollapsible, ldown, ly);
1059 
1060 #else
1061 
1062  // Build the problem with QCrouzeixRaviartElements
1064  <PseudoSolidNodeUpdateElement<
1065  QCrouzeixRaviartElement<2>,
1066  QPVDElement<2,3> > >
1067  problem(nup, ncollapsible, ndown, ny,
1068  lup, lcollapsible, ldown, ly);
1069 
1070 #endif
1071 
1072  // Timestep. Note: Preliminary runs indicate that the period of
1073  // the oscillation is about 1 so this gives us 40 steps per period.
1074  double dt=1.0/40.0;
1075 
1076  // Initial time for the simulation
1077  double t_min=0.0;
1078 
1079  // Maximum time for simulation
1080  double t_max=3.5;
1081 
1082  // Initialise timestep
1083  problem.time_pt()->time()=t_min;
1084  problem.initialise_dt(dt);
1085 
1086  // Apply initial condition
1087  problem.set_initial_condition();
1088 
1089  //Set output directory
1090  DocInfo doc_info;
1091  doc_info.set_directory("RESLT");
1092 
1093  // Open a trace file
1094  ofstream trace_file;
1095  char filename[100];
1096  sprintf(filename,"%s/trace.dat",doc_info.directory().c_str());
1097  trace_file.open(filename);
1098 
1099  // Output the initial condition
1100  problem.doc_solution(doc_info, trace_file);
1101 
1102  // Increment step number
1103  doc_info.number()++;
1104 
1105  // Find number of timesteps (reduced for validation)
1106  unsigned nstep = unsigned((t_max-t_min)/dt);
1107  if (CommandLineArgs::Argc>1)
1108  {
1109  nstep=3;
1110  }
1111 
1112  // Timestepping loop
1113  for (unsigned istep=0;istep<nstep;istep++)
1114  {
1115  // Solve the problem
1116  problem.unsteady_newton_solve(dt);
1117 
1118  // Outpt the solution
1119  problem.doc_solution(doc_info, trace_file);
1120 
1121  // Step number
1122  doc_info.number()++;
1123  }
1124 
1125  // Close trace file.
1126  trace_file.close();
1127 
1128 }//end of main
1129 
void create_lagrange_multiplier_elements()
Create elements that enforce prescribed boundary motion by Lagrange multipliers.
SolidMesh * Lagrange_multiplier_mesh_pt
Pointers to mesh of Lagrange multiplier elements.
void actions_after_newton_solve()
Update the problem after solve (empty)
ConstitutiveLaw * Constitutive_law_pt
Constitutive law used to determine the mesh deformation.
ElasticCollapsibleChannelMesh< ELEMENT > * Bulk_mesh_pt
Pointer to the "bulk" mesh.
MeshAsGeomObject * Wall_geom_object_pt
Geometric object incarnation of the wall mesh.
ElasticCollapsibleChannelMesh< ELEMENT > * bulk_mesh_pt()
Access function for the specific bulk (fluid) mesh.
void delete_lagrange_multiplier_elements()
Delete elements that enforce prescribed boundary motion by Lagrange multipliers.
void actions_before_newton_convergence_check()
Update no slip before Newton convergence check.
void actions_before_newton_solve()
Update the problem specs before solve (empty)
OneDLagrangianMesh< FSIHermiteBeamElement > * wall_mesh_pt()
Access function for the wall mesh.
void delete_traction_elements(Mesh *const &traction_mesh_pt)
Delete prescribed traction elements from the surface mesh.
void create_traction_elements(const unsigned &b, Mesh *const &bulk_mesh_pt, Mesh *const &traction_mesh_pt)
Create the prescribed traction elements on boundary b.
void set_initial_condition()
Apply initial conditions.
FSICollapsibleChannelProblem(const unsigned &nup, const unsigned &ncollapsible, const unsigned &ndown, const unsigned &ny, const double &lup, const double &lcollapsible, const double &ldown, const double &ly)
Constructor: The arguments are the number of elements and the lengths of the domain.
void doc_solution(DocInfo &doc_info, ofstream &trace_file)
Doc the solution.
/////////////////////////////////////////////////////////////////////// /////////////////////////////...
virtual void d2position(const Vector< double > &zeta, Vector< double > &r, DenseMatrix< double > &drdzeta, RankThreeTensor< double > &ddrdzeta) const
Posn vector and its 1st & 2nd derivatives w.r.t. to coordinates: = drdzeta(alpha,...
void position(const unsigned &t, const Vector< double > &zeta, Vector< double > &r) const
Parametrised position on object: r(zeta). Evaluated at previous timestep. t=0: current time; t>0: pre...
void position(const Vector< double > &zeta, Vector< double > &r) const
Position vector at Lagrangian coordinate zeta.
UndeformedWall(const double &x0, const double &h)
Constructor: arguments are the starting point and the height above y=0.
int main(int argc, char *argv[])
Driver code for a collapsible channel problem with FSI. Presence of command line arguments indicates ...
Namespace to define the mapping [0,1] -> [0,1] that re-distributes nodal points across the channel wi...
double squash_fct(const double &s)
Mapping [0,1] -> [0,1] that re-distributes nodal points across the channel width.
double Delta
Boundary layer width.
double Fract_in_BL
Fraction of points in boundary layer.
Namespace for phyical parameters.
double P_ext
External pressure.
double ReSt
Womersley = Reynolds times Strouhal.
void prescribed_traction(const double &t, const Vector< double > &x, const Vector< double > &n, Vector< double > &traction)
Traction applied on the fluid at the left (inflow) boundary.
void load(const Vector< double > &xi, const Vector< double > &x, const Vector< double > &N, Vector< double > &load)
Load function: Apply a constant external pressure to the wall. Note: This is the load without the flu...
double Nu
Pseudo-solid Poisson ratio.
double Sigma0
2nd Piola Kirchhoff pre-stress. As in Jensen & Heil (2003) paper.
double Q
Fluid structure interaction parameter: Ratio of stresses used for non-dimensionalisation of fluid to ...
double Lambda_sq
Pseudo-solid mass density.
double P_up
Default pressure on the left boundary.
double H
Non-dimensional wall thickness. As in Jensen & Heil (2003) paper.