Files
mfem/miniapps/electromagnetics/maxwell_solver.cpp
T

644 lines
16 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 "maxwell_solver.hpp"
#ifdef MFEM_USE_MPI
using namespace std;
namespace mfem
{
using namespace common;
namespace electromagnetics
{
// Used for combining scalar coefficients
double prodFunc(double a, double b) { return a * b; }
MaxwellSolver::MaxwellSolver(ParMesh & pmesh, int order,
double (*eps )(const Vector&),
double (*muInv )(const Vector&),
double (*sigma )(const Vector&),
void (*j_src )(const Vector&, double, Vector&),
Array<int> & abcs,
Array<int> & dbcs,
void (*dEdt_bc )(const Vector&, double, Vector&))
: myid_(0),
num_procs_(1),
order_(order),
logging_(1),
dtMax_(-1.0),
dtScale_(1.0e6),
pmesh_(&pmesh),
HCurlFESpace_(NULL),
HDivFESpace_(NULL),
hDivMassMuInv_(NULL),
hCurlLosses_(NULL),
weakCurlMuInv_(NULL),
Curl_(NULL),
e_(NULL),
b_(NULL),
j_(NULL),
dedt_(NULL),
rhs_(NULL),
jd_(NULL),
M1Losses_(NULL),
M2MuInv_(NULL),
NegCurl_(NULL),
WeakCurlMuInv_(NULL),
E_(NULL),
B_(NULL),
HD_(NULL),
RHS_(NULL),
epsCoef_(NULL),
muInvCoef_(NULL),
sigmaCoef_(NULL),
etaInvCoef_(NULL),
eCoef_(NULL),
bCoef_(NULL),
jCoef_(NULL),
dEdtBCCoef_(NULL),
eps_(eps),
muInv_(muInv),
sigma_(sigma),
j_src_(j_src),
dEdt_bc_(dEdt_bc),
visit_dc_(NULL)
{
// 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.
HCurlFESpace_ = new ND_ParFESpace(pmesh_,order_,pmesh_->Dimension());
HDivFESpace_ = new RT_ParFESpace(pmesh_,order_,pmesh_->Dimension());
this->height = HCurlFESpace_->GlobalTrueVSize();
this->width = HDivFESpace_->GlobalTrueVSize();
// Check for absorbing materials or boundaries
lossy_ = abcs.Size() > 0 || sigma_ != NULL;
// Require implicit handling of loss terms
type = lossy_ ? IMPLICIT : EXPLICIT;
// Electric permittivity
if ( eps_ == NULL )
{
epsCoef_ = new ConstantCoefficient(epsilon0_);
}
else
{
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Creating Permittivity Coefficient" << endl;
}
epsCoef_ = new FunctionCoefficient(eps_);
}
// Inverse of the magnetic permeability
if ( muInv_ == NULL )
{
muInvCoef_ = new ConstantCoefficient(1.0/mu0_);
}
else
{
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Creating Permeability Coefficient" << endl;
}
muInvCoef_ = new FunctionCoefficient(muInv_);
}
// Electric conductivity
if ( sigma_ != NULL )
{
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Creating Conductivity Coefficient" << endl;
}
sigmaCoef_ = new FunctionCoefficient(sigma_);
}
// Impedance of free space
if ( abcs.Size() > 0 )
{
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Creating Admittance Coefficient" << endl;
}
AttrToMarker(pmesh.bdr_attributes.Max(), abcs, abc_marker_);
etaInvCoef_ = new ConstantCoefficient(sqrt(epsilon0_/mu0_));
}
// Electric Field Boundary Condition
if ( dbcs.Size() > 0 )
{
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Configuring Dirichlet BC" << endl;
}
AttrToMarker(pmesh.bdr_attributes.Max(), dbcs, dbc_marker_);
HCurlFESpace_->GetEssentialTrueDofs(dbc_marker_, dbc_dofs_);
if ( dEdt_bc_ != NULL )
{
dEdtBCCoef_ = new VectorFunctionCoefficient(3,dEdt_bc_);
}
else
{
Vector ebc(3); ebc = 0.0;
dEdtBCCoef_ = new VectorConstantCoefficient(ebc);
}
}
// Bilinear Forms
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Creating H(Div) Mass Operator" << endl;
}
hDivMassMuInv_ = new ParBilinearForm(HDivFESpace_);
hDivMassMuInv_->AddDomainIntegrator(new VectorFEMassIntegrator(*muInvCoef_));
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Creating Weak Curl Operator" << endl;
}
weakCurlMuInv_ = new ParMixedBilinearForm(HDivFESpace_,HCurlFESpace_);
weakCurlMuInv_->AddDomainIntegrator(
new MixedVectorWeakCurlIntegrator(*muInvCoef_));
// Assemble Matrices
hDivMassMuInv_->Assemble();
weakCurlMuInv_->Assemble();
hDivMassMuInv_->Finalize();
weakCurlMuInv_->Finalize();
if ( sigmaCoef_ || etaInvCoef_ )
{
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Creating H(Curl) Loss Operator" << endl;
}
hCurlLosses_ = new ParBilinearForm(HCurlFESpace_);
if ( sigmaCoef_ )
{
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Adding domain integrator for conductive regions" << endl;
}
hCurlLosses_->AddDomainIntegrator(
new VectorFEMassIntegrator(*sigmaCoef_));
}
if ( etaInvCoef_ )
{
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Adding boundary integrator for absorbing boundary" << endl;
}
hCurlLosses_->AddBoundaryIntegrator(
new VectorFEMassIntegrator(*etaInvCoef_), abc_marker_);
}
hCurlLosses_->Assemble();
hCurlLosses_->Finalize();
M1Losses_ = hCurlLosses_->ParallelAssemble();
}
// Create Linear Algebra Matrices
M2MuInv_ = hDivMassMuInv_->ParallelAssemble();
WeakCurlMuInv_ = weakCurlMuInv_->ParallelAssemble();
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Creating discrete curl operator" << endl;
}
Curl_ = new ParDiscreteCurlOperator(HCurlFESpace_, HDivFESpace_);
Curl_->Assemble();
Curl_->Finalize();
NegCurl_ = Curl_->ParallelAssemble();
// Beware this modifies the matrix stored within the Curl_ object.
*NegCurl_ *= -1.0;
// Build grid functions
e_ = new ParGridFunction(HCurlFESpace_);
dedt_ = new ParGridFunction(HCurlFESpace_);
rhs_ = new ParGridFunction(HCurlFESpace_);
b_ = new ParGridFunction(HDivFESpace_);
E_ = e_->ParallelProject();
B_ = b_->ParallelProject();
HD_ = new HypreParVector(HDivFESpace_);
RHS_ = new HypreParVector(HCurlFESpace_);
// Initialize dedt to zero
*dedt_ = 0.0;
if ( j_src_)
{
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Creating Current Source" << endl;
}
jCoef_ = new VectorFunctionCoefficient(3,j_src_);
j_ = new ParGridFunction(HCurlFESpace_);
j_->ProjectCoefficient(*jCoef_);
jd_ = new ParLinearForm(HCurlFESpace_);
jd_->AddDomainIntegrator(new VectorFEDomainLFIntegrator(*jCoef_));
jd_->Assemble();
}
dtMax_ = GetMaximumTimeStep();
}
MaxwellSolver::~MaxwellSolver()
{
delete epsCoef_;
delete muInvCoef_;
delete etaInvCoef_;
delete jCoef_;
delete dEdtBCCoef_;
delete E_;
delete B_;
delete HD_;
delete RHS_;
delete e_;
delete b_;
delete j_;
delete dedt_;
delete rhs_;
delete jd_;
delete Curl_;
delete M1Losses_;
delete M2MuInv_;
delete NegCurl_;
delete WeakCurlMuInv_;
delete hDivMassMuInv_;
delete hCurlLosses_;
delete weakCurlMuInv_;
delete HCurlFESpace_;
delete HDivFESpace_;
map<int, ParBilinearForm*>::iterator mit1;
for (mit1=a1_.begin(); mit1!=a1_.end(); mit1++)
{
int i = mit1->first;
delete pcg_[i];
delete diagScale_[i];
delete A1_[i];
delete a1_[i];
}
map<int, Coefficient*>::iterator mit2;
for (mit2=dtCoef_.begin(); mit2!=dtCoef_.end(); mit2++)
{
delete mit2->second;
}
for (mit2=dtSigmaCoef_.begin(); mit2!=dtSigmaCoef_.end(); mit2++)
{
delete mit2->second;
}
for (mit2=dtEtaInvCoef_.begin(); mit2!=dtEtaInvCoef_.end(); mit2++)
{
delete mit2->second;
}
map<string, socketstream*>::iterator mit3;
for (mit3=socks_.begin(); mit3!=socks_.end(); mit3++)
{
delete mit3->second;
}
}
HYPRE_Int
MaxwellSolver::GetProblemSize()
{
return HCurlFESpace_->GlobalTrueVSize();
}
void
MaxwellSolver::PrintSizes()
{
HYPRE_Int size_nd = HCurlFESpace_->GlobalTrueVSize();
HYPRE_Int size_rt = HDivFESpace_->GlobalTrueVSize();
if ( myid_ == 0 )
{
cout << "Number of H(Curl) unknowns: " << size_nd << endl;
cout << "Number of H(Div) unknowns: " << size_rt << endl << flush;
}
}
void
MaxwellSolver::SetInitialEField(VectorCoefficient & EFieldCoef)
{
eCoef_ = &EFieldCoef;
e_->ProjectCoefficient(EFieldCoef);
e_->ParallelProject(*E_);
}
void
MaxwellSolver::SetInitialBField(VectorCoefficient & BFieldCoef)
{
bCoef_ = &BFieldCoef;
b_->ProjectCoefficient(BFieldCoef);
b_->ParallelProject(*B_);
}
void
MaxwellSolver::Mult(const Vector &B, Vector &dEdt) const
{
implicitSolve(0.0, B, dEdt);
}
void
MaxwellSolver::ImplicitSolve(double dt, const Vector &B, Vector &dEdt)
{
implicitSolve(dt, B, dEdt);
}
void
MaxwellSolver::setupSolver(const int idt, const double dt) const
{
if ( pcg_.find(idt) == pcg_.end() )
{
if ( myid_ == 0 && logging_ > 0 )
{
cout << "Creating implicit operator for dt = " << dt << endl;
}
a1_[idt] = new ParBilinearForm(HCurlFESpace_);
a1_[idt]->AddDomainIntegrator(
new VectorFEMassIntegrator(epsCoef_));
if ( idt != 0 )
{
dtCoef_[idt] = new ConstantCoefficient(0.5 * dt);
if ( sigmaCoef_ )
{
dtSigmaCoef_[idt] = new TransformedCoefficient(dtCoef_[idt],
sigmaCoef_,
prodFunc);
a1_[idt]->AddDomainIntegrator(
new VectorFEMassIntegrator(dtSigmaCoef_[idt]));
}
if ( etaInvCoef_ )
{
dtEtaInvCoef_[idt] = new TransformedCoefficient(dtCoef_[idt],
etaInvCoef_,
prodFunc);
a1_[idt]->AddBoundaryIntegrator(
new VectorFEMassIntegrator(dtEtaInvCoef_[idt]),
const_cast<Array<int>&>(abc_marker_));
}
}
a1_[idt]->Assemble();
a1_[idt]->Finalize();
A1_[idt] = a1_[idt]->ParallelAssemble();
diagScale_[idt] = new HypreDiagScale(*A1_[idt]);
pcg_[idt] = new HyprePCG(*A1_[idt]);
pcg_[idt]->SetTol(1.0e-12);
pcg_[idt]->SetMaxIter(200);
pcg_[idt]->SetPrintLevel(0);
pcg_[idt]->SetPreconditioner(*diagScale_[idt]);
}
}
void
MaxwellSolver::implicitSolve(double dt, const Vector &B, Vector &dEdt) const
{
int idt = hCurlLosses_ ? ((int)(dtScale_ * dt / dtMax_)) : 0;
b_->Distribute(B);
weakCurlMuInv_->Mult(*b_, *rhs_);
if ( hCurlLosses_ )
{
e_->Distribute(*E_);
hCurlLosses_->AddMult(*e_, *rhs_, -1.0);
}
if ( jd_ )
{
jCoef_->SetTime(t); // 't' is member data from mfem::TimeDependentOperator
jd_->Assemble();
*rhs_ -= *jd_;
}
if ( dEdtBCCoef_ )
{
dEdtBCCoef_->SetTime(t);
dedt_->ProjectBdrCoefficientTangent(*dEdtBCCoef_,
const_cast<Array<int>&>(dbc_marker_));
}
// Create objects and matrices for solving with the given time step
setupSolver(idt, dt);
// Apply essential BCs and determine true DoFs for the right hand side
a1_[idt]->FormLinearSystem(dbc_dofs_, *dedt_, *rhs_, *A1_[idt], dEdt, *RHS_);
// Solve for the time derivative of the electric field (true DoFs)
pcg_[idt]->Mult(*RHS_, dEdt);
// Distribute shared DoFs to relevant processors
a1_[idt]->RecoverFEMSolution(dEdt, *rhs_, *dedt_);
}
void
MaxwellSolver::SyncGridFuncs()
{
e_->Distribute(*E_);
b_->Distribute(*B_);
}
double
MaxwellSolver::GetMaximumTimeStep() const
{
if ( dtMax_ > 0.0 )
{
return dtMax_;
}
HypreParVector * v0 = new HypreParVector(HCurlFESpace_);
HypreParVector * v1 = new HypreParVector(HCurlFESpace_);
HypreParVector * u0 = new HypreParVector(HDivFESpace_);
v0->Randomize(1234);
int iter = 0, nstep = 20;
double dt0 = 1.0, dt1 = 1.0, change = 1.0, ptol = 0.001;
// Create Solver assuming no loss operators
setupSolver(0, 0.0);
// Use power method to approximate the largest eigenvalue of the update
// operator.
while ( iter < nstep && change > ptol )
{
double normV0 = InnerProduct(*v0,*v0);
*v0 /= sqrt(normV0);
NegCurl_->Mult(*v0,*u0);
M2MuInv_->Mult(*u0,*HD_);
NegCurl_->MultTranspose(*HD_,*RHS_);
pcg_[0]->Mult(*RHS_,*v1);
double lambda = InnerProduct(*v0,*v1);
dt1 = 2.0/sqrt(lambda);
change = fabs((dt1-dt0)/dt0);
dt0 = dt1;
if ( myid_ == 0 && logging_ > 1 )
{
cout << iter << ": " << dt0 << " " << change << endl;
}
std::swap(v0, v1);
iter++;
}
delete v0;
delete v1;
delete u0;
return dt0;
}
double
MaxwellSolver::GetEnergy() const
{
double energy = 0.0;
A1_[0]->Mult(*E_,*RHS_);
M2MuInv_->Mult(*B_,*HD_);
energy = InnerProduct(*E_,*RHS_) + InnerProduct(*B_,*HD_);
return 0.5 * energy;
}
void
MaxwellSolver::RegisterVisItFields(VisItDataCollection & visit_dc)
{
visit_dc_ = &visit_dc;
visit_dc.RegisterField("E", e_);
visit_dc.RegisterField("B", b_);
if ( j_ )
{
visit_dc.RegisterField("J", j_);
}
}
void
MaxwellSolver::WriteVisItFields(int it)
{
if ( visit_dc_ )
{
if ( myid_ == 0 && logging_ > 1 )
{ cout << "Writing VisIt files ..." << flush; }
if ( j_ )
{
jCoef_->SetTime(t);
j_->ProjectCoefficient(*jCoef_);
}
visit_dc_->SetCycle(it);
visit_dc_->SetTime(t);
visit_dc_->Save();
if ( myid_ == 0 && logging_ > 1 ) { cout << " " << endl << flush; }
}
}
void
MaxwellSolver::InitializeGLVis()
{
if ( myid_ == 0 && logging_ > 0 )
{ cout << "Opening GLVis sockets." << endl << flush; }
socks_["E"] = new socketstream;
socks_["E"]->precision(8);
socks_["B"] = new socketstream;
socks_["B"]->precision(8);
if ( j_ )
{
socks_["J"] = new socketstream;
socks_["J"]->precision(8);
}
if ( myid_ == 0 && logging_ > 0 )
{ cout << "GLVis sockets open." << endl << flush; }
}
void
MaxwellSolver::DisplayToGLVis()
{
if ( myid_ == 0 && logging_ > 1 )
{ 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_["E"], vishost, visport,
*e_, "Electric Field (E)", Wx, Wy, Ww, Wh);
Wx += offx;
VisualizeField(*socks_["B"], vishost, visport,
*b_, "Magnetic Flux Density (B)", Wx, Wy, Ww, Wh);
if ( j_ )
{
Wx = 0;
Wy += offy;
jCoef_->SetTime(t); // Is member data from mfem::TimeDependentOperator
j_->ProjectCoefficient(*jCoef_);
VisualizeField(*socks_["J"], vishost, visport,
*j_, "Current Density (J)", Wx, Wy, Ww, Wh);
}
if ( myid_ == 0 && logging_ > 1 ) { cout << " " << flush; }
}
} // namespace electromagnetics
} // namespace mfem
#endif // MFEM_USE_MPI