MFEM v4.9.0
Finite element discretization library
Loading...
Searching...
No Matches
navier_particles.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// 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#include "navier_particles.hpp"
13
14#ifdef MFEM_USE_GSLIB
15
16using namespace std;
17
18namespace mfem
19{
20namespace navier
21{
22
24{
25 // Ratio of time step history at dt(t_{n}) - dt(t_{n-1})
26 real_t rho1 = 0.0;
27
28 // Ratio of time step history at dt(t_{n-1}) - dt(t_{n-2})
29 real_t rho2 = 0.0;
30
31 rho1 = dthist[0] / dthist[1];
32 rho2 = dthist[1] / dthist[2];
33
34 for (int o = 0; o < 3; o++)
35 {
36 if (o == 0) // k=1
37 {
38 beta_k[o] = 0.0;
39 beta_k[o][0] = 1.0;
40 beta_k[o][1] = -1.0;
41
42 alpha_k[o] = 0.0;
43 alpha_k[o][0] = 1.0;
44 }
45 else if (o == 1) // k=2
46 {
47 beta_k[o][0] = (1.0 + 2.0 * rho1) / (1.0 + rho1);
48 beta_k[o][1] = -(1.0 + rho1);
49 beta_k[o][2] = pow(rho1, 2.0) / (1.0 + rho1);
50 beta_k[o][3] = 0.0;
51
52 alpha_k[o][0] = 1.0 + rho1;
53 alpha_k[o][1] = -rho1;
54 alpha_k[o][2] = 0.0;
55 }
56 else // k=3
57 {
58 beta_k[o][0] = 1.0 + rho1 / (1.0 + rho1)
59 + (rho2 * rho1) / (1.0 + rho2 * (1 + rho1));
60 beta_k[o][1] = -1.0 - rho1 -
61 (rho2 * rho1 * (1.0 + rho1)) / (1.0 + rho2);
62 beta_k[o][2] = pow(rho1, 2.0) * (rho2 + 1.0 / (1.0 + rho1));
63 beta_k[o][3] = -(pow(rho2, 3.0) * pow(rho1, 2.0) * (1.0 + rho1))
64 / ((1.0 + rho2) * (1.0 + rho2 + rho2 * rho1));
65
66 alpha_k[o][0] = ((1.0 + rho1) *
67 (1.0 + rho2 * (1.0 + rho1))) / (1.0 + rho2);
68 alpha_k[o][1] = -rho1 * (1.0 + rho2 * (1.0 + rho1));
69 alpha_k[o][2] = (pow(rho2, 2.0) * rho1 * (1.0 + rho1)) / (1.0 + rho2);
70 }
71 }
72}
73
75{
76 real_t w_n_ext = 0.0;
77
78 int order_idx = Order()[p] - 1;
79 const Array<real_t> &beta = beta_k[order_idx];
80 const Array<real_t> &alpha = alpha_k[order_idx];
81
82 const real_t &kappa = Kappa()[p];
83 const real_t &zeta = Zeta()[p];
84 const real_t &gamma = Gamma()[p];
85
86 // Extrapolate particle vorticity using EXTk (w_n is new vorticity at old
87 // particle loc)
88 // w_n_ext = alpha1*w_nm1 + alpha2*w_nm2 + alpha3*w_nm3
89 for (int j = 1; j <= 3; j++)
90 {
91 w_n_ext += alpha[j-1]*W(j)(p, 0);
92 }
93
94 // Assemble the 2D matrix B with implicit terms
95 DenseMatrix B({{beta[0]+dt*kappa, zeta*dt*w_n_ext},
96 { -zeta*dt*w_n_ext, beta[0]+dt*kappa}});
97
98 // Assemble the RHS with BDF and EXT terms
99 r = 0.0;
100 for (int j = 1; j <= 3; j++)
101 {
102 U(j).GetValuesRef(p, up);
103 V(j).GetValuesRef(p, vp);
104
105 // Add particle velocity component
106 add(r, -beta[j], vp, r);
107
108 // Create C
109 C = up;
110 C *= kappa;
111 add(C, -gamma, Vector({0_r, 1_r}), C);
112 add(C, zeta*w_n_ext, Vector({ up[1], -up[0]}), C);
113
114 // Add C
115 add(r, dt*alpha[j-1], C, r);
116 }
117
118 // Solve for particle velocity
119 DenseMatrixInverse B_inv(B);
120 V(0).GetValuesRef(p, vp);
121 B_inv.Mult(r, vp);
122
123 // Compute updated particle position
124 X(0).GetValuesRef(p, xpn);
125 xpn = 0.0;
126 for (int j = 1; j <= 3; j++)
127 {
128 X(j).GetValuesRef(p, xp);
129 add(xpn, -beta[j], xp, xpn);
130 }
131 V(0).GetValuesRef(p, vp);
132 add(xpn, dt, vp, xpn);
133 xpn *= 1.0/beta[0];
134}
135
137 bool inv_normal, Vector &normal)
138{
139 normal.SetSize(2);
140 Vector diff(p2);
141 diff -= p1;
142 if (inv_normal)
143 {
144 normal[0] = diff[1];
145 normal[1] = -diff[0];
146 }
147 else
148 {
149 normal[0] = -diff[1];
150 normal[1] = diff[0];
151 }
152 normal /= normal.Norml2(); // normalize
153}
154
156 const Vector &s1_end, const Vector &s2_start, const Vector &s2_end,
157 Vector &x_int, real_t *t1_ptr, real_t *t2_ptr)
158{
159 // Compute the intersection parametrically
160 // r_1 = s1_start + t1*[s1_end - s1_start]
161 // r_2 = s2_start + t2*[s2_end - s2_start]
162 real_t denom = (s1_end[0]-s1_start[0])*(s2_start[1] - s2_end[1]) -
163 (s1_end[1]-s1_start[1])*(s2_start[0] - s2_end[0]);
164
165 // If line is parallel, don't compute at all
166 // Note that nearly-parallel intersections are not well-posed (denom >>> 0)
167 real_t rho = abs(denom)/(s1_start.DistanceTo(s2_end)*s2_start.DistanceTo(
168 s2_end));
169 if (rho < 1e-12)
170 {
171 return false;
172 }
173
174 real_t t1 = ( (s2_start[0] - s1_start[0])*(s2_start[1]-s2_end[1]) -
175 (s2_start[1] - s1_start[1])*(s2_start[0]-s2_end[0]) ) / denom;
176 real_t t2 = ( (s1_end[0] - s1_start[0])*(s2_start[1] - s1_start[1]) -
177 (s1_end[1] - s1_start[1])*(s2_start[0] - s1_start[0]))/ denom;
178
179 // If intersection falls on line segment of s1_start to s1_end AND s2_start to s2_end, set x_int and return true
180 if ((0_r <= t1 && t1 <= 1_r) && (0_r <= t2 && t2 <= 1_r))
181 {
182 // Get the point of intersection
183 x_int = s2_end;
184 x_int -= s2_start;
185 x_int *= t2;
186 x_int += s2_start;
187 if (t1_ptr)
188 {
189 *t1_ptr = t1;
190 }
191 if (t2_ptr)
192 {
193 *t2_ptr = t2;
194 }
195 return true;
196 }
197 return false;
198}
199
201{
202 Vector normal;
203 Get2DNormal(bc.line_start, bc.line_end, bc.invert_normal, normal);
204
205 Vector p_xn(2), p_xnm1(2), p_xdiff(2), x_int(2), p_vn(2), p_vdiff(2);
206 for (int i = 0; i < fluid_particles.GetNParticles(); i++)
207 {
208 X().GetValuesRef(i, p_xn);
209 X(1).GetValuesRef(i, p_xnm1);
210 V().GetValuesRef(i, p_vn);
211
212 // If line_start to line_end and x_nm1 to x_n intersect, apply reflection
214 p_xnm1, p_xn, x_int))
215 {
216 // Verify that the particle moved INTO the wall
217 // (Important for cases where p_xnm1 is on the wall within
218 // machine precision)
219 p_xdiff = p_xn;
220 p_xdiff -= p_xnm1;
221 if (p_xdiff*normal > 0)
222 {
223 continue;
224 }
225
226 real_t dt_c = p_xnm1.DistanceTo(x_int)/p_vn.Norml2();
227
228 // Correct the velocity
229 p_vdiff = p_vn;
230 add(p_vn, -(1+bc.e)*(p_vn*normal), normal, p_vn);
231 p_vdiff -= p_vn;
232
233 // Correct the position
234 int &o = Order()[i];
235 add(p_xn, (1.0/beta_k[o][0])*(dt_c - dthist[0]), p_vdiff, p_xn);
236
237 // Set order to 0 (so that it becomes 1 on next iteration)
238 o = 0;
239 }
240 }
241}
242
244{
245 Vector inlet_normal(2), outlet_normal(2);
246 Get2DNormal(bc.inlet_start, bc.inlet_end, bc.invert_inlet_normal, inlet_normal);
248 outlet_normal);
249
250 real_t inlet_length = bc.inlet_start.DistanceTo(bc.inlet_end);
251 real_t outlet_length = bc.outlet_start.DistanceTo(
252 bc.outlet_end); // should be == inlet_length
253
254 Vector inlet_tan(2), outlet_tan(2);
255 inlet_tan = bc.inlet_end;
256 inlet_tan -= bc.inlet_start;
257 inlet_tan /= inlet_length;
258 outlet_tan = bc.outlet_end;
259 outlet_tan -= bc.outlet_start;
260 outlet_tan /= outlet_length;
261
262 real_t t1;
263 Vector p_xn(2), p_xnm1(2), x_int(2), p_xc(2), p_x_int_diff(2);
264 for (int i = 0; i < fluid_particles.GetNParticles(); i++)
265 {
266 X().GetValuesRef(i, p_xn);
267 X(1).GetValuesRef(i, p_xnm1);
268
269 // If outlet_start to outlet_end and x_nm1 to x_n intersect,
270 // apply recirculation
271 if (Get2DSegmentIntersection(bc.outlet_start, bc.outlet_end, p_xnm1, p_xn,
272 x_int, &t1))
273 {
274 // Compute the corresponding intersection location on inlet
275 p_xc = 0.0;
276 add(bc.inlet_start, t1*inlet_length, inlet_tan, p_xc);
277
278 // Compute the normal distance from p_xn to x_int, and add
279 p_x_int_diff = p_xn;
280 p_x_int_diff -= x_int;
281 real_t normal_dist = abs(p_x_int_diff*outlet_normal);
282 add(p_xc, normal_dist, inlet_normal, p_xc);
283
284 // Compute tangential distance from p_xn to x_int, and add
285 real_t tan_dist = abs(p_x_int_diff*outlet_tan);
286 add(p_xc, tan_dist, inlet_tan, p_xc);
287
288 // Update the position
289 p_xn = p_xc;
290
291 // Set order to 0, to avoid re-computing x history as well
292 Order()[i] = 0;
293 }
294
295 }
296}
297
299{
300 for (BCVariant &bc_v : bcs)
301 {
302 std::visit(
303 [this](auto &bc)
304 {
305 using T = std::decay_t<decltype(bc)>;
306 if constexpr(std::is_same_v<T, ReflectionBC_2D>)
307 {
309 }
310 else if constexpr(std::is_same_v<T, RecirculationBC_2D>)
311 {
313 }
314 },bc_v);
315
316 }
317}
318
319NavierParticles::NavierParticles(MPI_Comm comm, int num_particles, Mesh &m)
320 : fluid_particles(comm, num_particles, m.SpaceDimension()),
321 inactive_fluid_particles(comm, 0, m.SpaceDimension()),
322 finder(comm)
323{
324
325 for (int o = 0; o < 3; o++)
326 {
327 beta_k[o].SetSize(4);
328 alpha_k[o].SetSize(3);
329 }
330
331 finder.Setup(m);
332
333 int dim = fluid_particles.GetDim();
334
335 // Initialize kappa, zeta, gamma
336 // Ordering defaults to byVDIM and name defaults to 'Field_{field_idx}' if
337 // unspecified.
341
345
346 // Initialize fluid particle fields
347 for (int i = 0; i < N_HIST; i++)
348 {
352 if (i > 0)
353 {
355 }
356 }
357
358 // Initialize order (tag)
361
362 // Reserve num_particles for inactive_fluid_particles
363 inactive_fluid_particles.Reserve(num_particles);
364
365 r.SetSize(dim);
366 C.SetSize(dim);
367}
368
370 const ParGridFunction &w_gf)
371{
372 // Shift fluid velocity, fluid vorticity, particle velocity, and particle position
373 for (int i = N_HIST-1; i > 0; i--)
374 {
375 U(i) = std::move(U(i-1));
376 V(i) = std::move(V(i-1));
377 W(i) = std::move(W(i-1));
378 X(i) = std::move(X(i-1));
379 }
380 U(0).SetSize(U(1).Size());
381 V(0).SetSize(V(1).Size());
382 W(0).SetSize(W(1).Size());
383 X(0).SetSize(X(1).Size());
384
386
387 if (fluid_particles.GetDim() == 2)
388 {
389 for (int i = 0; i < fluid_particles.GetNParticles(); i++)
390 {
391 // Increment particle order
392 int &order = Order()[i];
393 if (order < 3)
394 {
395 order++;
396 }
397 ParticleStep2D(dt, i);
398 }
399 }
400 else
401 {
402 MFEM_ABORT("3D particles not yet implemented.");
403 }
404
405 // Apply any BCs
406 ApplyBCs();
407
408 // Re-interpolate fluid velocity + vorticity onto particles' new location
409 InterpolateUW(u_gf, w_gf);
410
411 // Move lost particles from active to inactive. We don't search for points again
412 // because that is already done in InterpolateUW.
414
415 // Rotate values in time step history
416 dthist[2] = dthist[1];
417 dthist[1] = dthist[0];
418 dthist[0] = dt;
419}
420
422 const ParGridFunction &w_gf)
423{
424 finder.FindPoints(X(), X().GetOrdering());
425
426 finder.Interpolate(u_gf, U());
427 Ordering::Reorder(U(), U().GetVDim(), u_gf.ParFESpace()->GetOrdering(),
428 U().GetOrdering());
429
430 finder.Interpolate(w_gf, W());
431 Ordering::Reorder(W(), W().GetVDim(), w_gf.ParFESpace()->GetOrdering(),
432 W().GetOrdering());
433}
434
436{
437 if (findpts)
438 {
439 finder.FindPoints(X(), X().GetOrdering());
440 }
441
443 Array<int> inactive_add_idxs;
444 inactive_fluid_particles.AddParticles(lost_idxs.Size(), &inactive_add_idxs);
445
446 Vector coords;
447 for (int i = 0; i < lost_idxs.Size(); i++)
448 {
449 X().GetValues(lost_idxs[i], coords);
450 inactive_fluid_particles.Coords().SetValues(inactive_add_idxs[i], coords);
451 inactive_fluid_particles.Field(0)[inactive_add_idxs[i]] = Kappa()[lost_idxs[i]];
452 inactive_fluid_particles.Field(1)[inactive_add_idxs[i]] = Zeta()[lost_idxs[i]];
453 inactive_fluid_particles.Field(2)[inactive_add_idxs[i]] = Gamma()[lost_idxs[i]];
454 }
455
457
458}
459
460} // namespace navier
461} // namespace mfem
462#endif // MFEM_USE_GSLIB
int Size() const
Return the logical size of the array.
Definition array.hpp:166
void Mult(const real_t *x, real_t *y) const
Matrix vector multiplication with the inverse of dense matrix.
Data type dense matrix using column-major storage.
Definition densemat.hpp:24
void FindPoints(const Vector &point_pos, int point_pos_ordering=Ordering::byNODES)
Definition gslib.cpp:236
void Setup(Mesh &m, const double bb_t=0.1, const double newt_tol=1.0e-12, const int npt_max=256)
Definition gslib.cpp:183
virtual void Interpolate(const GridFunction &field_in, Vector &field_out)
Definition gslib.cpp:1759
Array< unsigned int > GetPointsNotFoundIndices() const
Get array of indices of not-found points.
Definition gslib.cpp:2122
Ordering::Type GetOrdering() const
Return the ordering method.
Definition fespace.hpp:854
Mesh data type.
Definition mesh.hpp:65
static void Reorder(Vector &v, int vdim, Type in_ord, Type out_ord)
Reorder Vector v from its current ordering in_ord to out_ord.
Definition ordering.cpp:38
Class for parallel grid function.
Definition pgridfunc.hpp:50
ParFiniteElementSpace * ParFESpace() const
ParticleVector & Coords()
Get a reference to the coordinates ParticleVector.
int AddTag(const char *tag_name=nullptr)
Add a tag to the ParticleSet.
ParticleVector & Field(int f)
Get a reference to field f 's ParticleVector.
int GetDim() const
Get the spatial dimension.
int GetNParticles() const
Get the number of active particles currently held by this ParticleSet.
Array< int > & Tag(int t)
Get a reference to tag t 's Array<int>.
int AddField(int vdim, Ordering::Type field_ordering=Ordering::byVDIM, const char *field_name=nullptr)
Add a field to the ParticleSet.
void Reserve(int res)
Reserve memory for res particles.
void RemoveParticles(const Array< int > &list)
Remove particle data specified by list of particle indices.
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...
int AddNamedField(int vdim, const char *field_name, Ordering::Type field_ordering=Ordering::byVDIM)
Add a field to the ParticleSet.
void SetValues(int i, const Vector &nvals)
Set particle i 's data to nvals .
void GetValues(int i, Vector &nvals) const
Get a copy of particle i 's data.
void GetValuesRef(int i, Vector &nref)
For GetOrdering == Ordering::byVDIM, set nref to refer to particle i 's data.
Vector data type.
Definition vector.hpp:82
real_t Norml2() const
Returns the l2 norm of the vector.
Definition vector.cpp:968
void SetSize(int s)
Resize the vector to size s.
Definition vector.hpp:584
real_t DistanceTo(const real_t *p) const
Compute the Euclidean distance to another vector.
Definition vector.hpp:748
Array< real_t > dthist
Timestep history.
void SetTimeIntegrationCoefficients()
Update beta_k and alpha_k using current dthist.
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 .
static constexpr int N_HIST
Number of timesteps to store (includes current)
struct mfem::navier::NavierParticles::FluidParticleIndices fp_idx
void InterpolateUW(const ParGridFunction &u_gf, const ParGridFunction &w_gf)
Interpolate fluid velocity and vorticity onto current particles' location.
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 .
void ParticleStep2D(const real_t &dt, int p)
2D particle step for particle at index p
static bool Get2DSegmentIntersection(const Vector &s1_start, const Vector &s1_end, const Vector &s2_start, const Vector &s2_end, Vector &x_int, real_t *t1_ptr=nullptr, real_t *t2_ptr=nullptr)
Given two 2D segments, get the intersection point if it exists.
ParticleVector & X(int nm=0)
Get reference to the position ParticleVector at time n - nm .
void ApplyBCs()
Apply all BCs in bcs.
std::variant< ReflectionBC_2D, RecirculationBC_2D > BCVariant
ParticleVector & Gamma()
Get reference to the γ ParticleVector.
void DeactivateLostParticles(bool findpts)
Move any particles that have left the domain to the inactive ParticleSet.
void Step(const real_t dt, const ParGridFunction &u_gf, const ParGridFunction &w_gf)
Step the particles in time.
void Apply2DReflectionBC(const ReflectionBC_2D &bc)
Apply 2D reflection BCs to update particle positions and velocities.
std::array< Array< real_t >, 3 > beta_k
BDFk coefficients, k=1,2,3.
ParticleVector & Kappa()
Get reference to the κ ParticleVector.
void Apply2DRecirculationBC(const RecirculationBC_2D &bc)
Apply 2D recirculation BCs.
NavierParticles(MPI_Comm comm, int num_particles, Mesh &m)
Initialize NavierParticles with num_particles using fluid mesh m .
static void Get2DNormal(const Vector &p1, const Vector &p2, bool inv_normal, Vector &normal)
Given two 2D points, get the unit normal to the line connecting them.
ParticleSet fluid_particles
Active fluid particle set.
std::array< Array< real_t >, 3 > alpha_k
EXTk coefficients, k=1,2,3.
ParticleVector & Zeta()
Get reference to the ζ ParticleVector.
std::vector< BCVariant > bcs
std::vector of all boundary condition structs
const real_t alpha
Definition ex15.cpp:369
real_t kappa
Definition ex24.cpp:54
int dim
Definition ex24.cpp:53
void add(const Vector &v1, const Vector &v2, Vector &v)
Definition vector.cpp:414
float real_t
Definition config.hpp:46
real_t p(const Vector &x, real_t t)
MFEM_HOST_DEVICE real_t abs(const Complex &z)
struct mfem::navier::NavierParticles::FluidParticleIndices::FieldIndices field
struct mfem::navier::NavierParticles::FluidParticleIndices::TagIndices tag
2D recirculation boundary condition struct
2D wall reflection boundary condition struct