MFEM v4.9.0
Finite element discretization library
Loading...
Searching...
No Matches
navier_bifurcation.cpp
Go to the documentation of this file.
1// Copyright (c) 2010-2025, 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// ---------------------------------------------------------------------
9// Navier Bifurcation: Tracer Particles in a 2D Bifurcating Channel Flow
10// ---------------------------------------------------------------------
11//
12// Note: MFEM must be compiled with GSLIB for this miniapp to include
13// particles - otherwise, it will just compute the fluid flow.
14//
15// This miniapp demonstrates the usage of tracer particles (i.e. one-way
16// coupled) in fluid flow. The fluid flow is computed with NavierSolver and
17// the particles are advected with NavierParticles. Particles are injected
18// periodically at random locations along the inlet boundary. Reflection
19// boundary conditions are used on the channel walls.
20//
21// Sample run:
22// * mpirun -np 10 navier_bifurcation -rs 3 -npt 100 -nt 4e5 -traj 10
23
24
25#include "navier_solver.hpp"
26#include "navier_particles.hpp"
30#include <fstream>
31#include <iostream>
32
33using namespace std;
34using namespace mfem;
35using namespace navier;
36using namespace mfem::common;
37
38struct flow_context
39{
40 // common
41 real_t dt = 1e-3;
42 real_t nt = 10000;
43
44 // fluid
45 int rs_levels = 3;
46 int order = 4;
47 real_t Re = 1000; // Reynolds number
48 int paraview_freq = 500; // frequency of ParaView output
49
50 // particle
51 int add_particles_freq = 300; // frequency of particle injection
52 int num_add_particles = 100; // total particles added each injection
53 real_t kappa_min = 1.0; // drag property min
54 real_t kappa_max = 10.0; // drag property max
55 real_t gamma = 0.0; // gravity property
56 real_t zeta = 0.19; // lift property
57 int print_csv_freq = 500; // frequency of particle CSV outputting
58
59 // GLVis visualization for solution and (optionally) particles
60 bool visualization = true;
61 // Particle trajectory visualization [visualization must be set to true]
62 // traj_len_update_freq = 0 means no trajectory visualization.
63 // traj_len_update_freq > 0 means update trajectory every
64 // traj_len_update_freq time steps. This is useful for long simulations
65 // with small time-steps.
66 int traj_len_update_freq = 0;
68
69#ifdef MFEM_USE_GSLIB
70// Set properties for injected particles. The location is set randomly to be
71// on the inlet boundary (x=0), velocity is initialized to 0, and
72// kappa (drag property) is set randomly in [kappa_min, kappa_max]
73// using kappa_seed.
74void SetInjectedParticles(NavierParticles &particle_solver,
75 const Array<int> &p_idxs,
76 real_t kappa_min, real_t kappa_max, int kappa_seed,
77 real_t zeta, real_t gamma, int step);
78#endif
79
80// Dirichlet conditions for velocity
81void vel_dbc(const Vector &x, real_t t, Vector &u);
82
83int main(int argc, char *argv[])
84{
85 // Initialize MPI and HYPRE.
86 Mpi::Init(argc, argv);
87 int rank = Mpi::WorldRank();
89
90 // Parse command line arguments
91 OptionsParser args(argc, argv);
92 args.AddOption(&ctx.dt, "-dt", "--time-step", "Time step.");
93 args.AddOption(&ctx.nt, "-nt", "--num-timesteps", "Number of time steps.");
94 args.AddOption(&ctx.rs_levels, "-rs", "--refine-serial",
95 "Number of times to refine the mesh in serial.");
96 args.AddOption(&ctx.order, "-o", "--order",
97 "Order (degree) of the finite elements.");
98 args.AddOption(&ctx.Re, "-Re", "--reynolds-number", "Reynolds number.");
99 args.AddOption(&ctx.paraview_freq, "-pv", "--paraview-freq",
100 "ParaView data collection write frequency. 0 to disable.");
101 args.AddOption(&ctx.visualization, "-vis", "--visualization", "-no-vis",
102 "--no-visualization",
103 "Enable or disable GLVis visualization.");
104 args.AddOption(&ctx.add_particles_freq, "-ipf", "--inject-particles-freq",
105 "Frequency of particle injection at domain inlet.");
106 args.AddOption(&ctx.num_add_particles, "-npt", "--num-particles-inject",
107 "Number of particles to add each injection.");
108 args.AddOption(&ctx.kappa_min, "-kmin", "--kappa-min",
109 "Kappa constant minimum.");
110 args.AddOption(&ctx.kappa_max, "-kmax", "--kappa-max",
111 "Kappa constant maximum.");
112 args.AddOption(&ctx.gamma, "-g", "--gamma", "Gamma constant.");
113 args.AddOption(&ctx.zeta, "-z", "--zeta", "Zeta constant.");
114 args.AddOption(&ctx.print_csv_freq, "-csv", "--csv-freq",
115 "Frequency of particle CSV outputting. 0 to disable.");
116 args.AddOption(&ctx.traj_len_update_freq, "-traj", "--traj-freq",
117 "Frequency of particle trajectory update. 0 to disable.");
118 args.Parse();
119 if (!args.Good())
120 {
121 args.PrintUsage(cout);
122 return 1;
123 }
124 if (rank == 0)
125 {
126 args.PrintOptions(cout);
127 }
128
129 // Load mesh + complete any serial refinements
130 Mesh mesh("../../data/channel-bifurcation-2d.mesh");
131 for (int lev = 0; lev < ctx.rs_levels; lev++)
132 {
133 mesh.UniformRefinement();
134 }
135
136 // Parallel decompose mesh
137 ParMesh pmesh(MPI_COMM_WORLD, mesh);
138 pmesh.EnsureNodes();
139 mesh.Clear();
140
141 // Create the flow solver
142 NavierSolver flow_solver(&pmesh, ctx.order, 1.0/ctx.Re);
143 flow_solver.EnablePA(true);
144
145 real_t time = 0.0;
146
147 // Initialize fluid IC
149 ParGridFunction &u_gf = *flow_solver.GetCurrentVelocity();
150 u_excoeff.SetTime(time);
151
152 // Set fluid BCs
153 Array<int> attr(pmesh.bdr_attributes.Max());
154 attr = 0;
155 // Inlet is attribute 1.
156 attr[0] = 1;
157 // Walls is attribute 2.
158 attr[1] = 1;
159 flow_solver.AddVelDirichletBC(vel_dbc, attr);
160
161#ifdef MFEM_USE_GSLIB
162 ParGridFunction &w_gf = *flow_solver.GetCurrentVorticity();
163 // Create the particle solver
164 NavierParticles particle_solver(MPI_COMM_WORLD, 0, pmesh);
165 int nparticles = (ctx.nt/ctx.add_particles_freq) *
166 ctx.num_add_particles / Mpi::WorldSize();
167 particle_solver.GetParticles().Reserve(nparticles);
168
169 // Set particle BCs - left normal for line connecting start to end must
170 // point into the domain. If not, we set invert_normal to true.
171 particle_solver.Add2DReflectionBC(Vector({0.0, 1.0}), Vector({8.0, 1.0}),
172 1.0, true);
173 particle_solver.Add2DReflectionBC(Vector({8.0, 1.0}), Vector({8.0, 9.0}),
174 1.0, true);
175 particle_solver.Add2DReflectionBC(Vector({9.0, 9.0}), Vector({9.0, 1.0}),
176 1.0, true);
177 particle_solver.Add2DReflectionBC(Vector({9.0, 1.0}), Vector({17.0, 1.0}),
178 1.0, true);
179 particle_solver.Add2DReflectionBC(Vector({0.0, 0.0}), Vector({17.0, 0.0}),
180 1.0, false);
181#endif
182
183 // Set up solution and particle visualization
184 char vishost[] = "localhost";
185 int visport = 19916;
186 socketstream vis_sol;
187 int Ww = 500, Wh = 500; // window size
188 int Wx = 10, Wy = 0; // window position
189 char keys[] = "mAcRjlmm]]]]]]]]]";
190 std::unique_ptr<ParticleTrajectories> traj_vis;
191 // Extract boundary mesh for particle visualization
192 int nattr = pmesh.bdr_attributes.Max();
193 Array<int> subdomain_attributes(nattr);
194 for (int i = 0; i < nattr; i++)
195 {
196 subdomain_attributes[i] = i+1;
197 }
198 auto psubmesh = std::unique_ptr<ParMesh>(new ParMesh(
199 ParSubMesh::CreateFromBoundary(pmesh, subdomain_attributes)));
200
201 if (ctx.visualization)
202 {
203 VisualizeField(vis_sol, vishost, visport, u_gf, "Velocity",
204 Wx, Wy, Ww, Wh, keys);
205#ifdef MFEM_USE_GSLIB
206 if (ctx.traj_len_update_freq > 0)
207 {
208 int traj_length = 4; // length of trajectory in number of segments
209 traj_vis = std::make_unique<ParticleTrajectories>(
210 particle_solver.GetParticles(),
211 traj_length, vishost, visport,
212 "Particle Trajectories",
213 Ww+Wx, Wy, Ww, Wh, "bbm");
214 traj_vis->AddMeshForVisualization(psubmesh.get());
215 traj_vis->Visualize();
216 }
217#endif
218 }
219
220 // Initialize ParaView DC (if freq != 0)
221 std::unique_ptr<ParaViewDataCollection> pvdc;
222 if (ctx.paraview_freq > 0)
223 {
224 pvdc = std::make_unique<ParaViewDataCollection>("Bifurcation", &pmesh);
225 pvdc->SetPrefixPath("ParaView");
226 pvdc->SetLevelsOfDetail(ctx.order);
227 pvdc->SetDataFormat(VTKFormat::BINARY);
228 pvdc->SetHighOrderOutput(true);
229 pvdc->RegisterField("Velocity",flow_solver.GetCurrentVelocity());
230 pvdc->RegisterField("Vorticity",flow_solver.GetCurrentVorticity());
231 pvdc->SetTime(time);
232 pvdc->SetCycle(0);
233 pvdc->Save();
234 }
235
236#ifdef MFEM_USE_GSLIB
237 std::string csv_prefix = "Navier_Bifurcation_";
238 // Setup arrays to indicate what to print.
239 // Particle id, rank, and coordinates are included by default. We also
240 // include the first field (i.e. \kappa with index 0 inside the ParticleSet)
241 Array<int> print_field_idxs({0});
242 // Print the first tag for each particle.
243 Array<int> print_tag_idxs({0});
244 if (ctx.print_csv_freq > 0)
245 {
246 std::string file_name = csv_prefix +
247 mfem::to_padded_string(0, 6) + ".csv";
248 particle_solver.GetParticles().PrintCSV(file_name.c_str(),
249 print_field_idxs,
250 print_tag_idxs);
251 }
252 particle_solver.Setup(ctx.dt);
253#endif
254
255 flow_solver.Setup(ctx.dt);
256
257 u_gf.ProjectCoefficient(u_excoeff);
258 int vis_count = 1;
259
260 Array<int> add_particle_idxs;
261 real_t cfl;
262 for (int step = 1; step <= ctx.nt; step++)
263 {
264 flow_solver.Step(time, ctx.dt, step-1);
265
266#ifdef MFEM_USE_GSLIB
267 // Inject particles at inlet and initialize their properties
268 if (step % ctx.add_particles_freq == 0)
269 {
270 int size = Mpi::WorldSize();
271 int rank_num_particles = ctx.num_add_particles/size +
272 (rank < (ctx.num_add_particles % size) ?
273 1 : 0);
274 // Add particles to the ParticleSet
275 particle_solver.GetParticles().AddParticles(rank_num_particles,
276 &add_particle_idxs);
277 // Initialize properties of the new particles
278 SetInjectedParticles(particle_solver, add_particle_idxs,
279 ctx.kappa_min, ctx.kappa_max,
280 (rank+1)*step, ctx.zeta, ctx.gamma, step);
281 }
282 // Step the particles
283 particle_solver.Step(ctx.dt, u_gf, w_gf);
284
285 // Output particle data to csv
286 if (ctx.print_csv_freq > 0 && step % ctx.print_csv_freq == 0)
287 {
288 vis_count++;
289 std::string file_name = csv_prefix +
290 mfem::to_padded_string(step, 6) + ".csv";
291 particle_solver.GetParticles().PrintCSV(file_name.c_str(),
292 print_field_idxs,
293 print_tag_idxs);
294 }
295#endif
296
297 // Output flow data for ParaView
298 if (ctx.paraview_freq > 0 && step % ctx.paraview_freq == 0)
299 {
300 pvdc->SetTime(vis_count);
301 pvdc->SetCycle(step);
302 pvdc->Save();
303 }
304
305 // GLVis visualization
306 if (ctx.visualization)
307 {
308 VisualizeField(vis_sol, vishost, visport, u_gf,
309 "Velocity", Wx, Wy, Ww, Wh, keys);
310#ifdef MFEM_USE_GSLIB
311 if (ctx.traj_len_update_freq > 0 &&
312 step % ctx.traj_len_update_freq == 0)
313 {
314 traj_vis->Visualize();
315 }
316#endif
317 }
318
319 cfl = flow_solver.ComputeCFL(u_gf, ctx.dt);
320#ifdef MFEM_USE_GSLIB
321 auto global_np = particle_solver.GetParticles().GetGlobalNParticles();
322 auto inactive_global_np =
323 particle_solver.GetInactiveParticles().GetGlobalNParticles();
324#endif
325 if (rank == 0)
326 {
327 printf("\n%-11s %-11s %-11s %-11s\n", "Step", "Time", "dt", "CFL");
328 printf("%-11i %-11.5E %-11.5E %-11.5E\n", step, time, ctx.dt, cfl);
329#ifdef MFEM_USE_GSLIB
330 printf("\n%16s: %-9llu\n", "Active Particles", global_np);
331 printf("%16s: %-9llu\n", "Lost Particles", inactive_global_np);
332#endif
333 printf("-----------------------------------------------\n");
334 fflush(stdout);
335 }
336 }
337
338 flow_solver.PrintTimingData();
339
340 return 0;
341}
342
343#ifdef MFEM_USE_GSLIB
345 const Array<int> &p_idxs, real_t kappa_min,
346 real_t kappa_max, int kappa_seed,
347 real_t zeta, real_t gamma, int step)
348{
349 // Set initial conditions for new particles.
350 MPI_Comm comm = particle_solver.GetParticles().GetComm();
351 int my_rank;
352 MPI_Comm_rank(comm, &my_rank);
353 Vector rand_init_yloc(p_idxs.Size());
354 rand_init_yloc.Randomize(my_rank + step);
355 for (int i = 0; i < p_idxs.Size(); i++)
356 {
357 int idx = p_idxs[i];
358
359 for (int j = 0; j < 4; j++)
360 {
361 if (j == 0)
362 {
363 // Set position randomly along inlet
364 real_t yval = rand_init_yloc(i);
365 particle_solver.X().SetValues(idx, Vector({0.0, yval}));
366 }
367 else
368 {
369 // Zero-out position history
370 particle_solver.X(j).SetValues(idx, Vector({0.0,0.0}));
371 }
372
373 // Zero-out particle velocities, fluid velocities, and fluid vorticities
374 particle_solver.V(j).SetValues(idx, Vector({0.0,0.0}));
375 particle_solver.U(j).SetValues(idx, Vector({0.0,0.0}));
376 particle_solver.W(j).SetValues(idx, Vector({0.0,0.0}));
377
378 // Set Kappa, Zeta, Gamma
379 std::mt19937 gen(kappa_seed);
380 std::uniform_real_distribution<> real_dist(0.0,1.0);
381 particle_solver.Kappa()[idx] = kappa_min + real_dist(gen)*
382 (kappa_max - kappa_min);
383 particle_solver.Zeta()[idx] = zeta;
384 particle_solver.Gamma()[idx] = gamma;
385 }
386
387 // Set order to 0
388 particle_solver.Order()[idx] = 0;
389 }
390}
391#endif
392
393// Dirichlet conditions for velocity
394void vel_dbc(const Vector &x, real_t t, Vector &u)
395{
396 real_t yi = x(1);
397 real_t height = 1.0;
398 u(0) = 0.;
399 u(1) = 0.;
400 if (std::fabs(yi)<1.0) { u(0) = 6.0*yi*(height-yi)/(height*height); }
401}
T Max() const
Find the maximal element in the array, using the comparison operator < for class T.
Definition array.cpp:69
int Size() const
Return the logical size of the array.
Definition array.hpp:166
static void Init()
Initialize hypre by calling HYPRE_Init() and set default options. After calling Hypre::Init(),...
Definition hypre.cpp:33
Mesh data type.
Definition mesh.hpp:65
Array< int > bdr_attributes
A list of all unique boundary attributes used by the Mesh.
Definition mesh.hpp:304
void EnsureNodes()
Make sure that the mesh has valid nodes, i.e. its geometry is described by a vector finite element gr...
Definition mesh.cpp:6747
void Clear()
Clear the contents of the Mesh.
Definition mesh.hpp:827
void UniformRefinement(int i, const DSTable &, int *, int *, int *)
Definition mesh.cpp:11637
static int WorldRank()
Return the MPI rank in MPI_COMM_WORLD.
static int WorldSize()
Return the size of MPI_COMM_WORLD.
static void Init(int &argc, char **&argv, int required=default_thread_required, int *provided=nullptr)
Singleton creation with Mpi::Init(argc, argv).
void Parse()
Parse the command-line options. Note that this function expects all the options provided through the ...
void PrintUsage(std::ostream &out) const
Print the usage message.
void PrintOptions(std::ostream &out) const
Print the options.
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 'var' to receive the value. Enable/disable tags are used to set the bool...
Definition optparser.hpp:82
bool Good() const
Return true if the command line options were parsed successfully.
Class for parallel grid function.
Definition pgridfunc.hpp:50
void ProjectCoefficient(Coefficient &coeff) override
Project coeff Coefficient to this GridFunction. The projection computation depends on the choice of t...
Class for parallel meshes.
Definition pmesh.hpp:34
static ParSubMesh CreateFromBoundary(const ParMesh &parent, const Array< int > &boundary_attributes)
Create a surface ParSubMesh from its parent.
Definition psubmesh.cpp:33
MPI_Comm GetComm() const
Get the MPI communicator for this ParticleSet.
IDType GetGlobalNParticles() const
Get the global number of active particles across all ranks.
void PrintCSV(const char *fname, int precision=16)
Print all particle data to a comma-delimited CSV file.
void Reserve(int res)
Reserve memory for res particles.
void AddParticles(const Array< IDType > &new_ids, Array< int > *new_indices=nullptr)
Add particles with global identifiers new_ids and optionally get the local indices of new particles i...
void SetValues(int i, const Vector &nvals)
Set particle i 's data to nvals .
virtual void SetTime(real_t t)
Set the time for time dependent coefficients.
A general vector function coefficient.
Vector data type.
Definition vector.hpp:82
void Randomize(int seed=0)
Set random values in the vector.
Definition vector.cpp:955
Transient Navier-Stokes fluid-particles solver.
ParticleVector & V(int nm=0)
Get reference to the particle velocity ParticleVector at time n - nm .
ParticleVector & W(int nm=0)
Get reference to the fluid vorticity-interpolated ParticleVector at time n - nm .
Array< int > & Order()
Get reference to the order Array<int>.
ParticleVector & U(int nm=0)
Get reference to the fluid velocity-interpolated ParticleVector at time n - nm .
ParticleSet & GetInactiveParticles()
Get reference to the inactive ParticleSet.
ParticleSet & GetParticles()
Get reference to the active ParticleSet.
ParticleVector & X(int nm=0)
Get reference to the position ParticleVector at time n - nm .
void Setup(const real_t dt)
Set initial timestep in time history array.
void Add2DReflectionBC(const Vector &line_start, const Vector &line_end, real_t e, bool invert_normal)
Add a 2D wall reflective boundary condition.
ParticleVector & Gamma()
Get reference to the γ ParticleVector.
void Step(const real_t dt, const ParGridFunction &u_gf, const ParGridFunction &w_gf)
Step the particles in time.
ParticleVector & Kappa()
Get reference to the κ ParticleVector.
ParticleVector & Zeta()
Get reference to the ζ ParticleVector.
Transient incompressible Navier Stokes solver in a split scheme formulation.
void Setup(real_t dt)
Initialize forms, solvers and preconditioners.
real_t ComputeCFL(ParGridFunction &u, real_t dt)
Compute CFL.
void AddVelDirichletBC(VectorCoefficient *coeff, Array< int > &attr)
Add a Dirichlet boundary condition to the velocity field.
ParGridFunction * GetCurrentVorticity()
Return a pointer to the current vorticity ParGridFunction.
void PrintTimingData()
Print timing summary of the solving routine.
void EnablePA(bool pa)
Enable partial assembly for every operator.
ParGridFunction * GetCurrentVelocity()
Return a pointer to the current velocity ParGridFunction.
void Step(real_t &time, real_t dt, int cur_step, bool provisional=false)
Compute solution at the next time step t+dt.
int main()
int Ww
int Wy
int Wx
int Wh
void VisualizeField(socketstream &sock, const char *vishost, int visport, GridFunction &gf, const char *title, int x, int y, int w, int h, const char *keys, bool vec)
real_t u(const Vector &xvec)
Definition lor_mms.hpp:22
std::string to_padded_string(int i, int digits)
Convert an integer to a 0-padded string with the given number of digits.
Definition text.hpp:54
float real_t
Definition config.hpp:46
const char vishost[]
void SetInjectedParticles(NavierParticles &particle_solver, const Array< int > &p_idxs, real_t kappa_min, real_t kappa_max, int kappa_seed, real_t zeta, real_t gamma, int step)
struct flow_context ctx
void vel_dbc(const Vector &x, real_t t, Vector &u)
Fluid data.