MFEM  v4.3.0
Finite element discretization library
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Pages
navier_kovasznay_vs.cpp
Go to the documentation of this file.
1 // Copyright (c) 2010-2021, Lawrence Livermore National Security, LLC. Produced
2 // at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3 // LICENSE and NOTICE for details. LLNL-CODE-806117.
4 //
5 // This file is part of the MFEM library. For more information and source code
6 // availability visit https://mfem.org.
7 //
8 // MFEM is free software; you can redistribute it and/or modify it under the
9 // terms of the BSD-3 license. We welcome feedback and contributions, see file
10 // CONTRIBUTING.md for details.
11 //
12 // Navier Kovasznay example with variable time step
13 //
14 // Solve for the steady Kovasznay flow at Re = 40 defined by
15 //
16 // u = [1 - exp(L * x) * cos(2 * pi * y),
17 // L / (2 * pi) * exp(L * x) * sin(2 * pi * y)],
18 //
19 // p = 1/2 * (1 - exp(2 * L * x)),
20 //
21 // with L = Re/2 - sqrt(Re^2/4 + 4 * pi^2).
22 //
23 // The problem domain is set up like this
24 //
25 // +-------------+
26 // | |
27 // | |
28 // | |
29 // | |
30 // Inflow -> | | -> Outflow
31 // | |
32 // | |
33 // | |
34 // | |
35 // | |
36 // +-------------+
37 //
38 // and Dirichlet boundary conditions are applied for the velocity on every
39 // boundary. The problem, although steady state, is time integrated up to the
40 // final time and the solution is compared with the known exact solution.
41 //
42 // Additionally, this example shows the usage of variable time steps with
43 // Navier. A basic sample algorithm for determining the next time step is
44 // provided based on a CFL restriction on the velocity.
45 
46 #include "navier_solver.hpp"
47 #include <fstream>
48 
49 using namespace mfem;
50 using namespace navier;
51 
52 struct s_NavierContext
53 {
54  int ser_ref_levels = 1;
55  int order = 6;
56  double kinvis = 1.0 / 40.0;
57  double t_final = 10 * 0.001;
58  double dt = 0.001;
59  double reference_pressure = 0.0;
60  double reynolds = 1.0 / kinvis;
61  double lam = 0.5 * reynolds
62  - sqrt(0.25 * reynolds * reynolds + 4.0 * M_PI * M_PI);
63  bool pa = true;
64  bool ni = false;
65  bool visualization = false;
66  bool checkres = false;
67 } ctx;
68 
69 void vel_kovasznay(const Vector &x, double t, Vector &u)
70 {
71  double xi = x(0);
72  double yi = x(1);
73 
74  u(0) = 1.0 - exp(ctx.lam * xi) * cos(2.0 * M_PI * yi);
75  u(1) = ctx.lam / (2.0 * M_PI) * exp(ctx.lam * xi) * sin(2.0 * M_PI * yi);
76 }
77 
78 double pres_kovasznay(const Vector &x, double t)
79 {
80  double xi = x(0);
81 
82  return 0.5 * (1.0 - exp(2.0 * ctx.lam * xi)) + ctx.reference_pressure;
83 }
84 
85 int main(int argc, char *argv[])
86 {
87  MPI_Session mpi(argc, argv);
88 
89  OptionsParser args(argc, argv);
90  args.AddOption(&ctx.ser_ref_levels,
91  "-rs",
92  "--refine-serial",
93  "Number of times to refine the mesh uniformly in serial.");
94  args.AddOption(&ctx.order,
95  "-o",
96  "--order",
97  "Order (degree) of the finite elements.");
98  args.AddOption(&ctx.dt, "-dt", "--time-step", "Time step.");
99  args.AddOption(&ctx.t_final, "-tf", "--final-time", "Final time.");
100  args.AddOption(&ctx.pa,
101  "-pa",
102  "--enable-pa",
103  "-no-pa",
104  "--disable-pa",
105  "Enable partial assembly.");
106  args.AddOption(&ctx.ni,
107  "-ni",
108  "--enable-ni",
109  "-no-ni",
110  "--disable-ni",
111  "Enable numerical integration rules.");
112  args.AddOption(&ctx.visualization,
113  "-vis",
114  "--visualization",
115  "-no-vis",
116  "--no-visualization",
117  "Enable or disable GLVis visualization.");
118  args.AddOption(
119  &ctx.checkres,
120  "-cr",
121  "--checkresult",
122  "-no-cr",
123  "--no-checkresult",
124  "Enable or disable checking of the result. Returns -1 on failure.");
125  args.Parse();
126  if (!args.Good())
127  {
128  if (mpi.Root())
129  {
130  args.PrintUsage(mfem::out);
131  }
132  return 1;
133  }
134  if (mpi.Root())
135  {
136  args.PrintOptions(mfem::out);
137  }
138 
139  Mesh mesh = Mesh::MakeCartesian2D(2, 4, Element::QUADRILATERAL, false, 1.5,
140  2.0);
141 
142  mesh.EnsureNodes();
143  GridFunction *nodes = mesh.GetNodes();
144  *nodes -= 0.5;
145 
146  for (int i = 0; i < ctx.ser_ref_levels; ++i)
147  {
148  mesh.UniformRefinement();
149  }
150 
151  if (mpi.Root())
152  {
153  std::cout << "Number of elements: " << mesh.GetNE() << std::endl;
154  }
155 
156  auto *pmesh = new ParMesh(MPI_COMM_WORLD, mesh);
157  mesh.Clear();
158 
159  // Create the flow solver.
160  NavierSolver flowsolver(pmesh, ctx.order, ctx.kinvis);
161  flowsolver.EnablePA(ctx.pa);
162  flowsolver.EnableNI(ctx.ni);
163 
164  // Set the initial condition.
165  ParGridFunction *u_ic = flowsolver.GetCurrentVelocity();
166  VectorFunctionCoefficient u_excoeff(pmesh->Dimension(), vel_kovasznay);
167  u_ic->ProjectCoefficient(u_excoeff);
168 
170 
171  // Add Dirichlet boundary conditions to velocity space restricted to
172  // selected attributes on the mesh.
173  Array<int> attr(pmesh->bdr_attributes.Max());
174  attr = 1;
175  flowsolver.AddVelDirichletBC(vel_kovasznay, attr);
176 
177  double t = 0.0;
178  double dt = ctx.dt;
179  double t_final = ctx.t_final;
180  bool last_step = false;
181 
182  flowsolver.Setup(dt);
183 
184  double err_u = 0.0;
185  double err_p = 0.0;
186  ParGridFunction *u_next_gf = nullptr;
187  ParGridFunction *u_gf = nullptr;
188  ParGridFunction *p_gf = nullptr;
189 
190  ParGridFunction p_ex_gf(flowsolver.GetCurrentPressure()->ParFESpace());
191  GridFunctionCoefficient p_ex_gf_coeff(&p_ex_gf);
192 
193  double cfl_max = 0.8;
194  double cfl_tol = 1e-4;
195 
196  for (int step = 0; !last_step; ++step)
197  {
198  if (t + dt >= t_final - dt / 2)
199  {
200  last_step = true;
201  }
202 
203  // Take a provisional step
204  flowsolver.Step(t, dt, step, true);
205 
206  // Retrieve the computed provisional velocity
207  u_next_gf = flowsolver.GetProvisionalVelocity();
208 
209  // Compute the CFL based on the provisional velocity
210  double cfl = flowsolver.ComputeCFL(*u_next_gf, dt);
211 
212  double error_est = cfl / (cfl_max + cfl_tol);
213  if (error_est >= 1.0)
214  {
215  // Reject the time step
216  if (mpi.Root())
217  {
218  std::cout
219  << "Step reached maximum CFL, retrying with smaller step size..."
220  << std::endl;
221  }
222  dt *= 0.5;
223  step -= 1;
224  }
225  else
226  {
227  // Accept the time step
228  t += dt;
229 
230  // Predict new step size
231  double fac_safety = 2.0;
232  double eta = pow(1.0 / (fac_safety * error_est), 1.0 / (1.0 + 3.0));
233  double fac_min = 0.1;
234  double fac_max = 1.4;
235  dt = dt * std::min(fac_max, std::max(fac_min, eta));
236 
237  // Queue new time step in the history array
238  flowsolver.UpdateTimestepHistory(dt);
239  }
240 
241  u_gf = flowsolver.GetCurrentVelocity();
242  p_gf = flowsolver.GetCurrentPressure();
243 
244  u_excoeff.SetTime(t);
245  p_excoeff.SetTime(t);
246 
247  // Remove mean value from exact pressure solution.
248  p_ex_gf.ProjectCoefficient(p_excoeff);
249  flowsolver.MeanZero(p_ex_gf);
250 
251  err_u = u_gf->ComputeL2Error(u_excoeff);
252  err_p = p_gf->ComputeL2Error(p_ex_gf_coeff);
253 
254  if (mpi.Root())
255  {
256  printf("%5s %8s %8s %8s %11s %11s\n",
257  "Order",
258  "CFL",
259  "Time",
260  "dt",
261  "err_u",
262  "err_p");
263  printf("%5.2d %8.2E %.2E %.2E %.5E %.5E err\n",
264  ctx.order,
265  cfl,
266  t,
267  dt,
268  err_u,
269  err_p);
270  fflush(stdout);
271  }
272  }
273 
274  if (ctx.visualization)
275  {
276  char vishost[] = "localhost";
277  int visport = 19916;
278  socketstream sol_sock(vishost, visport);
279  sol_sock.precision(8);
280  sol_sock << "parallel " << mpi.WorldSize() << " " << mpi.WorldRank()
281  << "\n";
282  sol_sock << "solution\n" << *pmesh << *u_ic << std::flush;
283  }
284 
285  flowsolver.PrintTimingData();
286 
287  // Test if the result for the test run is as expected.
288  if (ctx.checkres)
289  {
290  double tol_u = 1e-6;
291  double tol_p = 1e-5;
292  if (err_u > tol_u || err_p > tol_p)
293  {
294  if (mpi.Root())
295  {
296  mfem::out << "Result has a larger error than expected."
297  << std::endl;
298  }
299  return -1;
300  }
301  }
302 
303  delete pmesh;
304 
305  return 0;
306 }
int WorldSize() const
Return MPI_COMM_WORLD&#39;s size.
void PrintTimingData()
Print timing summary of the solving routine.
Class for grid function - Vector with associated FE space.
Definition: gridfunc.hpp:30
Coefficient defined by a GridFunction. This coefficient is mesh dependent.
int GetNE() const
Returns number of elements.
Definition: mesh.hpp:846
virtual void ProjectCoefficient(Coefficient &coeff)
Project coeff Coefficient to this GridFunction. The projection computation depends on the choice of t...
Definition: pgridfunc.cpp:493
void Setup(double dt)
Initialize forms, solvers and preconditioners.
void MeanZero(ParGridFunction &v)
Remove the mean from a ParGridFunction.
A simple convenience class that calls MPI_Init() at construction and MPI_Finalize() at destruction...
void Parse()
Parse the command-line options. Note that this function expects all the options provided through the ...
Definition: optparser.cpp:150
double ComputeCFL(ParGridFunction &u, double dt)
Compute CFL.
constexpr char vishost[]
void UniformRefinement(int i, const DSTable &, int *, int *, int *)
Definition: mesh.cpp:9143
constexpr int visport
ParGridFunction * GetCurrentPressure()
Return a pointer to the current pressure ParGridFunction.
bool Root() const
Return true if WorldRank() == 0.
void EnablePA(bool pa)
Enable partial assembly for every operator.
void AddVelDirichletBC(VectorCoefficient *coeff, Array< int > &attr)
Add a Dirichlet boundary condition to the velocity field.
void PrintUsage(std::ostream &out) const
Print the usage message.
Definition: optparser.cpp:457
void SetTime(double t)
Set the time for time dependent coefficients.
Definition: coefficient.hpp:48
A general vector function coefficient.
int WorldRank() const
Return MPI_COMM_WORLD&#39;s rank.
void AddOption(bool *var, const char *enable_short_name, const char *enable_long_name, const char *disable_short_name, const char *disable_long_name, const char *description, bool required=false)
Add a boolean option and set &#39;var&#39; to receive the value. Enable/disable tags are used to set the bool...
Definition: optparser.hpp:82
static Mesh MakeCartesian2D(int nx, int ny, Element::Type type, bool generate_edges=false, double sx=1.0, double sy=1.0, bool sfc_ordering=true)
Definition: mesh.cpp:3287
ParGridFunction * GetCurrentVelocity()
Return a pointer to the current velocity ParGridFunction.
virtual double ComputeL2Error(Coefficient *exsol[], const IntegrationRule *irs[]=NULL) const
Definition: pgridfunc.hpp:273
void UpdateTimestepHistory(double dt)
Rotate entries in the time step and solution history arrays.
ParGridFunction * GetProvisionalVelocity()
Return a pointer to the provisional velocity ParGridFunction.
void PrintOptions(std::ostream &out) const
Print the options.
Definition: optparser.cpp:327
RefCoord t[3]
void Clear()
Clear the contents of the Mesh.
Definition: mesh.hpp:828
A general function coefficient.
Vector data type.
Definition: vector.hpp:60
void GetNodes(Vector &node_coord) const
Definition: mesh.cpp:7343
void Step(double &time, double dt, int cur_step, bool provisional=false)
Compute solution at the next time step t+dt.
double u(const Vector &xvec)
Definition: lor_mms.hpp:24
Class for parallel grid function.
Definition: pgridfunc.hpp:32
OutStream out(std::cout)
Global stream used by the library for standard output. Initially it uses the same std::streambuf as s...
Definition: globals.hpp:66
Class for parallel meshes.
Definition: pmesh.hpp:32
Transient incompressible Navier Stokes solver in a split scheme formulation.
int main()
void EnsureNodes()
Definition: mesh.cpp:4849
ParFiniteElementSpace * ParFESpace() const
Definition: pgridfunc.hpp:108
bool Good() const
Return true if the command line options were parsed successfully.
Definition: optparser.hpp:150