The InterpolationManager used in ParNCH1FaceRestriction is still a bit different.
1090 lines
33 KiB
C++
1090 lines
33 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 "../config/config.hpp"
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
|
|
#include "restriction.hpp"
|
|
#include "prestriction.hpp"
|
|
#include "pgridfunc.hpp"
|
|
#include "pfespace.hpp"
|
|
#include "fespace.hpp"
|
|
#include "fe/face_map_utils.hpp"
|
|
#include "../general/forall.hpp"
|
|
|
|
namespace mfem
|
|
{
|
|
|
|
ParNCH1FaceRestriction::ParNCH1FaceRestriction(const ParFiniteElementSpace &fes,
|
|
ElementDofOrdering f_ordering,
|
|
FaceType type)
|
|
: H1FaceRestriction(fes, f_ordering, type, false),
|
|
type(type),
|
|
interpolations(fes, f_ordering, type)
|
|
{
|
|
if (nf==0) { return; }
|
|
x_interp.UseDevice(true);
|
|
|
|
// Check that the space is H1 (not currently implemented for ND or RT spaces)
|
|
const bool is_h1 = dynamic_cast<const H1_FECollection*>(fes.FEColl());
|
|
MFEM_VERIFY(is_h1, "ParNCH1FaceRestriction is only implemented for H1 spaces.")
|
|
|
|
CheckFESpace(f_ordering);
|
|
|
|
ComputeScatterIndicesAndOffsets(f_ordering, type);
|
|
|
|
ComputeGatherIndices(f_ordering, type);
|
|
}
|
|
|
|
void ParNCH1FaceRestriction::Mult(const Vector &x, Vector &y) const
|
|
{
|
|
H1FaceRestriction::Mult(x, y);
|
|
NonconformingInterpolation(y);
|
|
}
|
|
|
|
void ParNCH1FaceRestriction::NonconformingInterpolation(Vector& y) const
|
|
{
|
|
// Assumes all elements have the same number of dofs
|
|
const int nface_dofs = face_dofs;
|
|
const int vd = vdim;
|
|
auto d_y = Reshape(y.ReadWrite(), nface_dofs, vd, nf);
|
|
auto &nc_interp_config = interpolations.GetNCFaceInterpConfig();
|
|
const int num_nc_faces = nc_interp_config.Size();
|
|
if ( num_nc_faces == 0 ) { return; }
|
|
auto interp_config_ptr = nc_interp_config.Read();
|
|
const int nc_size = interpolations.GetNumInterpolators();
|
|
auto d_interp = Reshape(interpolations.GetInterpolators().Read(),
|
|
nface_dofs, nface_dofs, nc_size);
|
|
static constexpr int max_nd = 16*16;
|
|
MFEM_VERIFY(nface_dofs<=max_nd, "Too many degrees of freedom.");
|
|
mfem::forall_2D(num_nc_faces, nface_dofs, 1, [=] MFEM_HOST_DEVICE (int nc_face)
|
|
{
|
|
MFEM_SHARED real_t dof_values[max_nd];
|
|
const NCInterpConfig conf = interp_config_ptr[nc_face];
|
|
if ( conf.is_non_conforming && conf.master_side == 0 )
|
|
{
|
|
const int interp_index = conf.index;
|
|
const int face = conf.face_index;
|
|
for (int c = 0; c < vd; ++c)
|
|
{
|
|
MFEM_FOREACH_THREAD(dof,x,nface_dofs)
|
|
{
|
|
dof_values[dof] = d_y(dof, c, face);
|
|
}
|
|
MFEM_SYNC_THREAD;
|
|
MFEM_FOREACH_THREAD(dof_out,x,nface_dofs)
|
|
{
|
|
real_t res = 0.0;
|
|
for (int dof_in = 0; dof_in<nface_dofs; dof_in++)
|
|
{
|
|
res += d_interp(dof_out, dof_in, interp_index)*dof_values[dof_in];
|
|
}
|
|
d_y(dof_out, c, face) = res;
|
|
}
|
|
MFEM_SYNC_THREAD;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void ParNCH1FaceRestriction::AddMultTranspose(const Vector &x, Vector &y,
|
|
const real_t a) const
|
|
{
|
|
MFEM_VERIFY(a == 1.0, "General coefficient case is not yet supported!");
|
|
if (nf==0) { return; }
|
|
NonconformingTransposeInterpolation(x);
|
|
H1FaceRestriction::AddMultTranspose(x_interp, y);
|
|
}
|
|
|
|
void ParNCH1FaceRestriction::AddMultTransposeInPlace(Vector &x, Vector &y) const
|
|
{
|
|
if (nf==0) { return; }
|
|
NonconformingTransposeInterpolationInPlace(x);
|
|
H1FaceRestriction::AddMultTranspose(x, y);
|
|
}
|
|
|
|
void ParNCH1FaceRestriction::NonconformingTransposeInterpolation(
|
|
const Vector& x) const
|
|
{
|
|
if (x_interp.Size()==0)
|
|
{
|
|
x_interp.SetSize(x.Size());
|
|
}
|
|
x_interp = x;
|
|
NonconformingTransposeInterpolationInPlace(x_interp);
|
|
}
|
|
|
|
void ParNCH1FaceRestriction::NonconformingTransposeInterpolationInPlace(
|
|
Vector& x) const
|
|
{
|
|
// Assumes all elements have the same number of dofs
|
|
const int nface_dofs = face_dofs;
|
|
const int vd = vdim;
|
|
if ( type==FaceType::Interior )
|
|
{
|
|
// Interpolation from slave to master face dofs
|
|
auto d_x = Reshape(x.ReadWrite(), nface_dofs, vd, nf);
|
|
auto &nc_interp_config = interpolations.GetNCFaceInterpConfig();
|
|
const int num_nc_faces = nc_interp_config.Size();
|
|
if ( num_nc_faces == 0 ) { return; }
|
|
auto interp_config_ptr = nc_interp_config.Read();
|
|
const int nc_size = interpolations.GetNumInterpolators();
|
|
auto d_interp = Reshape(interpolations.GetInterpolators().Read(),
|
|
nface_dofs, nface_dofs, nc_size);
|
|
static constexpr int max_nd = 1024;
|
|
MFEM_VERIFY(nface_dofs<=max_nd, "Too many degrees of freedom.");
|
|
mfem::forall_2D(num_nc_faces, nface_dofs, 1,
|
|
[=] MFEM_HOST_DEVICE (int nc_face)
|
|
{
|
|
MFEM_SHARED real_t dof_values[max_nd];
|
|
const NCInterpConfig conf = interp_config_ptr[nc_face];
|
|
const int master_side = conf.master_side;
|
|
if ( conf.is_non_conforming && master_side==0 )
|
|
{
|
|
const int interp_index = conf.index;
|
|
const int face = conf.face_index;
|
|
// Interpolation from fine to coarse
|
|
for (int c = 0; c < vd; ++c)
|
|
{
|
|
MFEM_FOREACH_THREAD(dof,x,nface_dofs)
|
|
{
|
|
dof_values[dof] = d_x(dof, c, face);
|
|
}
|
|
MFEM_SYNC_THREAD;
|
|
MFEM_FOREACH_THREAD(dof_out,x,nface_dofs)
|
|
{
|
|
real_t res = 0.0;
|
|
for (int dof_in = 0; dof_in<nface_dofs; dof_in++)
|
|
{
|
|
res += d_interp(dof_in, dof_out, interp_index)*dof_values[dof_in];
|
|
}
|
|
d_x(dof_out, c, face) = res;
|
|
}
|
|
MFEM_SYNC_THREAD;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void ParNCH1FaceRestriction::ComputeScatterIndicesAndOffsets(
|
|
const ElementDofOrdering f_ordering,
|
|
const FaceType face_type)
|
|
{
|
|
Mesh &mesh = *fes.GetMesh();
|
|
|
|
// Initialization of the offsets
|
|
for (int i = 0; i <= ndofs; ++i)
|
|
{
|
|
gather_offsets[i] = 0;
|
|
}
|
|
|
|
// Computation of scatter indices and offsets
|
|
int f_ind = 0;
|
|
for (int f = 0; f < mesh.GetNumFacesWithGhost(); ++f)
|
|
{
|
|
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
|
|
if ( face.IsNonconformingCoarse() )
|
|
{
|
|
// We skip nonconforming coarse faces as they are treated
|
|
// by the corresponding nonconforming fine faces.
|
|
continue;
|
|
}
|
|
else if (face_type==FaceType::Interior && face.IsInterior())
|
|
{
|
|
if ( face.IsConforming() )
|
|
{
|
|
interpolations.RegisterFaceConformingInterpolation(face,f_ind);
|
|
SetFaceDofsScatterIndices(face, f_ind, f_ordering);
|
|
f_ind++;
|
|
}
|
|
else // Non-conforming face
|
|
{
|
|
SetFaceDofsScatterIndices(face, f_ind, f_ordering);
|
|
if ( face.element[0].conformity==Mesh::ElementConformity::Superset )
|
|
{
|
|
// In this case the local face is the master (coarse) face, thus
|
|
// we need to interpolate the values on the slave (fine) face.
|
|
interpolations.RegisterFaceCoarseToFineInterpolation(face,f_ind);
|
|
}
|
|
else
|
|
{
|
|
// Treated as a conforming face since we only extract values from
|
|
// the local slave (fine) face.
|
|
interpolations.RegisterFaceConformingInterpolation(face,f_ind);
|
|
}
|
|
f_ind++;
|
|
}
|
|
}
|
|
else if (face_type==FaceType::Boundary && face.IsBoundary())
|
|
{
|
|
interpolations.RegisterFaceConformingInterpolation(face,f_ind);
|
|
SetFaceDofsScatterIndices(face, f_ind, f_ordering);
|
|
f_ind++;
|
|
}
|
|
}
|
|
MFEM_VERIFY(f_ind==nf, "Unexpected number of faces.");
|
|
|
|
// Summation of the offsets
|
|
for (int i = 1; i <= ndofs; ++i)
|
|
{
|
|
gather_offsets[i] += gather_offsets[i - 1];
|
|
}
|
|
|
|
// Transform the interpolation matrix map into a contiguous memory structure.
|
|
interpolations.LinearizeInterpolatorMapIntoVector();
|
|
interpolations.InitializeNCInterpConfig();
|
|
}
|
|
|
|
void ParNCH1FaceRestriction::ComputeGatherIndices(
|
|
const ElementDofOrdering f_ordering,
|
|
const FaceType face_type)
|
|
{
|
|
Mesh &mesh = *fes.GetMesh();
|
|
|
|
// Computation of gather_indices
|
|
int f_ind = 0;
|
|
for (int f = 0; f < mesh.GetNumFacesWithGhost(); ++f)
|
|
{
|
|
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
|
|
if ( face.IsNonconformingCoarse() )
|
|
{
|
|
// We skip nonconforming coarse faces as they are treated
|
|
// by the corresponding nonconforming fine faces.
|
|
continue;
|
|
}
|
|
else if (face.IsOfFaceType(face_type))
|
|
{
|
|
SetFaceDofsGatherIndices(face, f_ind, f_ordering);
|
|
f_ind++;
|
|
}
|
|
}
|
|
MFEM_VERIFY(f_ind==nf, "Unexpected number of faces.");
|
|
|
|
// Reset offsets to their correct value
|
|
for (int i = ndofs; i > 0; --i)
|
|
{
|
|
gather_offsets[i] = gather_offsets[i - 1];
|
|
}
|
|
gather_offsets[0] = 0;
|
|
}
|
|
|
|
ParL2FaceRestriction::ParL2FaceRestriction(const ParFiniteElementSpace &pfes_,
|
|
ElementDofOrdering f_ordering,
|
|
FaceType type,
|
|
L2FaceValues m,
|
|
bool build)
|
|
: L2FaceRestriction(pfes_, f_ordering, type, m, false),
|
|
pfes(pfes_)
|
|
{
|
|
if (!build) { return; }
|
|
if (nf==0) { return; }
|
|
|
|
CheckFESpace();
|
|
|
|
ComputeScatterIndicesAndOffsets();
|
|
|
|
ComputeGatherIndices();
|
|
}
|
|
|
|
ParL2FaceRestriction::ParL2FaceRestriction(const ParFiniteElementSpace &fes,
|
|
ElementDofOrdering f_ordering,
|
|
FaceType type,
|
|
L2FaceValues m)
|
|
: ParL2FaceRestriction(fes, f_ordering, type, m, true)
|
|
{ }
|
|
|
|
void ParL2FaceRestriction::DoubleValuedConformingMult(
|
|
const Vector& x, Vector& y) const
|
|
{
|
|
MFEM_ASSERT(
|
|
m == L2FaceValues::DoubleValued,
|
|
"This method should be called when m == L2FaceValues::DoubleValued.");
|
|
|
|
Vector face_nbr_data = GetLVectorFaceNbrData(fes, x, type);
|
|
|
|
// Early return only after calling ParGridFunction::ExchangeFaceNbrData,
|
|
// otherwise MPI communication can hang.
|
|
if (nf == 0) { return; }
|
|
|
|
// Assumes all elements have the same number of dofs
|
|
const int nface_dofs = face_dofs;
|
|
const int vd = vdim;
|
|
const bool t = byvdim;
|
|
const int threshold = ndofs;
|
|
const int nsdofs = pfes.GetFaceNbrVSize();
|
|
auto d_indices1 = scatter_indices1.Read();
|
|
auto d_indices2 = scatter_indices2.Read();
|
|
auto d_x = Reshape(x.Read(), t?vd:ndofs, t?ndofs:vd);
|
|
auto d_x_shared = Reshape(face_nbr_data.Read(),
|
|
t?vd:nsdofs, t?nsdofs:vd);
|
|
auto d_y = Reshape(y.Write(), nface_dofs, vd, 2, nf);
|
|
mfem::forall(nfdofs, [=] MFEM_HOST_DEVICE (int i)
|
|
{
|
|
const int dof = i % nface_dofs;
|
|
const int face = i / nface_dofs;
|
|
const int idx1 = d_indices1[i];
|
|
for (int c = 0; c < vd; ++c)
|
|
{
|
|
d_y(dof, c, 0, face) = d_x(t?c:idx1, t?idx1:c);
|
|
}
|
|
const int idx2 = d_indices2[i];
|
|
for (int c = 0; c < vd; ++c)
|
|
{
|
|
if (idx2>-1 && idx2<threshold) // interior face
|
|
{
|
|
d_y(dof, c, 1, face) = d_x(t?c:idx2, t?idx2:c);
|
|
}
|
|
else if (idx2>=threshold) // shared boundary
|
|
{
|
|
d_y(dof, c, 1, face) = d_x_shared(t?c:(idx2-threshold),
|
|
t?(idx2-threshold):c);
|
|
}
|
|
else // true boundary
|
|
{
|
|
d_y(dof, c, 1, face) = 0.0;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void ParL2FaceRestriction::Mult(const Vector& x, Vector& y) const
|
|
{
|
|
if (m==L2FaceValues::DoubleValued)
|
|
{
|
|
DoubleValuedConformingMult(x, y);
|
|
}
|
|
else
|
|
{
|
|
SingleValuedConformingMult(x, y);
|
|
}
|
|
}
|
|
|
|
static MFEM_HOST_DEVICE int AddNnz(const int iE, int *I, const int dofs)
|
|
{
|
|
int val = AtomicAdd(I[iE],dofs);
|
|
return val;
|
|
}
|
|
|
|
void ParL2FaceRestriction::FillI(SparseMatrix &mat,
|
|
const bool keep_nbr_block) const
|
|
{
|
|
if (keep_nbr_block)
|
|
{
|
|
return L2FaceRestriction::FillI(mat, keep_nbr_block);
|
|
}
|
|
const int nface_dofs = face_dofs;
|
|
const int Ndofs = ndofs;
|
|
auto d_indices1 = scatter_indices1.Read();
|
|
auto d_indices2 = scatter_indices2.Read();
|
|
auto I = mat.ReadWriteI();
|
|
mfem::forall(nf*nface_dofs, [=] MFEM_HOST_DEVICE (int fdof)
|
|
{
|
|
const int f = fdof/nface_dofs;
|
|
const int iF = fdof%nface_dofs;
|
|
const int iE1 = d_indices1[f*nface_dofs+iF];
|
|
if (iE1 < Ndofs)
|
|
{
|
|
AddNnz(iE1,I,nface_dofs);
|
|
}
|
|
const int iE2 = d_indices2[f*nface_dofs+iF];
|
|
if (iE2 < Ndofs)
|
|
{
|
|
AddNnz(iE2,I,nface_dofs);
|
|
}
|
|
});
|
|
}
|
|
|
|
void ParL2FaceRestriction::FillI(SparseMatrix &mat,
|
|
SparseMatrix &face_mat) const
|
|
{
|
|
const int nface_dofs = face_dofs;
|
|
const int Ndofs = ndofs;
|
|
auto d_indices1 = scatter_indices1.Read();
|
|
auto d_indices2 = scatter_indices2.Read();
|
|
auto I = mat.ReadWriteI();
|
|
auto I_face = face_mat.ReadWriteI();
|
|
mfem::forall(ne*elem_dofs*vdim+1, [=] MFEM_HOST_DEVICE (int i)
|
|
{
|
|
I_face[i] = 0;
|
|
});
|
|
mfem::forall(nf*nface_dofs, [=] MFEM_HOST_DEVICE (int fdof)
|
|
{
|
|
const int f = fdof/nface_dofs;
|
|
const int iF = fdof%nface_dofs;
|
|
const int iE1 = d_indices1[f*nface_dofs+iF];
|
|
if (iE1 < Ndofs)
|
|
{
|
|
for (int jF = 0; jF < nface_dofs; jF++)
|
|
{
|
|
const int jE2 = d_indices2[f*nface_dofs+jF];
|
|
if (jE2 < Ndofs)
|
|
{
|
|
AddNnz(iE1,I,1);
|
|
}
|
|
else
|
|
{
|
|
AddNnz(iE1,I_face,1);
|
|
}
|
|
}
|
|
}
|
|
const int iE2 = d_indices2[f*nface_dofs+iF];
|
|
if (iE2 < Ndofs)
|
|
{
|
|
for (int jF = 0; jF < nface_dofs; jF++)
|
|
{
|
|
const int jE1 = d_indices1[f*nface_dofs+jF];
|
|
if (jE1 < Ndofs)
|
|
{
|
|
AddNnz(iE2,I,1);
|
|
}
|
|
else
|
|
{
|
|
AddNnz(iE2,I_face,1);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void ParL2FaceRestriction::FillJAndData(const Vector &ea_data,
|
|
SparseMatrix &mat,
|
|
const bool keep_nbr_block) const
|
|
{
|
|
if (keep_nbr_block)
|
|
{
|
|
return L2FaceRestriction::FillJAndData(ea_data, mat, keep_nbr_block);
|
|
}
|
|
const int nface_dofs = face_dofs;
|
|
const int Ndofs = ndofs;
|
|
auto d_indices1 = scatter_indices1.Read();
|
|
auto d_indices2 = scatter_indices2.Read();
|
|
auto mat_fea = Reshape(ea_data.Read(), nface_dofs, nface_dofs, 2, nf);
|
|
auto I = mat.ReadWriteI();
|
|
auto J = mat.WriteJ();
|
|
auto Data = mat.WriteData();
|
|
mfem::forall(nf*nface_dofs, [=] MFEM_HOST_DEVICE (int fdof)
|
|
{
|
|
const int f = fdof/nface_dofs;
|
|
const int iF = fdof%nface_dofs;
|
|
const int iE1 = d_indices1[f*nface_dofs+iF];
|
|
if (iE1 < Ndofs)
|
|
{
|
|
const int offset = AddNnz(iE1,I,nface_dofs);
|
|
for (int jF = 0; jF < nface_dofs; jF++)
|
|
{
|
|
const int jE2 = d_indices2[f*nface_dofs+jF];
|
|
J[offset+jF] = jE2;
|
|
Data[offset+jF] = mat_fea(jF,iF,1,f);
|
|
}
|
|
}
|
|
const int iE2 = d_indices2[f*nface_dofs+iF];
|
|
if (iE2 < Ndofs)
|
|
{
|
|
const int offset = AddNnz(iE2,I,nface_dofs);
|
|
for (int jF = 0; jF < nface_dofs; jF++)
|
|
{
|
|
const int jE1 = d_indices1[f*nface_dofs+jF];
|
|
J[offset+jF] = jE1;
|
|
Data[offset+jF] = mat_fea(jF,iF,0,f);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void ParL2FaceRestriction::FillJAndData(const Vector &ea_data,
|
|
SparseMatrix &mat,
|
|
SparseMatrix &face_mat) const
|
|
{
|
|
const int nface_dofs = face_dofs;
|
|
const int Ndofs = ndofs;
|
|
auto d_indices1 = scatter_indices1.Read();
|
|
auto d_indices2 = scatter_indices2.Read();
|
|
auto mat_fea = Reshape(ea_data.Read(), nface_dofs, nface_dofs, 2, nf);
|
|
auto I = mat.ReadWriteI();
|
|
auto I_face = face_mat.ReadWriteI();
|
|
auto J = mat.WriteJ();
|
|
auto J_face = face_mat.WriteJ();
|
|
auto Data = mat.WriteData();
|
|
auto Data_face = face_mat.WriteData();
|
|
mfem::forall(nf*nface_dofs, [=] MFEM_HOST_DEVICE (int fdof)
|
|
{
|
|
const int f = fdof/nface_dofs;
|
|
const int iF = fdof%nface_dofs;
|
|
const int iE1 = d_indices1[f*nface_dofs+iF];
|
|
if (iE1 < Ndofs)
|
|
{
|
|
for (int jF = 0; jF < nface_dofs; jF++)
|
|
{
|
|
const int jE2 = d_indices2[f*nface_dofs+jF];
|
|
if (jE2 < Ndofs)
|
|
{
|
|
const int offset = AddNnz(iE1,I,1);
|
|
J[offset] = jE2;
|
|
Data[offset] = mat_fea(jF,iF,1,f);
|
|
}
|
|
else
|
|
{
|
|
const int offset = AddNnz(iE1,I_face,1);
|
|
J_face[offset] = jE2-Ndofs;
|
|
Data_face[offset] = mat_fea(jF,iF,1,f);
|
|
}
|
|
}
|
|
}
|
|
const int iE2 = d_indices2[f*nface_dofs+iF];
|
|
if (iE2 < Ndofs)
|
|
{
|
|
for (int jF = 0; jF < nface_dofs; jF++)
|
|
{
|
|
const int jE1 = d_indices1[f*nface_dofs+jF];
|
|
if (jE1 < Ndofs)
|
|
{
|
|
const int offset = AddNnz(iE2,I,1);
|
|
J[offset] = jE1;
|
|
Data[offset] = mat_fea(jF,iF,0,f);
|
|
}
|
|
else
|
|
{
|
|
const int offset = AddNnz(iE2,I_face,1);
|
|
J_face[offset] = jE1-Ndofs;
|
|
Data_face[offset] = mat_fea(jF,iF,0,f);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void ParL2FaceRestriction::ComputeScatterIndicesAndOffsets()
|
|
{
|
|
Mesh &mesh = *fes.GetMesh();
|
|
|
|
// Initialization of the offsets
|
|
for (int i = 0; i <= ndofs; ++i)
|
|
{
|
|
gather_offsets[i] = 0;
|
|
}
|
|
|
|
// Computation of scatter indices and offsets
|
|
int f_ind=0;
|
|
for (int f = 0; f < pfes.GetNF(); ++f)
|
|
{
|
|
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
|
|
if (type==FaceType::Interior && face.IsInterior())
|
|
{
|
|
SetFaceDofsScatterIndices1(face,f_ind);
|
|
if (m==L2FaceValues::DoubleValued)
|
|
{
|
|
if (face.IsShared())
|
|
{
|
|
PermuteAndSetSharedFaceDofsScatterIndices2(face,f_ind);
|
|
}
|
|
else
|
|
{
|
|
PermuteAndSetFaceDofsScatterIndices2(face,f_ind);
|
|
}
|
|
}
|
|
f_ind++;
|
|
}
|
|
else if (type==FaceType::Boundary && face.IsBoundary())
|
|
{
|
|
SetFaceDofsScatterIndices1(face,f_ind);
|
|
if (m==L2FaceValues::DoubleValued)
|
|
{
|
|
SetBoundaryDofsScatterIndices2(face,f_ind);
|
|
}
|
|
f_ind++;
|
|
}
|
|
}
|
|
MFEM_VERIFY(f_ind==nf, "Unexpected number of faces.");
|
|
|
|
// Summation of the offsets
|
|
for (int i = 1; i <= ndofs; ++i)
|
|
{
|
|
gather_offsets[i] += gather_offsets[i - 1];
|
|
}
|
|
}
|
|
|
|
|
|
void ParL2FaceRestriction::ComputeGatherIndices()
|
|
{
|
|
Mesh &mesh = *fes.GetMesh();
|
|
|
|
// Computation of gather_indices
|
|
int f_ind = 0;
|
|
for (int f = 0; f < fes.GetNF(); ++f)
|
|
{
|
|
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
|
|
if (face.IsOfFaceType(type))
|
|
{
|
|
SetFaceDofsGatherIndices1(face,f_ind);
|
|
if (m==L2FaceValues::DoubleValued &&
|
|
type==FaceType::Interior &&
|
|
face.IsLocal())
|
|
{
|
|
PermuteAndSetFaceDofsGatherIndices2(face,f_ind);
|
|
}
|
|
f_ind++;
|
|
}
|
|
}
|
|
MFEM_VERIFY(f_ind==nf, "Unexpected number of faces.");
|
|
|
|
// Reset offsets to their correct value
|
|
for (int i = ndofs; i > 0; --i)
|
|
{
|
|
gather_offsets[i] = gather_offsets[i - 1];
|
|
}
|
|
gather_offsets[0] = 0;
|
|
}
|
|
|
|
ParNCL2FaceRestriction::ParNCL2FaceRestriction(const ParFiniteElementSpace &fes,
|
|
ElementDofOrdering f_ordering,
|
|
FaceType type,
|
|
L2FaceValues m)
|
|
: L2FaceRestriction(fes, f_ordering, type, m, false),
|
|
NCL2FaceRestriction(fes, f_ordering, type, m, false),
|
|
ParL2FaceRestriction(fes, f_ordering, type, m, false)
|
|
{
|
|
if (nf==0) { return; }
|
|
x_interp.UseDevice(true);
|
|
|
|
CheckFESpace();
|
|
|
|
ComputeScatterIndicesAndOffsets();
|
|
|
|
ComputeGatherIndices();
|
|
}
|
|
|
|
void ParNCL2FaceRestriction::SingleValuedNonconformingMult(
|
|
const Vector& x, Vector& y) const
|
|
{
|
|
if (nf == 0) { return; }
|
|
MFEM_ASSERT(
|
|
m == L2FaceValues::SingleValued,
|
|
"This method should be called when m == L2FaceValues::SingleValued.");
|
|
// Assumes all elements have the same number of dofs
|
|
const int nface_dofs = face_dofs;
|
|
const int vd = vdim;
|
|
const bool t = byvdim;
|
|
const int threshold = ndofs;
|
|
auto d_indices1 = scatter_indices1.Read();
|
|
auto d_x = Reshape(x.Read(), t?vd:ndofs, t?ndofs:vd);
|
|
auto d_y = Reshape(y.Write(), nface_dofs, vd, nf);
|
|
auto interp_config_ptr = interpolations.GetFaceInterpConfig().Read();
|
|
auto interpolators = interpolations.GetInterpolators().Read();
|
|
const int nc_size = interpolations.GetNumInterpolators();
|
|
auto d_interp = Reshape(interpolators, nface_dofs, nface_dofs, nc_size);
|
|
static constexpr int max_nd = 16*16;
|
|
MFEM_VERIFY(nface_dofs<=max_nd, "Too many degrees of freedom.");
|
|
mfem::forall_2D(nf, nface_dofs, 1, [=] MFEM_HOST_DEVICE (int face)
|
|
{
|
|
MFEM_SHARED real_t dof_values[max_nd];
|
|
const InterpConfig conf = interp_config_ptr[face];
|
|
const int master_side = conf.master_side;
|
|
const int interp_index = conf.index;
|
|
const int side = 0;
|
|
if ( !conf.is_non_conforming || side!=master_side )
|
|
{
|
|
MFEM_FOREACH_THREAD(dof,x,nface_dofs)
|
|
{
|
|
const int i = face*nface_dofs + dof;
|
|
const int idx = d_indices1[i];
|
|
if (idx>-1 && idx<threshold) // interior face
|
|
{
|
|
for (int c = 0; c < vd; ++c)
|
|
{
|
|
d_y(dof, c, face) = d_x(t?c:idx, t?idx:c);
|
|
}
|
|
}
|
|
else // true boundary
|
|
{
|
|
for (int c = 0; c < vd; ++c)
|
|
{
|
|
d_y(dof, c, face) = 0.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // Interpolation from coarse to fine
|
|
{
|
|
for (int c = 0; c < vd; ++c)
|
|
{
|
|
MFEM_FOREACH_THREAD(dof,x,nface_dofs)
|
|
{
|
|
const int i = face*nface_dofs + dof;
|
|
const int idx = d_indices1[i];
|
|
if (idx>-1 && idx<threshold) // interior face
|
|
{
|
|
dof_values[dof] = d_x(t?c:idx, t?idx:c);
|
|
}
|
|
else // true boundary
|
|
{
|
|
dof_values[dof] = 0.0;
|
|
}
|
|
}
|
|
MFEM_SYNC_THREAD;
|
|
MFEM_FOREACH_THREAD(dof_out,x,nface_dofs)
|
|
{
|
|
real_t res = 0.0;
|
|
for (int dof_in = 0; dof_in<nface_dofs; dof_in++)
|
|
{
|
|
res += d_interp(dof_out, dof_in, interp_index)*dof_values[dof_in];
|
|
}
|
|
d_y(dof_out, c, face) = res;
|
|
}
|
|
MFEM_SYNC_THREAD;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void ParNCL2FaceRestriction::DoubleValuedNonconformingMult(
|
|
const Vector& x, Vector& y) const
|
|
{
|
|
ParL2FaceRestriction::DoubleValuedConformingMult(x, y);
|
|
NCL2FaceRestriction::DoubleValuedNonconformingInterpolation(y);
|
|
}
|
|
|
|
void ParNCL2FaceRestriction::Mult(const Vector& x, Vector& y) const
|
|
{
|
|
if ( type==FaceType::Interior && m==L2FaceValues::DoubleValued )
|
|
{
|
|
DoubleValuedNonconformingMult(x, y);
|
|
}
|
|
else if ( type==FaceType::Boundary && m==L2FaceValues::DoubleValued )
|
|
{
|
|
DoubleValuedConformingMult(x, y);
|
|
}
|
|
else if ( type==FaceType::Interior && m==L2FaceValues::SingleValued )
|
|
{
|
|
SingleValuedNonconformingMult(x, y);
|
|
}
|
|
else if ( type==FaceType::Boundary && m==L2FaceValues::SingleValued )
|
|
{
|
|
SingleValuedConformingMult(x, y);
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Unknown type and multiplicity combination.");
|
|
}
|
|
}
|
|
|
|
void ParNCL2FaceRestriction::AddMultTranspose(const Vector &x, Vector &y,
|
|
const real_t a) const
|
|
{
|
|
MFEM_VERIFY(a == 1.0, "General coefficient case is not yet supported!");
|
|
if (nf==0) { return; }
|
|
if (type==FaceType::Interior)
|
|
{
|
|
if ( m==L2FaceValues::DoubleValued )
|
|
{
|
|
DoubleValuedNonconformingTransposeInterpolation(x);
|
|
DoubleValuedConformingAddMultTranspose(x_interp, y);
|
|
}
|
|
else // Single Valued
|
|
{
|
|
SingleValuedNonconformingTransposeInterpolation(x);
|
|
SingleValuedConformingAddMultTranspose(x_interp, y);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m==L2FaceValues::DoubleValued )
|
|
{
|
|
DoubleValuedConformingAddMultTranspose(x, y);
|
|
}
|
|
else // Single valued
|
|
{
|
|
SingleValuedConformingAddMultTranspose(x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParNCL2FaceRestriction::AddMultTransposeInPlace(Vector& x, Vector& y) const
|
|
{
|
|
if (nf==0) { return; }
|
|
if (type==FaceType::Interior)
|
|
{
|
|
if ( m==L2FaceValues::DoubleValued )
|
|
{
|
|
DoubleValuedNonconformingTransposeInterpolationInPlace(x);
|
|
DoubleValuedConformingAddMultTranspose(x, y);
|
|
}
|
|
else if ( m==L2FaceValues::SingleValued )
|
|
{
|
|
SingleValuedNonconformingTransposeInterpolationInPlace(x);
|
|
SingleValuedConformingAddMultTranspose(x, y);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m==L2FaceValues::DoubleValued )
|
|
{
|
|
DoubleValuedConformingAddMultTranspose(x, y);
|
|
}
|
|
else if ( m==L2FaceValues::SingleValued )
|
|
{
|
|
SingleValuedConformingAddMultTranspose(x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParNCL2FaceRestriction::FillI(SparseMatrix &mat,
|
|
const bool keep_nbr_block) const
|
|
{
|
|
if (keep_nbr_block)
|
|
{
|
|
return NCL2FaceRestriction::FillI(mat, keep_nbr_block);
|
|
}
|
|
const int nface_dofs = face_dofs;
|
|
const int Ndofs = ndofs;
|
|
auto d_indices1 = scatter_indices1.Read();
|
|
auto d_indices2 = scatter_indices2.Read();
|
|
auto I = mat.ReadWriteI();
|
|
mfem::forall(nf*nface_dofs, [=] MFEM_HOST_DEVICE (int fdof)
|
|
{
|
|
const int f = fdof/nface_dofs;
|
|
const int iF = fdof%nface_dofs;
|
|
const int iE1 = d_indices1[f*nface_dofs+iF];
|
|
if (iE1 < Ndofs)
|
|
{
|
|
AddNnz(iE1,I,nface_dofs);
|
|
}
|
|
const int iE2 = d_indices2[f*nface_dofs+iF];
|
|
if (iE2 < Ndofs)
|
|
{
|
|
AddNnz(iE2,I,nface_dofs);
|
|
}
|
|
});
|
|
}
|
|
|
|
void ParNCL2FaceRestriction::FillI(SparseMatrix &mat,
|
|
SparseMatrix &face_mat) const
|
|
{
|
|
MFEM_ABORT("Not yet implemented.");
|
|
}
|
|
|
|
void ParNCL2FaceRestriction::FillJAndData(const Vector &fea_data,
|
|
SparseMatrix &mat,
|
|
const bool keep_nbr_block) const
|
|
{
|
|
if (keep_nbr_block)
|
|
{
|
|
return NCL2FaceRestriction::FillJAndData(fea_data, mat, keep_nbr_block);
|
|
}
|
|
const int nface_dofs = face_dofs;
|
|
const int Ndofs = ndofs;
|
|
auto d_indices1 = scatter_indices1.Read();
|
|
auto d_indices2 = scatter_indices2.Read();
|
|
auto I = mat.ReadWriteI();
|
|
auto mat_fea = Reshape(fea_data.Read(), nface_dofs, nface_dofs, 2, nf);
|
|
auto J = mat.WriteJ();
|
|
auto Data = mat.WriteData();
|
|
auto interp_config_ptr = interpolations.GetFaceInterpConfig().Read();
|
|
auto interpolators = interpolations.GetInterpolators().Read();
|
|
const int nc_size = interpolations.GetNumInterpolators();
|
|
auto d_interp = Reshape(interpolators, nface_dofs, nface_dofs, nc_size);
|
|
mfem::forall(nf*nface_dofs, [=] MFEM_HOST_DEVICE (int fdof)
|
|
{
|
|
const int f = fdof/nface_dofs;
|
|
const InterpConfig conf = interp_config_ptr[f];
|
|
const int master_side = conf.master_side;
|
|
const int interp_index = conf.index;
|
|
const int iF = fdof%nface_dofs;
|
|
const int iE1 = d_indices1[f*nface_dofs+iF];
|
|
if (iE1 < Ndofs)
|
|
{
|
|
const int offset1 = AddNnz(iE1,I,nface_dofs);
|
|
for (int jF = 0; jF < nface_dofs; jF++)
|
|
{
|
|
const int jE2 = d_indices2[f*nface_dofs+jF];
|
|
J[offset1+jF] = jE2;
|
|
real_t val2 = 0.0;
|
|
if ( conf.is_non_conforming && master_side==0 )
|
|
{
|
|
for (int kF = 0; kF < nface_dofs; kF++)
|
|
{
|
|
val2 += d_interp(kF, iF, interp_index) * mat_fea(jF,kF,1,f);
|
|
}
|
|
}
|
|
else if ( conf.is_non_conforming && master_side==1 )
|
|
{
|
|
for (int kF = 0; kF < nface_dofs; kF++)
|
|
{
|
|
val2 += mat_fea(kF,iF,1,f) * d_interp(kF, jF, interp_index);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
val2 = mat_fea(jF,iF,1,f);
|
|
}
|
|
Data[offset1+jF] = val2;
|
|
}
|
|
}
|
|
const int iE2 = d_indices2[f*nface_dofs+iF];
|
|
if (iE2 < Ndofs)
|
|
{
|
|
const int offset2 = AddNnz(iE2,I,nface_dofs);
|
|
for (int jF = 0; jF < nface_dofs; jF++)
|
|
{
|
|
const int jE1 = d_indices1[f*nface_dofs+jF];
|
|
J[offset2+jF] = jE1;
|
|
real_t val1 = 0.0;
|
|
if ( conf.is_non_conforming && master_side==0 )
|
|
{
|
|
for (int kF = 0; kF < nface_dofs; kF++)
|
|
{
|
|
val1 += mat_fea(kF,iF,0,f) * d_interp(kF, jF, interp_index);
|
|
}
|
|
}
|
|
else if ( conf.is_non_conforming && master_side==1 )
|
|
{
|
|
for (int kF = 0; kF < nface_dofs; kF++)
|
|
{
|
|
val1 += d_interp(kF, iF, interp_index) * mat_fea(jF,kF,0,f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
val1 = mat_fea(jF,iF,0,f);
|
|
}
|
|
Data[offset2+jF] = val1;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void ParNCL2FaceRestriction::FillJAndData(const Vector &ea_data,
|
|
SparseMatrix &mat,
|
|
SparseMatrix &face_mat) const
|
|
{
|
|
MFEM_ABORT("Not yet implemented.");
|
|
}
|
|
|
|
void ParNCL2FaceRestriction::ComputeScatterIndicesAndOffsets()
|
|
{
|
|
Mesh &mesh = *fes.GetMesh();
|
|
|
|
// Initialization of the offsets
|
|
for (int i = 0; i <= ndofs; ++i)
|
|
{
|
|
gather_offsets[i] = 0;
|
|
}
|
|
|
|
// Computation of scatter and offsets indices
|
|
int f_ind=0;
|
|
for (int f = 0; f < mesh.GetNumFacesWithGhost(); ++f)
|
|
{
|
|
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
|
|
if ( face.IsNonconformingCoarse() )
|
|
{
|
|
// We skip nonconforming coarse faces as they are treated
|
|
// by the corresponding nonconforming fine faces.
|
|
continue;
|
|
}
|
|
else if ( type==FaceType::Interior && face.IsInterior() )
|
|
{
|
|
if ( face.IsConforming() )
|
|
{
|
|
SetFaceDofsScatterIndices1(face,f_ind);
|
|
if ( m==L2FaceValues::DoubleValued )
|
|
{
|
|
if ( face.IsShared() )
|
|
{
|
|
PermuteAndSetSharedFaceDofsScatterIndices2(face,f_ind);
|
|
}
|
|
else
|
|
{
|
|
PermuteAndSetFaceDofsScatterIndices2(face,f_ind);
|
|
}
|
|
}
|
|
}
|
|
else // Non-conforming face
|
|
{
|
|
SetFaceDofsScatterIndices1(face,f_ind);
|
|
if ( m==L2FaceValues::DoubleValued )
|
|
{
|
|
if ( face.IsShared() )
|
|
{
|
|
PermuteAndSetSharedFaceDofsScatterIndices2(face,f_ind);
|
|
}
|
|
else // local nonconforming slave
|
|
{
|
|
PermuteAndSetFaceDofsScatterIndices2(face,f_ind);
|
|
}
|
|
}
|
|
}
|
|
f_ind++;
|
|
}
|
|
else if (type==FaceType::Boundary && face.IsBoundary())
|
|
{
|
|
SetFaceDofsScatterIndices1(face,f_ind);
|
|
if ( m==L2FaceValues::DoubleValued )
|
|
{
|
|
SetBoundaryDofsScatterIndices2(face,f_ind);
|
|
}
|
|
f_ind++;
|
|
}
|
|
}
|
|
MFEM_VERIFY(f_ind==nf, "Unexpected number of " <<
|
|
(type==FaceType::Interior? "interior" : "boundary") <<
|
|
" faces: " << f_ind << " vs " << nf );
|
|
|
|
// Summation of the offsets
|
|
for (int i = 1; i <= ndofs; ++i)
|
|
{
|
|
gather_offsets[i] += gather_offsets[i - 1];
|
|
}
|
|
}
|
|
|
|
void ParNCL2FaceRestriction::ComputeGatherIndices()
|
|
{
|
|
Mesh &mesh = *fes.GetMesh();
|
|
|
|
// Computation of gather_indices
|
|
int f_ind = 0;
|
|
for (int f = 0; f < mesh.GetNumFacesWithGhost(); ++f)
|
|
{
|
|
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
|
|
if ( face.IsNonconformingCoarse() )
|
|
{
|
|
// We skip nonconforming coarse faces as they are treated
|
|
// by the corresponding nonconforming fine faces.
|
|
continue;
|
|
}
|
|
else if ( face.IsOfFaceType(type) )
|
|
{
|
|
SetFaceDofsGatherIndices1(face,f_ind);
|
|
if (m==L2FaceValues::DoubleValued &&
|
|
type==FaceType::Interior &&
|
|
face.IsLocal())
|
|
{
|
|
PermuteAndSetFaceDofsGatherIndices2(face,f_ind);
|
|
}
|
|
f_ind++;
|
|
}
|
|
}
|
|
MFEM_VERIFY(f_ind==nf, "Unexpected number of " <<
|
|
(type==FaceType::Interior? "interior" : "boundary") <<
|
|
" faces: " << f_ind << " vs " << nf );
|
|
|
|
// Switch back offsets to their correct value
|
|
for (int i = ndofs; i > 0; --i)
|
|
{
|
|
gather_offsets[i] = gather_offsets[i - 1];
|
|
}
|
|
gather_offsets[0] = 0;
|
|
}
|
|
|
|
} // namespace mfem
|
|
|
|
#endif
|