MFEM v4.10.0
Finite element discretization library
Loading...
Searching...
No Matches
lorentz.cpp
Go to the documentation of this file.
1// Copyright (c) 2010-2026, 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// -----------------------------------------------------
13// Lorentz Miniapp: Simple Lorentz Force Particle Mover
14// -----------------------------------------------------
15//
16// This miniapp computes the trajectories of a set of charged particles subject
17// to Lorentz forces.
18//
19// dp/dt = q (E + v x B)
20//
21// The method used is the explicit Boris algorithm which conserves phase space
22// volume for long term accuracy.
23//
24// The electric and magnetic fields are read from VisItDataCollection objects
25// such as those produced by the Volta and Tesla miniapps. It is notable that
26// these two fields do not need to be defined on the same mesh. At least
27// one of either an electric field or a magnetic field must be provided. The
28// particles' locations and momenta are randomly initialized within a bounding
29// box specified by command line input.
30//
31// This miniapp demonstrates the use of ParticleSet with FindPointsGSLIB. When
32// particles leave either domains, they are subject to removal. Redistribution
33// of particle data between MPI ranks is also demonstrated.
34//
35// Note that the VisItDataCollection objects must have been stored using the
36// parallel format e.g. visit_dc.SetFormat(DataCollection::PARALLEL_FORMAT);.
37// Without this optional format specifier the vector field lookups will fail.
38//
39// Compile with: make lorentz
40//
41// Sample runs:
42//
43// Particles accelerating in a constant electric field
44// mpirun -np 4 volta -m ../../data/inline-hex.mesh -dbcs '1 6' -dbcv '0 1'
45// mpirun -np 4 lorentz -er Volta-AMR-Parallel -npt 100 -xmin '0.0 0.0 0.0' -xmax '1.0 1.0 1.0' -pmin '1 0 0' -pmax '1 0 0' -rdf 0 -vt 0 -nt 100
46//
47// Particles accelerating in a constant magnetic field
48// mpirun -np 4 tesla -m ../../data/inline-hex.mesh -ubbc '0 0 1'
49// mpirun -np 4 lorentz -br Tesla-AMR-Parallel -npt 10 -xmin '0.0 0.0 0.0' -xmax '1.0 1.0 1.0' -pmin '0 0.1 0.05' -pmax '0 0.4 0.1' -nt 1000 -rdf 0 -vt 0
50//
51// Magnetic mirror effect near a charged sphere and a bar magnet
52// mpirun -np 4 volta -m ../../data/ball-nurbs.mesh -dbcs 1 -cs '0 0 0 0.1 2e-11' -rs 2 -maxit 4
53// mpirun -np 4 tesla -m ../../data/fichera.mesh -maxit 4 -rs 3 -bm '-0.1 -0.1 -0.1 0.1 0.1 0.1 0.1 -1e10'
54// mpirun -np 4 lorentz -er Volta-AMR-Parallel -ec 4 -br Tesla-AMR-Parallel -bc 4 -q -10 -dt 1e-4 -nt 2000 -npt 500 -vt 10 -rdf 500 -rdm 1 -vf 10 -pmin '-8 -4 4' -pmax '-8 -4 4' -xmin '-1 -1 -1' -xmax '1 1 1'
55// mpirun -np 4 lorentz -er Volta-AMR-Parallel -ec 4 -br Tesla-AMR-Parallel -bc 4 -q -10 -dt 1e-3 -npt 1 -vt 650 -rdf 500 -rdm 1 -vf 2 -pmin '-8 -4 4' -pmax '-8 -4 4' -xmin '0.8 0 0' -xmax '0.8 0 0' -nt 1300
56
57#include "mfem.hpp"
59
60#include "electromagnetics.hpp"
61#include <fstream>
62#include <iostream>
63
64using namespace std;
65using namespace mfem;
66using namespace mfem::common;
67using namespace mfem::electromagnetics;
68
69struct LorentzContext
70{
71 struct DColl
72 {
73 string coll_name;
74 string field_name;
75 int cycle;
76 int pad_digits_cycle;
77 int pad_digits_rank;
78 };
79 DColl E{"", "E", 10, 6, 6};
80 DColl B{"", "B", 10, 6, 6};
81
82 int ordering = 1; // 0 - byNODES, 1 - byVDIM
83 int npt = 1; // total number of particles
84 real_t q = 1.0; // particle charge
85 real_t m = 1.0; // particle mass
86 Vector x_min{-1.0,-1.0,-1.0}; // initial position min
87 Vector x_max{1.0,1.0,1.0}; // initial position max
88 Vector p_min{-1.0,-1.0,-1.0}; // initial momentum min
89 Vector p_max{1.0,1.0,1.0}; // initial momentum max
90 real_t dt = 1e-2; // time step
91 int nt = 1000; // number of timesteps
92 int redist_interval = 5; // redistribution interval
93 int redist_mesh = 0; // redistribution mesh: 0: E mesh, 1: B mesh
94 std::string device_config = "cpu";
96
97/// This class implements the Boris algorithm as described in the article
98/// `Why is Boris algorithm so good?` by H. Qin et al in Physics of Plasmas,
99/// Volume 20 Issue 8, August 2013, https://doi.org/10.1063/1.4818428.
100class Boris
101{
102public:
103 /// Field indices
104 /** Allows for convenient access to corresponding ParticleVector from
105 ParticleSet. */
106 enum Fields
107 {
108 MASS, // vdim = 1
109 CHARGE, // vdim = 1
110 MOM, // vdim = dim
111 EFIELD, // vdim = dim
112 BFIELD // vdim = dim
113 };
114protected:
115 /// Pointers to E and B field GridFunctions
116 GridFunction *E_gf = nullptr;
117 GridFunction *B_gf = nullptr;
118
119 /// FindPointsGSLIB objects for E and B field meshes
120 FindPointsGSLIB E_finder;
121 FindPointsGSLIB B_finder;
122
123 /// ParticleSet of charged particles
124 std::unique_ptr<ParticleSet> charged_particles;
125
126 // Temporary vectors for particle computation
127 mutable Vector pxB_, pm_, pp_;
128
129 /// Single particle Boris step
130 void ParticleStep(Particle &part, real_t &dt);
131public:
132
133 Boris(MPI_Comm comm, GridFunction *E_gf_, GridFunction *B_gf_,
134 int nparticles, Ordering::Type pdata_ordering, bool use_device);
135
136 /// Find Particles in mesh corresponding to E and B fields
137 void FindParticles();
138
139 /// Update E and B fields at particle locations. Must be called
140 /// right after FindParticles has been called.
141 void EvaluateFieldsAtParticles();
142
143 /// Advance particles one time step using Boris algorithm. Host version.
144 void Step(real_t &t, real_t &dt);
145
146 /// Advance particles one time step using Boris algorithm. Device version.
147 void StepDevice(real_t &t, real_t &dt);
148
149 /// Remove lost particles and return their indices
150 Array<int> RemoveLostParticles();
151
152 /** Redistribute particles based on \p redist_mesh (0 - E field,
153 1 - B field). FindParticles() must be called afterward to update
154 corresponding FindPointsGSLIB data. */
155 void Redistribute(int redist_mesh, Array<int> &removed_idxs);
156
157 /// Get reference to the ParticleSet of charged particles
158 ParticleSet& GetParticles() { return *charged_particles; }
159
160 /// Get reference to the E field FindPointsGSLIB object
161 FindPointsGSLIB& GetEFinder() { return E_finder; }
162};
163
164// Prints the program's logo to the given output stream
165void display_banner(ostream & os);
166
167// Open the named VisItDataCollection and read the named field.
168// Returns pointers to the two new objects.
169int ReadGridFunction(std::string coll_name, std::string field_name,
170 int pad_digits_cycle, int pad_digits_rank, int cycle,
171 std::unique_ptr<VisItDataCollection> &dc,
172 ParGridFunction *&gf);
173
174// Initialize particles from user input.
175void InitializeChargedParticles(ParticleSet &particles, const Vector &pos_min,
176 const Vector &pos_max, const Vector &x_init,
177 const Vector &p_init, real_t m,
178 real_t q);
179
180int main(int argc, char *argv[])
181{
182 Mpi::Init(argc, argv);
183 int num_ranks = Mpi::WorldSize();
184 int rank = Mpi::WorldRank();
185 Hypre::Init();
186
187 if ( Mpi::Root() ) { display_banner(cout); }
188
189 bool visualization = true; // enable visualization
190 int vis_tail_size = 5; // particle trajectory tail size
191 int vis_interval = 4; // visualization interval
192
193 OptionsParser args(argc, argv);
194 args.AddOption(&ctx.E.coll_name, "-er", "--e-root-file",
195 "Set the VisIt data collection E field root file prefix.");
196 args.AddOption(&ctx.E.field_name, "-ef", "--e-field-name",
197 "Set the VisIt data collection E field name");
198 args.AddOption(&ctx.E.cycle, "-ec", "--e-cycle",
199 "Set the E field cycle index to read.");
200 args.AddOption(&ctx.E.pad_digits_cycle, "-epdc", "--e-pad-digits-cycle",
201 "Number of digits in E field cycle.");
202 args.AddOption(&ctx.E.pad_digits_rank, "-epdr", "--e-pad-digits-rank",
203 "Number of digits in E field MPI rank.");
204 args.AddOption(&ctx.B.coll_name, "-br", "--b-root-file",
205 "Set the VisIt data collection B field root file prefix.");
206 args.AddOption(&ctx.B.field_name, "-bf", "--b-field-name",
207 "Set the VisIt data collection B field name");
208 args.AddOption(&ctx.B.cycle, "-bc", "--b-cycle",
209 "Set the B field cycle index to read.");
210 args.AddOption(&ctx.B.pad_digits_cycle, "-bpdc", "--b-pad-digits-cycle",
211 "Number of digits in B field cycle.");
212 args.AddOption(&ctx.B.pad_digits_rank, "-bpdr", "--b-pad-digits-rank",
213 "Number of digits in B field MPI rank.");
214 args.AddOption(&ctx.redist_interval, "-rdf", "--redist-interval",
215 "Redistribution after this many timesteps. 0 means "
216 "no redistribution.");
217 args.AddOption(&ctx.redist_mesh, "-rdm", "--redistribution-mesh",
218 "Particle domain mesh for redistribution. 0 for E field mesh."
219 " 1 for B field mesh.");
220 args.AddOption(&ctx.ordering, "-o", "--ordering",
221 "Ordering of particle data. 0 = byNODES, 1 = byVDIM.");
222 args.AddOption(&ctx.npt, "-npt", "--num-particles",
223 "Total number of particles.");
224 args.AddOption(&ctx.m, "-m", "--mass", "Particles' mass.");
225 args.AddOption(&ctx.q, "-q", "--charge", "Particles' charge.");
226 args.AddOption(&ctx.x_min, "-xmin", "--x-min",
227 "Minimum initial particle location.");
228 args.AddOption(&ctx.x_max, "-xmax", "--x-max",
229 "Maximum initial particle location.");
230 args.AddOption(&ctx.p_min, "-pmin", "--p-min",
231 "Minimum initial particle momentum.");
232 args.AddOption(&ctx.p_max, "-pmax", "--p-max",
233 "Maximum initial particle momentum.");
234 args.AddOption(&ctx.dt, "-dt", "--time-step", "Time Step.");
235 args.AddOption(&ctx.nt, "-nt", "--num-timesteps", "Number of timesteps.");
236 args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
237 "--no-visualization",
238 "Enable or disable GLVis visualization.");
239 args.AddOption(&vis_tail_size, "-vt", "--vis-tail-size",
240 "GLVis visualization trajectory truncation tail size.");
241 args.AddOption(&vis_interval, "-vf", "--vis-interval",
242 "GLVis visualization update after this many timesteps. "
243 "0 means no visualization.");
244 args.AddOption(&ctx.device_config, "-d", "--device",
245 "Device configuration definition string.");
246
247 args.Parse();
248 if (!args.Good())
249 {
250 if (Mpi::Root())
251 {
252 args.PrintUsage(cout);
253 }
254 return 1;
255 }
256
257 if (Mpi::Root())
258 {
259 args.PrintOptions(cout);
260 }
261
262 Device device(ctx.device_config);
263 if (Mpi::Root()) { device.Print(); }
264 bool use_device = (ctx.device_config != "cpu") && Device::IsEnabled();
265
266 std::unique_ptr<VisItDataCollection> E_dc, B_dc;
267 ParGridFunction *E_gf = nullptr, *B_gf = nullptr;
268 Vector bb_xmin, bb_xmax;
269
270 // Read E field if provided
271 if (ctx.E.coll_name != "")
272 {
273 if (ReadGridFunction(ctx.E.coll_name, ctx.E.field_name,
274 ctx.E.pad_digits_cycle, ctx.E.pad_digits_rank,
275 ctx.E.cycle, E_dc, E_gf))
276 {
277 mfem::err << "Error loading E field" << endl;
278 return 1;
279 }
280 E_gf->ParFESpace()->GetParMesh()->GetBoundingBox(bb_xmin, bb_xmax, 2);
281 E_gf->UseDevice(use_device);
282 }
283
284 // Read B field if provided
285 if (ctx.B.coll_name != "")
286 {
287 if (ReadGridFunction(ctx.B.coll_name, ctx.B.field_name,
288 ctx.B.pad_digits_cycle, ctx.B.pad_digits_rank,
289 ctx.B.cycle, B_dc, B_gf))
290 {
291 mfem::err << "Error loading B field" << endl;
292 return 1;
293 }
294 Vector bb_xmint, bb_xmaxt;
295 B_gf->ParFESpace()->GetParMesh()->GetBoundingBox(bb_xmint, bb_xmaxt, 2);
296 B_gf->UseDevice(use_device);
297 if (ctx.E.coll_name != "")
298 {
299 // compute intersection of bounding boxes
300 for (int d = 0; d < bb_xmin.Size(); d++)
301 {
302 bb_xmin[d] = std::max(bb_xmin[d], bb_xmint[d]);
303 bb_xmax[d] = std::min(bb_xmax[d], bb_xmaxt[d]);
304 }
305 }
306 else
307 {
308 bb_xmin = bb_xmint;
309 bb_xmax = bb_xmaxt;
310 }
311 }
312
313 Ordering::Type ordering_type = ctx.ordering == 0 ?
315
316 // Initialize particles
317 int num_particles = ctx.npt/num_ranks +
318 (rank < (ctx.npt % num_ranks) ? 1 : 0);
319 Boris boris(MPI_COMM_WORLD, E_gf, B_gf, num_particles, ordering_type,
320 use_device);
321 InitializeChargedParticles(boris.GetParticles(), ctx.x_min, ctx.x_max,
322 ctx.p_min, ctx.p_max, ctx.m, ctx.q);
323
324 Array<int> removed_idxs_dummy;
325 boris.FindParticles();
326 boris.Redistribute(ctx.redist_mesh, removed_idxs_dummy);
327 boris.FindParticles();
328 boris.EvaluateFieldsAtParticles();
329
330 real_t t = 0.0;
331 real_t dt = ctx.dt;
332
333 // Setup visualization
334 char vishost[] = "localhost";
335 socketstream pre_redist_sock, post_redist_sock;
336 std::unique_ptr<ParticleTrajectories> traj_vis;
337 bool do_vis = visualization && (vis_interval > 0);
338 if (do_vis)
339 {
340 const char *keys = "baaa";
341 traj_vis = std::make_unique<ParticleTrajectories>(boris.GetParticles(),
342 vis_tail_size,
343 vishost, 19916,
344 "Trajectories",
345 0, 0, 600, 600, keys);
346 traj_vis->SetVisualizationBoundingBox(bb_xmin, bb_xmax);
347 }
348
349 for (int step = 1; step <= ctx.nt; step++)
350 {
351 // Step the Boris algorithm
352 if (use_device)
353 {
354 boris.StepDevice(t, dt);
355 }
356 else
357 {
358 boris.Step(t, dt);
359 }
360 if (Mpi::Root())
361 {
362 mfem::out << "Step: " << step << " | Time: " << t << endl;
363 }
364
365 // Visualize trajectories
366 if (do_vis && step % vis_interval == 0)
367 {
368 traj_vis->Visualize();
369 }
370
371 // Remove lost particles from particle set and output
372 Array<int> removed_idxs = boris.RemoveLostParticles();
373
374 bool particles_removed = removed_idxs.Size() > 0;
375 MPI_Allreduce(MPI_IN_PLACE, &particles_removed, 1, MFEM_MPI_CXX_BOOL,
376 MPI_LOR, boris.GetParticles().GetComm());
377
378 // Redistribute
379 bool redistributed = false;
380 if (ctx.redist_interval > 0 && step % ctx.redist_interval == 0 &&
381 boris.GetParticles().GetGlobalNParticles() > 0)
382 {
383 // Redistribute particles - prior to redistribution, removed any lost
384 // particles that were just removed from the set.
385 boris.Redistribute(ctx.redist_mesh, removed_idxs);
386 redistributed = true;
387 }
388
389 // Keep FindPointsGSLIB data synchronized with the ParticleSet after
390 // particles have been removed or redistributed.
391 if (particles_removed || redistributed)
392 {
393 boris.FindParticles();
394 }
395 }
396}
397
398void Boris::ParticleStep(Particle &part, real_t &dt)
399{
400 Vector &x = part.Coords();
401 real_t m = part.FieldValue(MASS);
402 real_t q = part.FieldValue(CHARGE);
403 Vector &p = part.Field(MOM);
404 Vector &e = part.Field(EFIELD);
405 Vector &b = part.Field(BFIELD);
406
407 // Compute half of the contribution from q E
408 add(p, 0.5 * dt * q, e, pm_);
409
410 // Compute the contribution from q p x B
411 const real_t B2 = b * b;
412
413 // ... along pm x B
414 const real_t a1 = 4.0 * dt * q * m;
415 pm_.cross3D(b, pxB_);
416 pp_.Set(a1, pxB_);
417
418 // ... along pm
419 const real_t a2 = 4.0 * m * m -
420 dt * dt * q * q * B2;
421 pp_.Add(a2, pm_);
422
423 // ... along B
424 const real_t a3 = 2.0 * dt * dt * q * q * (b * pm_);
425 pp_.Add(a3, b);
426
427 // scale by common denominator
428 const real_t a4 = 4.0 * m * m +
429 dt * dt * q * q * B2;
430 pp_ /= a4;
431
432 // Update the momentum
433 add(pp_, 0.5 * dt * q, e, p);
434
435 // Update the position
436 x.Add(dt / m, p);
437}
438
439Boris::Boris(MPI_Comm comm, GridFunction *E_gf_, GridFunction *B_gf_,
440 int nparticles, Ordering::Type pdata_ordering, bool use_device)
441 : E_gf(E_gf_),
442 B_gf(B_gf_),
443 E_finder(comm),
444 B_finder(comm)
445{
446 MFEM_VERIFY(E_gf || B_gf, "Must pass an E field or B field to Boris.");
447
448 Mesh *E_mesh = E_gf ? E_gf->FESpace()->GetMesh() : nullptr;
449 Mesh *B_mesh = B_gf ? B_gf->FESpace()->GetMesh() : nullptr;
450 if (E_mesh && B_mesh)
451 {
452 int E_dim = E_mesh->SpaceDimension();
453 int B_dim = B_mesh->SpaceDimension();
454 MFEM_VERIFY(E_dim == B_dim,
455 "E mesh and B mesh must have the same spatial dimension.");
456 }
457 if (E_gf)
458 {
459 E_mesh->EnsureNodes();
460 E_finder.Setup(*E_mesh);
461 }
462 if (B_gf)
463 {
464 B_mesh->EnsureNodes();
465 B_finder.Setup(*B_mesh);
466 }
467
468 int dim = E_mesh ? E_mesh->SpaceDimension() : B_mesh->SpaceDimension();
469 MFEM_VERIFY(dim == 3, "Only 3D meshes are currently supported.");
470
471 pxB_.SetSize(dim); pm_.SetSize(dim); pp_.SetSize(dim);
472
473 /// Create particle set:
474 /// 2 scalars of mass and charge,
475 /// 3 vectors of size space dim for momentum, e field, and b field
476 Array<int> field_vdims({1, 1, dim, dim, dim});
477
478 charged_particles = std::make_unique<ParticleSet>
479 (comm, nparticles, dim, field_vdims, 0, pdata_ordering,
480 use_device);
481}
482
483void Boris::FindParticles()
484{
485 ParticleVector &X = charged_particles->Coords();
486
487 // Find particles in E and B field meshes
488 if (E_gf)
489 {
490 E_finder.FindPoints(X); // X.GetOrdering() used internally
491 }
492 if (B_gf)
493 {
494 B_finder.FindPoints(X); // X.GetOrdering() used internally
495 }
496}
497
498void Boris::EvaluateFieldsAtParticles()
499{
500 ParticleVector &E = charged_particles->Field(EFIELD);
501 ParticleVector &B = charged_particles->Field(BFIELD);
502
503 // Interpolate E-field + B-field onto particles
504 if (E_gf)
505 {
506 E_finder.Interpolate(*E_gf, E, E.GetOrdering());
507 }
508 else
509 {
510 E = 0.0;
511 }
512 if (B_gf)
513 {
514 B_finder.Interpolate(*B_gf, B, B.GetOrdering());
515 }
516 else
517 {
518 B = 0.0;
519 }
520}
521
522void Boris::Step(real_t &t, real_t &dt)
523{
524 // Interpolate E and B fields onto particles
525 EvaluateFieldsAtParticles();
526 // Individually step each particle. If all ParticleSet fields are ordered
527 // byVDIM, we can use GetParticleRef for better performance.
528 if (charged_particles->IsParticleRefValid())
529 {
530 for (int i = 0; i < charged_particles->GetNParticles(); i++)
531 {
532 Particle p = charged_particles->GetParticleRef(i);
533 ParticleStep(p, dt);
534 }
535 }
536 else
537 {
538 for (int i = 0; i < charged_particles->GetNParticles(); i++)
539 {
540 Particle p = charged_particles->GetParticle(i);
541 ParticleStep(p, dt);
542 charged_particles->SetParticle(i, p);
543 }
544 }
545
546 // Find updated particle locations in E and B field meshes
547 FindParticles();
548
549 // Update time
550 t += dt;
551}
552
553void Boris::StepDevice(real_t &t, real_t &dt)
554{
555 // Interpolate E and B fields onto particles
556 EvaluateFieldsAtParticles();
557 const int N = charged_particles->GetNParticles();
558 auto &X = charged_particles->Coords();
559 auto &M = charged_particles->Field(MASS);
560 auto &Q = charged_particles->Field(CHARGE);
561 auto &P = charged_particles->Field(MOM);
562 auto &E = charged_particles->Field(EFIELD);
563 auto &B = charged_particles->Field(BFIELD);
564
565 const int dim = X.GetVDim();
566
567 // Capture orderings for each field to ensure correct access
568 const bool byVDIM_X = (X.GetOrdering() == Ordering::byVDIM);
569 const bool byVDIM_P = (P.GetOrdering() == Ordering::byVDIM);
570 const bool byVDIM_E = (E.GetOrdering() == Ordering::byVDIM);
571 const bool byVDIM_B = (B.GetOrdering() == Ordering::byVDIM);
572
573 auto d_x = X.ReadWrite();
574 auto d_m = M.Read();
575 auto d_q = Q.Read();
576 auto d_p = P.ReadWrite();
577 auto d_e = E.Read();
578 auto d_b = B.Read();
579
580 mfem::forall(N, [=] MFEM_HOST_DEVICE (int i)
581 {
582 const real_t m = d_m[i];
583 const real_t q = d_q[i];
584
585 real_t x[3], p[3], e[3], b[3];
586 // Load data
587 for (int d = 0; d < dim; d++)
588 {
589 x[d] = d_x[byVDIM_X ? i * dim + d : i + d * N];
590 p[d] = d_p[byVDIM_P ? i * dim + d : i + d * N];
591 e[d] = d_e[byVDIM_E ? i * dim + d : i + d * N];
592 b[d] = d_b[byVDIM_B ? i * dim + d : i + d * N];
593 }
594
595 // Boris algorithm implementation
596 real_t pm[3], pxB[3], pp[3];
597
598 // Compute half of the contribution from q E
599 // pm = p + 0.5 * dt * q * e
600 for (int d = 0; d < dim; d++)
601 {
602 pm[d] = p[d] + (0.5 * dt * q) * e[d];
603 }
604
605 // Compute the contribution from q p x B
606 real_t B2 = 0.0;
607 for (int d = 0; d < dim; d++) { B2 += b[d] * b[d]; }
608
609 // ... along pm x B
610 // pxB = pm x b
611 pxB[0] = pm[1] * b[2] - pm[2] * b[1];
612 pxB[1] = pm[2] * b[0] - pm[0] * b[2];
613 pxB[2] = pm[0] * b[1] - pm[1] * b[0];
614
615 // pp = a1 * pxB
616 const real_t a1 = 4.0 * dt * q * m;
617 for (int d = 0; d < dim; d++) { pp[d] = a1 * pxB[d]; }
618
619 // ... along pm
620 // pp += a2 * pm
621 const real_t a2 = 4.0 * m * m - dt * dt * q * q * B2;
622 for (int d = 0; d < dim; d++) { pp[d] += a2 * pm[d]; }
623
624 // ... along B
625 real_t b_dot_pm = 0.0;
626 for (int d = 0; d < dim; d++) { b_dot_pm += b[d] * pm[d]; }
627 const real_t a3 = 2.0 * dt * dt * q * q * b_dot_pm;
628 // pp += a3 * b
629 for (int d = 0; d < dim; d++) { pp[d] += a3 * b[d]; }
630
631 // scale by common denominator
632 const real_t a4 = 4.0 * m * m + dt * dt * q * q * B2;
633 for (int d = 0; d < dim; d++) { pp[d] /= a4; }
634
635 // Update the momentum
636 // p = pp + 0.5 * dt * q * e
637 for (int d = 0; d < dim; d++)
638 {
639 p[d] = pp[d] + (0.5 * dt * q) * e[d];
640 }
641
642 // Update the position
643 // x += (dt / m) * p
644 // Store back to global arrays
645 for (int d = 0; d < dim; d++)
646 {
647 d_p[byVDIM_P ? i * dim + d : i + d * N] = p[d];
648 d_x[byVDIM_X ? i * dim + d : i + d * N] = x[d] + (dt / m) * p[d];
649 }
650 });
651
652 // Find updated particle locations in E and B field meshes
653 FindParticles();
654
655 // Update time
656 t += dt;
657}
658
659Array<int> Boris::RemoveLostParticles()
660{
661 Array<int> lost_idxs;
662 const Array<int> E_lost = E_finder.GetPointsNotFoundIndices();
663 const Array<int> B_lost = B_finder.GetPointsNotFoundIndices();
664
665 for (const int &elem : E_lost)
666 {
667 lost_idxs.Union(elem);
668 }
669
670 for (const int &elem : B_lost)
671 {
672 lost_idxs.Union(elem);
673 }
674
675 charged_particles->RemoveParticles(lost_idxs);
676 return lost_idxs;
677}
678
679void Boris::Redistribute(int redist_mesh, Array<int> &removed_idxs)
680{
681 if (redist_mesh == 0 && E_gf)
682 {
683 Array<int> proc_list = E_finder.GetProc();
684 proc_list.DeleteAt(removed_idxs);
685 charged_particles->Redistribute(proc_list);
686 }
687 else
688 {
689 Array<int> proc_list = B_finder.GetProc();
690 proc_list.DeleteAt(removed_idxs);
691 charged_particles->Redistribute(proc_list);
692 }
693}
694
695void display_banner(ostream & os)
696{
697 os << " ____ __ "
698 << endl
699 << " | | ___________ ____ _____/ |_________"
700 << endl
701 << " | | / _ \\_ __ \\_/ __ \\ / \\ __\\___ /"
702 << endl
703 << " | |__( <_> ) | \\/\\ ___/| | \\ | / / "
704 << endl
705 << " |_______ \\____/|__| \\___ >___| /__| /_____ \\"
706 << endl
707 << " \\/ \\/ \\/ \\/"
708 << endl << flush;
709}
710
711int ReadGridFunction(std::string coll_name, std::string field_name,
712 int pad_digits_cycle, int pad_digits_rank, int cycle,
713 std::unique_ptr<VisItDataCollection> &dc, ParGridFunction *&gf)
714{
715 dc = std::make_unique<VisItDataCollection>(MPI_COMM_WORLD, coll_name);
716 dc->SetPadDigitsCycle(pad_digits_cycle);
717 dc->SetPadDigitsRank(pad_digits_rank);
718 dc->Load(cycle);
719
720 if (dc->Error() != DataCollection::No_Error)
721 {
722 mfem::err << "Error loading VisIt data collection: "
723 << coll_name << endl;
724 return 1;
725 }
726
727 if (dc->HasField(field_name))
728 {
729 gf = dc->GetParField(field_name);
730 }
731
732 return 0;
733}
734
736 const Vector &x_min, const Vector &x_max, const Vector &p_min,
737 const Vector &p_max, real_t m, real_t q)
738{
739 int dim = charged_particles.Coords().GetVDim();
740 int rank;
741 MPI_Comm_rank(charged_particles.GetComm(), &rank);
742 std::mt19937 gen(rank);
743
744 // Set up uniform distribution for position
745 std::uniform_real_distribution<real_t> real_dist_x(0_r,1_r);
746
747 // Set up gaussian distribution for momentum. Centered between p_min and
748 // p_max with 3-sigma range covering the box.
749 Vector p_center(dim);
750 add(0.5, p_min, p_max, p_center);
751 Vector dp = p_max; dp -= p_min; dp *= 1_r/6_r; // 3-sigma range
752 std::vector<std::normal_distribution<real_t>> norm_dist_p;
753 for (int d = 0; d < dim; d++)
754 {
755 norm_dist_p.emplace_back(p_center[d], dp[d] > 0_r ? dp[d] : 1_r);
756 }
757
758 ParticleVector &X = charged_particles.Coords();
759 ParticleVector &P = charged_particles.Field(Boris::MOM);
760 ParticleVector &M = charged_particles.Field(Boris::MASS);
761 ParticleVector &Q = charged_particles.Field(Boris::CHARGE);
762
763 X.HostWrite();
764 P.HostWrite();
765 M.HostWrite();
766 Q.HostWrite();
767
768 for (int i = 0; i < charged_particles.GetNParticles(); i++)
769 {
770 for (int d = 0; d < dim; d++)
771 {
772 if (x_min[d] >= x_max[d]) { X(i,d) = x_min[d]; }
773 else
774 {
775 X(i,d) = x_min[d] + real_dist_x(gen)*(x_max[d] - x_min[d]);
776 }
777
778 // Initialize momentum
779 if (p_min[d] >= p_max[d]) { P(i,d) = p_min[d]; }
780 else
781 {
782 real_t p_val = norm_dist_p[d](gen);
783 while (p_val < p_min[d] || p_val > p_max[d])
784 {
785 p_val = norm_dist_p[d](gen);
786 }
787 P(i,d) = p_val;
788 }
789 }
790 // Initialize mass + charge
791 M(i) = m;
792 Q(i) = q;
793 }
794
795 X.Read();
796 P.Read();
797 M.Read();
798 Q.Read();
799}
int Union(const T &el)
Append element when it is not yet in the array, return index.
Definition array.hpp:988
int Size() const
Return the logical size of the array.
Definition array.hpp:192
void DeleteAt(const Array< int > &indices)
Delete entries at indices, and resize.
Definition array.hpp:1036
The MFEM Device class abstracts hardware devices such as GPUs, as well as programming models such as ...
Definition device.hpp:129
void Print(std::ostream &os=mfem::out)
Print the configuration of the MFEM virtual device object.
Definition device.cpp:319
static bool IsEnabled()
Return true if any backend other than Backend::CPU is enabled.
Definition device.hpp:252
FindPointsGSLIB can robustly evaluate a GridFunction on an arbitrary collection of points....
Definition gslib.hpp:115
void FindPoints(const Vector &point_pos, int point_pos_ordering=Ordering::byNODES)
Searches positions given in physical space by point_pos.
Definition gslib.cpp:1372
virtual void Interpolate(const GridFunction &field_in, Vector &field_out)
Interpolation of field values at prescribed reference space positions.
Definition gslib.cpp:3679
Array< unsigned int > GetPointsNotFoundIndices() const
Get array of indices of not-found points.
Definition gslib.cpp:4244
virtual const Array< unsigned int > & GetProc() const
Return MPI rank on which each point was found by FindPoints.
Definition gslib.hpp:610
Class for grid function - Vector with associated FE space.
Definition gridfunc.hpp:53
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:67
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:7159
int SpaceDimension() const
Dimension of the physical space containing the mesh.
Definition mesh.hpp:1317
static bool Root()
Return true if the rank in MPI_COMM_WORLD is zero.
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.
Type
Ordering methods:
Definition ordering.hpp:17
ParMesh * GetParMesh() const
Definition pfespace.hpp:341
Class for parallel grid function.
Definition pgridfunc.hpp:50
ParFiniteElementSpace * ParFESpace() const
void GetBoundingBox(Vector &p_min, Vector &p_max, int ref=2)
Definition pmesh.cpp:6419
ParticleSet initializes and manages data associated with particles.
MPI_Comm GetComm() const
Get the MPI communicator for this ParticleSet.
ParticleVector & Coords()
Get a reference to the coordinates ParticleVector.
ParticleVector & Field(int f)
Get a reference to field f 's ParticleVector.
int GetNParticles() const
Get the number of active particles currently held by this ParticleSet.
ParticleVector carries vector data (of a given vector dimension) for an arbitrary number of particles...
int GetVDim() const
Get the Vector dimension of the ParticleVector.
Ordering::Type GetOrdering() const
Get the ordering of data in the ParticleVector.
Container for data associated with a single particle.
Vector & Field(int f)
Get reference to field f Vector.
real_t & FieldValue(int f, int c=0)
Get reference to field f , component c value.
Vector & Coords()
Get reference to particle coordinates Vector.
Vector data type.
Definition vector.hpp:82
virtual const real_t * Read(bool on_dev=true) const
Shortcut for mfem::Read(vec.GetMemory(), vec.Size(), on_dev).
Definition vector.hpp:520
virtual real_t * ReadWrite(bool on_dev=true)
Shortcut for mfem::ReadWrite(vec.GetMemory(), vec.Size(), on_dev).
Definition vector.hpp:536
Vector & Set(const real_t a, const Vector &x)
(*this) = a * x
Definition vector.cpp:341
int Size() const
Returns the size of the vector.
Definition vector.hpp:234
virtual void UseDevice(bool use_dev) const
Enable execution of Vector operations using the mfem::Device.
Definition vector.hpp:145
virtual real_t * HostWrite()
Shortcut for mfem::Write(vec.GetMemory(), vec.Size(), false).
Definition vector.hpp:532
Vector & Add(const real_t a, const Vector &Va)
(*this) += a * Va
Definition vector.cpp:326
void cross3D(const Vector &vin, Vector &vout) const
Definition vector.cpp:639
int dim
Definition ex24.cpp:53
int main()
real_t b
Definition lissajous.cpp:42
int ReadGridFunction(std::string coll_name, std::string field_name, int pad_digits_cycle, int pad_digits_rank, int cycle, std::unique_ptr< VisItDataCollection > &dc, ParGridFunction *&gf)
Definition lorentz.cpp:711
struct LorentzContext ctx
void InitializeChargedParticles(ParticleSet &particles, const Vector &pos_min, const Vector &pos_max, const Vector &x_init, const Vector &p_init, real_t m, real_t q)
Definition lorentz.cpp:735
void display_banner(ostream &os)
Definition lorentz.cpp:695
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
void add(const Vector &v1, const Vector &v2, Vector &v)
Definition vector.cpp:414
OutStream err(std::cerr)
Global stream used by the library for standard error output. Initially it uses the same std::streambu...
Definition globals.hpp:71
float real_t
Definition config.hpp:46
void forall(int N, lambda &&body)
Definition forall.hpp:1134
const char vishost[]
STL namespace.
real_t p(const Vector &x, real_t t)