Files
mfem/miniapps/electromagnetics/volta_solver.cpp
T

647 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 "volta_solver.hpp"
#ifdef MFEM_USE_MPI
using namespace std;
namespace mfem
{
using namespace common;
namespace electromagnetics
{
VoltaSolver::VoltaSolver(ParMesh & pmesh, int order,
Array<int> & dbcs, Vector & dbcv,
Array<int> & nbcs, Vector & nbcv,
Coefficient & epsCoef,
double (*phi_bc )(const Vector&),
double (*rho_src)(const Vector&),
void (*p_src )(const Vector&, Vector&),
Vector & point_charges)
: myid_(0),
num_procs_(1),
order_(order),
pmesh_(&pmesh),
dbcs_(&dbcs),
dbcv_(&dbcv),
nbcs_(&nbcs),
nbcv_(&nbcv),
visit_dc_(NULL),
H1FESpace_(NULL),
HCurlFESpace_(NULL),
HDivFESpace_(NULL),
L2FESpace_(NULL),
divEpsGrad_(NULL),
h1Mass_(NULL),
h1SurfMass_(NULL),
hDivMass_(NULL),
hCurlHDivEps_(NULL),
hCurlHDiv_(NULL),
weakDiv_(NULL),
rhod_(NULL),
l2_vol_int_(NULL),
rt_surf_int_(NULL),
grad_(NULL),
phi_(NULL),
rho_src_(NULL),
rho_(NULL),
sigma_src_(NULL),
e_(NULL),
d_(NULL),
p_src_(NULL),
oneCoef_(1.0),
epsCoef_(&epsCoef),
phiBCCoef_(NULL),
rhoCoef_(NULL),
pCoef_(NULL),
phi_bc_func_(phi_bc),
rho_src_func_(rho_src),
p_src_func_(p_src),
point_charge_params_(point_charges),
point_charges_(0)
{
// 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());
L2FESpace_ = new L2_ParFESpace(pmesh_,order-1,pmesh_->Dimension());
// Select surface attributes for Dirichlet BCs
AttrToMarker(pmesh.bdr_attributes.Max(), *dbcs_, ess_bdr_);
// Setup various coefficients
// Potential on outer surface
if ( phi_bc_func_ != NULL )
{
phiBCCoef_ = new FunctionCoefficient(*phi_bc_func_);
}
// Volume Charge Density
if ( rho_src_func_ != NULL )
{
rhoCoef_ = new FunctionCoefficient(rho_src_func_);
}
// Polarization
if ( p_src_func_ != NULL )
{
pCoef_ = new VectorFunctionCoefficient(pmesh_->SpaceDimension(),
p_src_func_);
}
// Bilinear Forms
divEpsGrad_ = new ParBilinearForm(H1FESpace_);
divEpsGrad_->AddDomainIntegrator(new DiffusionIntegrator(*epsCoef_));
hDivMass_ = new ParBilinearForm(HDivFESpace_);
hDivMass_->AddDomainIntegrator(new VectorFEMassIntegrator);
hCurlHDivEps_ = new ParMixedBilinearForm(HCurlFESpace_,HDivFESpace_);
hCurlHDivEps_->AddDomainIntegrator(new VectorFEMassIntegrator(*epsCoef_));
rhod_ = new ParLinearForm(H1FESpace_);
l2_vol_int_ = new ParLinearForm(L2FESpace_);
l2_vol_int_->AddDomainIntegrator(new DomainLFIntegrator(oneCoef_));
rt_surf_int_ = new ParLinearForm(HDivFESpace_);
rt_surf_int_->AddBoundaryIntegrator(new VectorFEBoundaryFluxLFIntegrator);
// Discrete derivative operator
grad_ = new ParDiscreteGradOperator(H1FESpace_, HCurlFESpace_);
div_ = new ParDiscreteDivOperator(HDivFESpace_, L2FESpace_);
// Build grid functions
phi_ = new ParGridFunction(H1FESpace_);
d_ = new ParGridFunction(HDivFESpace_);
e_ = new ParGridFunction(HCurlFESpace_);
rho_ = new ParGridFunction(L2FESpace_);
if ( point_charge_params_.Size() > 0 )
{
int dim = pmesh_->Dimension();
int npts = point_charge_params_.Size() / (dim + 1);
point_charges_.resize(npts);
Vector cent(dim);
for (int i=0; i<npts; i++)
{
for (int d=0; d<dim; d++)
{
cent[d] = point_charge_params_[(dim + 1) * i + d];
}
double s = point_charge_params_[(dim + 1) * i + dim];
point_charges_[i] = new DeltaCoefficient();
point_charges_[i]->SetScale(s);
point_charges_[i]->SetDeltaCenter(cent);
rhod_->AddDomainIntegrator(new DomainLFIntegrator(*point_charges_[i]));
}
}
if ( rho_src_func_ )
{
rho_src_ = new ParGridFunction(H1FESpace_);
h1Mass_ = new ParBilinearForm(H1FESpace_);
h1Mass_->AddDomainIntegrator(new MassIntegrator);
}
if ( p_src_func_ )
{
p_src_ = new ParGridFunction(HCurlFESpace_);
hCurlHDiv_ = new ParMixedBilinearForm(HCurlFESpace_, HDivFESpace_);
hCurlHDiv_->AddDomainIntegrator(new VectorFEMassIntegrator);
weakDiv_ = new ParMixedBilinearForm(HCurlFESpace_, H1FESpace_);
weakDiv_->AddDomainIntegrator(new VectorFEWeakDivergenceIntegrator);
}
if ( nbcs_->Size() > 0 )
{
sigma_src_ = new ParGridFunction(H1FESpace_);
h1SurfMass_ = new ParBilinearForm(H1FESpace_);
h1SurfMass_->AddBoundaryIntegrator(new MassIntegrator);
}
}
VoltaSolver::~VoltaSolver()
{
delete phiBCCoef_;
delete rhoCoef_;
delete pCoef_;
delete phi_;
delete rho_src_;
delete rho_;
delete rhod_;
delete l2_vol_int_;
delete rt_surf_int_;
delete sigma_src_;
delete d_;
delete e_;
delete p_src_;
delete grad_;
delete div_;
delete divEpsGrad_;
delete h1Mass_;
delete h1SurfMass_;
delete hDivMass_;
delete hCurlHDivEps_;
delete hCurlHDiv_;
delete weakDiv_;
delete H1FESpace_;
delete HCurlFESpace_;
delete HDivFESpace_;
delete L2FESpace_;
for (unsigned int i=0; i<point_charges_.size(); i++)
{
delete point_charges_[i];
}
map<string,socketstream*>::iterator mit;
for (mit=socks_.begin(); mit!=socks_.end(); mit++)
{
delete mit->second;
}
}
HYPRE_Int
VoltaSolver::GetProblemSize()
{
return H1FESpace_->GlobalTrueVSize();
}
void
VoltaSolver::PrintSizes()
{
HYPRE_Int size_h1 = H1FESpace_->GlobalTrueVSize();
HYPRE_Int size_nd = HCurlFESpace_->GlobalTrueVSize();
HYPRE_Int size_rt = HDivFESpace_->GlobalTrueVSize();
HYPRE_Int size_l2 = L2FESpace_->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;
cout << "Number of L2 unknowns: " << size_l2 << endl;
}
}
void VoltaSolver::Assemble()
{
if (myid_ == 0) { cout << "Assembling ... " << flush; }
divEpsGrad_->Assemble();
divEpsGrad_->Finalize();
hDivMass_->Assemble();
hDivMass_->Finalize();
hCurlHDivEps_->Assemble();
hCurlHDivEps_->Finalize();
*rhod_ = 0.0;
rhod_->Assemble();
l2_vol_int_->Assemble();
rt_surf_int_->Assemble();
grad_->Assemble();
grad_->Finalize();
div_->Assemble();
div_->Finalize();
if ( h1Mass_ )
{
h1Mass_->Assemble();
h1Mass_->Finalize();
}
if ( h1SurfMass_ )
{
h1SurfMass_->Assemble();
h1SurfMass_->Finalize();
}
if ( hCurlHDiv_ )
{
hCurlHDiv_->Assemble();
hCurlHDiv_->Finalize();
}
if ( weakDiv_ )
{
weakDiv_->Assemble();
weakDiv_->Finalize();
}
if (myid_ == 0) { cout << "done." << endl << flush; }
}
void
VoltaSolver::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);
L2FESpace_->Update(false);
// Inform the grid functions that the space has changed.
phi_->Update();
rhod_->Update();
l2_vol_int_->Update();
rt_surf_int_->Update();
d_->Update();
e_->Update();
rho_->Update();
if ( rho_src_ ) { rho_src_->Update(); }
if ( sigma_src_ ) { sigma_src_->Update(); }
if ( p_src_ ) { p_src_->Update(); }
// Inform the bilinear forms that the space has changed.
divEpsGrad_->Update();
hDivMass_->Update();
hCurlHDivEps_->Update();
if ( h1Mass_ ) { h1Mass_->Update(); }
if ( h1SurfMass_ ) { h1SurfMass_->Update(); }
if ( hCurlHDiv_ ) { hCurlHDiv_->Update(); }
if ( weakDiv_ ) { weakDiv_->Update(); }
// Inform the other objects that the space has changed.
grad_->Update();
div_->Update();
}
void
VoltaSolver::Solve()
{
if (myid_ == 0) { cout << "Running solver ... " << endl; }
// Initialize the electric potential with its boundary conditions
*phi_ = 0.0;
if ( dbcs_->Size() > 0 )
{
if ( phiBCCoef_ )
{
// Apply gradient boundary condition
phi_->ProjectBdrCoefficient(*phiBCCoef_, ess_bdr_);
}
else
{
// Apply piecewise constant boundary condition
Array<int> dbc_bdr_attr(pmesh_->bdr_attributes.Max());
for (int i=0; i<dbcs_->Size(); i++)
{
ConstantCoefficient voltage((*dbcv_)[i]);
dbc_bdr_attr = 0;
if ((*dbcs_)[i] <= dbc_bdr_attr.Size())
{
dbc_bdr_attr[(*dbcs_)[i]-1] = 1;
}
phi_->ProjectBdrCoefficient(voltage, dbc_bdr_attr);
}
}
}
// Initialize the volumetric charge density
if ( rho_src_ )
{
rho_src_->ProjectCoefficient(*rhoCoef_);
h1Mass_->AddMult(*rho_src_, *rhod_);
}
// Initialize the Polarization
if ( p_src_ )
{
p_src_->ProjectCoefficient(*pCoef_);
weakDiv_->AddMult(*p_src_, *rhod_);
}
// Initialize the surface charge density
if ( sigma_src_ )
{
*sigma_src_ = 0.0;
Array<int> nbc_bdr_attr(pmesh_->bdr_attributes.Max());
for (int i=0; i<nbcs_->Size(); i++)
{
ConstantCoefficient sigma_coef((*nbcv_)[i]);
nbc_bdr_attr = 0;
if ((*nbcs_)[i] <= nbc_bdr_attr.Size())
{
nbc_bdr_attr[(*nbcs_)[i]-1] = 1;
}
sigma_src_->ProjectBdrCoefficient(sigma_coef, nbc_bdr_attr);
}
h1SurfMass_->AddMult(*sigma_src_, *rhod_);
}
// Determine the essential BC degrees of freedom
if ( dbcs_->Size() > 0 )
{
// From user supplied boundary attributes
H1FESpace_->GetEssentialTrueDofs(ess_bdr_, ess_bdr_tdofs_);
}
else
{
// Use the first DoF on processor zero by default
if ( myid_ == 0 )
{
ess_bdr_tdofs_.SetSize(1);
ess_bdr_tdofs_[0] = 0;
}
}
// Apply essential BC and form linear system
HypreParMatrix DivEpsGrad;
HypreParVector Phi(H1FESpace_);
HypreParVector RHS(H1FESpace_);
divEpsGrad_->FormLinearSystem(ess_bdr_tdofs_, *phi_, *rhod_, DivEpsGrad,
Phi, RHS);
// Define and apply a parallel PCG solver for AX=B with the AMG
// preconditioner from hypre.
HypreBoomerAMG amg(DivEpsGrad);
HyprePCG pcg(DivEpsGrad);
pcg.SetTol(1e-12);
pcg.SetMaxIter(500);
pcg.SetPrintLevel(2);
pcg.SetPreconditioner(amg);
pcg.Mult(RHS, Phi);
// Extract the parallel grid function corresponding to the finite
// element approximation Phi. This is the local solution on each
// processor.
divEpsGrad_->RecoverFEMSolution(Phi, *rhod_, *phi_);
// Compute the negative Gradient of the solution vector. This is
// the magnetic field corresponding to the scalar potential
// represented by phi.
grad_->Mult(*phi_, *e_); *e_ *= -1.0;
// Compute electric displacement (D) from E and P (if present)
if (myid_ == 0) { cout << "Computing D ..." << flush; }
ParGridFunction ed(HDivFESpace_);
hCurlHDivEps_->Mult(*e_, ed);
if ( p_src_ )
{
hCurlHDiv_->AddMult(*p_src_, ed, -1.0);
}
HypreParMatrix MassHDiv;
Vector ED, D;
Array<int> dbc_dofs_d;
hDivMass_->FormLinearSystem(dbc_dofs_d, *d_, ed, MassHDiv, D, ED);
HyprePCG pcgM(MassHDiv);
pcgM.SetTol(1e-12);
pcgM.SetMaxIter(500);
pcgM.SetPrintLevel(0);
HypreDiagScale diagM;
pcgM.SetPreconditioner(diagM);
pcgM.Mult(ED, D);
hDivMass_->RecoverFEMSolution(D, ed, *d_);
// Compute charge density from rho = Div(D)
div_->Mult(*d_, *rho_);
if (myid_ == 0) { cout << "done." << flush; }
{
// Compute total charge as volume integral of rho
double charge_rho = (*l2_vol_int_)(*rho_);
// Compute total charge as surface integral of D
double charge_D = (*rt_surf_int_)(*d_);
if (myid_ == 0)
{
cout << endl << "Total charge: \n"
<< " Volume integral of charge density: " << charge_rho
<< "\n Surface integral of dielectric flux: " << charge_D
<< endl << flush;
}
}
if (myid_ == 0) { cout << "Solver done. " << endl; }
}
void
VoltaSolver::GetErrorEstimates(Vector & errors)
{
if (myid_ == 0) { cout << "Estimating Error ... " << flush; }
// Space for the discontinuous (original) flux
DiffusionIntegrator flux_integrator(*epsCoef_);
L2_FECollection flux_fec(order_, pmesh_->Dimension());
// ND_FECollection flux_fec(order_, pmesh_->Dimension());
ParFiniteElementSpace flux_fes(pmesh_, &flux_fec, pmesh_->SpaceDimension());
// Space for the smoothed (conforming) flux
double norm_p = 1;
RT_FECollection smooth_flux_fec(order_-1, pmesh_->Dimension());
ParFiniteElementSpace smooth_flux_fes(pmesh_, &smooth_flux_fec);
L2ZZErrorEstimator(flux_integrator, *phi_,
smooth_flux_fes, flux_fes, errors, norm_p);
if (myid_ == 0) { cout << "done." << endl; }
}
void
VoltaSolver::RegisterVisItFields(VisItDataCollection & visit_dc)
{
visit_dc_ = &visit_dc;
visit_dc.RegisterField("Phi", phi_);
visit_dc.RegisterField("D", d_);
visit_dc.RegisterField("E", e_);
visit_dc.RegisterField("Rho", rho_);
if ( rho_src_ ) { visit_dc.RegisterField("Rho Source", rho_src_); }
if ( p_src_ ) { visit_dc.RegisterField("P Source", p_src_); }
if ( sigma_src_ ) { visit_dc.RegisterField("Sigma Source", sigma_src_); }
}
void
VoltaSolver::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
VoltaSolver::InitializeGLVis()
{
if ( myid_ == 0 ) { cout << "Opening GLVis sockets." << endl; }
socks_["Phi"] = new socketstream;
socks_["Phi"]->precision(8);
socks_["D"] = new socketstream;
socks_["D"]->precision(8);
socks_["E"] = new socketstream;
socks_["E"]->precision(8);
socks_["Rho"] = new socketstream;
socks_["Rho"]->precision(8);
if ( rho_src_ )
{
socks_["RhoSrc"] = new socketstream;
socks_["RhoSrc"]->precision(8);
}
if ( p_src_ )
{
socks_["PSrc"] = new socketstream;
socks_["PSrc"]->precision(8);
}
if ( sigma_src_ )
{
socks_["SigmaSrc"] = new socketstream;
socks_["SigmaSrc"]->precision(8);
}
}
void
VoltaSolver::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_["Phi"], vishost, visport,
*phi_, "Electric Potential (Phi)", Wx, Wy, Ww, Wh);
Wx += offx;
VisualizeField(*socks_["E"], vishost, visport,
*e_, "Electric Field (E)", Wx, Wy, Ww, Wh);
Wx += offx;
VisualizeField(*socks_["D"], vishost, visport,
*d_, "Electric Displacement (D)", Wx, Wy, Ww, Wh);
Wx += offx;
VisualizeField(*socks_["Rho"], vishost, visport,
*rho_, "Charge Density", Wx, Wy, Ww, Wh);
Wx = 0; Wy += offy; // next line
if ( rho_src_ )
{
VisualizeField(*socks_["RhoSrc"], vishost, visport,
*rho_src_, "Charge Density Source (Rho)", Wx, Wy, Ww, Wh);
Wx += offx;
}
if ( p_src_ )
{
VisualizeField(*socks_["PSrc"], vishost, visport,
*p_src_, "Electric Polarization Source (P)",
Wx, Wy, Ww, Wh);
Wx += offx;
}
if ( sigma_src_ )
{
VisualizeField(*socks_["SigmaSrc"], vishost, visport,
*sigma_src_, "Surface Charge Density Source (Sigma)",
Wx, Wy, Ww, Wh);
// Wx += offx; // not used
}
if (myid_ == 0) { cout << " done." << endl; }
}
} // namespace electromagnetics
} // namespace mfem
#endif // MFEM_USE_MPI