Files
mfem/examples/ex34.cpp
T

626 lines
21 KiB
C++

// MFEM Example 34
//
// Compile with: make ex34
//
// Sample runs: ex34 -o 2
// ex34 -o 2 -pa -hex
//
// Device sample runs:
// ex34 -o 2 -pa -hex -d cuda
// ex34 -o 2 -no-pa -d cuda
//
// Description: This example code solves a simple magnetostatic problem
// curl curl A = J where the current density J is computed on a
// subset of the domain as J = -sigma grad phi. We discretize the
// vector potential with Nedelec finite elements, the scalar
// potential with Lagrange finite elements, and the current
// density with Raviart-Thomas finite elements.
//
// The example demonstrates the use of a SubMesh to compute the
// scalar potential and its associated current density which is
// then transferred to the original mesh and used as a source
// function.
//
// Note that this example takes certain liberties with the
// current density which is not necessarily divergence free
// as it should be. This was done to focus on the use of the
// SubMesh to transfer information between a full mesh and a
// sub-domain. A more rigorous implementation might employ an
// H(div) saddle point solver to obtain a divergence free J on
// the SubMesh. It would then also need to ensure that the r.h.s.
// of curl curl A = J does in fact lie in the range of the weak
// curl operator by performing a divergence cleaning procedure
// before the solve. After divergence cleaning the delta
// parameter would probably not be needed.
//
// This example is designed to make use of a specific mesh which
// has a known configuration of elements and boundary attributes.
// Other meshes could be used but extra care would be required to
// properly define the SubMesh and the necessary boundaries.
//
// We recommend viewing examples 1 and 3 before viewing this
// example.
#include "mfem.hpp"
#include <fstream>
#include <iostream>
using namespace std;
using namespace mfem;
static bool pa_ = false;
static bool algebraic_ceed_ = false;
void ComputeCurrentDensityOnSubMesh(int order,
bool visualization,
const Array<int> &phi0_attr,
const Array<int> &phi1_attr,
const Array<int> &jn_zero_attr,
GridFunction &j_cond);
int main(int argc, char *argv[])
{
// 1. Parse command-line options.
const char *mesh_file = "../data/fichera-mixed.mesh";
Array<int> cond_attr;
Array<int> submesh_elems;
Array<int> sym_plane_attr;
Array<int> phi0_attr;
Array<int> phi1_attr;
Array<int> jn_zero_attr;
int ref_levels = 1;
int order = 1;
real_t delta_const = 1e-6;
bool mixed = true;
bool static_cond = false;
const char *device_config = "cpu";
bool visualization = true;
OptionsParser args(argc, argv);
args.AddOption(&ref_levels, "-r", "--refine",
"Number of times to refine the mesh uniformly.");
args.AddOption(&order, "-o", "--order",
"Finite element order (polynomial degree).");
args.AddOption(&delta_const, "-mc", "--magnetic-cond",
"Magnetic Conductivity");
args.AddOption(&static_cond, "-sc", "--static-condensation", "-no-sc",
"--no-static-condensation", "Enable static condensation.");
args.AddOption(&mixed, "-mixed", "--mixed-mesh", "-hex",
"--hex-mesh", "Mixed mesh of hexahedral mesh.");
args.AddOption(&pa_, "-pa", "--partial-assembly", "-no-pa",
"--no-partial-assembly", "Enable Partial Assembly.");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
#ifdef MFEM_USE_CEED
args.AddOption(&algebraic_ceed_, "-a", "--algebraic", "-no-a", "--no-algebraic",
"Use algebraic Ceed solver");
#endif
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(cout);
return 1;
}
args.PrintOptions(cout);
if (!mixed || pa_)
{
mesh_file = "../data/fichera.mesh";
}
if (submesh_elems.Size() == 0)
{
if (strcmp(mesh_file, "../data/fichera-mixed.mesh") == 0)
{
submesh_elems.SetSize(5);
submesh_elems[0] = 0;
submesh_elems[1] = 2;
submesh_elems[2] = 3;
submesh_elems[3] = 4;
submesh_elems[4] = 9;
}
else if (strcmp(mesh_file, "../data/fichera.mesh") == 0)
{
submesh_elems.SetSize(7);
submesh_elems[0] = 10;
submesh_elems[1] = 14;
submesh_elems[2] = 34;
submesh_elems[3] = 36;
submesh_elems[4] = 37;
submesh_elems[5] = 38;
submesh_elems[6] = 39;
}
}
if (sym_plane_attr.Size() == 0)
{
if (strcmp(mesh_file, "../data/fichera-mixed.mesh") == 0 ||
strcmp(mesh_file, "../data/fichera.mesh") == 0)
{
sym_plane_attr.SetSize(8);
sym_plane_attr[0] = 9;
sym_plane_attr[1] = 10;
sym_plane_attr[2] = 11;
sym_plane_attr[3] = 12;
sym_plane_attr[4] = 13;
sym_plane_attr[5] = 14;
sym_plane_attr[6] = 15;
sym_plane_attr[7] = 16;
}
}
if (phi0_attr.Size() == 0)
{
if (strcmp(mesh_file, "../data/fichera-mixed.mesh") == 0 ||
strcmp(mesh_file, "../data/fichera.mesh") == 0)
{
phi0_attr.Append(2);
}
}
if (phi1_attr.Size() == 0)
{
if (strcmp(mesh_file, "../data/fichera-mixed.mesh") == 0 ||
strcmp(mesh_file, "../data/fichera.mesh") == 0)
{
phi1_attr.Append(23);
}
}
if (jn_zero_attr.Size() == 0)
{
if (strcmp(mesh_file, "../data/fichera-mixed.mesh") == 0 ||
strcmp(mesh_file, "../data/fichera.mesh") == 0)
{
jn_zero_attr.Append(25);
}
for (int i=0; i<sym_plane_attr.Size(); i++)
{
jn_zero_attr.Append(sym_plane_attr[i]);
}
}
// 2. Enable hardware devices such as GPUs, and programming models such as
// CUDA, OCCA, RAJA and OpenMP based on command line options.
Device device(device_config);
device.Print();
// 3. Read the (serial) mesh from the given mesh file on all processors. We
// can handle triangular, quadrilateral, tetrahedral, hexahedral, surface
// and volume meshes with the same code.
Mesh mesh(mesh_file, 1, 1);
int dim = mesh.Dimension();
if (!mixed || pa_)
{
mesh.UniformRefinement();
if (ref_levels > 0)
{
ref_levels--;
}
}
int submesh_attr = -1;
if (cond_attr.Size() == 0 && submesh_elems.Size() > 0)
{
int max_attr = mesh.attributes.Max();
submesh_attr = max_attr + 1;
for (int i=0; i<submesh_elems.Size(); i++)
{
mesh.SetAttribute(submesh_elems[i], submesh_attr);
}
mesh.SetAttributes();
if (cond_attr.Size() == 0)
{
cond_attr.Append(submesh_attr);
}
}
// 4. Refine the serial mesh on all processors to increase the resolution. In
// this example we do 'ref_levels' of uniform refinement.
{
for (int l = 0; l < ref_levels; l++)
{
mesh.UniformRefinement();
}
}
// 5b. Extract a submesh covering a portion of the domain
SubMesh mesh_cond(SubMesh::CreateFromDomain(mesh, cond_attr));
// 6. Define a suitable finite element space on the SubMesh and compute
// the current density as an H(div) field.
RT_FECollection fec_cond_rt(order - 1, dim);
FiniteElementSpace fes_cond_rt(&mesh_cond, &fec_cond_rt);
GridFunction j_cond(&fes_cond_rt);
ComputeCurrentDensityOnSubMesh(order, visualization,
phi0_attr, phi1_attr, jn_zero_attr, j_cond);
// 6a. Save the SubMesh and associated current density in parallel. This
// output can be viewed later using GLVis:
// "glvis -np <np> -m cond_mesh -g cond_j"
{
ostringstream mesh_name, cond_name;
mesh_name << "cond.mesh";
cond_name << "cond_j.gf";
ofstream mesh_ofs(mesh_name.str().c_str());
mesh_ofs.precision(8);
mesh_cond.Print(mesh_ofs);
ofstream cond_ofs(cond_name.str().c_str());
cond_ofs.precision(8);
j_cond.Save(cond_ofs);
}
// 6b. Send the current density, computed on the SubMesh, to a GLVis server.
if (visualization)
{
char vishost[] = "localhost";
int visport = 19916;
socketstream port_sock(vishost, visport);
port_sock.precision(8);
port_sock << "solution\n" << mesh_cond << j_cond
<< "window_title 'Conductor J'"
<< "window_geometry 400 0 400 350" << flush;
}
// 7. Define a parallel finite element space on the full mesh. Here we use
// the H(curl) finite elements for the vector potential and H(div) for the
// current density.
ND_FECollection fec_nd(order, dim);
RT_FECollection fec_rt(order - 1, dim);
FiniteElementSpace fespace_nd(&mesh, &fec_nd);
FiniteElementSpace fespace_rt(&mesh, &fec_rt);
GridFunction j_full(&fespace_rt);
j_full = 0.0;
mesh_cond.Transfer(j_cond, j_full);
// 7a. Send the transferred current density to a GLVis server.
if (visualization)
{
char vishost[] = "localhost";
int visport = 19916;
socketstream sol_sock(vishost, visport);
sol_sock.precision(8);
sol_sock << "solution\n" << mesh << j_full
<< "window_title 'J Full'"
<< "window_geometry 400 430 400 350" << flush;
}
// 8. Determine the list of true (i.e. parallel conforming) essential
// boundary dofs. In this example, the boundary conditions are defined by
// marking all the boundary attributes except for those on a symmetry
// plane as essential (Dirichlet) and converting them to a list of true
// dofs.
Array<int> ess_tdof_list;
Array<int> ess_bdr;
if (mesh.bdr_attributes.Size())
{
ess_bdr.SetSize(mesh.bdr_attributes.Max());
ess_bdr = 1;
for (int i=0; i<sym_plane_attr.Size(); i++)
{
ess_bdr[sym_plane_attr[i]-1] = 0;
}
fespace_nd.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
}
// 9. Set up the parallel linear form b(.) which corresponds to the
// right-hand side of the FEM linear system, which in this case is
// (J,W_i) where J is given by the function H(div) field transferred
// from the SubMesh and W_i are the basis functions in the finite
// element fespace.
VectorGridFunctionCoefficient jCoef(&j_full);
LinearForm b(&fespace_nd);
b.AddDomainIntegrator(new VectorFEDomainLFIntegrator(jCoef));
b.Assemble();
// 10. Define the solution vector x as a parallel finite element grid
// function corresponding to fespace. Initialize x to zero.
GridFunction x(&fespace_nd);
x = 0.0;
// 11. Set up the parallel bilinear form corresponding to the EM diffusion
// operator curl muinv curl + delta I, by adding the curl-curl and the
// mass domain integrators. For standard magnetostatics equations choose
// delta << 1. Larger values of delta should make the linear system
// easier to solve at the expense of resembling a diffusive quasistatic
// magnetic field. A reasonable balance must be found whenever the mesh
// or problem setup is altered.
ConstantCoefficient muinv(1.0);
ConstantCoefficient delta(delta_const);
BilinearForm a(&fespace_nd);
if (pa_) { a.SetAssemblyLevel(AssemblyLevel::PARTIAL); }
a.AddDomainIntegrator(new CurlCurlIntegrator(muinv));
a.AddDomainIntegrator(new VectorFEMassIntegrator(delta));
// 12. Assemble the parallel bilinear form and the corresponding linear
// system, applying any necessary transformations such as: parallel
// assembly, eliminating boundary conditions, applying conforming
// constraints for non-conforming AMR, static condensation, etc.
if (static_cond) { a.EnableStaticCondensation(); }
a.Assemble();
OperatorPtr A;
Vector B, X;
a.FormLinearSystem(ess_tdof_list, x, b, A, X, B);
// 13. Solve the system AX=B
if (pa_) // Jacobi preconditioning in partial assembly mode
{
cout << "\nSolving for magnetic vector potential "
<< "using CG with a Jacobi preconditioner" << endl;
OperatorJacobiSmoother M(a, ess_tdof_list);
PCG(*A, M, B, X, 1, 1000, 1e-12, 0.0);
}
else
{
#ifndef MFEM_USE_SUITESPARSE
cout << "\nSolving for magnetic vector potential "
<< "using CG with a Gauss-Seidel preconditioner" << endl;
// 13a. Define a simple symmetric Gauss-Seidel preconditioner and use
// it to solve the system Ax=b with PCG.
GSSmoother M((SparseMatrix&)(*A));
PCG(*A, M, B, X, 1, 500, 1e-12, 0.0);
#else
cout << "\nSolving for magnetic vector potential "
<< "using UMFPack" << endl;
// 13a. If MFEM was compiled with SuiteSparse, use UMFPACK to solve the
// system.
UMFPackSolver umf_solver;
umf_solver.Control[UMFPACK_ORDERING] = UMFPACK_ORDERING_METIS;
umf_solver.SetOperator(*A);
umf_solver.Mult(B, X);
#endif
}
// 14. Recover the parallel grid function corresponding to X. This is the
// local finite element solution on each processor.
a.RecoverFEMSolution(X, b, x);
// 15. Save the refined mesh and the solution in parallel. This output can
// be viewed later using GLVis: "glvis -np <np> -m mesh -g sol".
{
ostringstream mesh_name, sol_name;
mesh_name << "refined.mesh";
sol_name << "sol.gf";
ofstream mesh_ofs(mesh_name.str().c_str());
mesh_ofs.precision(8);
mesh.Print(mesh_ofs);
ofstream sol_ofs(sol_name.str().c_str());
sol_ofs.precision(8);
x.Save(sol_ofs);
}
// 16. Send the solution by socket to a GLVis server.
if (visualization)
{
char vishost[] = "localhost";
int visport = 19916;
socketstream sol_sock(vishost, visport);
sol_sock.precision(8);
sol_sock << "solution\n" << mesh << x
<< "window_title 'Vector Potential'"
<< "window_geometry 800 0 400 350" << flush;
}
// 17. Compute the magnetic flux as the curl of the solution
DiscreteLinearOperator curl(&fespace_nd, &fespace_rt);
curl.AddDomainInterpolator(new CurlInterpolator);
curl.Assemble();
curl.Finalize();
GridFunction dx(&fespace_rt);
curl.Mult(x, dx);
// 18. Save the curl of the solution in parallel. This output can be viewed
// later using GLVis: "glvis -np <np> -m mesh -g dsol".
{
ostringstream dsol_name;
dsol_name << "dsol.gf";
ofstream dsol_ofs(dsol_name.str().c_str());
dsol_ofs.precision(8);
dx.Save(dsol_ofs);
}
// 19. Send the curl of the solution by socket to a GLVis server.
if (visualization)
{
char vishost[] = "localhost";
int visport = 19916;
socketstream sol_sock(vishost, visport);
sol_sock.precision(8);
sol_sock << "solution\n" << mesh << dx
<< "window_title 'Magnetic Flux'"
<< "window_geometry 1200 0 400 350" << flush;
}
// 20. Clean exit
return 0;
}
void ComputeCurrentDensityOnSubMesh(int order,
bool visualization,
const Array<int> &phi0_attr,
const Array<int> &phi1_attr,
const Array<int> &jn_zero_attr,
GridFunction &j_cond)
{
// Extract the finite element space and mesh on which j_cond is defined
FiniteElementSpace &fes_cond_rt = *j_cond.FESpace();
Mesh &mesh_cond = *fes_cond_rt.GetMesh();
int dim = mesh_cond.Dimension();
// Define a parallel finite element space on the SubMesh. Here we use the H1
// finite elements for the electrostatic potential.
H1_FECollection fec_h1(order, dim);
FiniteElementSpace fes_cond_h1(&mesh_cond, &fec_h1);
// Define the conductivity coefficient and the boundaries associated with the
// fixed potentials phi0 and phi1 which will drive the current.
ConstantCoefficient sigmaCoef(1.0);
Array<int> ess_bdr_phi(mesh_cond.bdr_attributes.Max());
Array<int> ess_bdr_j(mesh_cond.bdr_attributes.Max());
Array<int> ess_bdr_tdof_phi;
ess_bdr_phi = 0;
ess_bdr_j = 0;
for (int i=0; i<phi0_attr.Size(); i++)
{
ess_bdr_phi[phi0_attr[i]-1] = 1;
}
for (int i=0; i<phi1_attr.Size(); i++)
{
ess_bdr_phi[phi1_attr[i]-1] = 1;
}
for (int i=0; i<jn_zero_attr.Size(); i++)
{
ess_bdr_j[jn_zero_attr[i]-1] = 1;
}
fes_cond_h1.GetEssentialTrueDofs(ess_bdr_phi, ess_bdr_tdof_phi);
// Setup the bilinear form corresponding to -Div(sigma Grad phi)
BilinearForm a_h1(&fes_cond_h1);
a_h1.AddDomainIntegrator(new DiffusionIntegrator(sigmaCoef));
a_h1.Assemble();
// Set the r.h.s. to zero
LinearForm b_h1(&fes_cond_h1);
b_h1 = 0.0;
// Setup the boundary conditions on phi
ConstantCoefficient one(1.0);
ConstantCoefficient zero(0.0);
GridFunction phi_h1(&fes_cond_h1);
phi_h1 = 0.0;
Array<int> bdr0(mesh_cond.bdr_attributes.Max()); bdr0 = 0;
for (int i=0; i<phi0_attr.Size(); i++)
{
bdr0[phi0_attr[i]-1] = 1;
}
phi_h1.ProjectBdrCoefficient(zero, bdr0);
Array<int> bdr1(mesh_cond.bdr_attributes.Max()); bdr1 = 0;
for (int i=0; i<phi1_attr.Size(); i++)
{
bdr1[phi1_attr[i]-1] = 1;
}
phi_h1.ProjectBdrCoefficient(one, bdr1);
{
OperatorPtr A;
Vector B, X;
a_h1.FormLinearSystem(ess_bdr_tdof_phi, phi_h1, b_h1, A, X, B);
// Solve the linear system
if (!pa_)
{
#ifndef MFEM_USE_SUITESPARSE
cout << "\nSolving for electric potential using PCG "
<< "with a Gauss-Seidel preconditioner" << endl;
// Use a simple symmetric Gauss-Seidel preconditioner with PCG.
GSSmoother M((SparseMatrix&)(*A));
PCG(*A, M, B, X, 1, 200, 1e-12, 0.0);
#else
cout << "\nSolving for electric potential using UMFPack" << endl;
// If MFEM was compiled with SuiteSparse,
// use UMFPACK to solve the system.
UMFPackSolver umf_solver;
umf_solver.Control[UMFPACK_ORDERING] = UMFPACK_ORDERING_METIS;
umf_solver.SetOperator(*A);
umf_solver.Mult(B, X);
#endif
}
else
{
cout << "\nSolving for electric potential using CG" << endl;
if (UsesTensorBasis(fes_cond_h1))
{
if (algebraic_ceed_)
{
ceed::AlgebraicSolver M(a_h1, ess_bdr_tdof_phi);
PCG(*A, M, B, X, 1, 400, 1e-12, 0.0);
}
else
{
OperatorJacobiSmoother M(a_h1, ess_bdr_tdof_phi);
PCG(*A, M, B, X, 1, 400, 1e-12, 0.0);
}
}
else
{
CG(*A, B, X, 1, 400, 1e-12, 0.0);
}
}
a_h1.RecoverFEMSolution(X, b_h1, phi_h1);
}
if (visualization)
{
char vishost[] = "localhost";
int visport = 19916;
socketstream port_sock(vishost, visport);
port_sock.precision(8);
port_sock << "solution\n" << mesh_cond << phi_h1
<< "window_title 'Conductor Potential'"
<< "window_geometry 0 0 400 350" << flush;
}
// Solve for the current density J = -sigma Grad phi with boundary conditions
// J.n = 0 on the walls of the conductor but not on the ports where phi=0 and
// phi=1.
// J will be computed in H(div) so we need an RT mass matrix
BilinearForm m_rt(&fes_cond_rt);
m_rt.AddDomainIntegrator(new VectorFEMassIntegrator);
m_rt.Assemble();
// Assemble the (sigma Grad phi) operator
MixedBilinearForm d_h1(&fes_cond_h1, &fes_cond_rt);
d_h1.AddDomainIntegrator(new MixedVectorGradientIntegrator(sigmaCoef));
d_h1.Assemble();
// Compute the r.h.s, b_rt = sigma E = -sigma Grad phi
LinearForm b_rt(&fes_cond_rt);
d_h1.Mult(phi_h1, b_rt);
b_rt *= -1.0;
// Apply the necessary boundary conditions and solve for J in H(div)
cout << "\nSolving for current density in H(Div) "
<< "using diagonally scaled CG" << endl;
cout << "Size of linear system: "
<< fes_cond_rt.GetTrueVSize() << endl;
Array<int> ess_bdr_tdof_rt;
OperatorPtr M;
Vector B, X;
fes_cond_rt.GetEssentialTrueDofs(ess_bdr_j, ess_bdr_tdof_rt);
j_cond = 0.0;
m_rt.FormLinearSystem(ess_bdr_tdof_rt, j_cond, b_rt, M, X, B);
CGSolver cg;
cg.SetRelTol(1e-12);
cg.SetMaxIter(2000);
cg.SetPrintLevel(1);
cg.SetOperator(*M);
cg.Mult(B, X);
m_rt.RecoverFEMSolution(X, b_rt, j_cond);
}