Files
mfem/fem/pgridfunc.cpp
T
2020-04-30 02:26:25 -07:00

833 lines
23 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 "../config/config.hpp"
#ifdef MFEM_USE_MPI
#include "fem.hpp"
#include <iostream>
#include <limits>
#include "../general/forall.hpp"
using namespace std;
namespace mfem
{
ParGridFunction::ParGridFunction(ParFiniteElementSpace *pf, GridFunction *gf)
{
fes = pfes = pf;
SetDataAndSize(gf->GetData(), gf->Size());
}
ParGridFunction::ParGridFunction(ParFiniteElementSpace *pf, HypreParVector *tv)
: GridFunction(pf), pfes(pf)
{
Distribute(tv);
}
ParGridFunction::ParGridFunction(ParMesh *pmesh, const GridFunction *gf,
const int *partitioning)
{
const FiniteElementSpace *glob_fes = gf->FESpace();
// duplicate the FiniteElementCollection from 'gf'
fec = FiniteElementCollection::New(glob_fes->FEColl()->Name());
// create a local ParFiniteElementSpace from the global one:
fes = pfes = new ParFiniteElementSpace(pmesh, glob_fes, partitioning, fec);
SetSize(pfes->GetVSize());
if (partitioning)
{
// Assumption: the map "local element id" -> "global element id" is
// increasing, i.e. the local numbering preserves the element order from
// the global numbering.
Array<int> gvdofs, lvdofs;
Vector lnodes;
int element_counter = 0;
const int MyRank = pfes->GetMyRank();
const int glob_ne = glob_fes->GetNE();
for (int i = 0; i < glob_ne; i++)
{
if (partitioning[i] == MyRank)
{
pfes->GetElementVDofs(element_counter, lvdofs);
glob_fes->GetElementVDofs(i, gvdofs);
gf->GetSubVector(gvdofs, lnodes);
SetSubVector(lvdofs, lnodes);
element_counter++;
}
}
}
}
ParGridFunction::ParGridFunction(ParMesh *pmesh, std::istream &input)
: GridFunction(pmesh, input)
{
// Convert the FiniteElementSpace, fes, to a ParFiniteElementSpace:
pfes = new ParFiniteElementSpace(pmesh, fec, fes->GetVDim(),
fes->GetOrdering());
delete fes;
fes = pfes;
}
void ParGridFunction::Update()
{
face_nbr_data.Destroy();
GridFunction::Update();
}
void ParGridFunction::SetSpace(FiniteElementSpace *f)
{
face_nbr_data.Destroy();
GridFunction::SetSpace(f);
pfes = dynamic_cast<ParFiniteElementSpace*>(f);
MFEM_ASSERT(pfes != NULL, "not a ParFiniteElementSpace");
}
void ParGridFunction::SetSpace(ParFiniteElementSpace *f)
{
face_nbr_data.Destroy();
GridFunction::SetSpace(f);
pfes = f;
}
void ParGridFunction::MakeRef(FiniteElementSpace *f, double *v)
{
face_nbr_data.Destroy();
GridFunction::MakeRef(f, v);
pfes = dynamic_cast<ParFiniteElementSpace*>(f);
MFEM_ASSERT(pfes != NULL, "not a ParFiniteElementSpace");
}
void ParGridFunction::MakeRef(ParFiniteElementSpace *f, double *v)
{
face_nbr_data.Destroy();
GridFunction::MakeRef(f, v);
pfes = f;
}
void ParGridFunction::MakeRef(FiniteElementSpace *f, Vector &v, int v_offset)
{
face_nbr_data.Destroy();
GridFunction::MakeRef(f, v, v_offset);
pfes = dynamic_cast<ParFiniteElementSpace*>(f);
MFEM_ASSERT(pfes != NULL, "not a ParFiniteElementSpace");
}
void ParGridFunction::MakeRef(ParFiniteElementSpace *f, Vector &v, int v_offset)
{
face_nbr_data.Destroy();
GridFunction::MakeRef(f, v, v_offset);
pfes = f;
}
void ParGridFunction::Distribute(const Vector *tv)
{
const Operator *prolong = pfes->GetProlongationMatrix();
prolong->Mult(*tv, *this);
}
void ParGridFunction::AddDistribute(double a, const Vector *tv)
{
pfes->Dof_TrueDof_Matrix()->Mult(a, *tv, 1.0, *this);
}
HypreParVector *ParGridFunction::GetTrueDofs() const
{
HypreParVector *tv = pfes->NewTrueDofVector();
GetTrueDofs(*tv);
return tv;
}
void ParGridFunction::ParallelAverage(Vector &tv) const
{
MFEM_VERIFY(pfes->Conforming(), "not implemented for NC meshes");
pfes->GetProlongationMatrix()->MultTranspose(*this, tv);
pfes->DivideByGroupSize(tv);
}
void ParGridFunction::ParallelAverage(HypreParVector &tv) const
{
MFEM_VERIFY(pfes->Conforming(), "not implemented for NC meshes");
pfes->GetProlongationMatrix()->MultTranspose(*this, tv);
pfes->DivideByGroupSize(tv);
}
HypreParVector *ParGridFunction::ParallelAverage() const
{
HypreParVector *tv = pfes->NewTrueDofVector();
ParallelAverage(*tv);
return tv;
}
void ParGridFunction::ParallelProject(Vector &tv) const
{
pfes->GetRestrictionMatrix()->Mult(*this, tv);
}
void ParGridFunction::ParallelProject(HypreParVector &tv) const
{
pfes->GetRestrictionMatrix()->Mult(*this, tv);
}
HypreParVector *ParGridFunction::ParallelProject() const
{
HypreParVector *tv = pfes->NewTrueDofVector();
ParallelProject(*tv);
return tv;
}
void ParGridFunction::ParallelAssemble(Vector &tv) const
{
pfes->GetProlongationMatrix()->MultTranspose(*this, tv);
}
void ParGridFunction::ParallelAssemble(HypreParVector &tv) const
{
pfes->GetProlongationMatrix()->MultTranspose(*this, tv);
}
HypreParVector *ParGridFunction::ParallelAssemble() const
{
HypreParVector *tv = pfes->NewTrueDofVector();
ParallelAssemble(*tv);
return tv;
}
void ParGridFunction::ExchangeFaceNbrData()
{
pfes->ExchangeFaceNbrData();
if (pfes->GetFaceNbrVSize() <= 0)
{
return;
}
ParMesh *pmesh = pfes->GetParMesh();
face_nbr_data.SetSize(pfes->GetFaceNbrVSize());
Vector send_data(pfes->send_face_nbr_ldof.Size_of_connections());
int *send_offset = pfes->send_face_nbr_ldof.GetI();
const int *d_send_ldof = mfem::Read(pfes->send_face_nbr_ldof.GetJMemory(),
send_data.Size());
int *recv_offset = pfes->face_nbr_ldof.GetI();
MPI_Comm MyComm = pfes->GetComm();
int num_face_nbrs = pmesh->GetNFaceNeighbors();
MPI_Request *requests = new MPI_Request[2*num_face_nbrs];
MPI_Request *send_requests = requests;
MPI_Request *recv_requests = requests + num_face_nbrs;
MPI_Status *statuses = new MPI_Status[num_face_nbrs];
auto d_data = this->Read();
auto d_send_data = send_data.Write();
MFEM_FORALL(i, send_data.Size(),
{
const int ldof = d_send_ldof[i];
d_send_data[i] = d_data[ldof >= 0 ? ldof : -1-ldof];
});
bool mpi_gpu_aware = Device::GetGPUAwareMPI();
auto send_data_ptr = mpi_gpu_aware ? send_data.Read() : send_data.HostRead();
auto face_nbr_data_ptr = mpi_gpu_aware ? face_nbr_data.Write() :
face_nbr_data.HostWrite();
for (int fn = 0; fn < num_face_nbrs; fn++)
{
int nbr_rank = pmesh->GetFaceNbrRank(fn);
int tag = 0;
MPI_Isend(&send_data_ptr[send_offset[fn]],
send_offset[fn+1] - send_offset[fn],
MPI_DOUBLE, nbr_rank, tag, MyComm, &send_requests[fn]);
MPI_Irecv(&face_nbr_data_ptr[recv_offset[fn]],
recv_offset[fn+1] - recv_offset[fn],
MPI_DOUBLE, nbr_rank, tag, MyComm, &recv_requests[fn]);
}
MPI_Waitall(num_face_nbrs, send_requests, statuses);
MPI_Waitall(num_face_nbrs, recv_requests, statuses);
delete [] statuses;
delete [] requests;
}
double ParGridFunction::GetValue(int i, const IntegrationPoint &ip, int vdim)
const
{
Array<int> dofs;
Vector DofVal, LocVec;
int nbr_el_no = i - pfes->GetParMesh()->GetNE();
if (nbr_el_no >= 0)
{
int fes_vdim = pfes->GetVDim();
pfes->GetFaceNbrElementVDofs(nbr_el_no, dofs);
if (fes_vdim > 1)
{
int s = dofs.Size()/fes_vdim;
Array<int> _dofs(&dofs[(vdim-1)*s], s);
face_nbr_data.GetSubVector(_dofs, LocVec);
DofVal.SetSize(s);
}
else
{
face_nbr_data.GetSubVector(dofs, LocVec);
DofVal.SetSize(dofs.Size());
}
pfes->GetFaceNbrFE(nbr_el_no)->CalcShape(ip, DofVal);
}
else
{
fes->GetElementDofs(i, dofs);
fes->DofsToVDofs(vdim-1, dofs);
DofVal.SetSize(dofs.Size());
const FiniteElement *fe = fes->GetFE(i);
MFEM_ASSERT(fe->GetMapType() == FiniteElement::VALUE, "invalid FE map type");
fe->CalcShape(ip, DofVal);
GetSubVector(dofs, LocVec);
}
return (DofVal * LocVec);
}
void ParGridFunction::ProjectCoefficient(Coefficient &coeff)
{
DeltaCoefficient *delta_c = dynamic_cast<DeltaCoefficient *>(&coeff);
if (delta_c == NULL)
{
GridFunction::ProjectCoefficient(coeff);
}
else
{
double loc_integral, glob_integral;
ProjectDeltaCoefficient(*delta_c, loc_integral);
MPI_Allreduce(&loc_integral, &glob_integral, 1, MPI_DOUBLE, MPI_SUM,
pfes->GetComm());
(*this) *= (delta_c->Scale() / glob_integral);
}
}
void ParGridFunction::ProjectDiscCoefficient(VectorCoefficient &coeff)
{
// local maximal element attribute for each dof
Array<int> ldof_attr;
// local projection
GridFunction::ProjectDiscCoefficient(coeff, ldof_attr);
// global maximal element attribute for each dof
Array<int> gdof_attr;
ldof_attr.Copy(gdof_attr);
GroupCommunicator &gcomm = pfes->GroupComm();
gcomm.Reduce<int>(gdof_attr, GroupCommunicator::Max);
gcomm.Bcast(gdof_attr);
// set local value to zero if global maximal element attribute is larger than
// the local one, and mark (in gdof_attr) if we have the correct value
for (int i = 0; i < pfes->GetVSize(); i++)
{
if (gdof_attr[i] > ldof_attr[i])
{
(*this)(i) = 0.0;
gdof_attr[i] = 0;
}
else
{
gdof_attr[i] = 1;
}
}
// parallel averaging plus interpolation to determine final values
HypreParVector *tv = pfes->NewTrueDofVector();
gcomm.Reduce<int>(gdof_attr, GroupCommunicator::Sum);
gcomm.Bcast(gdof_attr);
for (int i = 0; i < fes->GetVSize(); i++)
{
(*this)(i) /= gdof_attr[i];
}
this->ParallelAssemble(*tv);
this->Distribute(tv);
delete tv;
}
void ParGridFunction::ProjectDiscCoefficient(Coefficient &coeff, AvgType type)
{
// Harmonic (x1 ... xn) = [ (1/x1 + ... + 1/xn) / n ]^-1.
// Arithmetic(x1 ... xn) = (x1 + ... + xn) / n.
// Number of zones that contain a given dof.
Array<int> zones_per_vdof;
AccumulateAndCountZones(coeff, type, zones_per_vdof);
// Count the zones globally.
GroupCommunicator &gcomm = pfes->GroupComm();
gcomm.Reduce<int>(zones_per_vdof, GroupCommunicator::Sum);
gcomm.Bcast(zones_per_vdof);
// Accumulate for all vdofs.
gcomm.Reduce<double>(data, GroupCommunicator::Sum);
gcomm.Bcast<double>(data);
ComputeMeans(type, zones_per_vdof);
}
void ParGridFunction::ProjectDiscCoefficient(VectorCoefficient &vcoeff,
AvgType type)
{
// Harmonic (x1 ... xn) = [ (1/x1 + ... + 1/xn) / n ]^-1.
// Arithmetic(x1 ... xn) = (x1 + ... + xn) / n.
// Number of zones that contain a given dof.
Array<int> zones_per_vdof;
AccumulateAndCountZones(vcoeff, type, zones_per_vdof);
// Count the zones globally.
GroupCommunicator &gcomm = pfes->GroupComm();
gcomm.Reduce<int>(zones_per_vdof, GroupCommunicator::Sum);
gcomm.Bcast(zones_per_vdof);
// Accumulate for all vdofs.
gcomm.Reduce<double>(data, GroupCommunicator::Sum);
gcomm.Bcast<double>(data);
ComputeMeans(type, zones_per_vdof);
}
void ParGridFunction::ProjectBdrCoefficient(
Coefficient *coeff[], VectorCoefficient *vcoeff, Array<int> &attr)
{
Array<int> values_counter;
AccumulateAndCountBdrValues(coeff, vcoeff, attr, values_counter);
Vector values(Size());
for (int i = 0; i < values.Size(); i++)
{
values(i) = values_counter[i] ? (*this)(i) : 0.0;
}
// Count the values globally.
GroupCommunicator &gcomm = pfes->GroupComm();
gcomm.Reduce<int>(values_counter, GroupCommunicator::Sum);
// Accumulate the values globally.
gcomm.Reduce<double>(values, GroupCommunicator::Sum);
// Only the values in the master are guaranteed to be correct!
for (int i = 0; i < values.Size(); i++)
{
if (values_counter[i])
{
(*this)(i) = values(i)/values_counter[i];
}
}
#ifdef MFEM_DEBUG
Array<int> ess_vdofs_marker;
pfes->GetEssentialVDofs(attr, ess_vdofs_marker);
for (int i = 0; i < values_counter.Size(); i++)
{
MFEM_ASSERT(pfes->GetLocalTDofNumber(i) == -1 ||
bool(values_counter[i]) == bool(ess_vdofs_marker[i]),
"internal error");
}
#endif
}
void ParGridFunction::ProjectBdrCoefficientTangent(VectorCoefficient &vcoeff,
Array<int> &bdr_attr)
{
Array<int> values_counter;
AccumulateAndCountBdrTangentValues(vcoeff, bdr_attr, values_counter);
Vector values(Size());
for (int i = 0; i < values.Size(); i++)
{
values(i) = values_counter[i] ? (*this)(i) : 0.0;
}
// Count the values globally.
GroupCommunicator &gcomm = pfes->GroupComm();
gcomm.Reduce<int>(values_counter, GroupCommunicator::Sum);
// Accumulate the values globally.
gcomm.Reduce<double>(values, GroupCommunicator::Sum);
// Only the values in the master are guaranteed to be correct!
for (int i = 0; i < values.Size(); i++)
{
if (values_counter[i])
{
(*this)(i) = values(i)/values_counter[i];
}
}
#ifdef MFEM_DEBUG
Array<int> ess_vdofs_marker;
pfes->GetEssentialVDofs(bdr_attr, ess_vdofs_marker);
for (int i = 0; i < values_counter.Size(); i++)
{
MFEM_ASSERT(pfes->GetLocalTDofNumber(i) == -1 ||
bool(values_counter[i]) == bool(ess_vdofs_marker[i]),
"internal error");
}
#endif
}
void ParGridFunction::Save(std::ostream &out) const
{
double *data_ = const_cast<double*>(HostRead());
for (int i = 0; i < size; i++)
{
if (pfes->GetDofSign(i) < 0) { data_[i] = -data_[i]; }
}
GridFunction::Save(out);
for (int i = 0; i < size; i++)
{
if (pfes->GetDofSign(i) < 0) { data_[i] = -data_[i]; }
}
}
#ifdef MFEM_USE_ADIOS2
void ParGridFunction::Save(adios2stream &out,
const std::string& variable_name,
const adios2stream::data_type type) const
{
double *data_ = const_cast<double*>(HostRead());
for (int i = 0; i < size; i++)
{
if (pfes->GetDofSign(i) < 0) { data_[i] = -data_[i]; }
}
GridFunction::Save(out, variable_name, type);
for (int i = 0; i < size; i++)
{
if (pfes->GetDofSign(i) < 0) { data_[i] = -data_[i]; }
}
}
#endif
void ParGridFunction::SaveAsOne(std::ostream &out)
{
int i, p;
MPI_Comm MyComm;
MPI_Status status;
int MyRank, NRanks;
MyComm = pfes -> GetComm();
MPI_Comm_size(MyComm, &NRanks);
MPI_Comm_rank(MyComm, &MyRank);
double **values = new double*[NRanks];
int *nv = new int[NRanks];
int *nvdofs = new int[NRanks];
int *nedofs = new int[NRanks];
int *nfdofs = new int[NRanks];
int *nrdofs = new int[NRanks];
values[0] = data;
nv[0] = pfes -> GetVSize();
nvdofs[0] = pfes -> GetNVDofs();
nedofs[0] = pfes -> GetNEDofs();
nfdofs[0] = pfes -> GetNFDofs();
if (MyRank == 0)
{
pfes -> Save(out);
out << '\n';
for (p = 1; p < NRanks; p++)
{
MPI_Recv(&nv[p], 1, MPI_INT, p, 455, MyComm, &status);
MPI_Recv(&nvdofs[p], 1, MPI_INT, p, 456, MyComm, &status);
MPI_Recv(&nedofs[p], 1, MPI_INT, p, 457, MyComm, &status);
MPI_Recv(&nfdofs[p], 1, MPI_INT, p, 458, MyComm, &status);
values[p] = new double[nv[p]];
MPI_Recv(values[p], nv[p], MPI_DOUBLE, p, 460, MyComm, &status);
}
int vdim = pfes -> GetVDim();
for (p = 0; p < NRanks; p++)
{
nrdofs[p] = nv[p]/vdim - nvdofs[p] - nedofs[p] - nfdofs[p];
}
if (pfes->GetOrdering() == Ordering::byNODES)
{
for (int d = 0; d < vdim; d++)
{
for (p = 0; p < NRanks; p++)
for (i = 0; i < nvdofs[p]; i++)
{
out << *values[p]++ << '\n';
}
for (p = 0; p < NRanks; p++)
for (i = 0; i < nedofs[p]; i++)
{
out << *values[p]++ << '\n';
}
for (p = 0; p < NRanks; p++)
for (i = 0; i < nfdofs[p]; i++)
{
out << *values[p]++ << '\n';
}
for (p = 0; p < NRanks; p++)
for (i = 0; i < nrdofs[p]; i++)
{
out << *values[p]++ << '\n';
}
}
}
else
{
for (p = 0; p < NRanks; p++)
for (i = 0; i < nvdofs[p]; i++)
for (int d = 0; d < vdim; d++)
{
out << *values[p]++ << '\n';
}
for (p = 0; p < NRanks; p++)
for (i = 0; i < nedofs[p]; i++)
for (int d = 0; d < vdim; d++)
{
out << *values[p]++ << '\n';
}
for (p = 0; p < NRanks; p++)
for (i = 0; i < nfdofs[p]; i++)
for (int d = 0; d < vdim; d++)
{
out << *values[p]++ << '\n';
}
for (p = 0; p < NRanks; p++)
for (i = 0; i < nrdofs[p]; i++)
for (int d = 0; d < vdim; d++)
{
out << *values[p]++ << '\n';
}
}
for (p = 1; p < NRanks; p++)
{
values[p] -= nv[p];
delete [] values[p];
}
out.flush();
}
else
{
MPI_Send(&nv[0], 1, MPI_INT, 0, 455, MyComm);
MPI_Send(&nvdofs[0], 1, MPI_INT, 0, 456, MyComm);
MPI_Send(&nedofs[0], 1, MPI_INT, 0, 457, MyComm);
MPI_Send(&nfdofs[0], 1, MPI_INT, 0, 458, MyComm);
MPI_Send(data, nv[0], MPI_DOUBLE, 0, 460, MyComm);
}
delete [] values;
delete [] nv;
delete [] nvdofs;
delete [] nedofs;
delete [] nfdofs;
delete [] nrdofs;
}
double GlobalLpNorm(const double p, double loc_norm, MPI_Comm comm)
{
double glob_norm;
if (p < infinity())
{
// negative quadrature weights may cause the error to be negative
if (loc_norm < 0.0)
{
loc_norm = -pow(-loc_norm, p);
}
else
{
loc_norm = pow(loc_norm, p);
}
MPI_Allreduce(&loc_norm, &glob_norm, 1, MPI_DOUBLE, MPI_SUM, comm);
if (glob_norm < 0.0)
{
glob_norm = -pow(-glob_norm, 1.0/p);
}
else
{
glob_norm = pow(glob_norm, 1.0/p);
}
}
else
{
MPI_Allreduce(&loc_norm, &glob_norm, 1, MPI_DOUBLE, MPI_MAX, comm);
}
return glob_norm;
}
void ParGridFunction::ComputeFlux(
BilinearFormIntegrator &blfi,
GridFunction &flux, bool wcoef, int subdomain)
{
ParFiniteElementSpace *ffes =
dynamic_cast<ParFiniteElementSpace*>(flux.FESpace());
MFEM_VERIFY(ffes, "the flux FE space must be ParFiniteElementSpace");
Array<int> count(flux.Size());
SumFluxAndCount(blfi, flux, count, wcoef, subdomain);
// Accumulate flux and counts in parallel
ffes->GroupComm().Reduce<double>(flux, GroupCommunicator::Sum);
ffes->GroupComm().Bcast<double>(flux);
ffes->GroupComm().Reduce<int>(count, GroupCommunicator::Sum);
ffes->GroupComm().Bcast<int>(count);
// complete averaging
for (int i = 0; i < count.Size(); i++)
{
if (count[i] != 0) { flux(i) /= count[i]; }
}
if (ffes->Nonconforming())
{
// On a partially conforming flux space, project on the conforming space.
// Using this code may lead to worse refinements in ex6, so we do not use
// it by default.
// Vector conf_flux;
// flux.ConformingProject(conf_flux);
// flux.ConformingProlongate(conf_flux);
}
}
double L2ZZErrorEstimator(BilinearFormIntegrator &flux_integrator,
const ParGridFunction &x,
ParFiniteElementSpace &smooth_flux_fes,
ParFiniteElementSpace &flux_fes,
Vector &errors,
int norm_p, double solver_tol, int solver_max_it)
{
// Compute fluxes in discontinuous space
GridFunction flux(&flux_fes);
flux = 0.0;
ParFiniteElementSpace *xfes = x.ParFESpace();
Array<int> xdofs, fdofs;
Vector el_x, el_f;
for (int i = 0; i < xfes->GetNE(); i++)
{
xfes->GetElementVDofs(i, xdofs);
x.GetSubVector(xdofs, el_x);
ElementTransformation *Transf = xfes->GetElementTransformation(i);
flux_integrator.ComputeElementFlux(*xfes->GetFE(i), *Transf, el_x,
*flux_fes.GetFE(i), el_f, false);
flux_fes.GetElementVDofs(i, fdofs);
flux.AddElementVector(fdofs, el_f);
}
// Assemble the linear system for L2 projection into the "smooth" space
ParBilinearForm *a = new ParBilinearForm(&smooth_flux_fes);
ParLinearForm *b = new ParLinearForm(&smooth_flux_fes);
VectorGridFunctionCoefficient f(&flux);
if (xfes->GetNE())
{
if (smooth_flux_fes.GetFE(0)->GetRangeType() == FiniteElement::SCALAR)
{
VectorMassIntegrator *vmass = new VectorMassIntegrator;
vmass->SetVDim(smooth_flux_fes.GetVDim());
a->AddDomainIntegrator(vmass);
b->AddDomainIntegrator(new VectorDomainLFIntegrator(f));
}
else
{
a->AddDomainIntegrator(new VectorFEMassIntegrator);
b->AddDomainIntegrator(new VectorFEDomainLFIntegrator(f));
}
}
b->Assemble();
a->Assemble();
a->Finalize();
// The destination of the projected discontinuous flux
ParGridFunction smooth_flux(&smooth_flux_fes);
smooth_flux = 0.0;
HypreParMatrix* A = a->ParallelAssemble();
HypreParVector* B = b->ParallelAssemble();
HypreParVector* X = smooth_flux.ParallelProject();
delete a;
delete b;
// Define and apply a parallel PCG solver for AX=B with the BoomerAMG
// preconditioner from hypre.
HypreBoomerAMG *amg = new HypreBoomerAMG(*A);
amg->SetPrintLevel(0);
HyprePCG *pcg = new HyprePCG(*A);
pcg->SetTol(solver_tol);
pcg->SetMaxIter(solver_max_it);
pcg->SetPrintLevel(0);
pcg->SetPreconditioner(*amg);
pcg->Mult(*B, *X);
// Extract the parallel grid function corresponding to the finite element
// approximation X. This is the local solution on each processor.
smooth_flux = *X;
delete A;
delete B;
delete X;
delete amg;
delete pcg;
// Proceed through the elements one by one, and find the Lp norm differences
// between the flux as computed per element and the flux projected onto the
// smooth_flux_fes space.
double total_error = 0.0;
errors.SetSize(xfes->GetNE());
for (int i = 0; i < xfes->GetNE(); i++)
{
errors(i) = ComputeElementLpDistance(norm_p, i, smooth_flux, flux);
total_error += pow(errors(i), norm_p);
}
double glob_error;
MPI_Allreduce(&total_error, &glob_error, 1, MPI_DOUBLE, MPI_SUM,
xfes->GetComm());
return pow(glob_error, 1.0/norm_p);
}
}
#endif // MFEM_USE_MPI