Files
mfem/miniapps/electromagnetics/tesla_solver.cpp
T

672 lines
17 KiB
C++

// Copyright (c) 2010-2020, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "tesla_solver.hpp"
#ifdef MFEM_USE_MPI
using namespace std;
namespace mfem
{
using namespace common;
namespace electromagnetics
{
TeslaSolver::TeslaSolver(ParMesh & pmesh, int order,
Array<int> & kbcs,
Array<int> & vbcs, Vector & vbcv,
Coefficient & muInvCoef,
void (*a_bc )(const Vector&, Vector&),
void (*j_src)(const Vector&, Vector&),
void (*m_src)(const Vector&, Vector&))
: myid_(0),
num_procs_(1),
order_(order),
pmesh_(&pmesh),
visit_dc_(NULL),
H1FESpace_(NULL),
HCurlFESpace_(NULL),
HDivFESpace_(NULL),
curlMuInvCurl_(NULL),
hCurlMass_(NULL),
hDivHCurlMuInv_(NULL),
weakCurlMuInv_(NULL),
grad_(NULL),
curl_(NULL),
a_(NULL),
b_(NULL),
h_(NULL),
jr_(NULL),
j_(NULL),
k_(NULL),
m_(NULL),
bd_(NULL),
jd_(NULL),
DivFreeProj_(NULL),
SurfCur_(NULL),
muInvCoef_(&muInvCoef),
aBCCoef_(NULL),
jCoef_(NULL),
mCoef_(NULL),
a_bc_(a_bc),
j_src_(j_src),
m_src_(m_src)
{
// Initialize MPI variables
MPI_Comm_size(pmesh_->GetComm(), &num_procs_);
MPI_Comm_rank(pmesh_->GetComm(), &myid_);
// Define compatible parallel finite element spaces on the parallel
// mesh. Here we use arbitrary order H1, Nedelec, and Raviart-Thomas finite
// elements.
H1FESpace_ = new H1_ParFESpace(pmesh_,order,pmesh_->Dimension());
HCurlFESpace_ = new ND_ParFESpace(pmesh_,order,pmesh_->Dimension());
HDivFESpace_ = new RT_ParFESpace(pmesh_,order,pmesh_->Dimension());
int irOrder = H1FESpace_->GetElementTransformation(0)->OrderW()
+ 2 * order;
int geom = H1FESpace_->GetFE(0)->GetGeomType();
const IntegrationRule * ir = &IntRules.Get(geom, irOrder);
// Select surface attributes for Dirichlet BCs
ess_bdr_.SetSize(pmesh.bdr_attributes.Max());
non_k_bdr_.SetSize(pmesh.bdr_attributes.Max());
ess_bdr_ = 1; // All outer surfaces
non_k_bdr_ = 1; // Surfaces without applied surface currents
for (int i=0; i<kbcs.Size(); i++)
{
non_k_bdr_[kbcs[i]-1] = 0;
}
// Setup various coefficients
// Vector Potential on the outer surface
if ( a_bc_ == NULL )
{
Vector Zero(3);
Zero = 0.0;
aBCCoef_ = new VectorConstantCoefficient(Zero);
}
else
{
aBCCoef_ = new VectorFunctionCoefficient(pmesh_->SpaceDimension(),
*a_bc_);
}
// Volume Current Density
if ( j_src_ != NULL )
{
jCoef_ = new VectorFunctionCoefficient(pmesh_->SpaceDimension(),
j_src_);
}
// Magnetization
if ( m_src_ != NULL )
{
mCoef_ = new VectorFunctionCoefficient(pmesh_->SpaceDimension(),
m_src_);
}
// Bilinear Forms
curlMuInvCurl_ = new ParBilinearForm(HCurlFESpace_);
curlMuInvCurl_->AddDomainIntegrator(new CurlCurlIntegrator(*muInvCoef_));
BilinearFormIntegrator * hCurlMassInteg = new VectorFEMassIntegrator;
hCurlMassInteg->SetIntRule(ir);
hCurlMass_ = new ParBilinearForm(HCurlFESpace_);
hCurlMass_->AddDomainIntegrator(hCurlMassInteg);
BilinearFormIntegrator * hDivHCurlInteg =
new VectorFEMassIntegrator(*muInvCoef_);
hDivHCurlInteg->SetIntRule(ir);
hDivHCurlMuInv_ = new ParMixedBilinearForm(HDivFESpace_, HCurlFESpace_);
hDivHCurlMuInv_->AddDomainIntegrator(hDivHCurlInteg);
// Discrete Curl operator
curl_ = new ParDiscreteCurlOperator(HCurlFESpace_, HDivFESpace_);
// Build grid functions
a_ = new ParGridFunction(HCurlFESpace_);
b_ = new ParGridFunction(HDivFESpace_);
h_ = new ParGridFunction(HCurlFESpace_);
bd_ = new ParGridFunction(HCurlFESpace_);
jd_ = new ParGridFunction(HCurlFESpace_);
if ( jCoef_ || kbcs.Size() > 0 )
{
grad_ = new ParDiscreteGradOperator(H1FESpace_, HCurlFESpace_);
}
if ( jCoef_ )
{
jr_ = new ParGridFunction(HCurlFESpace_);
j_ = new ParGridFunction(HCurlFESpace_);
DivFreeProj_ = new DivergenceFreeProjector(*H1FESpace_, *HCurlFESpace_,
irOrder, NULL, NULL, grad_);
}
if ( kbcs.Size() > 0 )
{
k_ = new ParGridFunction(HCurlFESpace_);
// Object to solve the subproblem of computing surface currents
SurfCur_ = new SurfaceCurrent(*H1FESpace_, *grad_,
kbcs, vbcs, vbcv);
}
if ( mCoef_ )
{
m_ = new ParGridFunction(HDivFESpace_);
weakCurlMuInv_ = new ParMixedBilinearForm(HDivFESpace_, HCurlFESpace_);
weakCurlMuInv_->AddDomainIntegrator(
new VectorFECurlIntegrator(*muInvCoef_));
}
}
TeslaSolver::~TeslaSolver()
{
delete jCoef_;
delete mCoef_;
delete aBCCoef_;
delete DivFreeProj_;
delete SurfCur_;
delete a_;
delete b_;
delete h_;
delete jr_;
delete j_;
delete k_;
delete m_;
delete bd_;
delete jd_;
delete grad_;
delete curl_;
delete curlMuInvCurl_;
delete hCurlMass_;
delete hDivHCurlMuInv_;
delete weakCurlMuInv_;
delete H1FESpace_;
delete HCurlFESpace_;
delete HDivFESpace_;
map<string,socketstream*>::iterator mit;
for (mit=socks_.begin(); mit!=socks_.end(); mit++)
{
delete mit->second;
}
}
HYPRE_Int
TeslaSolver::GetProblemSize()
{
return HCurlFESpace_->GlobalTrueVSize();
}
void
TeslaSolver::PrintSizes()
{
HYPRE_Int size_h1 = H1FESpace_->GlobalTrueVSize();
HYPRE_Int size_nd = HCurlFESpace_->GlobalTrueVSize();
HYPRE_Int size_rt = HDivFESpace_->GlobalTrueVSize();
if (myid_ == 0)
{
cout << "Number of H1 unknowns: " << size_h1 << endl;
cout << "Number of H(Curl) unknowns: " << size_nd << endl;
cout << "Number of H(Div) unknowns: " << size_rt << endl;
}
}
void
TeslaSolver::Assemble()
{
if (myid_ == 0) { cout << "Assembling ..." << flush; }
curlMuInvCurl_->Assemble();
curlMuInvCurl_->Finalize();
hDivHCurlMuInv_->Assemble();
hDivHCurlMuInv_->Finalize();
hCurlMass_->Assemble();
hCurlMass_->Finalize();
curl_->Assemble();
curl_->Finalize();
if ( grad_ )
{
grad_->Assemble();
grad_->Finalize();
}
if ( weakCurlMuInv_ )
{
weakCurlMuInv_->Assemble();
weakCurlMuInv_->Finalize();
}
if (myid_ == 0) { cout << " done." << endl; }
}
void
TeslaSolver::Update()
{
if (myid_ == 0) { cout << "Updating ..." << endl; }
// Inform the spaces that the mesh has changed
// Note: we don't need to interpolate any GridFunctions on the new mesh
// so we pass 'false' to skip creation of any transformation matrices.
H1FESpace_->Update(false);
HCurlFESpace_->Update(false);
HDivFESpace_->Update(false);
HCurlFESpace_->GetEssentialTrueDofs(ess_bdr_, ess_bdr_tdofs_);
// Inform the grid functions that the space has changed.
a_->Update();
h_->Update();
b_->Update();
bd_->Update();
jd_->Update();
if ( jr_ ) { jr_->Update(); }
if ( j_ ) { j_->Update(); }
if ( k_ ) { k_->Update(); }
if ( m_ ) { m_->Update(); }
// Inform the bilinear forms that the space has changed.
curlMuInvCurl_->Update();
hCurlMass_->Update();
hDivHCurlMuInv_->Update();
if ( weakCurlMuInv_ ) { weakCurlMuInv_->Update(); }
// Inform the other objects that the space has changed.
curl_->Update();
if ( grad_ ) { grad_->Update(); }
if ( DivFreeProj_ ) { DivFreeProj_->Update(); }
if ( SurfCur_ ) { SurfCur_->Update(); }
}
void
TeslaSolver::Solve()
{
if (myid_ == 0) { cout << "Running solver ... " << endl; }
// Initialize the magnetic vector potential with its boundary conditions
*a_ = 0.0;
// Apply surface currents if available
if ( k_ )
{
SurfCur_->ComputeSurfaceCurrent(*k_);
*a_ = *k_;
}
// Apply uniform B boundary condition on remaining surfaces
a_->ProjectBdrCoefficientTangent(*aBCCoef_, non_k_bdr_);
// Initialize the RHS vector to zero
*jd_ = 0.0;
// Initialize the volumetric current density
if ( jr_ )
{
jr_->ProjectCoefficient(*jCoef_);
// Compute the discretely divergence-free portion of jr_
DivFreeProj_->Mult(*jr_, *j_);
// Compute the dual of j_
hCurlMass_->AddMult(*j_, *jd_);
}
// Initialize the Magnetization
if ( m_ )
{
m_->ProjectCoefficient(*mCoef_);
weakCurlMuInv_->AddMult(*m_, *jd_, mu0_);
}
// Apply Dirichlet BCs to matrix and right hand side and otherwise
// prepare the linear system
HypreParMatrix CurlMuInvCurl;
HypreParVector A(HCurlFESpace_);
HypreParVector RHS(HCurlFESpace_);
curlMuInvCurl_->FormLinearSystem(ess_bdr_tdofs_, *a_, *jd_, CurlMuInvCurl,
A, RHS);
// Define and apply a parallel PCG solver for AX=B with the AMS
// preconditioner from hypre.
HypreAMS ams(CurlMuInvCurl, HCurlFESpace_);
ams.SetSingularProblem();
HyprePCG pcg (CurlMuInvCurl);
pcg.SetTol(1e-12);
pcg.SetMaxIter(50);
pcg.SetPrintLevel(2);
pcg.SetPreconditioner(ams);
pcg.Mult(RHS, A);
// Extract the parallel grid function corresponding to the finite
// element approximation A. This is the local solution on each
// processor.
curlMuInvCurl_->RecoverFEMSolution(A, *jd_, *a_);
// Compute the negative Gradient of the solution vector. This is
// the magnetic field corresponding to the scalar potential
// represented by phi.
curl_->Mult(*a_, *b_);
// Compute magnetic field (H) from B and M
if (myid_ == 0) { cout << "Computing H ... " << flush; }
hDivHCurlMuInv_->Mult(*b_, *bd_);
if ( m_ )
{
hDivHCurlMuInv_->AddMult(*m_, *bd_, -1.0 * mu0_);
}
HypreParMatrix MassHCurl;
Vector BD, H;
Array<int> dbc_dofs_h;
hCurlMass_->FormLinearSystem(dbc_dofs_h, *h_, *bd_, MassHCurl, H, BD);
HyprePCG pcgM(MassHCurl);
pcgM.SetTol(1e-12);
pcgM.SetMaxIter(500);
pcgM.SetPrintLevel(0);
HypreDiagScale diagM;
pcgM.SetPreconditioner(diagM);
pcgM.Mult(BD, H);
hCurlMass_->RecoverFEMSolution(H, *bd_, *h_);
if (myid_ == 0) { cout << "done." << flush; }
if (myid_ == 0) { cout << " Solver done. " << endl; }
}
void
TeslaSolver::GetErrorEstimates(Vector & errors)
{
if (myid_ == 0) { cout << "Estimating Error ... " << flush; }
// Space for the discontinuous (original) flux
CurlCurlIntegrator flux_integrator(*muInvCoef_);
RT_FECollection flux_fec(order_-1, pmesh_->SpaceDimension());
ParFiniteElementSpace flux_fes(pmesh_, &flux_fec);
// Space for the smoothed (conforming) flux
double norm_p = 1;
ND_FECollection smooth_flux_fec(order_, pmesh_->Dimension());
ParFiniteElementSpace smooth_flux_fes(pmesh_, &smooth_flux_fec);
L2ZZErrorEstimator(flux_integrator, *a_,
smooth_flux_fes, flux_fes, errors, norm_p);
if (myid_ == 0) { cout << "done." << endl; }
}
void
TeslaSolver::RegisterVisItFields(VisItDataCollection & visit_dc)
{
visit_dc_ = &visit_dc;
visit_dc.RegisterField("A", a_);
visit_dc.RegisterField("B", b_);
visit_dc.RegisterField("H", h_);
if ( j_ ) { visit_dc.RegisterField("J", j_); }
if ( k_ ) { visit_dc.RegisterField("K", k_); }
if ( m_ ) { visit_dc.RegisterField("M", m_); }
if ( SurfCur_ ) { visit_dc.RegisterField("Psi", SurfCur_->GetPsi()); }
}
void
TeslaSolver::WriteVisItFields(int it)
{
if ( visit_dc_ )
{
if (myid_ == 0) { cout << "Writing VisIt files ..." << flush; }
HYPRE_Int prob_size = this->GetProblemSize();
visit_dc_->SetCycle(it);
visit_dc_->SetTime(prob_size);
visit_dc_->Save();
if (myid_ == 0) { cout << " done." << endl; }
}
}
void
TeslaSolver::InitializeGLVis()
{
if ( myid_ == 0 ) { cout << "Opening GLVis sockets." << endl; }
socks_["A"] = new socketstream;
socks_["A"]->precision(8);
socks_["B"] = new socketstream;
socks_["B"]->precision(8);
socks_["H"] = new socketstream;
socks_["H"]->precision(8);
if ( j_ )
{
socks_["J"] = new socketstream;
socks_["J"]->precision(8);
}
if ( k_ )
{
socks_["K"] = new socketstream;
socks_["K"]->precision(8);
socks_["Psi"] = new socketstream;
socks_["Psi"]->precision(8);
}
if ( m_ )
{
socks_["M"] = new socketstream;
socks_["M"]->precision(8);
}
if ( myid_ == 0 ) { cout << "GLVis sockets open." << endl; }
}
void
TeslaSolver::DisplayToGLVis()
{
if (myid_ == 0) { cout << "Sending data to GLVis ..." << flush; }
char vishost[] = "localhost";
int visport = 19916;
int Wx = 0, Wy = 0; // window position
int Ww = 350, Wh = 350; // window size
int offx = Ww+10, offy = Wh+45; // window offsets
VisualizeField(*socks_["A"], vishost, visport,
*a_, "Vector Potential (A)", Wx, Wy, Ww, Wh);
Wx += offx;
VisualizeField(*socks_["B"], vishost, visport,
*b_, "Magnetic Flux Density (B)", Wx, Wy, Ww, Wh);
Wx += offx;
VisualizeField(*socks_["H"], vishost, visport,
*h_, "Magnetic Field (H)", Wx, Wy, Ww, Wh);
Wx += offx;
if ( j_ )
{
VisualizeField(*socks_["J"], vishost, visport,
*j_, "Current Density (J)", Wx, Wy, Ww, Wh);
}
Wx = 0; Wy += offy; // next line
if ( k_ )
{
VisualizeField(*socks_["K"], vishost, visport,
*k_, "Surface Current Density (K)", Wx, Wy, Ww, Wh);
Wx += offx;
VisualizeField(*socks_["Psi"], vishost, visport,
*SurfCur_->GetPsi(),
"Surface Current Potential (Psi)", Wx, Wy, Ww, Wh);
Wx += offx;
}
if ( m_ )
{
VisualizeField(*socks_["M"], vishost, visport,
*m_, "Magnetization (M)", Wx, Wy, Ww, Wh);
// Wx += offx; // not used
}
if (myid_ == 0) { cout << " done." << endl; }
}
SurfaceCurrent::SurfaceCurrent(ParFiniteElementSpace & H1FESpace,
ParDiscreteGradOperator & grad,
Array<int> & kbcs,
Array<int> & vbcs, Vector & vbcv)
: H1FESpace_(&H1FESpace),
grad_(&grad),
kbcs_(&kbcs),
vbcs_(&vbcs),
vbcv_(&vbcv),
s0_(NULL),
psi_(NULL),
rhs_(NULL)
{
// Initialize MPI variables
MPI_Comm_rank(H1FESpace_->GetParMesh()->GetComm(), &myid_);
s0_ = new ParBilinearForm(H1FESpace_);
s0_->AddBoundaryIntegrator(new DiffusionIntegrator);
s0_->Assemble();
s0_->Finalize();
S0_ = new HypreParMatrix;
AttrToMarker(H1FESpace_->GetParMesh()->bdr_attributes.Max(),
*vbcs_, ess_bdr_);
H1FESpace_->GetEssentialTrueDofs(ess_bdr_, ess_bdr_tdofs_);
non_k_bdr_.SetSize(H1FESpace_->GetParMesh()->bdr_attributes.Max());
non_k_bdr_ = 1;
for (int i=0; i<kbcs_->Size(); i++)
{
non_k_bdr_[(*kbcs_)[i]-1] = 0;
}
psi_ = new ParGridFunction(H1FESpace_);
rhs_ = new ParGridFunction(H1FESpace_);
pcg_ = NULL;
amg_ = NULL;
}
SurfaceCurrent::~SurfaceCurrent()
{
delete psi_;
delete rhs_;
delete pcg_;
delete amg_;
delete S0_;
delete s0_;
}
void
SurfaceCurrent::InitSolver() const
{
delete pcg_;
delete amg_;
amg_ = new HypreBoomerAMG(*S0_);
amg_->SetPrintLevel(0);
pcg_ = new HyprePCG(*S0_);
pcg_->SetTol(1e-14);
pcg_->SetMaxIter(200);
pcg_->SetPrintLevel(0);
pcg_->SetPreconditioner(*amg_);
}
void
SurfaceCurrent::ComputeSurfaceCurrent(ParGridFunction & k)
{
if (myid_ == 0) { cout << "Computing K ... " << flush; }
// Apply piecewise constant voltage boundary condition
*psi_ = 0.0;
*rhs_ = 0.0;
Array<int> vbc_bdr_attr(H1FESpace_->GetParMesh()->bdr_attributes.Max());
for (int i=0; i<vbcs_->Size(); i++)
{
ConstantCoefficient voltage((*vbcv_)[i]);
vbc_bdr_attr = 0;
vbc_bdr_attr[(*vbcs_)[i]-1] = 1;
psi_->ProjectBdrCoefficient(voltage, vbc_bdr_attr);
}
// Apply essential BC and form linear system
s0_->FormLinearSystem(ess_bdr_tdofs_, *psi_, *rhs_, *S0_, Psi_, RHS_);
// Solve the linear system for Psi
if ( pcg_ == NULL ) { this->InitSolver(); }
pcg_->Mult(RHS_, Psi_);
// Compute the parallel grid function corresponding to Psi
s0_->RecoverFEMSolution(Psi_, *rhs_, *psi_);
// Compute the surface current from psi
grad_->Mult(*psi_, k);
// Force the tangential part of k to be zero away from the intended surfaces
Vector vZero(3); vZero = 0.0;
VectorConstantCoefficient Zero(vZero);
k.ProjectBdrCoefficientTangent(Zero, non_k_bdr_);
if (myid_ == 0) { cout << "done." << endl; }
}
void
SurfaceCurrent::Update()
{
delete pcg_; pcg_ = NULL;
delete amg_; amg_ = NULL;
delete S0_; S0_ = new HypreParMatrix;
psi_->Update();
rhs_->Update();
s0_->Update();
s0_->Assemble();
s0_->Finalize();
H1FESpace_->GetEssentialTrueDofs(ess_bdr_, ess_bdr_tdofs_);
}
} // namespace electromagnetics
} // namespace mfem
#endif // MFEM_USE_MPI