Files
mfem/miniapps/common/dist_solver.cpp
T

824 lines
22 KiB
C++

// Copyright (c) 2010-2025, 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 "dist_solver.hpp"
#ifdef MFEM_USE_MPI
namespace mfem
{
namespace common
{
void DiffuseField(ParGridFunction &field, int smooth_steps)
{
// Setup the Laplacian operator.
ParBilinearForm *Lap = new ParBilinearForm(field.ParFESpace());
Lap->AddDomainIntegrator(new DiffusionIntegrator());
Lap->Assemble();
Lap->Finalize();
HypreParMatrix *A = Lap->ParallelAssemble();
HypreSmoother *S = new HypreSmoother(*A,0,smooth_steps);
S->iterative_mode = true;
Vector tmp(A->Width());
field.SetTrueVector();
Vector fieldtrue = field.GetTrueVector();
tmp = 0.0;
S->Mult(tmp, fieldtrue);
field.SetFromTrueDofs(fieldtrue);
delete A;
delete S;
delete Lap;
}
real_t AvgElementSize(ParMesh &pmesh)
{
// Compute average mesh size (assumes similar cells).
real_t dx, loc_area = 0.0;
for (int i = 0; i < pmesh.GetNE(); i++)
{
loc_area += pmesh.GetElementVolume(i);
}
real_t glob_area;
MPI_Allreduce(&loc_area, &glob_area, 1, MPITypeMap<real_t>::mpi_type,
MPI_SUM, pmesh.GetComm());
const int glob_zones = pmesh.GetGlobalNE();
switch (pmesh.GetTypicalElementGeometry())
{
case Geometry::SEGMENT:
dx = glob_area / glob_zones; break;
case Geometry::SQUARE:
dx = sqrt(glob_area / glob_zones); break;
case Geometry::TRIANGLE:
dx = sqrt(2.0 * glob_area / glob_zones); break;
case Geometry::CUBE:
dx = pow(glob_area / glob_zones, 1.0/3.0); break;
case Geometry::TETRAHEDRON:
dx = pow(6.0 * glob_area / glob_zones, 1.0/3.0); break;
default: MFEM_ABORT("Unknown zone type!"); dx = 0.0;
}
return dx;
}
void DistanceSolver::ScalarDistToVector(ParGridFunction &dist_s,
ParGridFunction &dist_v)
{
ParFiniteElementSpace &pfes = *dist_s.ParFESpace();
MFEM_VERIFY(pfes.GetOrdering()==Ordering::byNODES,
"Only Ordering::byNODES is supported.");
const int dim = pfes.GetMesh()->Dimension();
const int size = dist_s.Size();
ParGridFunction der(&pfes);
Vector magn(size);
magn = 0.0;
for (int d = 0; d < dim; d++)
{
dist_s.GetDerivative(1, d, der);
for (int i = 0; i < size; i++)
{
magn(i) += der(i) * der(i);
// The vector must point towards the level zero set.
dist_v(i + d*size) = (dist_s(i) > 0.0) ? -der(i) : der(i);
}
}
for (int i = 0; i < size; i++)
{
const real_t vec_magn = std::sqrt(magn(i) + 1e-12);
for (int d = 0; d < dim; d++)
{
dist_v(i + d*size) *= fabs(dist_s(i)) / vec_magn;
}
}
}
void DistanceSolver::ComputeVectorDistance(Coefficient &zero_level_set,
ParGridFunction &distance)
{
ParFiniteElementSpace &pfes = *distance.ParFESpace();
MFEM_VERIFY(pfes.GetVDim() == pfes.GetMesh()->Dimension(),
"This function expects a vector ParGridFunction!");
ParFiniteElementSpace pfes_s(pfes.GetParMesh(), pfes.FEColl());
ParGridFunction dist_s(&pfes_s);
ComputeScalarDistance(zero_level_set, dist_s);
ScalarDistToVector(dist_s, distance);
}
void HeatDistanceSolver::ComputeScalarDistance(Coefficient &zero_level_set,
ParGridFunction &distance)
{
ParFiniteElementSpace &pfes = *distance.ParFESpace();
auto check_h1 = dynamic_cast<const H1_FECollection *>(pfes.FEColl());
MFEM_VERIFY(check_h1 && pfes.GetVDim() == 1,
"This solver supports only scalar H1 spaces.");
// Compute average mesh size (assumes similar cells).
ParMesh &pmesh = *pfes.GetParMesh();
// Step 0 - transform the input level set into a source-type bump.
ParGridFunction source(&pfes);
source.ProjectCoefficient(zero_level_set);
// Optional smoothing of the initial level set.
if (smooth_steps > 0) { DiffuseField(source, smooth_steps); }
// Transform so that the peak is at 0.
// Assumes range [-1, 1].
if (transform)
{
for (int i = 0; i < source.Size(); i++)
{
const real_t x = source(i);
source(i) = ((x < -1.0) || (x > 1.0)) ? 0.0 : (1.0 - x) * (1.0 + x);
}
}
int amg_print_level = 0;
// Solver.
CGSolver cg(MPI_COMM_WORLD);
cg.SetRelTol(1e-12);
cg.SetMaxIter(100);
cg.SetPrintLevel(print_level);
OperatorPtr A;
Vector B, X;
// Step 1 - diffuse.
ParGridFunction diffused_source(&pfes);
for (int i = 0; i < diffuse_iter; i++)
{
// Set up RHS.
ParLinearForm b(&pfes);
GridFunctionCoefficient src_coeff(&source);
b.AddDomainIntegrator(new DomainLFIntegrator(src_coeff));
b.Assemble();
// Diffusion and mass terms in the LHS.
ParBilinearForm a_d(&pfes);
a_d.AddDomainIntegrator(new MassIntegrator);
ConstantCoefficient t_coeff(parameter_t);
a_d.AddDomainIntegrator(new DiffusionIntegrator(t_coeff));
a_d.Assemble();
// Solve with Dirichlet BC.
Array<int> ess_tdof_list;
if (pmesh.bdr_attributes.Size())
{
Array<int> ess_bdr(pmesh.bdr_attributes.Max());
ess_bdr = 1;
pfes.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
}
ParGridFunction u_dirichlet(&pfes);
u_dirichlet = 0.0;
a_d.FormLinearSystem(ess_tdof_list, u_dirichlet, b, A, X, B);
auto *prec = new HypreBoomerAMG;
prec->SetPrintLevel(amg_print_level);
cg.SetPreconditioner(*prec);
cg.SetOperator(*A);
cg.Mult(B, X);
a_d.RecoverFEMSolution(X, b, u_dirichlet);
delete prec;
// Diffusion and mass terms in the LHS.
ParBilinearForm a_n(&pfes);
a_n.AddDomainIntegrator(new MassIntegrator);
a_n.AddDomainIntegrator(new DiffusionIntegrator(t_coeff));
a_n.Assemble();
// Solve with Neumann BC.
ParGridFunction u_neumann(&pfes);
ess_tdof_list.DeleteAll();
a_n.FormLinearSystem(ess_tdof_list, u_neumann, b, A, X, B);
auto *prec2 = new HypreBoomerAMG;
prec2->SetPrintLevel(amg_print_level);
cg.SetPreconditioner(*prec2);
cg.SetOperator(*A);
cg.Mult(B, X);
a_n.RecoverFEMSolution(X, b, u_neumann);
delete prec2;
for (int ii = 0; ii < diffused_source.Size(); ii++)
{
// This assumes that the magnitudes of the two solutions are somewhat
// similar; otherwise one of the solutions would dominate and the BC
// won't look correct. To avoid this, it's good to have the source
// away from the boundary (i.e. have more resolution).
diffused_source(ii) = 0.5 * (u_neumann(ii) + u_dirichlet(ii));
}
source = diffused_source;
}
// Step 2 - solve for the distance using the normalized gradient.
{
// RHS - normalized gradient.
ParLinearForm b2(&pfes);
NormalizedGradCoefficient grad_u(diffused_source, pmesh.Dimension());
b2.AddDomainIntegrator(new DomainLFGradIntegrator(grad_u));
b2.Assemble();
// LHS - diffusion.
ParBilinearForm a2(&pfes);
a2.AddDomainIntegrator(new DiffusionIntegrator);
a2.Assemble();
// No BC.
Array<int> no_ess_tdofs;
a2.FormLinearSystem(no_ess_tdofs, distance, b2, A, X, B);
auto *prec = new HypreBoomerAMG;
prec->SetPrintLevel(amg_print_level);
OrthoSolver ortho(pfes.GetComm());
ortho.SetSolver(*prec);
cg.SetPreconditioner(ortho);
cg.SetOperator(*A);
cg.Mult(B, X);
a2.RecoverFEMSolution(X, b2, distance);
delete prec;
}
// Shift the distance values to have minimum at zero.
real_t d_min_loc = distance.Min();
real_t d_min_glob;
MPI_Allreduce(&d_min_loc, &d_min_glob, 1, MPITypeMap<real_t>::mpi_type,
MPI_MIN, pfes.GetComm());
distance -= d_min_glob;
if (vis_glvis)
{
char vishost[] = "localhost";
int visport = 19916;
ParFiniteElementSpace fespace_vec(&pmesh, pfes.FEColl(),
pmesh.Dimension());
NormalizedGradCoefficient grad_u(diffused_source, pmesh.Dimension());
ParGridFunction x(&fespace_vec);
x.ProjectCoefficient(grad_u);
socketstream sol_sock_x(vishost, visport);
sol_sock_x << "parallel " << pfes.GetNRanks() << " "
<< pfes.GetMyRank() << "\n";
sol_sock_x.precision(8);
sol_sock_x << "solution\n" << pmesh << x;
sol_sock_x << "window_geometry " << 0 << " " << 0 << " "
<< 500 << " " << 500 << "\n"
<< "window_title '" << "Heat Directions" << "'\n"
<< "keys evvRj*******A\n" << std::flush;
}
}
real_t NormalizationDistanceSolver::NormalizationCoeff::
Eval(ElementTransformation &T,const IntegrationPoint &ip)
{
T.SetIntPoint(&ip);
Vector u_grad;
u.GetGradient(T, u_grad);
const real_t u_value = u.GetValue(T, ip);
return u_value / sqrt(u_value * u_value + u_grad * u_grad + 1e-12);
}
void NormalizationDistanceSolver::ComputeScalarDistance(Coefficient &u_coeff,
ParGridFunction &dist)
{
ParFiniteElementSpace &pfes = *dist.ParFESpace();
ParGridFunction u_gf(&pfes);
u_gf.ProjectCoefficient(u_coeff);
NormalizationCoeff rv_coeff(u_gf);
dist.ProjectDiscCoefficient(rv_coeff, GridFunction::AvgType::ARITHMETIC);
}
void PLapDistanceSolver::ComputeScalarDistance(Coefficient &func,
ParGridFunction &fdist)
{
ParFiniteElementSpace* fesd=fdist.ParFESpace();
auto check_h1 = dynamic_cast<const H1_FECollection *>(fesd->FEColl());
auto check_l2 = dynamic_cast<const L2_FECollection *>(fesd->FEColl());
MFEM_VERIFY((check_h1 || check_l2) && fesd->GetVDim() == 1,
"This solver supports only scalar H1 or L2 spaces.");
ParMesh* mesh=fesd->GetParMesh();
const int dim=mesh->Dimension();
MPI_Comm lcomm=fesd->GetComm();
int myrank;
MPI_Comm_rank(lcomm,&myrank);
const int order = fesd->GetOrder(0);
H1_FECollection fecp(order, dim);
ParFiniteElementSpace fesp(mesh, &fecp, 1, Ordering::byVDIM);
ParGridFunction wf(&fesp);
wf.ProjectCoefficient(func);
GradientGridFunctionCoefficient gf(&wf); //gradient of wf
ParGridFunction xf(&fesp);
HypreParVector *sv = xf.GetTrueDofs();
*sv=1.0;
ParNonlinearForm *nf = new ParNonlinearForm(&fesp);
PUMPLaplacian* pint = new PUMPLaplacian(&func,&gf,false);
nf->AddDomainIntegrator(pint);
pint->SetPower(2);
//define the solvers
HypreBoomerAMG *prec = new HypreBoomerAMG();
prec->SetPrintLevel(0);
GMRESSolver *gmres;
gmres = new GMRESSolver(lcomm);
gmres->SetAbsTol(newton_abs_tol/10);
gmres->SetRelTol(newton_rel_tol/10);
gmres->SetMaxIter(100);
gmres->SetPrintLevel(0);
gmres->SetPreconditioner(*prec);
NewtonSolver ns(lcomm);
ns.iterative_mode = true;
ns.SetSolver(*gmres);
ns.SetOperator(*nf);
ns.SetPrintLevel(print_level);
ns.SetRelTol(newton_rel_tol);
ns.SetAbsTol(newton_abs_tol);
ns.SetMaxIter(newton_iter);
Vector b; // RHS is zero
ns.Mult(b, *sv);
for (int pp=3; pp<maxp; pp++)
{
if (myrank == 0 && (print_level.summary || print_level.iterations))
{
std::cout << "pp = " << pp << std::endl;
}
pint->SetPower(pp);
ns.Mult(b, *sv);
}
xf.SetFromTrueDofs(*sv);
GridFunctionCoefficient gfx(&xf);
PProductCoefficient tsol(func,gfx);
fdist.ProjectCoefficient(tsol);
delete gmres;
delete prec;
delete nf;
delete sv;
}
real_t ScreenedPoisson::GetElementEnergy(const FiniteElement &el,
ElementTransformation &trans,
const Vector &elfun)
{
real_t energy = 0.0;
int ndof = el.GetDof();
int ndim = el.GetDim();
const IntegrationRule *ir = NULL;
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
ir = &IntRules.Get(el.GetGeomType(), order);
Vector shapef(ndof);
real_t fval;
real_t pval;
DenseMatrix B(ndof, ndim);
Vector qval(ndim);
B=0.0;
real_t w;
real_t ngrad2;
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
trans.SetIntPoint(&ip);
w = trans.Weight();
w = ip.weight * w;
fval=func->Eval(trans,ip);
el.CalcPhysDShape(trans, B);
el.CalcPhysShape(trans,shapef);
B.MultTranspose(elfun,qval);
ngrad2=0.0;
for (int jj=0; jj<ndim; jj++)
{
ngrad2 = ngrad2 + qval(jj)*qval(jj);
}
energy = energy + w * ngrad2 * diffcoef * 0.5;
// add the external load -1 if fval > 0.0; 1 if fval < 0.0;
pval=shapef*elfun;
energy = energy + w * pval * pval * 0.5;
if (fval>0.0)
{
energy = energy - w*pval;
}
else if (fval<0.0)
{
energy = energy + w*pval;
}
}
return energy;
}
void ScreenedPoisson::AssembleElementVector(const FiniteElement &el,
ElementTransformation &trans,
const Vector &elfun,
Vector &elvect)
{
int ndof = el.GetDof();
int ndim = el.GetDim();
const IntegrationRule *ir = NULL;
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
ir = &IntRules.Get(el.GetGeomType(), order);
elvect.SetSize(ndof);
elvect=0.0;
Vector shapef(ndof);
real_t fval;
real_t pval;
DenseMatrix B(ndof, ndim); //[diff_x,diff_y,diff_z]
Vector qval(ndim); //[diff_x,diff_y,diff_z,u]
Vector lvec(ndof); //residual at ip
B=0.0;
qval=0.0;
real_t w;
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
trans.SetIntPoint(&ip);
w = trans.Weight();
w = ip.weight * w;
fval=func->Eval(trans,ip);
el.CalcPhysDShape(trans, B);
el.CalcPhysShape(trans,shapef);
B.MultTranspose(elfun,qval);
B.Mult(qval,lvec);
elvect.Add(w * diffcoef,lvec);
pval=shapef*elfun;
elvect.Add(w * pval, shapef);
//add the load
//add the external load -1 if fval > 0.0; 1 if fval < 0.0;
pval=shapef*elfun;
if (fval>0.0)
{
elvect.Add( -w, shapef);
}
else if (fval<0.0)
{
elvect.Add( w, shapef);
}
}
}
void ScreenedPoisson::AssembleElementGrad(const FiniteElement &el,
ElementTransformation &trans,
const Vector &elfun,
DenseMatrix &elmat)
{
int ndof = el.GetDof();
int ndim = el.GetDim();
const IntegrationRule *ir = NULL;
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
ir = &IntRules.Get(el.GetGeomType(), order);
elmat.SetSize(ndof,ndof);
elmat=0.0;
Vector shapef(ndof);
DenseMatrix B(ndof, ndim); //[diff_x,diff_y,diff_z]
B = 0.0;
real_t w;
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
trans.SetIntPoint(&ip);
w = trans.Weight();
w = ip.weight * w;
el.CalcPhysDShape(trans, B);
el.CalcPhysShape(trans,shapef);
AddMult_a_VVt(w, shapef, elmat);
AddMult_a_AAt(w * diffcoef, B, elmat);
}
}
real_t PUMPLaplacian::GetElementEnergy(const FiniteElement &el,
ElementTransformation &trans,
const Vector &elfun)
{
real_t energy = 0.0;
int ndof = el.GetDof();
int ndim = el.GetDim();
const IntegrationRule *ir = NULL;
int order = 2 * el.GetOrder() + trans.OrderGrad(&el);
ir = &IntRules.Get(el.GetGeomType(), order);
Vector shapef(ndof);
real_t fval;
real_t pval;
real_t tval;
Vector vgrad(ndim);
DenseMatrix dshape(ndof, ndim);
DenseMatrix B(ndof, ndim);
Vector qval(ndim);
Vector tmpv(ndof);
B=0.0;
real_t w;
real_t ngrad2;
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
trans.SetIntPoint(&ip);
w = trans.Weight();
w = ip.weight * w;
fval=func->Eval(trans,ip);
fgrad->Eval(vgrad,trans,ip);
tval=fval;
if (fval<0.0)
{
fval=-fval;
vgrad*=-1.0;
}
el.CalcPhysDShape(trans, dshape);
el.CalcPhysShape(trans,shapef);
for (int jj=0; jj<ndim; jj++)
{
dshape.GetColumn(jj,tmpv);
tmpv*=fval;
tmpv.Add(vgrad[jj],shapef);
B.SetCol(jj,tmpv);
}
B.MultTranspose(elfun,qval);
ngrad2=0.0;
for (int jj=0; jj<ndim; jj++)
{
ngrad2 = ngrad2 + qval(jj)*qval(jj);
}
energy = energy + w * std::pow(ngrad2+ee*ee,pp/2.0)/pp;
// add the external load -1 if fval > 0.0; 1 if fval < 0.0;
pval=shapef*elfun;
if (tval>0.0)
{
energy = energy - w * pval * tval;
}
else if (tval<0.0)
{
energy = energy + w * pval * tval;
}
}
return energy;
}
void PUMPLaplacian::AssembleElementVector(const FiniteElement &el,
ElementTransformation &trans,
const Vector &elfun,
Vector &elvect)
{
int ndof = el.GetDof();
int ndim = el.GetDim();
const IntegrationRule *ir = NULL;
int order = 2 * el.GetOrder() + trans.OrderGrad(&el)+1;
ir = &IntRules.Get(el.GetGeomType(), order);
elvect.SetSize(ndof);
elvect=0.0;
Vector shapef(ndof);
real_t fval;
real_t tval;
Vector vgrad(3);
DenseMatrix dshape(ndof, ndim);
DenseMatrix B(ndof, ndim); // [diff_x,diff_y,diff_z]
Vector qval(ndim); // [diff_x,diff_y,diff_z,u]
Vector lvec(ndof); // residual at ip
Vector tmpv(ndof);
B=0.0;
qval=0.0;
real_t w;
real_t ngrad2;
real_t aa;
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
trans.SetIntPoint(&ip);
w = trans.Weight();
w = ip.weight * w;
fval=func->Eval(trans,ip);
fgrad->Eval(vgrad,trans,ip);
tval=fval;
if (fval<0.0)
{
fval=-fval;
vgrad*=-1.0;
}
el.CalcPhysDShape(trans, dshape);
el.CalcPhysShape(trans,shapef);
for (int jj=0; jj<ndim; jj++)
{
dshape.GetColumn(jj,tmpv);
tmpv*=fval;
tmpv.Add(vgrad[jj],shapef);
B.SetCol(jj,tmpv);
}
B.MultTranspose(elfun,qval);
ngrad2=0.0;
for (int jj=0; jj<ndim; jj++)
{
ngrad2 = ngrad2 + qval(jj)*qval(jj);
}
aa = ngrad2 + ee*ee;
aa = std::pow(aa, (pp - 2.0) / 2.0);
B.Mult(qval,lvec);
elvect.Add(w * aa,lvec);
// add the load
// add the external load -1 if tval > 0.0; 1 if tval < 0.0;
if (tval>0.0)
{
elvect.Add( -w*fval, shapef);
}
else if (tval<0.0)
{
elvect.Add( w*fval, shapef);
}
}
}
void PUMPLaplacian::AssembleElementGrad(const FiniteElement &el,
ElementTransformation &trans,
const Vector &elfun,
DenseMatrix &elmat)
{
int ndof = el.GetDof();
int ndim = el.GetDim();
const IntegrationRule *ir = NULL;
int order = 2 * el.GetOrder() + trans.OrderGrad(&el)+1;
ir = &IntRules.Get(el.GetGeomType(), order);
elmat.SetSize(ndof,ndof);
elmat=0.0;
Vector shapef(ndof);
real_t fval;
Vector vgrad(ndim);
Vector qval(ndim); // [diff_x,diff_y,diff_z,u]
DenseMatrix dshape(ndof, ndim);
DenseMatrix B(ndof, ndim); // [diff_x,diff_y,diff_z]
Vector lvec(ndof);
Vector tmpv(ndof);
B=0.0;
real_t w;
real_t ngrad2;
real_t aa, aa0, aa1;
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
trans.SetIntPoint(&ip);
w = trans.Weight();
w = ip.weight * w;
fval=func->Eval(trans,ip);
fgrad->Eval(vgrad,trans,ip);
if (fval<0.0)
{
fval=-fval;
vgrad*=-1.0;
}
el.CalcPhysDShape(trans, dshape);
el.CalcPhysShape(trans,shapef);
for (int jj=0; jj<ndim; jj++)
{
dshape.GetColumn(jj,tmpv);
tmpv*=fval;
tmpv.Add(vgrad[jj],shapef);
B.SetCol(jj,tmpv);
}
B.MultTranspose(elfun,qval);
B.Mult(qval,lvec);
ngrad2=0.0;
for (int jj=0; jj<ndim; jj++)
{
ngrad2 = ngrad2 + qval(jj)*qval(jj);
}
aa = ngrad2 + ee * ee;
aa1 = std::pow(aa, (pp - 2.0) / 2.0);
aa0 = (pp-2.0) * std::pow(aa, (pp - 4.0) / 2.0);
AddMult_a_VVt(w * aa0, lvec, elmat);
AddMult_a_AAt(w * aa1, B, elmat);
}
}
void PDEFilter::Filter(Coefficient &func, ParGridFunction &ffield)
{
if (sint == nullptr)
{
sint = new ScreenedPoisson(func, rr);
nf->AddDomainIntegrator(sint);
*sv = 0.0;
gmres->SetOperator(nf->GetGradient(*sv));
}
else { sint->SetInput(func); }
// form RHS
*sv = 0.0;
Vector rhs(sv->Size());
nf->Mult(*sv, rhs);
// filter the input field
gmres->Mult(rhs, *sv);
gf.SetFromTrueDofs(*sv);
gf.Neg();
GridFunctionCoefficient gfc(&gf);
ffield.ProjectCoefficient(gfc);
}
} // namespace common
} // namespace mfem
#endif