MFEM  v3.1
Finite element discretization library
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Pages
tesla.cpp
Go to the documentation of this file.
1 // Copyright (c) 2010, Lawrence Livermore National Security, LLC. Produced at
2 // the Lawrence Livermore National Laboratory. LLNL-CODE-443211. All Rights
3 // reserved. See file COPYRIGHT for details.
4 //
5 // This file is part of the MFEM library. For more information and source code
6 // availability see http://mfem.org.
7 //
8 // MFEM is free software; you can redistribute it and/or modify it under the
9 // terms of the GNU Lesser General Public License (as published by the Free
10 // Software Foundation) version 2.1 dated February 1999.
11 //
12 // -----------------------------------------------------
13 // Tesla Miniapp: Simple Magnetostatics Simulation Code
14 // -----------------------------------------------------
15 //
16 // This miniapp solves a simple 3D magnetostatic problem.
17 //
18 // Curl 1/mu Curl A = J + Curl mu0/mu M
19 //
20 // The permeability function is that of the vacuum with an optional diamagnetic
21 // or paramagnetic spherical shell. The optional current density takes the form
22 // of a user defined ring of current. The optional magnetization consists of a
23 // cylindrical bar of constant magnetization.
24 //
25 // The boundary conditions either apply a user selected uniform magnetic flux
26 // density or a surface current flowing between user defined surfaces.
27 //
28 // We discretize the vector potential with H(Curl) finite elements. The magnetic
29 // flux B is discretized with H(Div) finite elements.
30 //
31 // Compile with: make tesla
32 //
33 // Sample runs:
34 //
35 // A cylindrical bar magnet in a metal sphere:
36 // mpirun -np 4 tesla -bm '0 -0.5 0 0 0.5 0 0.2 1'
37 //
38 // A spherical shell of paramagnetic material in a uniform B field:
39 // mpirun -np 4 tesla -ubbc '0 0 1' -ms '0 0 0 0.2 0.4 10'
40 //
41 // A ring of current in a metal sphere:
42 // mpirun -np 4 tesla -cr '0 0 -0.2 0 0 0.2 0.2 0.4 1'
43 //
44 // An example demonstrating the use of surface currents:
45 // mpirun -np 4 tesla -m ./square-angled-pipe.mesh
46 // -kbcs '3' -vbcs '1 2' -vbcv '-0.5 0.5'
47 //
48 // An example combining the paramagnetic shell, permanent magnet,
49 // and current ring:
50 // mpirun -np 4 tesla -m ../../data/inline-hex.mesh
51 // -ms '0.5 0.5 0.5 0.4 0.45 20'
52 // -bm '0.5 0.5 0.3 0.5 0.5 0.7 0.1 1'
53 // -cr '0.5 0.5 0.45 0.5 0.5 0.55 0.2 0.3 1'
54 //
55 // By default the sources and fields are all zero:
56 // mpirun -np 4 tesla
57 
58 #include "tesla_solver.hpp"
59 #include <fstream>
60 #include <iostream>
61 
62 using namespace std;
63 using namespace mfem;
64 using namespace mfem::electromagnetics;
65 
66 // Permeability Function
67 static Vector ms_params_(0); // Center, Inner and Outer Radii, and
68 // Permeability of magnetic shell
69 double magnetic_shell(const Vector &);
70 double muInv(const Vector & x) { return 1.0/magnetic_shell(x); }
71 
72 // Current Density Function
73 static Vector cr_params_(0); // Axis Start, Axis End, Inner Ring Radius,
74 // Outer Ring Radius, and Total Current
75 // of current ring (annulus)
76 void current_ring(const Vector &, Vector &);
77 
78 // Magnetization
79 static Vector bm_params_(0); // Axis Start, Axis End, Bar Radius,
80 // and Magnetic Field Magnitude
81 void bar_magnet(const Vector &, Vector &);
82 
83 // A Field Boundary Condition for B = (Bx,By,Bz)
84 static Vector b_uniform_(0);
85 void a_bc_uniform(const Vector &, Vector&);
86 
87 // Phi_M Boundary Condition for H = (0,0,1)
88 double phi_m_bc_uniform(const Vector &x);
89 
90 // Prints the program's logo to the given output stream
91 void display_banner(ostream & os);
92 
93 int main(int argc, char *argv[])
94 {
95  // Initialize MPI.
96  int num_procs, myid;
97  MPI_Init(&argc, &argv);
98  MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
99  MPI_Comm_rank(MPI_COMM_WORLD, &myid);
100 
101  if ( myid == 0 ) { display_banner(cout); }
102 
103  // Parse command-line options.
104  const char *mesh_file = "butterfly_3d.mesh";
105  int order = 1;
106  int sr = 0, pr = 0;
107  bool visualization = true;
108  bool visit = true;
109 
110  Array<int> kbcs;
111  Array<int> vbcs;
112 
113  Vector vbcv;
114 
115  OptionsParser args(argc, argv);
116  args.AddOption(&mesh_file, "-m", "--mesh",
117  "Mesh file to use.");
118  args.AddOption(&order, "-o", "--order",
119  "Finite element order (polynomial degree).");
120  args.AddOption(&sr, "-rs", "--serial-ref-levels",
121  "Number of serial refinement levels.");
122  args.AddOption(&pr, "-rp", "--parallel-ref-levels",
123  "Number of parallel refinement levels.");
124  args.AddOption(&b_uniform_, "-ubbc", "--uniform-b-bc",
125  "Specify if the three components of the constant magnetic flux density");
126  args.AddOption(&ms_params_, "-ms", "--magnetic-shell-params",
127  "Center, Inner Radius, Outer Radius, and Permeability of Magnetic Shell");
128  args.AddOption(&cr_params_, "-cr", "--current-ring-params",
129  "Axis End Points, Inner Radius, Outer Radius and Total Current of Annulus");
130  args.AddOption(&bm_params_, "-bm", "--bar-magnet-params",
131  "Axis End Points, Radius, and Magnetic Field of Cylindrical Magnet");
132  args.AddOption(&kbcs, "-kbcs", "--surface-current-bc",
133  "Surfaces for the Surface Current (K) Boundary Condition");
134  args.AddOption(&vbcs, "-vbcs", "--voltage-bc-surf",
135  "Voltage Boundary Condition Surfaces (to drive K)");
136  args.AddOption(&vbcv, "-vbcv", "--voltage-bc-vals",
137  "Voltage Boundary Condition Values (to drive K)");
138  args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
139  "--no-visualization",
140  "Enable or disable GLVis visualization.");
141  args.AddOption(&visit, "-visit", "--visit", "-no-visit", "--no-visit",
142  "Enable or disable VisIt visualization.");
143  args.Parse();
144  if (!args.Good())
145  {
146  if (myid == 0)
147  {
148  args.PrintUsage(cout);
149  }
150  MPI_Finalize();
151  return 1;
152  }
153  if (myid == 0)
154  {
155  args.PrintOptions(cout);
156  }
157 
158  // Read the (serial) mesh from the given mesh file on all processors. We
159  // can handle triangular, quadrilateral, tetrahedral, hexahedral, surface
160  // and volume meshes with the same code.
161  Mesh *mesh;
162  ifstream imesh(mesh_file);
163  if (!imesh)
164  {
165  if (myid == 0)
166  {
167  cerr << "\nCan not open mesh file: " << mesh_file << '\n' << endl;
168  }
169  MPI_Finalize();
170  return 2;
171  }
172  mesh = new Mesh(imesh, 1, 1);
173  imesh.close();
174 
175  // Refine the serial mesh on all processors to increase the resolution. In
176  // this example we do 'ref_levels' of uniform refinement. NURBS meshes are
177  // refined at least twice, as they are typically coarse.
178  if (myid == 0) { cout << "Starting initialization." << endl; }
179  {
180  int ref_levels = sr;
181  if (mesh->NURBSext && ref_levels < 2)
182  {
183  ref_levels = 2;
184  }
185  for (int l = 0; l < ref_levels; l++)
186  {
187  mesh->UniformRefinement();
188  }
189  }
190 
191  // Project a NURBS mesh to a piecewise-quadratic curved mesh. Make sure that
192  // the mesh is non-conforming.
193  if (mesh->NURBSext)
194  {
195  mesh->SetCurvature(2);
196  }
197  mesh->EnsureNCMesh();
198 
199  // Define a parallel mesh by a partitioning of the serial mesh. Refine
200  // this mesh further in parallel to increase the resolution. Once the
201  // parallel mesh is defined, the serial mesh can be deleted.
202  ParMesh pmesh(MPI_COMM_WORLD, *mesh);
203  delete mesh;
204 
205  // Refine this mesh in parallel to increase the resolution.
206  int par_ref_levels = pr;
207  for (int l = 0; l < par_ref_levels; l++)
208  {
209  pmesh.UniformRefinement();
210  }
211 
212  // If values for Voltage BCs were not set issue a warning and exit
213  if ( ( vbcs.Size() > 0 && kbcs.Size() == 0 ) ||
214  ( kbcs.Size() > 0 && vbcs.Size() == 0 ) ||
215  ( vbcv.Size() < vbcs.Size() ) )
216  {
217  if ( myid == 0 )
218  {
219  cout << "The surface current (K) boundary condition requires "
220  << "surface current boundary condition surfaces (with -kbcs), "
221  << "voltage boundary condition surface (with -vbcs), "
222  << "and voltage boundary condition values (with -vbcv)."
223  << endl;
224  }
225  MPI_Finalize();
226  return 3;
227  }
228 
229  // Create the Magnetostatic solver
230  TeslaSolver Tesla(pmesh, order, kbcs, vbcs, vbcv,
231  (ms_params_.Size() > 0 ) ? muInv : NULL,
232  (b_uniform_.Size() > 0 ) ? a_bc_uniform : NULL,
233  (cr_params_.Size() > 0 ) ? current_ring : NULL,
234  (bm_params_.Size() > 0 ) ? bar_magnet : NULL);
235 
236  // Initialize GLVis visualization
237  if (visualization)
238  {
239  Tesla.InitializeGLVis();
240  }
241 
242  // Initialize VisIt visualization
243  VisItDataCollection visit_dc("Tesla-AMR-Parallel", &pmesh);
244 
245  if ( visit )
246  {
247  Tesla.RegisterVisItFields(visit_dc);
248  }
249  if (myid == 0) { cout << "Initialization done." << endl; }
250 
251  // The main AMR loop. In each iteration we solve the problem on the current
252  // mesh, visualize the solution, estimate the error on all elements, refine
253  // the worst elements and update all objects to work with the new mesh. We
254  // refine until the maximum number of dofs in the Nedelec finite element
255  // space reaches 10 million.
256  const int max_dofs = 10000000;
257  for (int it = 1; it <= 100; it++)
258  {
259  if (myid == 0)
260  {
261  cout << "\nAMR Iteration " << it << endl;
262  }
263 
264  // Display the current number of DoFs in each finite element space
265  Tesla.PrintSizes();
266 
267  // Solve the system and compute any auxiliary fields
268  Tesla.Solve();
269 
270  // Determine the current size of the linear system
271  int prob_size = Tesla.GetProblemSize();
272 
273  // Write fields to disk for VisIt
274  if ( visit )
275  {
276  Tesla.WriteVisItFields(it);
277  }
278 
279  // Send the solution by socket to a GLVis server.
280  if (visualization)
281  {
282  Tesla.DisplayToGLVis();
283  }
284  if (myid == 0 && (visit || visualization)) { cout << "done." << endl; }
285 
286  if (myid == 0)
287  {
288  cout << "AMR iteration " << it << " complete." << endl;
289  }
290 
291  // Check stopping criteria
292  if (prob_size > max_dofs)
293  {
294  if (myid == 0)
295  {
296  cout << "Reached maximum number of dofs, exiting..." << endl;
297  }
298  break;
299  }
300 
301  // Wait for user input. Ask every 10th iteration.
302  char c = 'c';
303  if (myid == 0 && (it % 10 == 0))
304  {
305  cout << "press (q)uit or (c)ontinue --> " << flush;
306  cin >> c;
307  }
308  MPI_Bcast(&c, 1, MPI_CHAR, 0, MPI_COMM_WORLD);
309 
310  if (c != 'c')
311  {
312  break;
313  }
314 
315  // Estimate element errors using the Zienkiewicz-Zhu error estimator.
316  Vector errors(pmesh.GetNE());
317  Tesla.GetErrorEstimates(errors);
318 
319  double local_max_err = errors.Max();
320  double global_max_err;
321  MPI_Allreduce(&local_max_err, &global_max_err, 1,
322  MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
323 
324  // Make a list of elements whose error is larger than a fraction
325  // of the maximum element error. These elements will be refined.
326  Array<int> ref_list;
327  const double frac = 0.5;
328  double threshold = frac * global_max_err;
329  for (int i = 0; i < errors.Size(); i++)
330  {
331  if (errors[i] >= threshold) { ref_list.Append(i); }
332  }
333 
334  // Refine the selected elements. Since we are going to transfer the
335  // grid function x from the coarse mesh to the new fine mesh in the
336  // next step, we need to request the "two-level state" of the mesh.
337  if (myid == 0) { cout << " Refinement ..." << flush; }
338  pmesh.GeneralRefinement(ref_list);
339 
340  // Update the magnetostatic solver to reflect the new state of the mesh.
341  Tesla.Update();
342  }
343 
344  MPI_Finalize();
345 
346  return 0;
347 }
348 
349 // Print the Volta ascii logo to the given ostream
350 void display_banner(ostream & os)
351 {
352  os << " ___________ __ " << endl
353  << " \\__ ___/___ _____| | _____ " << endl
354  << " | |_/ __ \\ / ___/ | \\__ \\ " << endl
355  << " | |\\ ___/ \\___ \\| |__/ __ \\_ " << endl
356  << " |____| \\___ >____ >____(____ / " << endl
357  << " \\/ \\/ \\/ " << endl << flush;
358 }
359 
360 // A spherical shell with constant permeability. The sphere has inner
361 // and outer radii, center, and relative permeability specified on the
362 // command line and stored in ms_params_.
363 double magnetic_shell(const Vector &x)
364 {
365  double r2 = 0.0;
366 
367  for (int i=0; i<x.Size(); i++)
368  {
369  r2 += (x(i)-ms_params_(i))*(x(i)-ms_params_(i));
370  }
371 
372  if ( sqrt(r2) >= ms_params_(x.Size()) &&
373  sqrt(r2) <= ms_params_(x.Size()+1) )
374  {
375  return mu0_*ms_params_(x.Size()+2);
376  }
377  return mu0_;
378 }
379 
380 // An annular ring of current density. The ring has two axis end
381 // points, inner and outer radii, and a constant current in Amperes.
382 void current_ring(const Vector &x, Vector &j)
383 {
384  MFEM_ASSERT(x.Size() == 3, "current_ring source requires 3D space.");
385 
386  j.SetSize(x.Size());
387  j = 0.0;
388 
389  Vector a(x.Size()); // Normalized Axis vector
390  Vector xu(x.Size()); // x vector relative to the axis end-point
391  Vector ju(x.Size()); // Unit vector in direction of current
392 
393  xu = x;
394 
395  for (int i=0; i<x.Size(); i++)
396  {
397  xu[i] -= cr_params_[i];
398  a[i] = cr_params_[x.Size()+i] - cr_params_[i];
399  }
400 
401  double h = a.Norml2();
402 
403  if ( h == 0.0 )
404  {
405  return;
406  }
407 
408  double ra = cr_params_[2*x.Size()+0];
409  double rb = cr_params_[2*x.Size()+1];
410  if ( ra > rb )
411  {
412  double rc = ra;
413  ra = rb;
414  rb = rc;
415  }
416  double xa = xu*a;
417 
418  if ( h > 0.0 )
419  {
420  xu.Add(-xa/(h*h),a);
421  }
422 
423  double xp = xu.Norml2();
424 
425  if ( xa >= 0.0 && xa <= h*h && xp >= ra && xp <= rb )
426  {
427  ju(0) = a(1) * xu(2) - a(2) * xu(1);
428  ju(1) = a(2) * xu(0) - a(0) * xu(2);
429  ju(2) = a(0) * xu(1) - a(1) * xu(0);
430  ju /= h;
431 
432  j.Add(cr_params_[2*x.Size()+2]/(h*(rb-ra)),ju);
433  }
434 }
435 
436 // A Cylindrical Rod of constant magnetization. The cylinder has two
437 // axis end points, a radius, and a constant magnetic field oriented
438 // along the axis.
439 void bar_magnet(const Vector &x, Vector &m)
440 {
441  m.SetSize(x.Size());
442  m = 0.0;
443 
444  Vector a(x.Size()); // Normalized Axis vector
445  Vector xu(x.Size()); // x vector relative to the axis end-point
446 
447  xu = x;
448 
449  for (int i=0; i<x.Size(); i++)
450  {
451  xu[i] -= bm_params_[i];
452  a[i] = bm_params_[x.Size()+i] - bm_params_[i];
453  }
454 
455  double h = a.Norml2();
456 
457  if ( h == 0.0 )
458  {
459  return;
460  }
461 
462  double r = bm_params_[2*x.Size()];
463  double xa = xu*a;
464 
465  if ( h > 0.0 )
466  {
467  xu.Add(-xa/(h*h),a);
468  }
469 
470  double xp = xu.Norml2();
471 
472  if ( xa >= 0.0 && xa <= h*h && xp <= r )
473  {
474  m.Add(bm_params_[2*x.Size()+1]/h,a);
475  }
476 }
477 
478 // To produce a uniform magnetic flux the vector potential can be set
479 // to ( By z, Bz x, Bx y).
480 void a_bc_uniform(const Vector & x, Vector & a)
481 {
482  a.SetSize(3);
483  a(0) = b_uniform_(1) * x(2);
484  a(1) = b_uniform_(2) * x(0);
485  a(2) = b_uniform_(0) * x(1);
486 }
487 
488 // To produce a uniform magnetic field the scalar potential can be set
489 // to -z (or -y in 2D).
490 double phi_m_bc_uniform(const Vector &x)
491 {
492  return -x(x.Size()-1);
493 }
int Size() const
Logical size of the array.
Definition: array.hpp:109
double magnetic_shell(const Vector &)
Definition: tesla.cpp:363
double muInv(const Vector &x)
Definition: tesla.cpp:70
void SetSize(int s)
Resizes the vector if the new size is different.
Definition: vector.hpp:259
double Norml2() const
Returns the l2 norm of the vector.
Definition: vector.cpp:644
int Size() const
Returns the size of the vector.
Definition: vector.hpp:84
void bar_magnet(const Vector &, Vector &)
Definition: tesla.cpp:439
int GetNE() const
Returns number of elements.
Definition: mesh.hpp:454
void GetErrorEstimates(Vector &errors)
double phi_m_bc_uniform(const Vector &x)
Definition: tesla.cpp:490
int Append(const T &el)
Append element to array, resize if necessary.
Definition: array.hpp:368
void UniformRefinement(int i, const DSTable &, int *, int *, int *)
Definition: mesh.cpp:7316
void current_ring(const Vector &, Vector &)
Definition: tesla.cpp:382
Data collection with VisIt I/O routines.
void SetCurvature(int order, bool discont=false, int space_dim=-1, int ordering=1)
Definition: mesh.cpp:3736
void PrintUsage(std::ostream &out) const
Definition: optparser.cpp:434
void RegisterVisItFields(VisItDataCollection &visit_dc)
int main(int argc, char *argv[])
Definition: ex1.cpp:45
MPI_Comm GetComm() const
Definition: pmesh.hpp:86
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)
Definition: optparser.hpp:74
void a_bc_uniform(const Vector &, Vector &)
Definition: tesla.cpp:480
NURBSExtension * NURBSext
Definition: mesh.hpp:142
Vector & Add(const double a, const Vector &Va)
(*this) += a * Va
Definition: vector.cpp:200
void PrintOptions(std::ostream &out) const
Definition: optparser.cpp:304
void display_banner(ostream &os)
Definition: tesla.cpp:350
void EnsureNCMesh()
Definition: mesh.cpp:6929
Vector data type.
Definition: vector.hpp:33
void GeneralRefinement(const Array< Refinement > &refinements, int nonconforming=-1, int nc_limit=0)
Definition: mesh.cpp:6864
Class for parallel meshes.
Definition: pmesh.hpp:28
bool Good() const
Definition: optparser.hpp:120