Compare commits

..
72 changed files with 2869 additions and 7429 deletions
+6 -2
View File
@@ -29,12 +29,16 @@ Runs a number of static repository-level sanity checks.
- `branch-history` guards against accidental commits of large files using the `--history` option of the `config/githooks/pre-push` script.
## `mfem-analysis.yml` (`build-analysis`)
Checks if the code builds and satisfies minimal requirements.
- `gitignore` builds hypre, METIS, and MFEM using `mfem/github-actions/build-hypre`, `mfem/github-actions/build-metis`, and `mfem/github-actions/build-mfem` and checks for correct `.gitignore` settings by running the `tests/scripts/gitignore` script.
## `builds-and-tests.yml`
Runs a matrix of builds and tests runs with different compilers, OS, mfem/hypre settings, etc. Also processes and upload Codecov reports.
One matrix job runs `tests/scripts/gitignore` after `make test-noclean` to check generated artifacts against `.gitignore`.
Uses the following GitHub Actions from <https://github.com/mfem/github-actions>:
- `mfem/github-actions/build-hypre`
+1 -14
View File
@@ -111,7 +111,6 @@ jobs:
build-system: make
hypre-target: int64
precision: fp64
gitignore-check: YES
- os: ubuntu-latest
target: opt
codecov: NO
@@ -318,13 +317,7 @@ jobs:
- name: tests
if: matrix.build-system == 'make' && (matrix.target == 'opt' || matrix.os == 'ubuntu-latest')
run: |
cd ${{ env.MFEM_TOP_DIR }}
if [[ "${{ matrix.gitignore-check }}" == "YES" ]]; then
make test-noclean
else
make test
fi
shell: bash
cd ${{ env.MFEM_TOP_DIR }} && make test
- name: cmake checks
if: matrix.build-system == 'cmake' && matrix.target == 'dbg'
@@ -376,9 +369,3 @@ jobs:
directories: "fem general linalg mesh"
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: gitignore
if: matrix.gitignore-check == 'YES'
run: |
cd ${{ env.MFEM_TOP_DIR }}/tests/scripts
./runtest gitignore
-10
View File
@@ -14,19 +14,9 @@ name: "Static Analysis"
on:
push:
branches: ["master", "next"]
paths-ignore: &docs-only-paths
- "**/*.md"
- "doc/**"
- ".binder/**"
- "CITATION.cff"
- "LICENSE"
- "NOTICE"
- "CHANGELOG"
- "INSTALL"
pull_request:
# The branches below must be a subset of the branches above
branches: ["master"]
paths-ignore: *docs-only-paths
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
+102
View File
@@ -0,0 +1,102 @@
# 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.
name: "Build Analysis"
permissions:
actions: write
on:
push:
branches:
- master
- next
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
HYPRE_ARCHIVE: v2.19.0.tar.gz
HYPRE_TOP_DIR: hypre-2.19.0
METIS_ARCHIVE: metis-4.0.3.tar.gz
METIS_TOP_DIR: metis-4.0.3
COVERAGE_ENV: mfem-coverage
MFEM_ACTIONS_VERSION: v2.7
jobs:
gitignore:
runs-on: ubuntu-latest
steps:
- name: checkout MFEM
uses: actions/checkout@v6
with:
path: mfem
- name: Get MPI (Linux)
run: |
sudo apt-get install openmpi-bin libopenmpi-dev
export OMPI_MCA_rmaps_base_oversubscribe=1
- name: Cache Hypre Install
id: hypre-cache
uses: actions/cache@v5
with:
path: ${{ env.HYPRE_TOP_DIR }}
key: ${{ runner.os }}-ompi-build-${{ env.HYPRE_TOP_DIR }}-int32-fp64-${{ env.MFEM_ACTIONS_VERSION }}
- name: Get Hypre
if: steps.hypre-cache.outputs.cache-hit != 'true'
uses: mfem/github-actions/build-hypre@v2.7
with:
archive: ${{ env.HYPRE_ARCHIVE }}
dir: ${{ env.HYPRE_TOP_DIR }}
target: int32
precision: fp64
- name: Cache Metis Install
id: metis-cache
uses: actions/cache@v5
with:
path: ${{ env.METIS_TOP_DIR }}
key: ${{ runner.os }}-build-${{ env.METIS_TOP_DIR }}-${{ env.MFEM_ACTIONS_VERSION }}
- name: Install Metis
if: steps.metis-cache.outputs.cache-hit != 'true'
uses: mfem/github-actions/build-metis@v2.7
with:
archive: ${{ env.METIS_ARCHIVE }}
dir: ${{ env.METIS_TOP_DIR }}
# MFEM build and test
- name: build-mfem
uses: mfem/github-actions/build-mfem@v2.7
with:
os: ${{ runner.os }}
target: opt
codecov: NO
mpi: par
build-system: make
hypre-dir: ${{ env.HYPRE_TOP_DIR }}
metis-dir: ${{ env.METIS_TOP_DIR }}
mfem-dir: mfem
- name: test (no clean)
run: |
cd mfem && make test-noclean
- name: gitignore
run: |
cd mfem/tests/scripts
./runtest gitignore
-10
View File
@@ -17,17 +17,7 @@ permissions:
on:
push:
branches: ["master", "next"]
paths-ignore: &docs-only-paths
- "**/*.md"
- "doc/**"
- ".binder/**"
- "CITATION.cff"
- "LICENSE"
- "NOTICE"
- "CHANGELOG"
- "INSTALL"
pull_request:
paths-ignore: *docs-only-paths
workflow_dispatch:
concurrency:
-6
View File
@@ -15,10 +15,6 @@ Version 4.9.1 (development)
Discretization improvements
---------------------------
- Improved FindPointsGSLIB surface mesh capability with support for simplices
and an option to specify axis-aligned bounding box padding for near-surface
point queries.
- Added GPU-enabled partial assembly for simplicial Bernstein H1 basis based on
ragged tensor algorithms (see DOI: 10.1137/11082539X) for mass and diffusion
integrators.
@@ -70,8 +66,6 @@ Linear and nonlinear solvers
GPU computing
-------------
- Added device assembly support for 3D H(curl) VectorFEDomainLFIntegrator.
- Added NVIDIA cuDSS library interface. Implementation examples have been
added to ex1 and ex1p. See https://developer.nvidia.com/cudss for more
details. Supported versions >= 0.6.0.
+1
View File
@@ -6,6 +6,7 @@
<a href="https://github.com/mfem/mfem/blob/master/LICENSE"><img alt="License" src="https://img.shields.io/badge/License-BSD-blue.svg"></a>
<a href="https://github.com/mfem/mfem/releases/latest"><img alt="GitHub release" src="https://img.shields.io/github/v/release/mfem/mfem"></a>
<a href="https://github.com/mfem/mfem/actions/workflows/repo-check.yml?query=branch%3Amaster"><img alt="Repo check" src="https://github.com/mfem/mfem/actions/workflows/repo-check.yml/badge.svg?branch=master"></a>
<a href="https://github.com/mfem/mfem/actions/workflows/mfem-analysis.yml?query=branch%3Amaster"><img alt="Build Analysis" src="https://github.com/mfem/mfem/actions/workflows/mfem-analysis.yml/badge.svg?branch=master"></a>
<a href="https://github.com/mfem/mfem/actions/workflows/builds-and-tests.yml?query=branch%3Amaster"><img alt="Builds and Tests" src="https://github.com/mfem/mfem/actions/workflows/builds-and-tests.yml/badge.svg?branch=master"></a>
<a href="https://ci.appveyor.com/project/mfem/mfem"><img alt="Build Status" src="https://ci.appveyor.com/api/projects/status/19non9sqm6msi2wy?svg=true"></a>
<a href="https://docs.mfem.org/html/index.html"><img alt="Documentation" src="https://img.shields.io/badge/code-documented-brightgreen.svg"></a>
+10 -8
View File
@@ -18,17 +18,19 @@
if (MFEM_FETCH_GSLIB OR MFEM_FETCH_TPLS)
enable_language(C)
set(GSLIB_FETCH_VERSION 1.0.9)
add_library(GSLIB STATIC IMPORTED)
# set options (technically flags because GSLIB does not use cmake)
string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_TYPE)
set(GSLIB_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${BUILD_TYPE}}")
if (BUILD_SHARED_LIBS)
set(GSLIB_FLAGS "${GSLIB_FLAGS} -fPIC")
set(GSLIB_FETCH_VERSION 1.0.9)
set(GSLIB_C_FLAGS ${CMAKE_C_FLAGS_${BUILD_TYPE}})
if (CMAKE_C_FLAGS)
set(GSLIB_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${BUILD_TYPE}}")
endif()
if (BUILD_SHARED_LIBS)
set(GSLIB_C_FLAGS "${GSLIB_C_FLAGS} -fPIC")
endif()
add_library(GSLIB STATIC IMPORTED)
# define external project and create future include directory so it is present
# to pass CMake checks at end of MFEM configuration step
message(STATUS "Will fetch GSLIB ${GSLIB_FETCH_VERSION} to be built with ${GSLIB_FLAGS}")
message(STATUS "Will fetch GSLIB ${GSLIB_FETCH_VERSION} to be built with ${GSLIB_C_FLAGS}")
set(PREFIX ${CMAKE_BINARY_DIR}/fetch/gslib)
include(ExternalProject)
ExternalProject_Add(gslib
@@ -38,7 +40,7 @@ if (MFEM_FETCH_GSLIB OR MFEM_FETCH_TPLS)
UPDATE_DISCONNECTED TRUE
PREFIX ${PREFIX}
CONFIGURE_COMMAND ""
BUILD_COMMAND cd ${PREFIX}/src/gslib && $(MAKE) clean && $(MAKE) DESTDIR=${PREFIX} MPI=$<BOOL:${MFEM_USE_MPI}> "CFLAGS=${GSLIB_FLAGS}"
BUILD_COMMAND cd ${PREFIX}/src/gslib && $(MAKE) clean && $(MAKE) DESTDIR=${PREFIX} MPI=$<BOOL:${MFEM_USE_MPI}> "CFLAGS= ${GSLIB_C_FLAGS}"
INSTALL_COMMAND "")
file(MAKE_DIRECTORY ${PREFIX}/include)
# set imported library target properties
+1 -3
View File
@@ -44,9 +44,6 @@ if (MFEM_FETCH_HYPRE OR MFEM_FETCH_TPLS)
# set options and associated dependencies
set(HYPRE_CMAKE_OPTIONS "")
list(APPEND HYPRE_CMAKE_OPTIONS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE})
if (BUILD_SHARED_LIBS)
list(APPEND HYPRE_CMAKE_OPTIONS -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON)
endif()
# collect all HYPRE_ENABLE variables and pass them to hypre, assuming they are BOOL.
get_cmake_property(all_vars VARIABLES)
foreach(var ${all_vars})
@@ -98,6 +95,7 @@ if (MFEM_FETCH_HYPRE OR MFEM_FETCH_TPLS)
UPDATE_DISCONNECTED TRUE
SOURCE_SUBDIR src
PREFIX ${HYPRE_INSTALL}
BUILD_COMMAND ${CMAKE_COMMAND} --build . -- -j${CMAKE_BUILD_PARALLEL_LEVEL}
CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${HYPRE_INSTALL} -DCMAKE_INSTALL_LIBDIR:PATH=lib ${HYPRE_CMAKE_OPTIONS})
file(MAKE_DIRECTORY ${HYPRE_INSTALL}/include)
# set imported library target properties
+2 -10
View File
@@ -19,18 +19,10 @@
# - METIS_VERSION_5 (cache variable)
if (MFEM_FETCH_METIS OR MFEM_FETCH_TPLS)
enable_language(C)
set(METIS_FETCH_VERSION 4.0.3)
add_library(METIS STATIC IMPORTED)
# set options (technically flags because METIS does not use cmake)
set(METIS_FLAGS "-Wno-implicit-int -Wno-incompatible-pointer-types")
string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_TYPE)
set(METIS_FLAGS "${METIS_FLAGS} ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${BUILD_TYPE}}")
if (BUILD_SHARED_LIBS)
set(METIS_FLAGS "${METIS_FLAGS} -fPIC")
endif()
# define external project
message(STATUS "Will fetch METIS ${METIS_FETCH_VERSION} to be built with ${METIS_FLAGS}")
message(STATUS "Will fetch METIS ${METIS_FETCH_VERSION} to be built with default options")
set(PREFIX ${CMAKE_BINARY_DIR}/fetch/metis)
include(ExternalProject)
ExternalProject_Add(metis
@@ -40,7 +32,7 @@ if (MFEM_FETCH_METIS OR MFEM_FETCH_TPLS)
UPDATE_DISCONNECTED TRUE
PREFIX ${PREFIX}
CONFIGURE_COMMAND tar -xzf ../metis/metis-${METIS_FETCH_VERSION}-mac.tgz --strip=1
BUILD_COMMAND $(MAKE) clean && $(MAKE) "OPTFLAGS=${METIS_FLAGS}"
BUILD_COMMAND $(MAKE) COPTIONS=-Wno-incompatible-pointer-types
INSTALL_COMMAND mkdir -p ${PREFIX}/lib && cp libmetis.a ${PREFIX}/lib/)
# set imported library target properties
add_dependencies(METIS metis)
+9 -9
View File
@@ -22,15 +22,15 @@ include(MfemCmakeUtilities)
mfem_find_package(SuiteSparse SuiteSparse SuiteSparse_DIR "" "" "" ""
"Paths to headers required by SuiteSparse."
"Libraries required by SuiteSparse."
ADD_COMPONENT "UMFPACK" "include;include/suitesparse;suitesparse" umfpack.h "lib" umfpack
ADD_COMPONENT "KLU" "include;include/suitesparse;suitesparse" klu.h "lib" klu
ADD_COMPONENT "AMD" "include;include/suitesparse;suitesparse" amd.h "lib" amd
ADD_COMPONENT "BTF" "include;include/suitesparse;suitesparse" btf.h "lib" btf
ADD_COMPONENT "CHOLMOD" "include;include/suitesparse;suitesparse" cholmod.h "lib" cholmod
ADD_COMPONENT "COLAMD" "include;include/suitesparse;suitesparse" colamd.h "lib" colamd
ADD_COMPONENT "CAMD" "include;include/suitesparse;suitesparse" camd.h "lib" camd
ADD_COMPONENT "CCOLAMD" "include;include/suitesparse;suitesparse" ccolamd.h "lib" ccolamd
ADD_COMPONENT "config" "include;include/suitesparse;suitesparse" SuiteSparse_config.h "lib"
ADD_COMPONENT "UMFPACK" "include;suitesparse" umfpack.h "lib" umfpack
ADD_COMPONENT "KLU" "include;suitesparse" klu.h "lib" klu
ADD_COMPONENT "AMD" "include;suitesparse" amd.h "lib" amd
ADD_COMPONENT "BTF" "include;suitesparse" btf.h "lib" btf
ADD_COMPONENT "CHOLMOD" "include;suitesparse" cholmod.h "lib" cholmod
ADD_COMPONENT "COLAMD" "include;suitesparse" colamd.h "lib" colamd
ADD_COMPONENT "CAMD" "include;suitesparse" camd.h "lib" camd
ADD_COMPONENT "CCOLAMD" "include;suitesparse" ccolamd.h "lib" ccolamd
ADD_COMPONENT "config" "include;suitesparse" SuiteSparse_config.h "lib"
suitesparseconfig)
if (SuiteSparse_FOUND AND METIS_VERSION_5)
+1 -2
View File
@@ -133,7 +133,7 @@ set(SRCS
tmop/assemble/diag2.cpp
tmop/assemble/grad2_limit.cpp
tmop/assemble/grad2.cpp
tmop/assemble/diag3_limit.cpp
tmop/assemble/diag3_limit.cpp
tmop/assemble/diag3.cpp
tmop/assemble/grad3_limit.cpp
tmop/assemble/grad3.cpp
@@ -311,7 +311,6 @@ set(HDRS
tmop_tools.hpp
tmop_amr.hpp
gslib.hpp
gslib/gslib_kernel_helpers.hpp
transfer.hpp
hyperbolic.hpp
integrator.hpp
-6
View File
@@ -54,8 +54,6 @@ void Coefficient::Project(QuadratureFunction &qf)
QuadratureSpaceBase &qspace = *qf.GetSpace();
const int ne = qspace.GetNE();
Vector values;
// GetValues makes a reference, but we need it to be valid on Host
qf.HostWrite();
for (int iel = 0; iel < ne; ++iel)
{
qf.GetValues(iel, values);
@@ -329,8 +327,6 @@ void VectorCoefficient::Project(QuadratureFunction &qf)
const int ne = qspace.GetNE();
DenseMatrix values;
Vector col;
// GetValues makes a reference, but we need it to be valid on Host
qf.HostWrite();
for (int iel = 0; iel < ne; ++iel)
{
qf.GetValues(iel, values);
@@ -699,8 +695,6 @@ void MatrixCoefficient::Project(QuadratureFunction &qf, bool transpose)
QuadratureSpaceBase &qspace = *qf.GetSpace();
const int ne = qspace.GetNE();
DenseMatrix values, matrix;
// GetValues makes a reference, but we need it to be valid on Host
qf.HostWrite();
for (int iel = 0; iel < ne; ++iel)
{
qf.GetValues(iel, values);
-75
View File
@@ -237,81 +237,6 @@ ComplexGridFunction::ProjectBdrCoefficientTangent(VectorCoefficient
gfi->SyncAliasMemory(*this);
}
real_t
ComplexGridFunction::ComputeLpError(const real_t p,
Coefficient &exsolr,
Coefficient &exsoli,
Coefficient *weight,
const IntegrationRule *irs[],
const Array<int> *elems) const
{
real_t error = 0.0;
const FiniteElement *fe;
ElementTransformation *T;
Vector valsr;
Vector valsi;
const GridFunction& gf_r = real();
const GridFunction& gf_i = imag();
for (int i = 0; i < fes->GetNE(); i++)
{
if (elems != NULL && (*elems)[i] == 0) { continue; }
fe = fes->GetFE(i);
const IntegrationRule *ir;
if (irs)
{
ir = irs[fe->GetGeomType()];
}
else
{
int intorder = 2*fe->GetOrder() + 3;
ir = &(IntRules.Get(fe->GetGeomType(), intorder));
}
real_t elem_error = 0.0;
gf_r.GetValues(i, *ir, valsr);
gf_i.GetValues(i, *ir, valsi);
T = fes->GetElementTransformation(i);
for (int j = 0; j < ir->GetNPoints(); j++)
{
const IntegrationPoint &ip = ir->IntPoint(j);
T->SetIntPoint(&ip);
real_t diffr = valsr(j) - exsolr.Eval(*T, ip);
real_t diffi = valsi(j) - exsoli.Eval(*T, ip);
real_t diff = hypot(diffr, diffi);
if (p < infinity())
{
diff = pow(diff, p);
if (weight)
{
diff *= weight->Eval(*T, ip);
}
elem_error += ip.weight * T->Weight() * diff;
}
else
{
if (weight)
{
diff *= weight->Eval(*T, ip);
}
error = std::max(error, diff);
}
}
if (p < infinity())
{
// negative quadrature weights may cause the error to be negative
error += fabs(elem_error);
}
}
if (p < infinity())
{
error = pow(error, 1./p);
}
return error;
}
void ComplexGridFunction::Save(std::ostream &os) const
{
os << "ComplexGridFunction\n";
-69
View File
@@ -166,75 +166,6 @@ public:
return sqrt(err_r * err_r + err_i * err_i);
}
/// @brief Returns Max|u_ex - u_h| error for complex-valued H1 or L2 elements
///
/// Compute the $L_\infty$ error across the entire domain.
///
/// @param[in] exsolr Coefficient object reproducing the real part of the
/// anticipated values of the scalar field, Re(u_ex).
/// @param[in] exsoli Coefficient object reproducing the imaginary part of
/// the anticipated values of the scalar field, Im(u_ex).
/// @param[in] irs Optional pointer to an array of custom integration
/// rules e.g. higher order than the default rules. If
/// present the array will be indexed by
/// Geometry::Type.
///
/// @note Uses ComputeLpError internally. See the ComputeLpError
/// documentation for generalizations of this error computation.
///
/// @note If an array of integration rules is provided through @a irs, be
/// sure to include valid rules for each element type that may occur
/// in the list of elements.
///
virtual real_t ComputeMaxError(Coefficient &exsolr,
Coefficient &exsoli,
const IntegrationRule *irs[] = NULL) const
{
return ComputeLpError(infinity(), exsolr, exsoli, NULL, irs);
}
/// @brief Returns ||u_ex - u_h||_Lp for complex-valued H1 or L2 elements
///
/// Computes:
/// $$(\sum_{elems} \int_{elem} w \, |u_{ex} - u_h|^p)^{1/p}$$
/// Where:
/// $$|u_{ex} - u_h| = \sqrt{Re(u_{ex} - u_h)^2 + Im(u_{ex} - u_h)^2}$$
///
/// @param[in] p Real value indicating the exponent of the $L^p$ norm.
/// To avoid domain errors p should have a positive value,
/// either finite or infinite.
/// @param[in] exsolr Coefficient object reproducing the real part of the
/// anticipated values of the scalar field, Re(u_ex).
/// @param[in] exsoli Coefficient object reproducing the imaginary part of
/// the anticipated values of the scalar field, Im(u_ex).
/// @param[in] weight Optional pointer to a Coefficient object reproducing
/// a weighting function, w.
/// @param[in] irs Optional pointer to an array of custom integration
/// rules e.g. higher order than the default rules. If
/// present the array will be indexed by Geometry::Type.
/// @param[in] elems Optional pointer to a marker array, with a length
/// equal to the number of local elements, indicating
/// which elements to integrate over. Only those elements
/// corresponding to non-zero entries in @a elems will
/// contribute to the computed L2 error.
///
/// @note If an array of integration rules is provided through @a irs, be
/// sure to include valid rules for each element type that may occur
/// in the list of elements.
///
/// @note Quadratures with negative weights (as in some simplex integration
/// rules in MFEM) can produce negative integrals even with
/// non-negative integrands. To avoid returning negative errors this
/// function uses the absolute values of the element-wise integrals.
/// This may lead to results which are not entirely consistent with
/// such integration rules.
virtual real_t ComputeLpError(const real_t p,
Coefficient &exsolr,
Coefficient &exsoli,
Coefficient *weight = NULL,
const IntegrationRule *irs[] = NULL,
const Array<int> *elems = NULL) const;
/// Save the ComplexGridFunction to an output stream.
virtual void Save(std::ostream &out) const;
+1 -3
View File
@@ -1181,14 +1181,12 @@ void ParaViewDataCollection::SaveGFieldVTU(std::ostream &os, int ref_,
DenseMatrix vval, pmat;
std::vector<char> buf;
int vec_dim = it->second->VectorDim();
int map_type = it->second->FESpace()->GetTypicalFE()->GetMapType();
os << "<DataArray type=\"" << GetDataTypeString()
<< "\" Name=\"" << it->first
<< "\" NumberOfComponents=\"" << vec_dim << "\" "
<< VTKComponentLabels(vec_dim) << " "
<< "format=\"" << GetDataFormatString() << "\" >" << '\n';
if (vec_dim == 1 && (map_type == FiniteElement::VALUE ||
map_type == FiniteElement::INTEGRAL))
if (vec_dim == 1)
{
for (int i = 0; i < mesh->GetNE(); i++)
{
+30 -79
View File
@@ -25,35 +25,21 @@
namespace mfem
{
/// Lightweight adaptor over an std::map from type K to type to V
template<typename K, typename V,
typename = typename std::enable_if<std::is_default_constructible<V>::value>::type>
class GenericFieldMap
/// Lightweight adaptor over an std::map from strings to pointer to T
template<typename T>
class NamedFieldsMap
{
private:
static constexpr bool ValueIsPointer = std::is_pointer<V>::value;
public:
typedef std::map<K, V> MapType;
typedef std::map<std::string, T*> MapType;
typedef typename MapType::iterator iterator;
typedef typename MapType::const_iterator const_iterator;
/// Register field @a field with name @a key
/// Only enabled if the template parameter V is not a pointer
template<typename = std::enable_if<!ValueIsPointer, bool>>
void Register(const K& key, V field)
/// Register field @a field with name @a fname
/** Replace existing field associated with @a fname (and optionally
delete associated pointer if @a own_data is true) */
void Register(const std::string& fname, T* field, bool own_data)
{
field_map[key] = field;
}
/// Register field @a field with name @a key
/** Replace existing field associated with @a key (and optionally
delete associated pointer if @a own_data is true).
Only enabled if the template parameter V is a pointer*/
template<typename = std::enable_if<ValueIsPointer, bool>>
void Register(const K& key, V field, bool own_data)
{
V& ref = field_map[key];
T*& ref = field_map[fname];
if (own_data)
{
delete ref; // if newly allocated -> ref is null -> OK
@@ -61,40 +47,23 @@ public:
ref = field;
}
/// Unregister association between field @a field and name @a key
/// Only enabled if the template parameter V is not a pointer
template<typename = std::enable_if<!ValueIsPointer, bool>>
void Deregister(const K& key)
/// Unregister association between field @a field and name @a fname
/** Optionally delete associated pointer if @a own_data is true */
void Deregister(const std::string& fname, bool own_data)
{
iterator it = field_map.find(key);
if ( it != field_map.end() )
{
field_map.erase(it);
}
}
/// Unregister association between field @a field and name @a key
/** Optionally delete associated pointer if @a own_data is true.
Only enabled if the template parameter V is a pointer */
template<typename = std::enable_if<ValueIsPointer, bool>>
void Deregister(const K& key, bool own_data)
{
iterator it = field_map.find(key);
iterator it = field_map.find(fname);
if ( it != field_map.end() )
{
if (own_data)
{
delete it->second;
it->second = nullptr;
}
field_map.erase(it);
}
}
/// Clear all associations between names and fields
/** Delete associated pointers when @a own_data is true.
Only enabled if the template parameter V is a pointer */
template<typename = std::enable_if<ValueIsPointer, bool>>
/** Delete associated pointers when @a own_data is true */
void DeleteData(bool own_data)
{
for (iterator it = field_map.begin(); it != field_map.end(); ++it)
@@ -107,37 +76,22 @@ public:
}
}
/// Predicate to check if a field is associated with name @a key
bool Has(const K& key) const
/// Predicate to check if a field is associated with name @a fname
bool Has(const std::string& fname) const
{
return field_map.find(key) != field_map.end();
return field_map.find(fname) != field_map.end();
}
/// Get a pointer to the field associated with name @a key
/** @return Field associated with @a key or NULL,
if value is pointer and key not found */
V Get(const K& key) const
/// Get a pointer to the field associated with name @a fname
/** @return Pointer to field associated with @a fname or NULL */
T* Get(const std::string& fname) const
{
const_iterator it = field_map.find(key);
if (it != field_map.end())
{
return it->second;
}
else
{
if constexpr (ValueIsPointer)
{
return nullptr;
}
else
{
return V(); // Return default-constructed value for non-pointer types
}
}
const_iterator it = field_map.find(fname);
return it != field_map.end() ? it->second : NULL;
}
/// Returns a const reference to the underlying map
const MapType &GetMap() const { return field_map; }
const MapType& GetMap() const { return field_map; }
/// Returns the number of registered fields
int NumFields() const { return field_map.size(); }
@@ -152,24 +106,21 @@ public:
/// Returns an end const iterator to the registered fields
const_iterator end() const { return field_map.end(); }
/// Returns an iterator to the field @a key
iterator find(const K& key)
{ return field_map.find(key); }
/// Returns an iterator to the field @a fname
iterator find(const std::string& fname)
{ return field_map.find(fname); }
/// Returns a const iterator to the field @a key
const_iterator find(const K& key) const
{ return field_map.find(key); }
/// Returns a const iterator to the field @a fname
const_iterator find(const std::string& fname) const
{ return field_map.find(fname); }
/// Clears the map of registered fields
/// Clears the map of registered fields without reclaiming memory
void clear() { field_map.clear(); }
protected:
MapType field_map;
};
/// Lightweight adaptor over an std::map from strings to pointer to T
template<typename T>
using NamedFieldsMap = GenericFieldMap<std::string, T*>;
/** A class for collecting finite element data that is part of the same
simulation. Currently, this class groups together grid functions (fields),
+2 -16
View File
@@ -671,20 +671,6 @@ public:
MPI_COMM_WORLD);
}
Operator& GetGradient(const Vector &x0) const override
{
x = x0;
f.UseDevice(x.UseDevice());
xpev.UseDevice(x.UseDevice());
op.Mult(x, f);
const real_t xnorm_local = x.Norml2();
MPI_Allreduce(&xnorm_local, &xnorm, 1, MPITypeMap<real_t>::mpi_type, MPI_SUM,
MPI_COMM_WORLD);
return const_cast<FDJacobian&>(*this);
}
void Mult(const Vector &v, Vector &y) const override
{
// See [1] for choice of eps.
@@ -739,11 +725,11 @@ public:
private:
const Operator &op;
mutable Vector x, f;
Vector x, f;
mutable Vector xpev;
real_t lambda = 1.0e-6;
real_t fixed_eps;
mutable real_t xnorm;
real_t xnorm;
};
/// @brief Find the index of a field descriptor in a vector of field descriptors.
+725 -1233
View File
File diff suppressed because it is too large Load Diff
+51 -166
View File
@@ -12,9 +12,6 @@
#ifndef MFEM_GSLIB
#define MFEM_GSLIB
#include <map>
#include <vector>
#include "../config/config.hpp"
#ifdef MFEM_USE_MPI
#include "pgridfunc.hpp"
@@ -122,11 +119,6 @@ protected:
// IntegrationRules for simplex->Quad/Hex and to project to p_max in-case of
// p-refinement.
Array<IntegrationRule *> ir_split;
/// Integration rules built at the field polynomial order (only for surface
/// meshes when mesh order is not the same as gridfunction order).
Array<IntegrationRule *> ir_split_sol;
/// Order at which #ir_split_sol was built; -1 means not built.
int ir_split_sol_order = -1;
Array<FiniteElementSpace *> fes_rst_map; //FESpaces to map Quad/Hex->Simplex
Array<GridFunction *> gf_rst_map; // GridFunctions to map Quad/Hex->Simplex
FiniteElementCollection *fec_map_lin;
@@ -142,8 +134,6 @@ protected:
AvgType avgtype; // average type used for L2 functions
Array<int> split_element_map;
Array<int> split_element_index;
// Geometry::Type (as int) of the original element for each split quad.
Array<int> split_element_geom;
int NE_split_total; // total number of elements after mesh splitting
int mesh_points_cnt; // number of mesh nodes
// Tolerance to ignore points found beyond the mesh boundary.
@@ -151,12 +141,6 @@ protected:
double bdr_tol;
// Use CPU functions for Mesh/GridFunction on device for gslib1.0.7
bool gpu_to_cpu_fallback = false;
// Check if a point is inside the oriented bounding box of an
// element before the Newton iteration.
// Note: only used in MFEM implementation (not in gslib) which currently
// supports GPU kernels for area meshes in 2D, volume meshes in 3D,
// and surface meshes in 1D/2D/3D.
bool obb_check = true;
// Device specific data used for FindPoints
struct DEV_STRUCT
@@ -178,16 +162,11 @@ protected:
mutable double surf_dist_tol;
} DEV;
// Helper function to setup and free gslib's crystal router.
void SetupCrystal(); // Called inside Setup and SetupSurf_base
void FreeCrystal(); // Called inside FreeData
/// Use GSLIB for communication and interpolation. Updates field_out on
/// host.
/// Use GSLIB for communication and interpolation
virtual void InterpolateH1(const GridFunction &field_in, Vector &field_out,
const int field_out_ordering);
/// Uses GSLIB Crystal Router for communication followed by MFEM's
/// interpolation functions. Updates field_out on host.
/// interpolation functions
virtual void InterpolateGeneral(const GridFunction &field_in,
Vector &field_out,
const int field_out_ordering);
@@ -202,26 +181,12 @@ protected:
IntegrationRule *irule,
int order);
/** @brief Build integration rules at the given @a order for each split mesh
* and store them in @a ir_out. Requires that \ref SetupSplitMeshes has
* already been called. */
virtual void SetupIntegrationRules(const int order,
Array<IntegrationRule *> &ir_out);
/** @brief Helper function that calls \ref SetupSplitMeshes and
* \ref SetupIntegrationRules. */
* \ref SetupIntegrationRuleForSplitMesh. */
virtual void SetupSplitMeshesAndIntegrationRules(const int order);
/** @brief Get GridFunction value at the points expected by GSLIB.
* @param[in] gf_in Grid function to evaluate.
* @param[out] node_vals Output values.
* @param[in] ir_in If non-null, use these rules instead of #ir_split.
* @param[in] by_element If true, output has element-major layout
* [nel][vdim][ndofs]; otherwise component-major
* layout [vdim][total_pts]. */
virtual void GetNodalValues(const GridFunction *gf_in, Vector &node_vals,
const Array<IntegrationRule *> *ir_in = nullptr,
bool by_element = false) const;
/// Get GridFunction value at the points expected by GSLIB.
virtual void GetNodalValues(const GridFunction *gf_in, Vector &node_vals) const;
/** @brief Map {r,s,t} coordinates from [-1,1] to [0,1] for MFEM. For
* simplices, find the original element number (that was split into
@@ -326,60 +291,29 @@ protected:
void findptsedge_setup_2(DEV_STRUCT &devs,
const double *const elx[2],
const unsigned n,
const unsigned int nel,
const uint nel,
const unsigned m,
const double bbox_rel_size_inc,
const unsigned int local_hash_size,
const unsigned int global_hash_size,
const Vector *aabb_sz_inc);
const double bbox_tol,
const uint local_hash_size,
const uint global_hash_size);
/// Preprocess 3D surface mesh needed for FindPoints.
void findptssurf_setup_3(DEV_STRUCT &devs,
const double *const elx[3],
const unsigned n,
const unsigned int nel,
const uint nel,
const unsigned m,
const double bbox_rel_size_inc,
const unsigned int local_hash_size,
const unsigned int global_hash_size,
const int rD,
const Vector *aabb_sz_inc);
const double bbox_tol,
const uint local_hash_size,
const uint global_hash_size,
const int rD);
/** @brief Shared implementation for the public surface-setup methods.
*
* @details Initializes the surface-search data structures, builds the
* split-element representation expected by gslib, and constructs the
* element bounding boxes used by the MFEM surface kernels.
*
* If @a aabb_sz_inc is null, the setup stores the default oriented
* bounding boxes and uses @a bbox_rel_size_inc as their relative size
* increase factor.
*
* If @a aabb_sz_inc is non-null, the setup stores axis-aligned bounding
* boxes only, applies the requested absolute AABB expansion in each
* physical direction, and adjusts the tolerance @a bdr_tol so points
* found in the expanded region are classified as border points.
*
* @param[in] m Input surface mesh.
* @param[in] bbox_rel_size_inc Relative size increase applied when
* expanding each element bounding box during
* setup.
* @param[in] aabb_sz_inc Optional total absolute AABB expansion
* applied to the stored axis-aligned
* bounding boxes after construction.
* @param[in] newt_tol Newton tolerance for the point-search
* kernels.
*/
void SetupSurf_Base(Mesh &m,
const double bbox_rel_size_inc,
const Vector *aabb_sz_inc,
const double newt_tol);
public:
/// Serial constructor
FindPointsGSLIB();
/// Serial constructor + setup with given Mesh (see \ref Setup)
FindPointsGSLIB(Mesh &mesh_in, const double bbox_rel_size_inc = 0.1,
FindPointsGSLIB(Mesh &mesh_in, const double bb_t = 0.1,
const double newt_tol = 1.0e-12,
const int npt_max = 256);
@@ -388,7 +322,7 @@ public:
FindPointsGSLIB(MPI_Comm comm_);
/// Constructor + setup with given ParMesh (see \ref Setup)
FindPointsGSLIB(ParMesh &mesh_in, const double bbox_rel_size_inc = 0.1,
FindPointsGSLIB(ParMesh &mesh_in, const double bb_t = 0.1,
const double newt_tol = 1.0e-12,
const int npt_max = 256);
#endif
@@ -404,59 +338,23 @@ public:
Note: not tested with periodic (L2).
Note: the input mesh \p m must have Nodes set.
@param[in] m Input mesh.
@param[in] bbox_rel_size_inc (Optional) Relative size increase applied
when expanding each element bounding box.
@param[in] newt_tol (Optional) Newton tolerance for the gslib
search methods.
@param[in] npt_max (Optional) Number of points for
simultaneous iteration. This alters
performance and memory footprint.
@param[in] m Input mesh.
@param[in] bb_t (Optional) Relative size of bounding box around
each element.
@param[in] newt_tol (Optional) Newton tolerance for the gslib
search methods.
@param[in] npt_max (Optional) Number of points for simultaneous
iteration. This alters performance and
memory footprint.
*/
void Setup(Mesh &m, const double bbox_rel_size_inc = 0.1,
const double newt_tol = 1.0e-12,
void Setup(Mesh &m, const double bb_t = 0.1, const double newt_tol = 1.0e-12,
const int npt_max = 256);
/// Preprocess the surface mesh to compute data for FindPoints.
void SetupSurf(Mesh &m,
const double bbox_rel_size_inc = 0.1,
const double newt_tol = 1.0e-12);
/** @brief Preprocess the surface mesh to compute data for FindPoints using
* absolute AABB expansion.
*
* @details This method computes only axis-aligned bounding boxes and
* increases their total length by a user-specified amount in each
* physical direction. The absolute AABB expansion is applied
* symmetrically to the lower and upper bounds.
*
* The size of @a aabb_sz_inc determines how the expansion values are
* interpreted:
* - `1`: one expansion value used in every direction for every element
* - `NElements`: one expansion value per element, reused in x/y/z
* directions
* - `SpaceDim`: one expansion value per physical direction, reused for
* every element
* - `NElements*SpaceDim`: one expansion value per element and direction,
* ordered as `(dx1,dy1,dz1, ... dxN,dyN,dzN)`
*
* This method disables the oriented bounding-box precheck because the
* stored boxes are modified only in their axis-aligned representation.
*
* @param[in] m Input surface mesh.
* @param[in] aabb_sz_inc Total absolute AABB expansion applied in
* each physical direction to the stored
* axis-aligned bounding boxes.
* @param[in] newt_tol Newton tolerance for the point-search
* kernels.
*
* @note We disable the oriented bounding box check with this setup.
* @a bdr_tol is also adjusted so that all points in the AABBs can
* be found.
*/
void SetupSurfWithAABBExpansion(Mesh &m, const Vector &aabb_sz_inc,
const double newt_tol = 1.0e-12);
const double bb_t = 0.1,
const double newt_tol = 1.0e-12,
const int npt_max = 256);
/** @brief Searches positions given in physical space by \p point_pos.
@@ -503,8 +401,7 @@ public:
/// Setup FindPoints and search positions
void FindPoints(Mesh &m, const Vector &point_pos,
const int point_pos_ordering = Ordering::byNODES,
const double bbox_rel_size_inc = 0.1,
const double newt_tol = 1.0e-12,
const double bb_t = 0.1, const double newt_tol = 1.0e-12,
const int npt_max = 256);
/** @brief Interpolation of field values at prescribed reference space
@@ -516,11 +413,7 @@ public:
mesh that was given to Setup().
@param[out] field_out Interpolated values. For points that are not found
the value is set to #default_interp_value.
The output ordering is determined from field_in.
@note: field_out is moved to device if field_in is on device. Otherwise,
field_out memory allocation is not changed.
*/
The output ordering is determined from field_in.*/
virtual void Interpolate(const GridFunction &field_in, Vector &field_out);
/// Interpolation of field values, with output ordering specification.
@@ -575,12 +468,7 @@ public:
* @details When using FindPoints, gslib may return points as found on the
* boundary even when they are slightly outside the domain. This tolerance
* is used to filter such points based on the distance^2 value and mark them
* as not found.
*
* @note When the SetupSurfWithAABBExpansion method is used for surface
* meshes, this tolerance is automatically computed based on the size of
* expanded AABBs. Using this method will override that computed tolerance.
* */
* as not found.*/
virtual void SetDistanceToleranceForPointsFoundOnBoundary(double bdr_tol_)
{
bdr_tol = bdr_tol_;
@@ -715,28 +603,25 @@ public:
Note: not tested with periodic meshes (L2).
Note: the input mesh \p m must have Nodes set.
@param[in] m Input mesh.
@param[in] meshid A unique # for each overlapping mesh.
This id is used to make sure that points
being searched are not looked for in the
mesh that they belong to.
@param[in] gfmax (Optional) GridFunction in H1 that is used
as a discriminator when one point is
located in multiple meshes. The mesh that
maximizes gfmax is chosen. For example,
using the distance field based on the
overlapping boundaries is helpful for
convergence during Schwarz iterations.
@param[in] bbox_rel_size_inc (Optional) Relative size increase applied
when expanding each element bounding box.
@param[in] newt_tol (Optional) Newton tolerance for the gslib
search methods.
@param[in] npt_max (Optional) Number of points for
simultaneous iteration. This alters
performance and memory footprint.*/
void Setup(Mesh &m, const int meshid, GridFunction *gfmax = nullptr,
const double bbox_rel_size_inc = 0.1,
const double newt_tol = 1.0e-12,
@param[in] m Input mesh.
@param[in] meshid A unique # for each overlapping mesh. This id is
used to make sure that points being searched are not
looked for in the mesh that they belong to.
@param[in] gfmax (Optional) GridFunction in H1 that is used as a
discriminator when one point is located in multiple
meshes. The mesh that maximizes gfmax is chosen.
For example, using the distance field based on the
overlapping boundaries is helpful for convergence
during Schwarz iterations.
@param[in] bb_t (Optional) Relative size of bounding box around
each element.
@param[in] newt_tol (Optional) Newton tolerance for the gslib
search methods.
@param[in] npt_max (Optional) Number of points for simultaneous
iteration. This alters performance and
memory footprint.*/
void Setup(Mesh &m, const int meshid, GridFunction *gfmax = NULL,
const double bb_t = 0.1, const double newt_tol = 1.0e-12,
const int npt_max = 256);
/** Searches positions given in physical space by \p point_pos. All output
@@ -792,7 +677,7 @@ class GSOPGSLIB
protected:
struct gslib::crystal *cr; // gslib's internal data
struct gslib::comm *gsl_comm; // gslib's internal data
struct gslib::gs_data *gsl_data = nullptr;
struct gslib::gs_data *gsl_data = NULL;
int num_ids;
public:
+170 -64
View File
@@ -11,7 +11,7 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#include "../../linalg/kernels.hpp"
#ifdef MFEM_USE_GSLIB
@@ -27,6 +27,8 @@
#pragma GCC diagnostic pop
#endif
#include <climits>
namespace mfem
{
#if GSLIB_RELEASE_VERSION >= 10009
@@ -52,14 +54,127 @@ struct findptsElementGPT_t
double x[DIM], jac[DIM * DIM], hes[4];
};
using dbl_range_t = gslib::dbl_range_t;
using obbox_t = gslib::obbox_t<DIM>;
using findptsLocalHashData_t = gslib::findptsLocalHashData_t<DIM>;
using gslib::bbox_test;
using gslib::hash_index;
using gslib::l2norm2;
using gslib::lag_eval_first_der;
using gslib::lag_eval_second_der;
struct dbl_range_t
{
double min, max;
};
struct obbox_t
{
double c0[DIM], A[DIM * DIM];
dbl_range_t x[DIM];
};
struct findptsLocalHashData_t
{
int hash_n;
dbl_range_t bnd[DIM];
double fac[DIM];
unsigned int *offset;
int max;
};
// Eval the ith Lagrange interpolant and its first derivative at x.
// Note: lCoeff stores pre-computed coefficients for fast evaluation.
static MFEM_HOST_DEVICE inline void lag_eval_first_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
double d_j = 2 * (x - z[j]);
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
p0[i] = lCoeff[i] * u0;
p0[pN+i] = 2.0 * lCoeff[i] * u1;
}
// Eval the ith Lagrange interpolant and its first and second derivative at x.
// Note: lCoeff stores pre-computed coefficients for fast evaluation.
static MFEM_HOST_DEVICE inline void lag_eval_second_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0, u2 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
double d_j = 2 * (x - z[j]);
u2 = d_j * u2 + u1;
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
p0[i] = lCoeff[i] * u0;
p0[pN+i] = 2.0 * lCoeff[i] * u1;
p0[2*pN+i] = 8.0 * lCoeff[i] * u2;
}
// Axis-aligned bounding box test.
static MFEM_HOST_DEVICE inline double AABB_test(const obbox_t *const b,
const double x[2])
{
double test = 1;
for (int d = 0; d < 2; ++d)
{
double b_d = (x[d] - b->x[d].min) * (b->x[d].max - x[d]);
test = test < 0 ? test : b_d;
}
return test;
}
// Axis-aligned bounding box test followed by oriented bounding-box test.
static MFEM_HOST_DEVICE inline double bbox_test(const obbox_t *const b,
const double x[2])
{
const double bxyz = AABB_test(b, x);
if (bxyz < 0)
{
return bxyz;
}
else
{
double dxyz[2];
for (int d = 0; d < 2; ++d)
{
dxyz[d] = x[d] - b->c0[d];
}
double test = 1;
for (int d = 0; d < 2; ++d)
{
double rst = 0;
for (int e = 0; e < 2; ++e)
{
rst += b->A[d * 2 + e] * dxyz[e];
}
double brst = (rst + 1) * (1 - rst);
test = test < 0 ? test : brst;
}
return test;
}
}
// Element index corresponding to hash mesh that the point is located in.
static MFEM_HOST_DEVICE inline int hash_index(const findptsLocalHashData_t *p,
const double x[2])
{
const int n = p->hash_n;
int sum = 0;
for (int d = 2 - 1; d >= 0; --d)
{
sum *= n;
int i = (int)floor((x[d] - p->bnd[d].min) * p->fac[d]);
sum += i < 0 ? 0 : (n - 1 < i ? n - 1 : i);
}
return sum;
}
/*Solve Ax=y. A is row-major */
static MFEM_HOST_DEVICE inline void lin_solve_2(double x[2], const double A[4],
@@ -70,6 +185,12 @@ static MFEM_HOST_DEVICE inline void lin_solve_2(double x[2], const double A[4],
x[1] = idet*(A[0]*y[1] - A[2]*y[0]);
}
/* L2 norm squared. */
static MFEM_HOST_DEVICE inline double l2norm2(const double x[2])
{
return x[0] * x[0] + x[1] * x[1];
}
/* the bit structure of flags is CSSRR
the C bit --- 1<<4 --- is set when the point is converged
RR is 0 = 00b if r is unconstrained,
@@ -231,7 +352,7 @@ static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *res,
const findptsElementPoint_t *p,
const double tol)
{
const double dist2 = l2norm2<2>(resid);
const double dist2 = l2norm2(resid);
const double decr = p->dist2 - dist2;
const double pred = p->dist2p;
for (int d = 0; d < 2; ++d)
@@ -574,25 +695,25 @@ static MFEM_HOST_DEVICE double tensor_ig2_j(double *g_partials,
}
template<int T_D1D = 0>
static void FindPointsLocal2DKernel(const int npt,
const double tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0)
static void FindPointsLocal2D_Kernel(const int npt,
const double tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0)
{
const int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
const int D1D = T_D1D ? T_D1D : pN;
@@ -1054,45 +1175,30 @@ void FindPointsGSLIB::FindPointsLocal2(const Vector &point_pos,
switch (DEV.dof1d)
{
case 2:
FindPointsLocal2DKernel<2>(npt, DEV.newt_tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsLocal2D_Kernel<2>(
npt, DEV.newt_tol, pp, point_pos_ordering, pgslm, NE_split_total, pwt,
pbb, DEV.lh_nx, plhm, plhf, plho, pcode, pelem, pref, pdist,
pgll1d, plc);
case 3:
FindPointsLocal2DKernel<3>(npt, DEV.newt_tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsLocal2D_Kernel<3>(
npt, DEV.newt_tol, pp, point_pos_ordering, pgslm, NE_split_total, pwt,
pbb, DEV.lh_nx, plhm, plhf, plho, pcode, pelem, pref, pdist,
pgll1d, plc);
case 4:
FindPointsLocal2DKernel<4>(npt, DEV.newt_tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsLocal2D_Kernel<4>(
npt, DEV.newt_tol, pp, point_pos_ordering, pgslm, NE_split_total, pwt,
pbb, DEV.lh_nx, plhm, plhf, plho, pcode, pelem, pref, pdist,
pgll1d, plc);
case 5:
FindPointsLocal2DKernel<5>(npt, DEV.newt_tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsLocal2D_Kernel<5>(
npt, DEV.newt_tol, pp, point_pos_ordering, pgslm, NE_split_total, pwt,
pbb, DEV.lh_nx, plhm, plhf, plho, pcode, pelem, pref, pdist,
pgll1d, plc);
default:
FindPointsLocal2DKernel(npt, DEV.newt_tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc, DEV.dof1d);
break;
return FindPointsLocal2D_Kernel(npt, DEV.newt_tol, pp, point_pos_ordering,
pgslm, NE_split_total, pwt, pbb, DEV.lh_nx,
plhm, plhf, plho, pcode, pelem,
pref, pdist, pgll1d, plc, DEV.dof1d);
}
}
#undef DIM2
+157 -29
View File
@@ -11,7 +11,9 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#include "../../linalg/kernels.hpp"
#include <climits>
#ifdef MFEM_USE_GSLIB
@@ -57,15 +59,128 @@ struct findptsElemPt
double x[DIM], jac[DIM * DIM], hes[18];
};
using dbl_range_t = gslib::dbl_range_t;
using obbox_t = gslib::obbox_t<DIM>;
using findptsLocalHashData_t = gslib::findptsLocalHashData_t<DIM>;
using gslib::bbox_test;
using gslib::hash_index;
using gslib::l2norm2;
using gslib::lag_eval_first_der;
using gslib::lag_eval_second_der;
using gslib::lin_solve_sym_2;
struct dbl_range_t
{
double min, max;
};
struct obbox_t
{
double c0[DIM], A[DIM * DIM];
dbl_range_t x[DIM];
};
struct findptsLocalHashData_t
{
int hash_n;
dbl_range_t bnd[DIM];
double fac[DIM];
unsigned int *offset;
// int max;
};
// Eval the ith Lagrange interpolant and its first derivative at x.
// Note: lCoeff stores pre-computed coefficients for fast evaluation.
static MFEM_HOST_DEVICE inline void lag_eval_first_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
double d_j = 2*(x-z[j]);
u1 = d_j*u1+u0;
u0 = d_j*u0;
}
}
p0[i] = lCoeff[i]*u0;
p0[pN+i] = 2.0*lCoeff[i]*u1;
}
// Eval the ith Lagrange interpolant and its first and second derivative at x.
// Note: lCoeff stores pre-computed coefficients for fast evaluation.
static MFEM_HOST_DEVICE inline void lag_eval_second_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0, u2 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
double d_j = 2*(x-z[j]);
u2 = d_j*u2+u1;
u1 = d_j*u1+u0;
u0 = d_j*u0;
}
}
p0[i] = lCoeff[i]*u0;
p0[pN+i] = 2.0*lCoeff[i]*u1;
p0[2*pN+i] = 8.0*lCoeff[i]*u2;
}
// Axis-aligned bounding box test.
static MFEM_HOST_DEVICE inline double AABB_test(const obbox_t *const b,
const double x[3])
{
double b_d;
for (int d = 0; d < 3; ++d)
{
b_d = (x[d]-b->x[d].min)*(b->x[d].max-x[d]);
if (b_d < 0) { return b_d; }
}
return b_d;
}
// Axis-aligned bounding box test followed by oriented bounding-box test.
static MFEM_HOST_DEVICE inline double bbox_test(const obbox_t *const b,
const double x[3])
{
const double bxyz = AABB_test(b, x);
if (bxyz < 0)
{
return bxyz;
}
else
{
double dxyz[3];
for (int d = 0; d < 3; ++d)
{
dxyz[d] = x[d]-b->c0[d];
}
double test = 1;
for (int d = 0; d < 3; ++d)
{
double rst = 0;
for (int e = 0; e < 3; ++e)
{
rst += b->A[d*3+e]*dxyz[e];
}
double brst = (rst+1)*(1-rst);
test = test < 0 ? test : brst;
}
return test;
}
}
// Element index corresponding to hash mesh that the point is located in.
static MFEM_HOST_DEVICE inline int hash_index(const findptsLocalHashData_t *p,
const double x[3])
{
const int n = p->hash_n;
int sum = 0;
for (int d = 3-1; d >= 0; --d)
{
sum *= n;
int i = (int)floor((x[d]-p->bnd[d].min)*p->fac[d]);
sum += i < 0 ? 0 : (n-1 < i ? n-1 : i);
}
return sum;
}
// Solve Ax=y. A is row-major.
static MFEM_HOST_DEVICE inline void lin_solve_3(double x[3], const double A[9],
@@ -84,6 +199,22 @@ static MFEM_HOST_DEVICE inline void lin_solve_3(double x[3], const double A[9],
x[2] = idet*(inv6*y[0]+inv7*y[1]+inv8*y[2]);
}
// Solve Ax=y. A is a symmetric 2x2 matrix.
static MFEM_HOST_DEVICE inline void lin_solve_sym_2(double x[2],
const double A[3],
const double y[2])
{
const double idet = 1 / (A[0]*A[2]-A[1]*A[1]);
x[0] = idet*(A[2]*y[0]-A[1]*y[1]);
x[1] = idet*(A[0]*y[1]-A[1]*y[0]);
}
// L2 norm.
static MFEM_HOST_DEVICE inline double l2norm2(const double x[3])
{
return x[0]*x[0]+x[1]*x[1]+x[2]*x[2];
}
/* the bit structure of flags is CTTSSRR
the C bit --- 1<<6 --- is set when the point is converged
RR is 0 = 00b if r is unconstrained,
@@ -328,7 +459,7 @@ static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsPt *res,
const findptsPt *p,
const double tol)
{
const double dist2 = l2norm2<3>(resid);
const double dist2 = l2norm2(resid);
const double decr = p->dist2-dist2;
const double pred = p->dist2p;
for (int d = 0; d < 3; ++d)
@@ -1678,36 +1809,33 @@ void FindPointsGSLIB::FindPointsLocal3(const Vector &point_pos,
{
case 2:
FindPointsLocal3DKernel<2>(npt, DEV.newt_tol, pp, point_pos_ordering,
pgslm, NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist, pgll1d, plc);
pgslm, NE_split_total, pwt, pbb, DEV.lh_nx, plhm,
plhf, plho, pcode, pelem, pref, pdist, pgll1d,
plc);
break;
case 3:
FindPointsLocal3DKernel<3>(npt, DEV.newt_tol, pp, point_pos_ordering,
pgslm, NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist, pgll1d, plc);
pgslm, NE_split_total, pwt, pbb, DEV.lh_nx, plhm,
plhf, plho, pcode, pelem, pref, pdist, pgll1d,
plc);
break;
case 4:
FindPointsLocal3DKernel<4>(npt, DEV.newt_tol, pp, point_pos_ordering,
pgslm, NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist, pgll1d, plc);
pgslm, NE_split_total, pwt, pbb, DEV.lh_nx, plhm,
plhf, plho, pcode, pelem, pref, pdist, pgll1d,
plc);
break;
case 5:
FindPointsLocal3DKernel<5>(npt, DEV.newt_tol, pp, point_pos_ordering,
pgslm, NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist, pgll1d, plc);
pgslm, NE_split_total, pwt, pbb, DEV.lh_nx, plhm,
plhf, plho, pcode, pelem, pref, pdist, pgll1d,
plc);
break;
default:
FindPointsLocal3DKernel(npt, DEV.newt_tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist, pgll1d, plc,
FindPointsLocal3DKernel(npt, DEV.newt_tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc,
DEV.dof1d);
break;
}
}
#undef pMax
+176 -107
View File
@@ -11,7 +11,6 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
@@ -53,14 +52,113 @@ struct findptsElementGPT_t
double x[sDIM], jac[sDIM*rDIM], hes[sDIM*rDIM];
};
using dbl_range_t = gslib::dbl_range_t;
using obbox_t = gslib::obbox_t<sDIM>;
using findptsLocalHashData_t = gslib::findptsLocalHashData_t<sDIM>;
using gslib::AABB_test;
using gslib::bbox_test;
using gslib::hash_index;
using gslib::l2norm2;
using gslib::lag_eval_second_der;
struct dbl_range_t
{
double min, max;
};
struct obbox_t
{
double c0[sDIM], A[sDIM*sDIM];
dbl_range_t x[sDIM];
};
struct findptsLocalHashData_t
{
int hash_n;
dbl_range_t bnd[sDIM];
double fac[sDIM];
unsigned int *offset;
};
static MFEM_HOST_DEVICE inline void lag_eval_second_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0, u2 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
double d_j = 2 * (x-z[j]);
u2 = d_j * u2 + u1;
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
double *p1 = p0 + pN, *p2 = p0 + 2 * pN;
p0[i] = lCoeff[i] * u0;
p1[i] = 2.0 * lCoeff[i] * u1;
p2[i] = 8.0 * lCoeff[i] * u2;
}
/* positive when possibly inside */
static MFEM_HOST_DEVICE inline double obbox_axis_test(const obbox_t *const b,
const double x[sDIM])
{
double b_d;
for (int d=0; d<sDIM; ++d)
{
b_d = (x[d] - b->x[d].min) * (b->x[d].max - x[d]);
if (b_d < 0) // if outside in any dimension
{
return b_d;
}
}
return b_d; // only positive if inside
}
/* positive when given point is possibly inside given obbox b */
static MFEM_HOST_DEVICE inline double obbox_test(const obbox_t *const b,
const double x[sDIM])
{
const double bxyz = obbox_axis_test(b,x);
if (bxyz<0) // test if point is in AABB
{
return bxyz;
}
else // test OBB only if inside AABB
{
double dxyz[sDIM];
for (int d=0; d<sDIM; ++d)
{
dxyz[d] = x[d] - b->c0[d];
}
double test = 1;
for (int d=0; d<sDIM; ++d)
{
double rst = 0;
for (int e=0; e<sDIM; ++e)
{
rst += b->A[d*2 + e] * dxyz[e];
}
double brst = (rst+1)*(1-rst);
test = test<0 ? test : brst;
}
return test;
}
}
/* Hash index in the hash table to the elements that possibly contain the point x */
static MFEM_HOST_DEVICE inline int hash_index(const findptsLocalHashData_t *p,
const double x[2])
{
const int n = p->hash_n;
int sum = 0;
for (int d=sDIM-1; d>=0; --d)
{
sum *= n;
int i = (int)floor((x[d] - p->bnd[d].min) * p->fac[d]);
sum += i<0 ? 0 : (n-1 < i ? n-1 : i);
}
return sum;
}
static MFEM_HOST_DEVICE inline double l2norm2(const double x[2])
{
return x[0] * x[0] + x[1] * x[1];
}
/* the bit structure of flags is CRR
the C bit --- 1<<2 --- is set when the point is converged
@@ -89,29 +187,29 @@ static MFEM_HOST_DEVICE inline int point_index(const int x)
/* check reduction in objective against prediction, and adjust
trust region radius (p->tr) accordingly;
may reject the prior step, returning 1; otherwise returns 0
sets out_pt->dist2, out_pt->index, out_pt->x, out_pt->oldr in any event,
leaving out_pt->r, out_pt->dr, out_pt->flags to be set when returning 0 */
static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out_pt,
sets out->dist2, out->index, out->x, out->oldr in any event,
leaving out->r, out->dr, out->flags to be set when returning 0 */
static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out,
const double resid[2],
const findptsElementPoint_t *p,
const double tol)
{
const double dist2 = l2norm2<2>(resid);
const double dist2 = l2norm2(resid);
const double decr = p->dist2 - dist2;
const double pred = p->dist2p;
out_pt->x[0] = p->x[0];
out_pt->x[1] = p->x[1];
out_pt->oldr = p->r;
out_pt->dist2 = dist2;
out->x[0] = p->x[0];
out->x[1] = p->x[1];
out->oldr = p->r;
out->dist2 = dist2;
if (decr >= 0.01*pred)
{
if (decr >= 0.9*pred) // very good iteration
{
out_pt->tr = p->tr*2;
out->tr = p->tr*2;
}
else // somewhat good iteration
{
out_pt->tr = p->tr;
out->tr = p->tr;
}
return false;
}
@@ -122,21 +220,21 @@ static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out_pt,
"very good iteration" --- this doubles the trust radius,
which is why we divide by 4 below */
double v0 = fabs(p->r - p->oldr);
out_pt->tr = v0/4.0;
out_pt->dist2 = p->dist2;
out_pt->r = p->oldr;
out_pt->flags = p->flags>>3;
out_pt->dist2p = -HUGE_VAL;
out->tr = v0/4.0;
out->dist2 = p->dist2;
out->r = p->oldr;
out->flags = p->flags>>3;
out->dist2p = -HUGE_VAL;
if (pred < dist2*tol)
{
out_pt->flags |= CONVERGED_FLAG;
out->flags |= CONVERGED_FLAG;
}
return true;
}
}
static MFEM_HOST_DEVICE inline void newton_edge( findptsElementPoint_t *const
out_pt,
out,
const double jac[2],
const double rhess,
const double resid[2],
@@ -206,9 +304,9 @@ newton_edge_fin:
{
new_flags |= CONVERGED_FLAG;
}
out_pt->r = newr;
out_pt->dist2p = -v;
out_pt->flags = flags | new_flags | ((p->flags & FLAG_MASK)<<3);
out->r = newr;
out->dist2p = -v;
out->flags = flags | new_flags | ((p->flags & FLAG_MASK)<<3);
}
static MFEM_HOST_DEVICE void seed_j( const double *elx[sDIM],
@@ -234,27 +332,26 @@ static MFEM_HOST_DEVICE void seed_j( const double *elx[sDIM],
}
template<int T_D1D = 0>
static void FindPointsEdgeLocal2DKernel( const int npt,
const double tol,
const double dist2tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const bool obb_check,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0 )
static void FindPointsEdgeLocal2D_Kernel( const int npt,
const double tol,
const double dist2tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0 )
{
const int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
const int D1D = T_D1D ? T_D1D : pN;
@@ -315,34 +412,22 @@ static void FindPointsEdgeLocal2DKernel( const int npt,
{
const unsigned int el = *elp;
const int n_box_ents = obb_check ? (3*sDIM + sDIM2) : (2*sDIM);
bool pass_bb = true;
obbox_t box;
if (obb_check)
int n_box_ents = 3*sDIM + sDIM2;
for (int idx = 0; idx < sDIM; ++idx)
{
for (int idx = 0; idx < sDIM; ++idx)
{
box.c0[idx] = boxinfo[n_box_ents*el + idx];
box.x[idx].min = boxinfo[n_box_ents*el + sDIM + idx];
box.x[idx].max = boxinfo[n_box_ents*el + 2*sDIM + idx];
}
for (int idx = 0; idx < sDIM2; ++idx)
{
box.A[idx] = boxinfo[n_box_ents*el + 3*sDIM + idx];
}
pass_bb = (bbox_test(&box, x_i) >= 0);
}
else
{
for (int d = 0; d < sDIM; ++d)
{
box.x[d].min = boxinfo[n_box_ents*el + d];
box.x[d].max = boxinfo[n_box_ents*el + sDIM + d];
}
pass_bb = (AABB_test(&box, x_i) >= 0);
box.c0[idx] = boxinfo[n_box_ents*el + idx];
box.x[idx].min = boxinfo[n_box_ents*el + sDIM + idx];
box.x[idx].max = boxinfo[n_box_ents*el + 2*sDIM + idx];
}
if (pass_bb)
for (int idx = 0; idx < sDIM2; ++idx)
{
box.A[idx] = boxinfo[n_box_ents*el + 3*sDIM + idx];
}
if (obbox_test(&box,x_i)>=0)
{
//------------ findpts_local ------------------
{
@@ -431,14 +516,11 @@ static void FindPointsEdgeLocal2DKernel( const int npt,
double *hess = jac + sDIM*rDIM;
findptsElementGEdge_t edge;
for (int d=0; d<sDIM; ++d)
{
edge.x[d] = constraint_workspace + d*D1D;
}
MFEM_FOREACH_THREAD(j,x,D1D)
{
for (int d=0; d<sDIM; ++d)
{
edge.x[d] = constraint_workspace + d*D1D;
edge.x[d][j] = elx[d][j];
}
}
@@ -599,41 +681,28 @@ void FindPointsGSLIB::FindPointsEdgeLocal2( const Vector &point_pos,
auto pgll1d = DEV.gll1d.ReadWrite(use_dev);
auto plc = DEV.lagcoeff.Read(use_dev);
double dist2tol = DEV.surf_dist_tol;
const bool obb_chk = obb_check;
switch (DEV.dof1d)
{
case 2:
FindPointsEdgeLocal2DKernel<2>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsEdgeLocal2D_Kernel<2>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
case 3:
FindPointsEdgeLocal2DKernel<3>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsEdgeLocal2D_Kernel<3>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
case 4:
FindPointsEdgeLocal2DKernel<4>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsEdgeLocal2D_Kernel<4>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
default:
FindPointsEdgeLocal2DKernel(npt, DEV.newt_tol, dist2tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc, DEV.dof1d);
break;
return FindPointsEdgeLocal2D_Kernel(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc, DEV.dof1d);
}
}
#undef sDIM
+181 -109
View File
@@ -11,7 +11,6 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
@@ -55,14 +54,117 @@ struct findptsElementGPT_t
double x[sDIM], jac[sDIM], hes[sDIM*(1+1)];
};
using dbl_range_t = gslib::dbl_range_t;
using obbox_t = gslib::obbox_t<sDIM>;
using findptsLocalHashData_t = gslib::findptsLocalHashData_t<sDIM>;
using gslib::AABB_test;
using gslib::bbox_test;
using gslib::hash_index;
using gslib::l2norm2;
using gslib::lag_eval_second_der;
struct dbl_range_t
{
double min, max;
};
struct obbox_t
{
double c0[sDIM], A[sDIM*sDIM];
dbl_range_t x[sDIM];
};
struct findptsLocalHashData_t
{
int hash_n;
dbl_range_t bnd[sDIM];
double fac[sDIM];
unsigned int *offset;
};
static MFEM_HOST_DEVICE inline void lag_eval_second_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0, u2 = 0;
for (int j=0; j<pN; ++j)
{
if (i!=j)
{
double d_j = 2 * (x-z[j]);
u2 = d_j * u2 + u1;
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
double *p1 = p0 + pN, *p2 = p0 + 2 * pN;
p0[i] = lCoeff[i] * u0;
p1[i] = 2.0 * lCoeff[i] * u1;
p2[i] = 8.0 * lCoeff[i] * u2;
}
/* positive when possibly inside */
static MFEM_HOST_DEVICE inline double obbox_axis_test(const obbox_t *const b,
const double x[sDIM])
{
double b_d;
for (int d=0; d<sDIM; ++d)
{
b_d = (x[d] - b->x[d].min) * (b->x[d].max - x[d]);
if (b_d < 0) // if outside in any dimension
{
return b_d;
}
}
return b_d; // only positive if inside in all dimensions
}
/* positive when possibly inside */
static MFEM_HOST_DEVICE inline double obbox_test(const obbox_t *const b,
const double x[sDIM])
{
const double bxyz = obbox_axis_test(b, x);
if (bxyz<0)
{
return bxyz;
}
else
{
double dxyz[3];
// dxyz: distance of the point from the center of the OBB
for (int d=0; d<sDIM; ++d)
{
dxyz[d] = x[d] - b->c0[d];
}
// transform dxyz to the local coordinate system of the OBB,
// and check if the point is inside the OBB [-1,1]^sDIM
double test = 1;
for (int d=0; d<sDIM; ++d)
{
double rst = 0;
for (int e=0; e<sDIM; ++e)
{
rst += b->A[d*sDIM + e] * dxyz[e];
}
double brst = (rst+1)*(1-rst);
test = test<0 ? test : brst;
}
return test;
}
}
/* Hash index in the hash table to the elements that possibly contain the point x */
static MFEM_HOST_DEVICE inline int hash_index(const findptsLocalHashData_t *p,
const double x[sDIM])
{
const int n = p->hash_n;
int sum = 0;
for (int d=sDIM-1; d>=0; --d)
{
sum *= n;
int i = (int)floor((x[d] - p->bnd[d].min) * p->fac[d]);
sum += i<0 ? 0 : (n-1 < i ? n-1 : i);
}
return sum;
}
static MFEM_HOST_DEVICE inline double norm2(const double x[sDIM])
{
return ( x[0]*x[0] + x[1]*x[1] + x[2]*x[2] );
}
/* the bit structure of flags is CRR
the C bit --- 1<<2 --- is set when the point is converged
@@ -73,46 +175,47 @@ using gslib::lag_eval_second_der;
#define CONVERGED_FLAG (1u<<2)
#define FLAG_MASK 0x07u
/* returns the number of constrained reference coordinates, max 1
/* returns the number of constrained reference coordinates, max 2
*/
static MFEM_HOST_DEVICE inline int num_constrained(const int flags)
{
return ((flags | flags>>1) & 1u);
const int y = (flags | flags>>1);
return (y & 1u) + (y>>2 & 1u);
}
static MFEM_HOST_DEVICE inline int point_index(const int x)
{
return ((x>>1)&1u);
return ((x>>1)&1u) | ((x>>2)&2u);
}
/* check reduction in objective against prediction, and adjust
trust region radius (p->tr) accordingly;
may reject the prior step, returning 1; otherwise returns 0
sets out_pt->dist2, out_pt->index, out_pt->x, out_pt->oldr in any event,
leaving out_pt->r, out_pt->dr, out_pt->flags to be set when returning 0 */
static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out_pt,
sets out->dist2, out->index, out->x, out->oldr in any event,
leaving out->r, out->dr, out->flags to be set when returning 0 */
static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out,
const double resid[3],
const findptsElementPoint_t *p,
const double tol)
{
const double dist2 = l2norm2<sDIM>(resid);
const double dist2 = norm2(resid);
const double decr = p->dist2 - dist2;
const double pred = p->dist2p;
for (int d=0; d<sDIM; ++d)
{
out_pt->x[d] = p->x[d];
out->x[d] = p->x[d];
}
out_pt->oldr = p->r;
out_pt->dist2 = dist2;
out->oldr = p->r;
out->dist2 = dist2;
if (decr>=0.01*pred)
{
if (decr>=0.9*pred) // very good iteration
{
out_pt->tr = 2*p->tr;
out->tr = 2*p->tr;
}
else // good iteration
{
out_pt->tr = p->tr;
out->tr = p->tr;
}
return false;
}
@@ -123,21 +226,21 @@ static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out_pt,
"very good iteration" --- this doubles the trust radius,
which is why we divide by 4 below */
double v0 = fabs(p->r - p->oldr);
out_pt->tr = v0/4.0;
out_pt->dist2 = p->dist2;
out_pt->r = p->oldr;
out_pt->flags = p->flags>>3;
out_pt->dist2p = -HUGE_VAL;
out->tr = v0/4.0;
out->dist2 = p->dist2;
out->r = p->oldr;
out->flags = p->flags>>3;
out->dist2p = -HUGE_VAL;
if (pred<dist2*tol)
{
out_pt->flags |= CONVERGED_FLAG;
out->flags |= CONVERGED_FLAG;
}
return true;
}
}
static MFEM_HOST_DEVICE inline void newton_edge(findptsElementPoint_t *const
out_pt,
out,
const double jac[sDIM*rDIM],
const double rhes,
const double resid[sDIM],
@@ -211,9 +314,9 @@ newton_edge_fin:
{
new_flags |= CONVERGED_FLAG;
}
out_pt->r = nr;
out_pt->dist2p = -v;
out_pt->flags = flags | new_flags | ((p->flags & FLAG_MASK)<<3);
out->r = nr;
out->dist2p = -v;
out->flags = flags | new_flags | ((p->flags & FLAG_MASK)<<3);
#undef EVAL
}
@@ -235,32 +338,31 @@ static MFEM_HOST_DEVICE void seed_j(const double *elx[sDIM],
{
dx[d] = x[d] - elx[d][ir];
}
dist2[ir] = l2norm2(dx);
dist2[ir] = norm2(dx);;
r[ir] = z[ir];
}
template<int T_D1D = 0>
static void FindPointsEdgeLocal3DKernel(const int npt,
const double tol,
const double dist2tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const bool obb_check,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0)
static void FindPointsEdgeLocal3D_Kernel(const int npt,
const double tol,
const double dist2tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0)
{
const int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
const int D1D = T_D1D ? T_D1D : pN;
@@ -317,35 +419,21 @@ static void FindPointsEdgeLocal3DKernel(const int npt,
for (; elp!=ele; ++elp)
{
const unsigned int el = *elp;
const int n_box_ents = obb_check ? (3*sDIM + sDIM2) : (2*sDIM);
bool pass_bb = true;
obbox_t box;
if (obb_check)
int n_box_ents = 3*sDIM + sDIM2;
for (int idx = 0; idx < sDIM; ++idx)
{
for (int idx = 0; idx < sDIM; ++idx)
{
box.c0[idx] = boxinfo[n_box_ents*el + idx];
box.x[idx].min = boxinfo[n_box_ents*el + sDIM + idx];
box.x[idx].max = boxinfo[n_box_ents*el + 2*sDIM + idx];
}
for (int idx = 0; idx < sDIM2; ++idx)
{
box.A[idx] = boxinfo[n_box_ents*el + 3*sDIM + idx];
}
pass_bb = (bbox_test(&box, x_i) >= 0);
box.c0[idx] = boxinfo[n_box_ents*el + idx];
box.x[idx].min = boxinfo[n_box_ents*el + sDIM + idx];
box.x[idx].max = boxinfo[n_box_ents*el + 2*sDIM + idx];
}
else
for (int idx = 0; idx < sDIM2; ++idx)
{
for (int d = 0; d < sDIM; ++d)
{
box.x[d].min = boxinfo[n_box_ents*el + d];
box.x[d].max = boxinfo[n_box_ents*el + sDIM + d];
}
pass_bb = (AABB_test(&box, x_i) >= 0);
box.A[idx] = boxinfo[n_box_ents*el + 3*sDIM + idx];
}
if (pass_bb)
if (obbox_test(&box, x_i)>=0)
{
//// findpts_local ////
{
@@ -433,14 +521,11 @@ static void FindPointsEdgeLocal3DKernel(const int npt,
double *hess = jac + sDIM*rDIM;
findptsElementGEdge_t edge;
for (int d=0; d<sDIM; ++d)
{
edge.x[d] = constraint_workspace + d*D1D;
}
MFEM_FOREACH_THREAD(j,x,D1D)
{
for (int d=0; d<sDIM; ++d)
{
edge.x[d] = constraint_workspace + d*D1D;
edge.x[d][j] = elx[d][j];
}
}
@@ -603,41 +688,28 @@ void FindPointsGSLIB::FindPointsEdgeLocal3(const Vector &point_pos,
auto pgll1d = DEV.gll1d.ReadWrite(use_dev);
auto plc = DEV.lagcoeff.Read(use_dev);
double dist2tol = DEV.surf_dist_tol;
const bool obb_chk = obb_check;
switch (DEV.dof1d)
{
case 2:
FindPointsEdgeLocal3DKernel<2>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsEdgeLocal3D_Kernel<2>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
case 3:
FindPointsEdgeLocal3DKernel<3>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsEdgeLocal3D_Kernel<3>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
case 4:
FindPointsEdgeLocal3DKernel<4>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsEdgeLocal3D_Kernel<4>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
default:
FindPointsEdgeLocal3DKernel(npt, DEV.newt_tol, dist2tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc, DEV.dof1d);
break;
return FindPointsEdgeLocal3D_Kernel(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc, DEV.dof1d);
}
}
#undef rDIM2
+206 -131
View File
@@ -11,7 +11,6 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
#ifdef MFEM_HAVE_GCC_PRAGMA_DIAGNOSTIC
@@ -52,15 +51,124 @@ struct findptsElementGPT_t
double x[sDIM], jac[sDIM*rDIM], hes[sDIM*(rDIM+1)];
};
using dbl_range_t = gslib::dbl_range_t;
using obbox_t = gslib::obbox_t<sDIM>;
using findptsLocalHashData_t = gslib::findptsLocalHashData_t<sDIM>;
using gslib::AABB_test;
using gslib::bbox_test;
using gslib::hash_index;
using gslib::l2norm2;
using gslib::lag_eval_second_der;
using gslib::lin_solve_sym_2;
struct dbl_range_t
{
double min, max;
};
struct obbox_t
{
double c0[sDIM], A[sDIM*sDIM];
dbl_range_t x[sDIM];
};
struct findptsLocalHashData_t
{
int hash_n;
dbl_range_t bnd[sDIM];
double fac[sDIM];
unsigned int *offset;
};
static MFEM_HOST_DEVICE inline void lag_eval_second_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0, u2 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
double d_j = 2 * (x - z[j]);
u2 = d_j * u2 + u1;
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
p0[i] = lCoeff[i] * u0;
p0[pN+i] = 2.0 * lCoeff[i] * u1;
p0[2*pN+i] = 8.0 * lCoeff[i] * u2;
}
/* positive when possibly inside */
static MFEM_HOST_DEVICE inline double AABB_test(const obbox_t *const b,
const double x[sDIM])
{
double b_d;
for (int d=0; d<sDIM; ++d)
{
b_d = (x[d] - b->x[d].min) * (b->x[d].max - x[d]);
if (b_d < 0) // if outside in any dimension
{
return b_d;
}
}
return b_d; // only positive if inside in all dimensions
}
/* positive when possibly inside */
static MFEM_HOST_DEVICE inline double bbox_test(const obbox_t *const b,
const double x[sDIM])
{
const double bxyz = AABB_test(b, x);
if (bxyz<0)
{
return bxyz;
}
else
{
double dxyz[3];
// dxyz: distance of the point from the center of the OBB
for (int d=0; d<sDIM; ++d)
{
dxyz[d] = x[d] - b->c0[d];
}
// tranform dxyz to the local coordinate system of the OBB,
// and check if the point is inside the OBB [-1,1]^sDIM
double test = 1;
for (int d=0; d<sDIM; ++d)
{
double rst = 0;
for (int e=0; e<sDIM; ++e)
{
rst += b->A[d*sDIM + e] * dxyz[e];
}
double brst = (rst+1)*(1-rst);
test = test<0 ? test : brst;
}
return test;
}
}
/* Hash index in the hash table to the elements that possibly contain the point x */
static MFEM_HOST_DEVICE inline int hash_index(const findptsLocalHashData_t *p,
const double x[sDIM])
{
const int n = p->hash_n;
int sum = 0;
for (int d=sDIM-1; d>=0; --d)
{
sum *= n;
int i = (int)floor((x[d] - p->bnd[d].min) * p->fac[d]);
sum += i<0 ? 0 : (n-1 < i ? n-1 : i);
}
return sum;
}
static MFEM_HOST_DEVICE inline void lin_solve_sym_2(double x[2],
const double A[3],
const double y[2])
{
const double idet = 1 / (A[0] * A[2] - A[1] * A[1]);
x[0] = idet * (A[2] * y[0] - A[1] * y[1]);
x[1] = idet * (A[0] * y[1] - A[1] * y[0]);
}
static MFEM_HOST_DEVICE inline double l2norm2(const double x[sDIM])
{
return ( x[0]*x[0] + x[1]*x[1] + x[2]*x[2]);
}
/* the bit structure of flags is CSSRR
the C bit --- 1<<4 --- is set when the point is converged
@@ -111,10 +219,18 @@ static MFEM_HOST_DEVICE inline int point_index(const int x)
return ((x>>1)&1u) | ((x>>2)&2u);
}
static MFEM_HOST_DEVICE inline void
static MFEM_HOST_DEVICE inline findptsElementGEdge_t
get_edge(const double *elx[3], const double *wtend, int ei,
int &side_init, int jidx, int pN, findptsElementGEdge_t &edge)
double *workspace, int &side_init, int jidx, int pN)
{
findptsElementGEdge_t edge;
for (int d=0; d<sDIM; ++d)
{
edge.x[d] = workspace + d*pN;
edge.dxdn[d] = workspace + sDIM*pN + d*pN;
edge.d2xdn[d] = workspace + 2*sDIM*pN + d*pN;
}
// given edge index, compute normal and tangential directions
const int dn = ei>>1, //0 for rmin/rmax, 1 for smin/smax
de = plus_1_mod_2(dn); // 1 for rmin/rmax, 0 for smin/smax
@@ -140,6 +256,7 @@ get_edge(const double *elx[3], const double *wtend, int ei,
edge.d2xdn[dd][jj] = sums_k[1];
#undef ELX
}
return edge;
}
static MFEM_HOST_DEVICE inline findptsElementGPT_t get_pt(const double *elx[3],
@@ -195,34 +312,34 @@ static MFEM_HOST_DEVICE inline findptsElementGPT_t get_pt(const double *elx[3],
/* check reduction in objective against prediction, and adjust
trust region radius (p->tr) accordingly;
may reject the prior step, returning 1; otherwise returns 0
sets out_pt->dist2, out_pt->index, out_pt->x, out_pt->oldr in any event,
leaving out_pt->r, out_pt->dr, out_pt->flags to be set when returning 0 */
static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out_pt,
sets out->dist2, out->index, out->x, out->oldr in any event,
leaving out->r, out->dr, out->flags to be set when returning 0 */
static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out,
const double resid[3],
const findptsElementPoint_t *p,
const double tol)
{
const double dist2 = l2norm2<sDIM>(resid);
const double dist2 = l2norm2(resid);
const double decr = p->dist2 - dist2;
const double pred = p->dist2p;
for (int d=0; d<sDIM; ++d)
{
out_pt->x[d] = p->x[d];
out->x[d] = p->x[d];
}
for (int d=0; d<rDIM; ++d)
{
out_pt->oldr[d] = p->r[d];
out->oldr[d] = p->r[d];
}
out_pt->dist2 = dist2;
out->dist2 = dist2;
if (decr>=0.01*pred)
{
if (decr>=0.9*pred) // very good iteration
{
out_pt->tr = 2*p->tr;
out->tr = 2*p->tr;
}
else // good iteration
{
out_pt->tr = p->tr;
out->tr = p->tr;
}
return false;
}
@@ -234,17 +351,17 @@ static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out_pt,
which is why we divide by 4 below */
double v0 = fabs(p->r[0] - p->oldr[0]),
v1 = fabs(p->r[1] - p->oldr[1]);
out_pt->tr = ( v0>v1 ? v0 : v1 )/4;
out_pt->dist2 = p->dist2;
out_pt->flags = p->flags >> 5;
out_pt->dist2p = -HUGE_VAL;
out->tr = ( v0>v1 ? v0 : v1 )/4;
out->dist2 = p->dist2;
out->flags = p->flags >> 5;
out->dist2p = -HUGE_VAL;
for (int d=0; d<rDIM; ++d)
{
out_pt->r[d] = p->oldr[d];
out->r[d] = p->oldr[d];
}
if (pred<dist2*tol)
{
out_pt->flags |= CONVERGED_FLAG;
out->flags |= CONVERGED_FLAG;
}
return true;
}
@@ -252,7 +369,7 @@ static MFEM_HOST_DEVICE bool reject_prior_step_q(findptsElementPoint_t *out_pt,
/* minimize ||resid - jac * dr||_2, with |dr| <= tr, |r0+dr|<=1
(exact solution of trust region problem) */
static MFEM_HOST_DEVICE void newton_face( findptsElementPoint_t *const out_pt,
static MFEM_HOST_DEVICE void newton_face( findptsElementPoint_t *const out,
const double jac[sDIM*rDIM],
const double rhes[3],
const double resid[sDIM],
@@ -423,19 +540,19 @@ newton_face_constrained:
}
newton_face_fin:
out_pt->dist2p = -2*v;
out->dist2p = -2*v;
dr[0] = r[0] - p->r[0];
dr[1] = r[1] - p->r[1];
if ( fabs(dr[0])+fabs(dr[1]) < tol)
{
new_flags |= CONVERGED_FLAG;
}
out_pt->r[0] = r[0], out_pt->r[1] = r[1];
out_pt->flags = new_flags | ((p->flags & FLAG_MASK)<<5);
out->r[0] = r[0], out->r[1] = r[1];
out->flags = new_flags | ((p->flags & FLAG_MASK)<<5);
}
static MFEM_HOST_DEVICE inline void newton_edge(findptsElementPoint_t *const
out_pt,
out,
const double jac[sDIM*rDIM],
const double rhes,
const double resid[sDIM],
@@ -520,10 +637,10 @@ newton_edge_fin:
{
new_flags |= CONVERGED_FLAG;
}
out_pt->r[de] = nr;
out_pt->r[dn] = p->r[dn];
out_pt->dist2p = -v;
out_pt->flags = flags | new_flags | ((p->flags & FLAG_MASK)<<5);
out->r[de] = nr;
out->r[dn] = p->r[dn];
out->dist2p = -v;
out->flags = flags | new_flags | ((p->flags & FLAG_MASK)<<5);
#undef EVAL
}
@@ -559,27 +676,26 @@ static MFEM_HOST_DEVICE void seed_j(const double *elx[sDIM],
// global memory access of element coordinates.
// Are the structs being stored in "local memory" or registers?
template<int T_D1D = 0>
static void FindPointsSurfLocal3DKernel(const int npt,
const double tol,
const double dist2tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const bool obb_check,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0)
static void FindPointsSurfLocal3D_Kernel(const int npt,
const double tol,
const double dist2tol,
const double *x,
const int point_pos_ordering,
const double *xElemCoord,
const int nel,
const double *wtend,
const double *boxinfo,
const int hash_n,
const double *hashMin,
const double *hashFac,
unsigned int *hashOffset,
unsigned int *const code_base,
unsigned int *const el_base,
double *const r_base,
double *const dist2_base,
const double *gll1D,
const double *lagcoeff,
const int pN = 0)
{
const int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
const int D1D = T_D1D ? T_D1D : pN;
@@ -637,36 +753,22 @@ static void FindPointsSurfLocal3DKernel(const int npt,
{
const unsigned int el = *elp;
const int n_box_ents = obb_check ? (3*sDIM + sDIM2) : (2*sDIM);
bool pass_bb = true;
// construct obbox on the fly
obbox_t box;
if (obb_check)
int n_box_ents = 3*sDIM + sDIM2;
for (int idx = 0; idx < sDIM; ++idx)
{
// construct obbox on the fly
for (int idx = 0; idx < sDIM; ++idx)
{
box.c0[idx] = boxinfo[n_box_ents*el + idx];
box.x[idx].min = boxinfo[n_box_ents*el + sDIM + idx];
box.x[idx].max = boxinfo[n_box_ents*el + 2*sDIM + idx];
}
for (int idx = 0; idx < sDIM2; ++idx)
{
box.A[idx] = boxinfo[n_box_ents*el + 3*sDIM + idx];
}
pass_bb = (bbox_test(&box, x_i) >= 0);
}
else
{
for (int d = 0; d < sDIM; ++d)
{
box.x[d].min = boxinfo[n_box_ents*el + d];
box.x[d].max = boxinfo[n_box_ents*el + sDIM + d];
}
pass_bb = (AABB_test(&box, x_i) >= 0);
box.c0[idx] = boxinfo[n_box_ents*el + idx];
box.x[idx].min = boxinfo[n_box_ents*el + sDIM + idx];
box.x[idx].max = boxinfo[n_box_ents*el + 2*sDIM + idx];
}
if (!pass_bb) { continue; }
for (int idx = 0; idx < sDIM2; ++idx)
{
box.A[idx] = boxinfo[n_box_ents*el + 3*sDIM + idx];
}
if (bbox_test(&box, x_i) < 0) { continue; }
//// findpts_local ////
{
@@ -866,19 +968,13 @@ static void FindPointsSurfLocal3DKernel(const int npt,
double *hes_T = jac + sDIM*rDIM;
double *hes = hes_T + hes_count*sDIM;
findptsElementGEdge_t edge;
for (int d=0; d<sDIM; ++d)
{
edge.x[d] = constraint_workspace + d*D1D;
edge.dxdn[d] = constraint_workspace + d*D1D
+ sDIM*D1D;
edge.d2xdn[d] = constraint_workspace + d*D1D
+ 2*sDIM*D1D;
}
MFEM_FOREACH_THREAD(j,x,D1D*sDIM)
{
// One thread per physical component and edge DOF.
get_edge(elx, wtend, ei, edge_init, j, D1D, edge);
// utilized first D1D threads
edge = get_edge(elx, wtend, ei,
constraint_workspace, edge_init, j,
D1D);
}
MFEM_SYNC_THREAD;
@@ -949,15 +1045,7 @@ static void FindPointsSurfLocal3DKernel(const int npt,
steep *= tmp->r[dn];
if (steep<0)
{
double face_hes[3] =
{
dn == 0 ? hes[2] : hes[0],
hes[1],
dn == 0 ? hes[0] : hes[2]
};
newton_face(fpt, jac, face_hes, resid,
tmp->flags & CONVERGED_FLAG,
tmp, tol);
newton_face( fpt,jac,hes,resid,tmp->flags&CONVERGED_FLAG,tmp,tol);
}
else
{
@@ -1123,42 +1211,29 @@ void FindPointsGSLIB::FindPointsSurfLocal3(const Vector &point_pos,
auto pgll1d = DEV.gll1d.ReadWrite(use_dev);
auto plc = DEV.lagcoeff.Read(use_dev);
double dist2tol = DEV.surf_dist_tol;
const bool obb_chk = obb_check;
switch (DEV.dof1d)
{
case 2:
FindPointsSurfLocal3DKernel<2>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsSurfLocal3D_Kernel<2>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
case 3:
FindPointsSurfLocal3DKernel<3>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsSurfLocal3D_Kernel<3>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
case 4:
FindPointsSurfLocal3DKernel<4>(npt, DEV.newt_tol, dist2tol,
pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc);
break;
return FindPointsSurfLocal3D_Kernel<4>(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc);
default:
FindPointsSurfLocal3DKernel(npt, DEV.newt_tol, dist2tol, pp,
point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, obb_chk,
DEV.lh_nx, plhm, plhf, plho,
pcode, pelem, pref, pdist,
pgll1d, plc, DEV.dof1d);
break;
return FindPointsSurfLocal3D_Kernel(
npt, DEV.newt_tol, dist2tol, pp, point_pos_ordering, pgslm,
NE_split_total, pwt, pbb, DEV.lh_nx, plhm, plhf,
plho, pcode, pelem, pref, pdist, pgll1d, plc, DEV.dof1d);
}
}
-190
View File
@@ -1,190 +0,0 @@
#ifndef MFEM_GSLIB_KERNEL_HELPERS_HPP
#define MFEM_GSLIB_KERNEL_HELPERS_HPP
#include "../../config/config.hpp"
#include <cmath>
namespace mfem
{
namespace gslib
{
struct dbl_range_t
{
double min, max;
};
template <int SDIM>
struct obbox_t
{
double c0[SDIM], A[SDIM * SDIM];
dbl_range_t x[SDIM];
};
template <int SDIM>
struct findptsLocalHashData_t
{
int hash_n;
dbl_range_t bnd[SDIM];
double fac[SDIM];
unsigned int *offset;
};
// Eval the ith Lagrange interpolant at x.
MFEM_HOST_DEVICE inline void lagrange_eval(double *p0, double x,
int i, int p_Nq,
double *z, double *lagrangeCoeff)
{
double p_i = (1 << (p_Nq - 1));
for (int j = 0; j < p_Nq; ++j)
{
const double d_j = x - z[j];
p_i *= j == i ? 1 : d_j;
}
p0[i] = lagrangeCoeff[i] * p_i;
}
// Eval the ith Lagrange interpolant and its first derivative at x.
MFEM_HOST_DEVICE inline void lag_eval_first_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
const double d_j = 2 * (x - z[j]);
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
p0[i] = lCoeff[i] * u0;
p0[pN + i] = 2.0 * lCoeff[i] * u1;
}
// Eval the ith Lagrange interpolant and its first and second derivative at x.
MFEM_HOST_DEVICE inline void lag_eval_second_der(double *p0, double x,
int i, const double *z,
const double *lCoeff,
int pN)
{
double u0 = 1, u1 = 0, u2 = 0;
for (int j = 0; j < pN; ++j)
{
if (i != j)
{
const double d_j = 2 * (x - z[j]);
u2 = d_j * u2 + u1;
u1 = d_j * u1 + u0;
u0 = d_j * u0;
}
}
p0[i] = lCoeff[i] * u0;
p0[pN + i] = 2.0 * lCoeff[i] * u1;
p0[2 * pN + i] = 8.0 * lCoeff[i] * u2;
}
// Solve Ax=y where A is a symmetric 2x2 matrix packed as {a00, a01, a11}.
MFEM_HOST_DEVICE inline void lin_solve_sym_2(double x[2],
const double A[3],
const double y[2])
{
const double idet = 1 / (A[0] * A[2] - A[1] * A[1]);
x[0] = idet * (A[2] * y[0] - A[1] * y[1]);
x[1] = idet * (A[0] * y[1] - A[1] * y[0]);
}
// Positive when the point is inside the axis-aligned bounding box.
template <int SDIM>
MFEM_HOST_DEVICE inline double AABB_test(const obbox_t<SDIM> *const b,
const double (&x)[SDIM])
{
double test = 1.0;
for (int d = 0; d < SDIM; ++d)
{
const double b_d = (x[d] - b->x[d].min) * (b->x[d].max - x[d]);
test = test < 0.0 ? test : b_d;
}
return test;
}
// Positive when the point is inside the oriented bounding box.
template <int SDIM>
MFEM_HOST_DEVICE inline double bbox_test(const obbox_t<SDIM> *const b,
const double (&x)[SDIM])
{
const double bxyz = AABB_test(b, x);
if (bxyz < 0.0)
{
return bxyz;
}
double dxyz[SDIM];
for (int d = 0; d < SDIM; ++d)
{
dxyz[d] = x[d] - b->c0[d];
}
double test = 1.0;
for (int d = 0; d < SDIM; ++d)
{
double rst = 0.0;
for (int e = 0; e < SDIM; ++e)
{
rst += b->A[d * SDIM + e] * dxyz[e];
}
const double brst = (rst + 1.0) * (1.0 - rst);
test = test < 0.0 ? test : brst;
}
return test;
}
// Hash index in the hash table for the point x.
template <int SDIM>
MFEM_HOST_DEVICE inline int hash_index(
const findptsLocalHashData_t<SDIM> *const p,
const double (&x)[SDIM])
{
const int n = p->hash_n;
int sum = 0;
for (int d = SDIM - 1; d >= 0; --d)
{
sum *= n;
const int i = (int)floor((x[d] - p->bnd[d].min) * p->fac[d]);
sum += i < 0 ? 0 : (n - 1 < i ? n - 1 : i);
}
return sum;
}
// Squared Euclidean norm.
template <int SDIM>
MFEM_HOST_DEVICE inline double l2norm2(const double (&x)[SDIM])
{
double sum = 0.0;
for (int d = 0; d < SDIM; ++d)
{
sum += x[d] * x[d];
}
return sum;
}
template <int SDIM>
MFEM_HOST_DEVICE inline double l2norm2(const double *x)
{
double sum = 0.0;
for (int d = 0; d < SDIM; ++d)
{
sum += x[d] * x[d];
}
return sum;
}
} // namespace gslib
} // namespace mfem
#endif
+27 -22
View File
@@ -11,7 +11,7 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#include "../../linalg/kernels.hpp"
#ifdef MFEM_USE_GSLIB
@@ -33,7 +33,17 @@ namespace mfem
#define CODE_BORDER 1
#define CODE_NOT_FOUND 2
using gslib::lagrange_eval;
static MFEM_HOST_DEVICE void lagrange_eval(double *p0, double x,
int i, int p_Nq,
double *z, double *lagrangeCoeff)
{
double p_i = (1 << (p_Nq - 1));
for (int j=0; j<p_Nq; ++j)
{
p_i *= j==i ? 1 : x-z[j];
}
p0[i] = lagrangeCoeff[i] * p_i;
}
template<int T_D1D = 0>
static void InterpolateLocal1DKernel(const double *const gf_in,
@@ -113,26 +123,21 @@ void FindPointsGSLIB::InterpolateLocal1( const Vector &field_in,
auto plcf = DEV.lagcoeff_sol.ReadWrite(use_dev);
switch (dof1Dsol)
{
case 2:
InterpolateLocal1DKernel<2>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 3:
InterpolateLocal1DKernel<3>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 4:
InterpolateLocal1DKernel<4>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 5:
InterpolateLocal1DKernel<5>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
default:
InterpolateLocal1DKernel(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf, dof1Dsol);
break;
case 2: return InterpolateLocal1DKernel<2>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 3: return InterpolateLocal1DKernel<3>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 4: return InterpolateLocal1DKernel<4>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 5: return InterpolateLocal1DKernel<5>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
default: return InterpolateLocal1DKernel(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf, dof1Dsol);
}
}
#undef CODE_INTERNAL
+27 -22
View File
@@ -11,7 +11,6 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
@@ -33,7 +32,18 @@ namespace mfem
#define CODE_BORDER 1
#define CODE_NOT_FOUND 2
using gslib::lagrange_eval;
static MFEM_HOST_DEVICE void lagrange_eval(double *p0, double x,
int i, int p_Nq,
double *z, double *lagrangeCoeff)
{
double p_i = (1 << (p_Nq - 1));
for (int j = 0; j < p_Nq; ++j)
{
double d_j = x - z[j];
p_i *= j == i ? 1 : d_j;
}
p0[i] = lagrangeCoeff[i] * p_i;
}
template<int T_D1D = 0>
static void InterpolateLocal2DKernel(const double *const gf_in,
@@ -122,26 +132,21 @@ void FindPointsGSLIB::InterpolateLocal2(const Vector &field_in,
auto plcf = DEV.lagcoeff_sol.ReadWrite(use_dev);
switch (dof1Dsol)
{
case 2:
InterpolateLocal2DKernel<2>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 3:
InterpolateLocal2DKernel<3>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 4:
InterpolateLocal2DKernel<4>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 5:
InterpolateLocal2DKernel<5>(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
default:
InterpolateLocal2DKernel(pfin, pgsl, pgslr, pfout,
npt, ncomp, pgll, plcf, dof1Dsol);
break;
case 2: return InterpolateLocal2DKernel<2>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 3: return InterpolateLocal2DKernel<3>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 4: return InterpolateLocal2DKernel<4>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 5: return InterpolateLocal2DKernel<5>(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf);
default: return InterpolateLocal2DKernel(pfin, pgsl, pgslr, pfout,
npt, ncomp,
pgll, plcf, dof1Dsol);
}
}
+27 -22
View File
@@ -11,7 +11,6 @@
#include "../gslib.hpp"
#include "../../general/forall.hpp"
#include "gslib_kernel_helpers.hpp"
#ifdef MFEM_USE_GSLIB
@@ -33,7 +32,18 @@ namespace mfem
#define CODE_BORDER 1
#define CODE_NOT_FOUND 2
using gslib::lagrange_eval;
static MFEM_HOST_DEVICE void lagrange_eval(double *p0, double x,
int i, int p_Nq,
double *z, double *lagrangeCoeff)
{
double p_i = (1 << (p_Nq - 1));
for (int j = 0; j < p_Nq; ++j)
{
double d_j = x - z[j];
p_i *= j == i ? 1 : d_j;
}
p0[i] = lagrangeCoeff[i] * p_i;
}
template<int T_D1D = 0>
static void InterpolateLocal3DKernel(const double *const gf_in,
@@ -125,26 +135,21 @@ void FindPointsGSLIB::InterpolateLocal3(const Vector &field_in,
auto plcf = DEV.lagcoeff_sol.ReadWrite(use_dev);
switch (dof1Dsol)
{
case 2:
InterpolateLocal3DKernel<2>(pfin, pgsle, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 3:
InterpolateLocal3DKernel<3>(pfin, pgsle, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 4:
InterpolateLocal3DKernel<4>(pfin, pgsle, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
case 5:
InterpolateLocal3DKernel<5>(pfin, pgsle, pgslr, pfout,
npt, ncomp, pgll, plcf);
break;
default:
InterpolateLocal3DKernel(pfin, pgsle, pgslr, pfout,
npt, ncomp, pgll, plcf, dof1Dsol);
break;
case 2: return InterpolateLocal3DKernel<2>(pfin, pgsle, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 3: return InterpolateLocal3DKernel<3>(pfin, pgsle, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 4: return InterpolateLocal3DKernel<4>(pfin, pgsle, pgslr, pfout,
npt, ncomp,
pgll, plcf);
case 5: return InterpolateLocal3DKernel<5>(pfin, pgsle, pgslr, pfout,
npt, ncomp,
pgll, plcf);
default: return InterpolateLocal3DKernel(pfin, pgsle, pgslr, pfout,
npt, ncomp,
pgll, plcf, dof1Dsol);
}
}
@@ -19,8 +19,6 @@
#include "../../linalg/vector.hpp"
#include "../bilininteg.hpp"
#include "bilininteg_diffusion_pa_simplices.hpp"
namespace mfem
{
-2
View File
@@ -19,8 +19,6 @@
#include "../../linalg/vector.hpp"
#include "../bilininteg.hpp"
#include "bilininteg_mass_pa_simplices.hpp"
namespace mfem
{
-500
View File
@@ -307,506 +307,6 @@ DomainLFIntegrator::AssembleKernels::Kernel()
MFEM_ABORT("");
}
template <int T_D1D = 0, int T_Q1D = 0>
static void HdivDLFAssemble2D(const int ne, const Array<int> &markers,
const Vector &jac, const Array<real_t> &weights,
const Array<real_t> &testBO,
const Array<real_t> &testBC, const Vector &coeff,
Vector &y, const int d, const int q)
{
MFEM_VERIFY(T_D1D || d <= DeviceDofQuadLimits::Get().HDIV_MAX_D1D,
"Problem size too large.");
MFEM_VERIFY(T_Q1D || q <= DeviceDofQuadLimits::Get().HDIV_MAX_Q1D,
"Problem size too large.");
MFEM_VERIFY(y.Size() == 2 * (d - 1) * d * ne, "");
constexpr int vdim = 2;
const auto F = coeff.Read();
const auto M = markers.Read();
const auto BO = Reshape(testBO.Read(), q, d-1);
const auto BC = Reshape(testBC.Read(), q, d);
const auto J = Reshape(jac.Read(), q, q, vdim, vdim, ne);
const auto W = Reshape(weights.Read(), q, q);
const bool cst = coeff.Size() == vdim;
const auto C = cst ? Reshape(F,vdim,1,1,1) : Reshape(F,vdim,q,q,ne);
auto Y = y.ReadWrite();
mfem::forall_3D(ne, q, q, vdim, [=] MFEM_HOST_DEVICE (int e)
{
constexpr int vdim = 2;
if (M[e] == 0) { return; } // ignore
constexpr int Q = T_Q1D ? T_Q1D : DofQuadLimits::HDIV_MAX_Q1D;
constexpr int D = T_D1D ? T_D1D : DofQuadLimits::HDIV_MAX_D1D;
MFEM_SHARED real_t sBot[Q*D];
MFEM_SHARED real_t sBct[Q*D];
MFEM_SHARED real_t sQQ[vdim*Q*Q];
MFEM_SHARED real_t sQD[vdim*Q*D];
// Bo and Bc into shared memory
const DeviceMatrix Bot(sBot, d-1, q);
kernels::internal::LoadB<D,Q>(d-1, q, BO, sBot);
const DeviceMatrix Bct(sBct, d, q);
kernels::internal::LoadB<D,Q>(d, q, BC, sBct);
const DeviceCube QQ(sQQ, q, q, vdim);
const DeviceCube QD(sQD, q, d, vdim);
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const real_t cst_val_0 = C(0,0,0,0);
const real_t cst_val_1 = C(1,0,0,0);
MFEM_FOREACH_THREAD(y,y,q)
{
MFEM_FOREACH_THREAD(x,x,q)
{
const real_t J0 = J(x,y,0,vd,e);
const real_t J1 = J(x,y,1,vd,e);
const real_t C0 = cst ? cst_val_0 : C(0,x,y,e);
const real_t C1 = cst ? cst_val_1 : C(1,x,y,e);
QQ(x,y,vd) = W(x,y)*(J0*C0 + J1*C1);
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
DeviceMatrix Btx = (vd == 0) ? Bct : Bot;
MFEM_FOREACH_THREAD(qy,y,q)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t qd = 0.0;
for (int qx = 0; qx < q; ++qx)
{
qd += QQ(qx,qy,vd) * Btx(dx,qx);
}
QD(dx,qy,vd) = qd;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
const int ny = (vd == 1) ? d : d-1;
DeviceMatrix Bty = (vd == 1) ? Bct : Bot;
DeviceTensor<4> Yxy(Y, nx, ny, vdim, ne);
MFEM_FOREACH_THREAD(dy,y,ny)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t dd = 0.0;
for (int qy = 0; qy < q; ++qy)
{
dd += QD(dx,qy,vd) * Bty(dy,qy);
}
Yxy(dx,dy,vd,e) += dd;
}
}
}
MFEM_SYNC_THREAD;
});
}
template <int T_D1D = 0, int T_Q1D = 0>
static void HdivDLFAssemble3D(const int ne, const Array<int> &markers,
const Vector &jac, const Array<real_t> &weights,
const Array<real_t> &testBO,
const Array<real_t> &testBC, const Vector &coeff,
Vector &y, const int d, const int q)
{
MFEM_VERIFY(T_D1D || d <= DeviceDofQuadLimits::Get().HDIV_MAX_D1D,
"Problem size too large.");
MFEM_VERIFY(T_Q1D || q <= DeviceDofQuadLimits::Get().HDIV_MAX_Q1D,
"Problem size too large.");
MFEM_VERIFY(y.Size() == 3 * (d - 1) * (d - 1) * d * ne, "y wrong length");
constexpr int vdim = 3;
const auto F = coeff.Read();
const auto M = markers.Read();
const auto BO = Reshape(testBO.Read(), q, d-1);
const auto BC = Reshape(testBC.Read(), q, d);
const auto J = Reshape(jac.Read(), q, q, q, vdim, vdim, ne);
const auto W = Reshape(weights.Read(), q, q, q);
const bool cst = coeff.Size() == vdim;
const auto C = cst ? Reshape(F,vdim,1,1,1,1) : Reshape(F,vdim,q,q,q,ne);
auto Y = y.ReadWrite();
mfem::forall_3D(ne, q, q, vdim, [=] MFEM_HOST_DEVICE (int e)
{
constexpr int vdim = 3;
if (M[e] == 0) { return; } // ignore
constexpr int Q = T_Q1D ? T_Q1D : DofQuadLimits::HDIV_MAX_Q1D;
constexpr int D = T_D1D ? T_D1D : DofQuadLimits::HDIV_MAX_D1D;
MFEM_SHARED real_t sBot[Q*D];
MFEM_SHARED real_t sBct[Q*D];
// Bo and Bc into shared memory
const DeviceMatrix Bot(sBot, d-1, q);
kernels::internal::LoadB<D,Q>(d-1, q, BO, sBot);
const DeviceMatrix Bct(sBct, d, q);
kernels::internal::LoadB<D,Q>(d, q, BC, sBct);
MFEM_SHARED real_t sm0[vdim*Q*Q*Q];
MFEM_SHARED real_t sm1[vdim*Q*Q*Q];
DeviceTensor<4> QQQ(sm1, q, q, q, vdim);
DeviceTensor<4> DQQ(sm0, d, q, q, vdim);
DeviceTensor<4> DDQ(sm1, d, d, q, vdim);
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const real_t cst_val_0 = C(0,0,0,0,0);
const real_t cst_val_1 = C(1,0,0,0,0);
const real_t cst_val_2 = C(2,0,0,0,0);
MFEM_FOREACH_THREAD(y,y,q)
{
MFEM_FOREACH_THREAD(x,x,q)
{
for (int z = 0; z < q; ++z)
{
const real_t J0 = J(x,y,z,0,vd,e);
const real_t J1 = J(x,y,z,1,vd,e);
const real_t J2 = J(x,y,z,2,vd,e);
const real_t C0 = cst ? cst_val_0 : C(0,x,y,z,e);
const real_t C1 = cst ? cst_val_1 : C(1,x,y,z,e);
const real_t C2 = cst ? cst_val_2 : C(2,x,y,z,e);
QQQ(x,y,z,vd) = W(x,y,z)*(J0*C0 + J1*C1 + J2*C2);
}
}
}
}
MFEM_SYNC_THREAD;
// Apply Bt operator
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
DeviceMatrix Btx = (vd == 0) ? Bct : Bot;
MFEM_FOREACH_THREAD(qy,y,q)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t u[Q];
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { u[qz] = 0.0; }
MFEM_UNROLL(Q)
for (int qx = 0; qx < q; ++qx)
{
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] += QQQ(qx,qy,qz,vd) * Btx(dx,qx);
}
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { DQQ(dx,qy,qz,vd) = u[qz]; }
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
const int ny = (vd == 1) ? d : d-1;
DeviceMatrix Bty = (vd == 1) ? Bct : Bot;
MFEM_FOREACH_THREAD(dy,y,ny)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t u[Q];
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { u[qz] = 0.0; }
MFEM_UNROLL(Q)
for (int qy = 0; qy < q; ++qy)
{
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] += DQQ(dx,qy,qz,vd) * Bty(dy,qy);
}
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { DDQ(dx,dy,qz,vd) = u[qz]; }
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
const int ny = (vd == 1) ? d : d-1;
const int nz = (vd == 2) ? d : d-1;
DeviceTensor<5> Yxyz(Y, nx, ny, nz, vdim, ne);
DeviceMatrix Btz = (vd == 2) ? Bct : Bot;
MFEM_FOREACH_THREAD(dy,y,ny)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t u[D];
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz) { u[dz] = 0.0; }
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz)
{
u[dz] += DDQ(dx,dy,qz,vd) * Btz(dz,qz);
}
}
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz) { Yxyz(dx,dy,dz,vd,e) += u[dz]; }
}
}
}
MFEM_SYNC_THREAD;
});
}
/// @param ne number of elements
/// @param markers array where entry markers[e] == 0 to skip assembly over
/// element e element
/// @param jac Spatial Jacobians evaluated at all quadrature points
/// @param weights 1D quadrature weights
/// @param testBO 1D open basis test functions
/// @param testBC 1D closed basis test functions
/// @param coeff coefficient values evaluated at quadrature points, possibly
/// compressed.
/// @param d number of 1D closed dofs
/// @param q number of 1D quadrature points
/// @tparam T_D1D maximum number of dofs along any direction, or 0
/// @tparam T_Q1D maximum number of quadrature points along any direction, or 0
template <int T_D1D = 0, int T_Q1D = 0>
static void HcurlDLFAssemble3D(const int ne, const Array<int> &markers,
const Vector &jac, const Array<real_t> &weights,
const Array<real_t> &testBO,
const Array<real_t> &testBC, const Vector &coeff,
Vector &y, const int d, const int q)
{
MFEM_VERIFY(T_D1D || d <= DeviceDofQuadLimits::Get().HCURL_MAX_D1D,
"Problem size too large.");
MFEM_VERIFY(T_Q1D || q <= DeviceDofQuadLimits::Get().HCURL_MAX_Q1D,
"Problem size too large.");
MFEM_VERIFY(y.Size() == 3 * (d - 1) * d * d * ne, "y wrong length");
constexpr int vdim = 3;
const auto F = coeff.Read();
const auto M = markers.Read();
const auto BO = Reshape(testBO.Read(), q, d-1);
const auto BC = Reshape(testBC.Read(), q, d);
const auto J = Reshape(jac.Read(), q, q, q, vdim, vdim, ne);
const auto W = Reshape(weights.Read(), q, q, q);
const bool cst = coeff.Size() == vdim;
const auto C = cst ? Reshape(F,vdim,1,1,1,1) : Reshape(F,vdim,q,q,q,ne);
auto Y = y.ReadWrite();
mfem::forall_3D(ne, q, q, vdim, [=] MFEM_HOST_DEVICE(int e)
{
if (M[e] == 0)
{
// ignore
return;
}
constexpr int vdim = 3;
constexpr int Q = T_Q1D ? T_Q1D : DofQuadLimits::HCURL_MAX_Q1D;
constexpr int D = T_D1D ? T_D1D : DofQuadLimits::HCURL_MAX_D1D;
MFEM_SHARED real_t sBot[Q * D];
MFEM_SHARED real_t sBct[Q * D];
// Bo and Bc into shared memory
const DeviceMatrix Bot(sBot, d - 1, q);
kernels::internal::LoadB<D, Q>(d - 1, q, BO, sBot);
const DeviceMatrix Bct(sBct, d, q);
kernels::internal::LoadB<D, Q>(d, q, BC, sBct);
MFEM_SHARED real_t sm0[vdim * Q * Q * Q];
MFEM_SHARED real_t sm1[vdim * Q * Q * Q];
DeviceTensor<4> QQQ(sm1, q, q, q, vdim);
DeviceTensor<4> DQQ(sm0, d, q, q, vdim);
DeviceTensor<4> DDQ(sm1, d, d, q, vdim);
const real_t cst_val_0 = C(0, 0, 0, 0, 0);
const real_t cst_val_1 = C(1, 0, 0, 0, 0);
const real_t cst_val_2 = C(2, 0, 0, 0, 0);
MFEM_FOREACH_THREAD(vd, z, vdim)
{
MFEM_FOREACH_THREAD(y, y, q)
{
MFEM_FOREACH_THREAD(x, x, q)
{
for (int z = 0; z < q; ++z)
{
real_t curr[3];
curr[0] = cst ? cst_val_0 : C(0, x, y, z, e);
curr[1] = cst ? cst_val_1 : C(1, x, y, z, e);
curr[2] = cst ? cst_val_2 : C(2, x, y, z, e);
const real_t J11 = J(x, y, z, 0, 0, e);
const real_t J21 = J(x, y, z, 1, 0, e);
const real_t J31 = J(x, y, z, 2, 0, e);
const real_t J12 = J(x, y, z, 0, 1, e);
const real_t J22 = J(x, y, z, 1, 1, e);
const real_t J32 = J(x, y, z, 2, 1, e);
const real_t J13 = J(x, y, z, 0, 2, e);
const real_t J23 = J(x, y, z, 1, 2, e);
const real_t J33 = J(x, y, z, 2, 2, e);
// adj(J)
const real_t A11 = (J22 * J33) - (J23 * J32);
const real_t A12 = (J32 * J13) - (J12 * J33);
const real_t A13 = (J12 * J23) - (J22 * J13);
const real_t A21 = (J31 * J23) - (J21 * J33);
const real_t A22 = (J11 * J33) - (J13 * J31);
const real_t A23 = (J21 * J13) - (J11 * J23);
const real_t A31 = (J21 * J32) - (J31 * J22);
const real_t A32 = (J31 * J12) - (J11 * J32);
const real_t A33 = (J11 * J22) - (J12 * J21);
const real_t A[9] = {A11, A12, A13, A21, A22,
A23, A31, A32, A33
};
QQQ(x, y, z, vd) = W(x, y, z) * (A[vd * vdim] * curr[0] +
A[vd * vdim + 1] * curr[1] +
A[vd * vdim + 2] * curr[2]);
}
}
}
}
MFEM_SYNC_THREAD;
// Apply Bt operator
MFEM_FOREACH_THREAD(vd, z, vdim)
{
const int nx = (vd == 0) ? d - 1 : d;
DeviceMatrix Btx = (vd == 0) ? Bot : Bct;
MFEM_FOREACH_THREAD(qy, y, q)
{
MFEM_FOREACH_THREAD(dx, x, nx)
{
real_t u[Q];
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] = 0.0;
}
MFEM_UNROLL(Q)
for (int qx = 0; qx < q; ++qx)
{
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] += QQQ(qx, qy, qz, vd) * Btx(dx, qx);
}
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
DQQ(dx, qy, qz, vd) = u[qz];
}
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd, z, vdim)
{
const int nx = (vd == 0) ? d - 1 : d;
const int ny = (vd == 1) ? d - 1 : d;
DeviceMatrix Bty = (vd == 1) ? Bot : Bct;
MFEM_FOREACH_THREAD(dy, y, ny)
{
MFEM_FOREACH_THREAD(dx, x, nx)
{
real_t u[Q];
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] = 0.0;
}
MFEM_UNROLL(Q)
for (int qy = 0; qy < q; ++qy)
{
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] += DQQ(dx, qy, qz, vd) * Bty(dy, qy);
}
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
DDQ(dx, dy, qz, vd) = u[qz];
}
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd, z, vdim)
{
const int nx = (vd == 0) ? d - 1 : d;
const int ny = (vd == 1) ? d - 1 : d;
const int nz = (vd == 2) ? d - 1 : d;
DeviceTensor<5> Yxyz(Y, nx, ny, nz, vdim, ne);
DeviceMatrix Btz = (vd == 2) ? Bot : Bct;
MFEM_FOREACH_THREAD(dy, y, ny)
{
MFEM_FOREACH_THREAD(dx, x, nx)
{
real_t u[D];
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz)
{
u[dz] = 0.0;
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz)
{
u[dz] += DDQ(dx, dy, qz, vd) * Btz(dz, qz);
}
}
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz)
{
Yxyz(dx, dy, dz, vd, e) += u[dz];
}
}
}
}
MFEM_SYNC_THREAD;
});
}
template <FiniteElement::DerivType TestType, int DIM, int TEST_D1D, int Q1D>
VectorFEDomainLFIntegrator::AssembleKernelType
VectorFEDomainLFIntegrator::AssembleKernels::Kernel()
{
if constexpr (TestType == FiniteElement::DIV)
{
if constexpr (DIM == 2)
{
return HdivDLFAssemble2D<TEST_D1D, Q1D>;
}
if constexpr (DIM == 3)
{
return HdivDLFAssemble3D<TEST_D1D, Q1D>;
}
}
if constexpr (TestType == FiniteElement::CURL)
{
if constexpr (DIM == 3)
{
return HcurlDLFAssemble3D<TEST_D1D, Q1D>;
}
}
MFEM_ABORT("");
}
/// \endcond DO_NOT_DOCUMENT
} // namespace mfem
+302 -69
View File
@@ -13,76 +13,317 @@
#include "../../fem/kernels.hpp"
#include "../fem.hpp"
#include "lininteg_domain_kernels.hpp"
namespace mfem
{
VectorFEDomainLFIntegrator::Kernels::Kernels()
template<int T_D1D = 0, int T_Q1D = 0>
static void HdivDLFAssemble2D(
const int ne, const int d, const int q, const int *markers, const real_t *bo,
const real_t *bc, const real_t *j, const real_t *weights,
const Vector &coeff, real_t *y)
{
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 1, 1>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 2, 2>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 3, 3>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 4, 4>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 5, 5>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 6, 6>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 7, 7>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 2, 8, 8>();
MFEM_VERIFY(T_D1D || d <= DeviceDofQuadLimits::Get().HDIV_MAX_D1D,
"Problem size too large.");
MFEM_VERIFY(T_Q1D || q <= DeviceDofQuadLimits::Get().HDIV_MAX_Q1D,
"Problem size too large.");
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 1, 1>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 2, 2>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 3, 3>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 4, 4>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 5, 5>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 6, 6>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 7, 7>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::DIV, 3, 8, 8>();
static constexpr int vdim = 2;
const auto F = coeff.Read();
const auto M = Reshape(markers, ne);
const auto BO = Reshape(bo, q, d-1);
const auto BC = Reshape(bc, q, d);
const auto J = Reshape(j, q, q, vdim, vdim, ne);
const auto W = Reshape(weights, q, q);
const bool cst = coeff.Size() == vdim;
const auto C = cst ? Reshape(F,vdim,1,1,1) : Reshape(F,vdim,q,q,ne);
auto Y = Reshape(y, 2*(d-1)*d, ne);
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 1, 1>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 2, 2>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 3, 3>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 4, 4>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 5, 5>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 6, 6>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 7, 7>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 8, 8>();
mfem::forall_3D(ne, q, q, vdim, [=] MFEM_HOST_DEVICE (int e)
{
if (M(e) == 0) { return; } // ignore
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 1, 2>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 2, 3>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 3, 4>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 4, 5>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 5, 6>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 6, 7>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 7, 8>();
VectorFEDomainLFIntegrator::AddSpecialization<FiniteElement::CURL, 3, 8, 9>();
constexpr int Q = T_Q1D ? T_Q1D : DofQuadLimits::HDIV_MAX_Q1D;
constexpr int D = T_D1D ? T_D1D : DofQuadLimits::HDIV_MAX_D1D;
MFEM_SHARED real_t sBot[Q*D];
MFEM_SHARED real_t sBct[Q*D];
MFEM_SHARED real_t sQQ[vdim*Q*Q];
MFEM_SHARED real_t sQD[vdim*Q*D];
// Bo and Bc into shared memory
const DeviceMatrix Bot(sBot, d-1, q);
kernels::internal::LoadB<D,Q>(d-1, q, BO, sBot);
const DeviceMatrix Bct(sBct, d, q);
kernels::internal::LoadB<D,Q>(d, q, BC, sBct);
const DeviceCube QQ(sQQ, q, q, vdim);
const DeviceCube QD(sQD, q, d, vdim);
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const real_t cst_val_0 = C(0,0,0,0);
const real_t cst_val_1 = C(1,0,0,0);
MFEM_FOREACH_THREAD(y,y,q)
{
MFEM_FOREACH_THREAD(x,x,q)
{
const real_t J0 = J(x,y,0,vd,e);
const real_t J1 = J(x,y,1,vd,e);
const real_t C0 = cst ? cst_val_0 : C(0,x,y,e);
const real_t C1 = cst ? cst_val_1 : C(1,x,y,e);
QQ(x,y,vd) = W(x,y)*(J0*C0 + J1*C1);
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
DeviceMatrix Btx = (vd == 0) ? Bct : Bot;
MFEM_FOREACH_THREAD(qy,y,q)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t qd = 0.0;
for (int qx = 0; qx < q; ++qx)
{
qd += QQ(qx,qy,vd) * Btx(dx,qx);
}
QD(dx,qy,vd) = qd;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
const int ny = (vd == 1) ? d : d-1;
DeviceMatrix Bty = (vd == 1) ? Bct : Bot;
DeviceTensor<4> Yxy(Y, nx, ny, vdim, ne);
MFEM_FOREACH_THREAD(dy,y,ny)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t dd = 0.0;
for (int qy = 0; qy < q; ++qy)
{
dd += QD(dx,qy,vd) * Bty(dy,qy);
}
Yxy(dx,dy,vd,e) += dd;
}
}
}
MFEM_SYNC_THREAD;
});
}
/// \cond DO_NOT_DOCUMENT
VectorFEDomainLFIntegrator::AssembleKernelType
VectorFEDomainLFIntegrator::AssembleKernels::Fallback(
FiniteElement::DerivType TestType, int DIM, int, int)
template<int T_D1D = 0, int T_Q1D = 0>
static void HdivDLFAssemble3D(
const int ne, const int d, const int q, const int *markers, const real_t *bo,
const real_t *bc, const real_t *j, const real_t *weights,
const Vector &coeff, real_t *y)
{
if (TestType == FiniteElement::DIV)
MFEM_VERIFY(T_D1D || d <= DeviceDofQuadLimits::Get().HDIV_MAX_D1D,
"Problem size too large.");
MFEM_VERIFY(T_Q1D || q <= DeviceDofQuadLimits::Get().HDIV_MAX_Q1D,
"Problem size too large.");
static constexpr int vdim = 3;
const auto F = coeff.Read();
const auto M = Reshape(markers, ne);
const auto BO = Reshape(bo, q, d-1);
const auto BC = Reshape(bc, q, d);
const auto J = Reshape(j, q, q, q, vdim, vdim, ne);
const auto W = Reshape(weights, q, q, q);
const bool cst = coeff.Size() == vdim;
const auto C = cst ? Reshape(F,vdim,1,1,1,1) : Reshape(F,vdim,q,q,q,ne);
auto Y = Reshape(y, 2*(d-1)*(d-1)*d, ne);
mfem::forall_3D(ne, q, q, vdim, [=] MFEM_HOST_DEVICE (int e)
{
if (DIM == 2)
if (M(e) == 0) { return; } // ignore
constexpr int Q = T_Q1D ? T_Q1D : DofQuadLimits::HDIV_MAX_Q1D;
constexpr int D = T_D1D ? T_D1D : DofQuadLimits::HDIV_MAX_D1D;
MFEM_SHARED real_t sBot[Q*D];
MFEM_SHARED real_t sBct[Q*D];
// Bo and Bc into shared memory
const DeviceMatrix Bot(sBot, d-1, q);
kernels::internal::LoadB<D,Q>(d-1, q, BO, sBot);
const DeviceMatrix Bct(sBct, d, q);
kernels::internal::LoadB<D,Q>(d, q, BC, sBct);
MFEM_SHARED real_t sm0[vdim*Q*Q*Q];
MFEM_SHARED real_t sm1[vdim*Q*Q*Q];
DeviceTensor<4> QQQ(sm1, q, q, q, vdim);
DeviceTensor<4> DQQ(sm0, d, q, q, vdim);
DeviceTensor<4> DDQ(sm1, d, d, q, vdim);
MFEM_FOREACH_THREAD(vd,z,vdim)
{
return HdivDLFAssemble2D<0, 0>;
const real_t cst_val_0 = C(0,0,0,0,0);
const real_t cst_val_1 = C(1,0,0,0,0);
const real_t cst_val_2 = C(2,0,0,0,0);
MFEM_FOREACH_THREAD(y,y,q)
{
MFEM_FOREACH_THREAD(x,x,q)
{
for (int z = 0; z < q; ++z)
{
const real_t J0 = J(x,y,z,0,vd,e);
const real_t J1 = J(x,y,z,1,vd,e);
const real_t J2 = J(x,y,z,2,vd,e);
const real_t C0 = cst ? cst_val_0 : C(0,x,y,z,e);
const real_t C1 = cst ? cst_val_1 : C(1,x,y,z,e);
const real_t C2 = cst ? cst_val_2 : C(2,x,y,z,e);
QQQ(x,y,z,vd) = W(x,y,z)*(J0*C0 + J1*C1 + J2*C2);
}
}
}
}
if (DIM == 3)
MFEM_SYNC_THREAD;
// Apply Bt operator
MFEM_FOREACH_THREAD(vd,z,vdim)
{
return HdivDLFAssemble3D<0, 0>;
const int nx = (vd == 0) ? d : d-1;
DeviceMatrix Btx = (vd == 0) ? Bct : Bot;
MFEM_FOREACH_THREAD(qy,y,q)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t u[Q];
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { u[qz] = 0.0; }
MFEM_UNROLL(Q)
for (int qx = 0; qx < q; ++qx)
{
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] += QQQ(qx,qy,qz,vd) * Btx(dx,qx);
}
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { DQQ(dx,qy,qz,vd) = u[qz]; }
}
}
}
}
else if (TestType == FiniteElement::CURL)
{
if (DIM == 3)
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
return HcurlDLFAssemble3D<0, 0>;
const int nx = (vd == 0) ? d : d-1;
const int ny = (vd == 1) ? d : d-1;
DeviceMatrix Bty = (vd == 1) ? Bct : Bot;
MFEM_FOREACH_THREAD(dy,y,ny)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t u[Q];
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { u[qz] = 0.0; }
MFEM_UNROLL(Q)
for (int qy = 0; qy < q; ++qy)
{
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
u[qz] += DQQ(dx,qy,qz,vd) * Bty(dy,qy);
}
}
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz) { DDQ(dx,dy,qz,vd) = u[qz]; }
}
}
}
}
MFEM_ABORT("");
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(vd,z,vdim)
{
const int nx = (vd == 0) ? d : d-1;
const int ny = (vd == 1) ? d : d-1;
const int nz = (vd == 2) ? d : d-1;
DeviceTensor<5> Yxyz(Y, nx, ny, nz, vdim, ne);
DeviceMatrix Btz = (vd == 2) ? Bct : Bot;
MFEM_FOREACH_THREAD(dy,y,ny)
{
MFEM_FOREACH_THREAD(dx,x,nx)
{
real_t u[D];
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz) { u[dz] = 0.0; }
MFEM_UNROLL(Q)
for (int qz = 0; qz < q; ++qz)
{
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz)
{
u[dz] += DDQ(dx,dy,qz,vd) * Btz(dz,qz);
}
}
MFEM_UNROLL(D)
for (int dz = 0; dz < nz; ++dz) { Yxyz(dx,dy,dz,vd,e) += u[dz]; }
}
}
}
MFEM_SYNC_THREAD;
});
}
static void HdivDLFAssemble(const FiniteElementSpace &fes,
const IntegrationRule *ir,
const Array<int> &markers,
const Vector &coeff,
Vector &y)
{
Mesh &mesh = *fes.GetMesh();
const int dim = mesh.Dimension();
const FiniteElement *el = fes.GetTypicalFE();
const auto *vel = dynamic_cast<const VectorTensorFiniteElement *>(el);
MFEM_VERIFY(vel != nullptr, "Must be VectorTensorFiniteElement");
const MemoryType mt = Device::GetDeviceMemoryType();
const DofToQuad &maps_o = vel->GetDofToQuadOpen(*ir, DofToQuad::TENSOR);
const DofToQuad &maps_c = vel->GetDofToQuad(*ir, DofToQuad::TENSOR);
const int d = maps_c.ndof, q = maps_c.nqpt;
constexpr int flags = GeometricFactors::JACOBIANS;
const GeometricFactors *geom = mesh.GetGeometricFactors(*ir, flags, mt);
decltype(&HdivDLFAssemble2D<>) ker =
dim == 2 ? HdivDLFAssemble2D<> : HdivDLFAssemble3D<>;
if (dim==2)
{
if (d==1 && q==1) { ker=HdivDLFAssemble2D<1,1>; }
if (d==2 && q==2) { ker=HdivDLFAssemble2D<2,2>; }
if (d==3 && q==3) { ker=HdivDLFAssemble2D<3,3>; }
if (d==4 && q==4) { ker=HdivDLFAssemble2D<4,4>; }
if (d==5 && q==5) { ker=HdivDLFAssemble2D<5,5>; }
if (d==6 && q==6) { ker=HdivDLFAssemble2D<6,6>; }
if (d==7 && q==7) { ker=HdivDLFAssemble2D<7,7>; }
if (d==8 && q==8) { ker=HdivDLFAssemble2D<8,8>; }
}
if (dim==3)
{
if (d==2 && q==2) { ker=HdivDLFAssemble3D<2,2>; }
if (d==3 && q==3) { ker=HdivDLFAssemble3D<3,3>; }
if (d==4 && q==4) { ker=HdivDLFAssemble3D<4,4>; }
if (d==5 && q==5) { ker=HdivDLFAssemble3D<5,5>; }
if (d==6 && q==6) { ker=HdivDLFAssemble3D<6,6>; }
if (d==7 && q==7) { ker=HdivDLFAssemble3D<7,7>; }
if (d==8 && q==8) { ker=HdivDLFAssemble3D<8,8>; }
}
MFEM_VERIFY(ker, "No kernel ndof " << d << " nqpt " << q);
const int ne = mesh.GetNE();
const int *M = markers.Read();
const real_t *Bo = maps_o.B.Read();
const real_t *Bc = maps_c.B.Read();
const real_t *J = geom->J.Read();
const real_t *W = ir->GetWeights().Read();
real_t *Y = y.ReadWrite();
ker(ne, d, q, M, Bo, Bc, J, W, coeff, Y);
}
/// \endcond DO_NOT_DOCUMENT
void VectorFEDomainLFIntegrator::AssembleDevice(const FiniteElementSpace &fes,
const Array<int> &markers,
@@ -96,23 +337,15 @@ void VectorFEDomainLFIntegrator::AssembleDevice(const FiniteElementSpace &fes,
QuadratureSpace qs(*fes.GetMesh(), *ir);
CoefficientVector coeff(QF, qs, CoefficientStorage::COMPRESSED);
const FiniteElement::DerivType fe_type =
static_cast<FiniteElement::DerivType>(fe.GetDerivType());
Mesh &mesh = *fes.GetMesh();
const int dim = mesh.Dimension();
const FiniteElement *el = fes.GetTypicalFE();
const auto *vel = dynamic_cast<const VectorTensorFiniteElement *>(el);
MFEM_VERIFY(vel != nullptr, "Must be VectorTensorFiniteElement");
const MemoryType mt = Device::GetDeviceMemoryType();
const DofToQuad &maps_o = vel->GetDofToQuadOpen(*ir, DofToQuad::TENSOR);
const DofToQuad &maps_c = vel->GetDofToQuad(*ir, DofToQuad::TENSOR);
const int d = maps_c.ndof, q = maps_c.nqpt;
constexpr int flags = GeometricFactors::JACOBIANS;
const GeometricFactors *geom = mesh.GetGeometricFactors(*ir, flags, mt);
AssembleKernels::Run(fe_type, dim, d, q, mesh.GetNE(), markers, geom->J,
ir->GetWeights(), maps_o.B, maps_c.B, coeff, b, d, q);
const int fe_type = fe.GetDerivType();
if (fe_type == FiniteElement::DIV)
{
HdivDLFAssemble(fes, ir, markers, coeff, b);
}
else
{
MFEM_ABORT("Not implemented.");
}
}
} // namespace mfem
-7
View File
@@ -471,13 +471,6 @@ void VectorBoundaryLFIntegrator::AssembleRHSElementVect(
}
}
VectorFEDomainLFIntegrator::VectorFEDomainLFIntegrator(
VectorCoefficient &F, const IntegrationRule *ir)
: DeltaLFIntegrator(F, ir), QF(F)
{
static Kernels kernels{};
}
void VectorFEDomainLFIntegrator::AssembleRHSElementVect(
const FiniteElement &el, ElementTransformation &Tr, Vector &elvect)
{
+2 -36
View File
@@ -369,8 +369,8 @@ private:
Vector vec;
public:
VectorFEDomainLFIntegrator(VectorCoefficient &F,
const IntegrationRule *ir = nullptr);
VectorFEDomainLFIntegrator(VectorCoefficient &F)
: DeltaLFIntegrator(F), QF(F) { }
void AssembleRHSElementVect(const FiniteElement &el,
ElementTransformation &Tr,
@@ -387,40 +387,6 @@ public:
Vector &b) override;
using LinearFormIntegrator::AssembleRHSElementVect;
/// @param ne number of elements
/// @param markers array where entry markers[e] == 0 to skip assembly over
/// element e element
/// @param jac Spatial Jacobians evaluated at all quadrature points
/// @param weights 1D quadrature weights
/// @param testBO 1D open basis test functions
/// @param testBC 1D closed basis test functions
/// @param coeff coefficient values evaluated at quadrature points, possibly
/// compressed.
/// @param d number of 1D closed dofs
/// @param q number of 1D quadrature points
using AssembleKernelType = void (*)(const int NE, const Array<int> &markers,
const Vector &jac,
const Array<real_t> &weights,
const Array<real_t> &testBO,
const Array<real_t> &testBC,
const Vector &coeff, Vector &y,
const int testd1d, const int q1d);
/// parameters: test_fetype, ndims, test_d1d, q1d
MFEM_REGISTER_KERNELS(AssembleKernels, AssembleKernelType,
(FiniteElement::DerivType, int, int, int));
struct Kernels
{
Kernels();
};
template <FiniteElement::DerivType TestType, int DIM, int TEST_D1D, int Q1D>
static void AddSpecialization()
{
AssembleKernels::Specialization<TestType, DIM, TEST_D1D, Q1D>::Add();
}
};
/// $ (Q, \mathrm{curl}(v))_{\Omega} $ for Nedelec Elements
-3
View File
@@ -224,9 +224,6 @@ public:
/** @see GetGradient(const Vector &) */
Operator &GetGradient(const Vector &x, bool finalize) const;
/// Suppress a warning about hiding overloaded virtual function.
using Operator::GetGradient;
/// Update the NonlinearForm to propagate updates of the associated FE space.
/** After calling this method, the essential boundary conditions need to be
set again. */
+62 -353
View File
@@ -10,7 +10,6 @@
// CONTRIBUTING.md for details.
#include "particleset.hpp"
#include "../general/forall.hpp"
#if defined(MFEM_USE_MPI) && defined(MFEM_USE_GSLIB)
@@ -226,7 +225,6 @@ void ParticleSet::AddParticles(const Array<IDType> &new_ids,
}
}
// Add new ids
ids.HostReadWrite();
ids.Append(new_ids);
// Update data
@@ -246,102 +244,6 @@ void ParticleSet::AddParticles(const Array<IDType> &new_ids,
#if defined(MFEM_USE_MPI) && defined(MFEM_USE_GSLIB)
/// \cond DO_NOT_DOCUMENT
// Static helper: gather selected particle-vector entries into a compact buffer.
// nvcc does not allow extended host/device lambdas in non-public members.
static void GatherParticleVectorDevice(const ParticleVector &pv,
const Array<int> &send_idxs,
Vector &send_data,
int nsend)
{
const int vdim = pv.GetVDim();
const int ordering = pv.GetOrdering();
const int num_particles = pv.GetNumParticles();
const MemoryClass device_mc = Device::GetDeviceMemoryClass();
send_data.SetSize(nsend*vdim);
real_t *d_send_data =
send_data.GetMemory().Write(device_mc, send_data.Size());
const real_t *d_src = pv.GetMemory().Read(device_mc, pv.Size());
const int *d_send_idxs = send_idxs.GetMemory().Read(device_mc, nsend);
mfem::forall(nsend, [=] MFEM_HOST_DEVICE (int i)
{
const int p = d_send_idxs[i];
const int offset = (ordering == Ordering::byVDIM) ? p * vdim : p;
const int stride = (ordering == Ordering::byVDIM) ? 1 : num_particles;
for (int c = 0; c < vdim; c++)
{
d_send_data[i*vdim + c] = d_src[offset + c*stride];
}
});
}
// Static helper: gather selected tag values into a compact buffer.
// nvcc does not allow extended host/device lambdas in non-public members.
static void GatherParticleTagsDevice(const Array<int> &tag,
const Array<int> &send_idxs,
Array<int> &send_tag,
int nsend)
{
const MemoryClass device_mc = Device::GetDeviceMemoryClass();
send_tag.SetSize(nsend);
int *d_send_tag = send_tag.GetMemory().Write(device_mc, nsend);
const int *d_tag = tag.GetMemory().Read(device_mc, tag.Size());
const int *d_send_idxs = send_idxs.GetMemory().Read(device_mc, nsend);
mfem::forall(nsend, [=] MFEM_HOST_DEVICE (int i)
{
d_send_tag[i] = d_tag[d_send_idxs[i]];
});
}
// Static helper: scatter compact particle-vector entries to particle storage.
// nvcc does not allow extended host/device lambdas in non-public members.
static void ScatterParticleVectorDevice(ParticleVector &pv,
const Vector &recv_data,
const Array<int> &recv_locs,
int nrecv)
{
const int vdim = pv.GetVDim();
const int ordering = pv.GetOrdering();
const int num_particles = pv.GetNumParticles();
const MemoryClass device_mc = Device::GetDeviceMemoryClass();
const real_t *d_recv_data =
recv_data.GetMemory().Read(device_mc, recv_data.Size());
const int *d_recv_locs = recv_locs.GetMemory().Read(device_mc, nrecv);
real_t *d_dst = pv.GetMemory().ReadWrite(device_mc, pv.Size());
mfem::forall(nrecv, [=] MFEM_HOST_DEVICE (int i)
{
const int p = d_recv_locs[i];
const int offset = (ordering == Ordering::byVDIM) ? p * vdim : p;
const int stride = (ordering == Ordering::byVDIM) ? 1 : num_particles;
for (int c = 0; c < vdim; c++)
{
d_dst[offset + c*stride] = d_recv_data[i*vdim + c];
}
});
}
// Static helper: scatter compact tag values to particle storage.
// nvcc does not allow extended host/device lambdas in non-public members.
static void ScatterParticleTagsDevice(Array<int> &tag,
const Array<int> &recv_tag,
const Array<int> &recv_locs,
int nrecv)
{
const MemoryClass device_mc = Device::GetDeviceMemoryClass();
const int *d_recv_tag = recv_tag.GetMemory().Read(device_mc, nrecv);
const int *d_recv_locs = recv_locs.GetMemory().Read(device_mc, nrecv);
int *d_tag = tag.GetMemory().ReadWrite(device_mc, tag.Size());
mfem::forall(nrecv, [=] MFEM_HOST_DEVICE (int i)
{
d_tag[d_recv_locs[i]] = d_recv_tag[i];
});
}
template<size_t NBytes>
void ParticleSet::TransferParticlesImpl(ParticleSet &pset,
const Array<int> &send_idxs,
@@ -364,129 +266,49 @@ void ParticleSet::TransferParticlesImpl(ParticleSet &pset,
array_init(parr_t, &gsl_arr, send_idxs.Size());
pdata_arr = (parr_t*) gsl_arr.ptr;
int nparticles = pset.GetNParticles();
int nsend = send_idxs.Size();
gsl_arr.n = send_idxs.Size();
const int *h_send_idxs_initial = send_idxs.HostRead();
const IDType *h_ids = pset.GetIDs().HostRead();
for (int i = 0; i < send_idxs.Size(); i++)
{
parr_t &pdata = pdata_arr[i];
pdata.id = h_ids[h_send_idxs_initial[i]];
}
pdata.id = pset.GetIDs()[send_idxs[i]];
// Pack coords and fields into the GSLIB send buffer. Device-resident data
// is first gathered into a compact device buffer so that only selected
// particles are copied back to host. Host-resident data is packed directly.
int max_vdim = pset.Coords().GetVDim();
for (int f = 0; f < pset.GetNFields(); f++)
{
int f_vdim = pset.Field(f).GetVDim();
if (f_vdim > max_vdim) { max_vdim = f_vdim; }
}
Vector send_data;
Array<int> send_tag;
if (Device::IsEnabled())
{
send_data.SetSize(nsend * max_vdim); // allocate max size over all fields
send_tag.SetSize(nsend);
}
size_t counter = 0;
for (int f = -1; f < pset.GetNFields(); f++)
{
const ParticleVector &pv = f == -1 ? pset.Coords() : pset.Field(f);
const int vdim = pv.GetVDim();
const int ordering = pv.GetOrdering();
const int num_particles = pv.GetNumParticles();
const bool use_dev = Device::IsEnabled() && pv.UseDevice();
if (use_dev)
// Copy particle data directly into pdata
size_t counter = 0;
for (int f = -1; f < pset.GetNFields(); f++)
{
GatherParticleVectorDevice(pv, send_idxs, send_data, nsend);
const real_t *h_send_data = send_data.HostRead();
for (int i = 0; i < nsend; i++)
ParticleVector &pv = (f == -1 ? pset.Coords() : pset.Field(f));
for (int c = 0; c < pv.GetVDim(); c++)
{
std::memcpy(pdata_arr[i].data.data() + counter,
h_send_data + i*vdim, vdim * sizeof(real_t));
}
}
else
{
const real_t *h_src = pv.HostRead();
const int *h_send_idxs = send_idxs.HostRead();
for (int i = 0; i < nsend; i++)
{
parr_t &pdata = pdata_arr[i];
const int p = h_send_idxs[i];
const int offset = (ordering == Ordering::byVDIM) ? p * vdim : p;
const int stride = (ordering == Ordering::byVDIM) ? 1 :
num_particles;
for (int c = 0; c < vdim; c++)
{
std::memcpy(pdata.data.data() + counter + c*sizeof(real_t),
h_src + offset + c*stride, sizeof(real_t));
}
std::memcpy(pdata.data.data() + counter, &pv(send_idxs[i], c),
sizeof(real_t));
counter += sizeof(real_t);
}
}
counter += vdim*sizeof(real_t);
}
// Pack tags after all real_t data. Each tag uses the same selective
// device gather path when its Array is device-resident.
for (int t = 0; t < pset.GetNTags(); t++)
{
const Array<int> &tag = pset.Tag(t);
const size_t tag_counter = counter + t*sizeof(int);
const bool use_dev = Device::IsEnabled() && tag.UseDevice();
if (use_dev)
// Copy tags
for (int t = 0; t < pset.GetNTags(); t++)
{
GatherParticleTagsDevice(tag, send_idxs, send_tag, nsend);
const int *h_send_tag = send_tag.HostRead();
for (int i = 0; i < nsend; i++)
{
std::memcpy(pdata_arr[i].data.data() + tag_counter,
h_send_tag + i, sizeof(int));
}
}
else
{
const int *h_tag = tag.HostRead();
const int *h_send_idxs = send_idxs.HostRead();
for (int i = 0; i < nsend; i++)
{
std::memcpy(pdata_arr[i].data.data() + tag_counter,
h_tag + h_send_idxs[i], sizeof(int));
}
Array<int> &tag_arr = pset.Tag(t);
std::memcpy(pdata.data.data() + counter, &tag_arr[send_idxs[i]],
sizeof(int));
counter += sizeof(int);
}
}
int nparticles = pset.GetNParticles();
int nsend = send_idxs.Size();
// Transfer particles
sarray_transfer_ext(parr_t, &gsl_arr, send_ranks.GetData(),
sizeof(unsigned int), pset.cr);
// Make sure we have enough space for received particles
int nrecv = (int) gsl_arr.n;
Vector recv_data;
Array<int> recv_tag;
if (Device::IsEnabled())
{
recv_data.SetSize(nrecv * max_vdim);
recv_tag.SetSize(nrecv);
}
int ndelete = nsend - nrecv;
if (ndelete > 0)
{
// Remove unneeded particles
auto datap = const_cast<int*>(send_idxs.HostRead());
auto datap = const_cast<int*>(send_idxs.GetData());
Array<int> delete_idxs(datap + nrecv, ndelete);
pset.RemoveParticles(delete_idxs);
}
@@ -497,133 +319,47 @@ void ParticleSet::TransferParticlesImpl(ParticleSet &pset,
pdata_arr = (parr_t*) gsl_arr.ptr;
// Make a list of new IDs to add
int num_new = nrecv > nsend ? nrecv - nsend : 0;
Array<IDType> new_ids(num_new);
for (int i = 0; i < num_new; i++)
{
new_ids[i] = pdata_arr[nsend + i].id;
}
// Add particles in batch
Array<int> new_indices;
if (num_new > 0)
{
pset.AddParticles(new_ids, &new_indices);
}
// Map each received packet to the local particle slot it updates.
Array<int> recv_locs(nrecv);
int *h_recv_locs = recv_locs.HostWrite();
const int *h_send_idxs_recv = send_idxs.HostRead();
// Add newly-recvd data directly to active state
for (int i = 0; i < nrecv; i++)
{
parr_t &pdata = pdata_arr[i];
IDType id = pdata.id;
int new_loc_idx;
if (i < nsend) // update existing particle
{
h_recv_locs[i] = h_send_idxs_recv[i];
pset.UpdateID(h_recv_locs[i], pdata.id);
new_loc_idx = send_idxs[i];
pset.UpdateID(new_loc_idx, id);
}
else
{
h_recv_locs[i] = new_indices[i - nsend];
// add new particle
Array<int> idx_temp;
pset.AddParticles(Array<IDType>({id}), &idx_temp);
new_loc_idx = idx_temp[0]; // Get index of newly-added particle
}
}
// Unpack coords and fields from GSLIB host packets. Device-resident
// destinations use a compact host buffer followed by a device scatter.
size_t recv_counter = 0;
for (int f = -1; f < pset.GetNFields(); f++)
{
ParticleVector &pv = (f == -1 ? pset.Coords() : pset.Field(f));
const int vdim = pv.GetVDim();
const int ordering = pv.GetOrdering();
const int num_particles = pv.GetNumParticles();
const bool use_dev = Device::IsEnabled() && pv.UseDevice();
if (use_dev)
size_t counter = 0;
for (int f = -1; f < pset.GetNFields(); f++)
{
recv_data.SetSize(nrecv*vdim);
real_t *h_recv_data = recv_data.HostWrite();
for (int i = 0; i < nrecv; i++)
ParticleVector &pv = (f == -1 ? pset.Coords() : pset.Field(f));
for (int c = 0; c < pv.GetVDim(); c++)
{
std::memcpy(h_recv_data + i*vdim,
pdata_arr[i].data.data() + recv_counter,
vdim*sizeof(real_t));
}
ScatterParticleVectorDevice(pv, recv_data, recv_locs, nrecv);
}
else
{
real_t *h_dst = pv.HostReadWrite();
const int *h_recv_locs_read = recv_locs.HostRead();
for (int i = 0; i < nrecv; i++)
{
parr_t &pdata = pdata_arr[i];
const int p = h_recv_locs_read[i];
const int offset = (ordering == Ordering::byVDIM) ? p * vdim : p;
const int stride = (ordering == Ordering::byVDIM) ? 1 :
num_particles;
for (int c = 0; c < vdim; c++)
{
std::memcpy(h_dst + offset + c*stride,
pdata.data.data() + recv_counter + c*sizeof(real_t),
sizeof(real_t));
}
real_t& val = pv(new_loc_idx, c);
std::memcpy(&val, pdata.data.data() + counter, sizeof(real_t));
counter += sizeof(real_t);
}
}
recv_counter += vdim*sizeof(real_t);
}
// Unpack tags after all real_t data, using the same compact scatter path
// for device-resident tag arrays.
for (int t = 0; t < pset.GetNTags(); t++)
{
Array<int> &tag = pset.Tag(t);
const size_t tag_counter = recv_counter + t*sizeof(int);
const bool use_dev = Device::IsEnabled() && tag.UseDevice();
if (use_dev)
for (int t = 0; t < pset.GetNTags(); t++)
{
recv_tag.SetSize(nrecv);
int *h_recv_tag = recv_tag.HostWrite();
for (int i = 0; i < nrecv; i++)
{
std::memcpy(h_recv_tag + i,
pdata_arr[i].data.data() + tag_counter, sizeof(int));
}
ScatterParticleTagsDevice(tag, recv_tag, recv_locs, nrecv);
}
else
{
int *h_tag = tag.HostReadWrite();
const int *h_recv_locs_read = recv_locs.HostRead();
for (int i = 0; i < nrecv; i++)
{
std::memcpy(h_tag + h_recv_locs_read[i],
pdata_arr[i].data.data() + tag_counter, sizeof(int));
}
Array<int> &tag_arr = pset.Tag(t);
std::memcpy(&tag_arr[new_loc_idx],
pdata.data.data() + counter, sizeof(int));
counter += sizeof(int);
}
}
array_free(&gsl_arr);
// Restore Device validity if needed
for (int f = -1; f < pset.GetNFields(); f++)
{
ParticleVector &pv = (f == -1 ? pset.Coords() : pset.Field(f));
pv.ReadWrite(pv.UseDevice());
}
for (int t = 0; t < pset.GetNTags(); t++)
{
Array<int> &tag_arr = pset.Tag(t);
if (tag_arr.UseDevice()) { tag_arr.ReadWrite(true); }
}
}
template<size_t NBytes>
@@ -790,14 +526,11 @@ ParticleSet::ParticleSet(int id_stride_, IDType id_counter_, int num_particles,
int dim, Ordering::Type coords_ordering, const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_,
bool use_device)
const Array<const char*> &tag_names_)
: id_stride(id_stride_),
id_counter(id_counter_),
coords(dim, coords_ordering)
{
if (use_device) { coords.UseDevice(true); }
// Initialize fields
for (int f = 0; f < field_vdims.Size(); f++)
{
@@ -847,22 +580,21 @@ bool ParticleSet::IsValidParticle(const Particle &p) const
}
ParticleSet::ParticleSet(int num_particles, int dim,
Ordering::Type coords_ordering,
bool use_device)
Ordering::Type coords_ordering)
: ParticleSet(1, 0, num_particles, dim, coords_ordering, Array<int>(),
Array<Ordering::Type>(), Array<const char*>(), 0,
Array<const char*>(), use_device)
Array<const char*>())
{
}
ParticleSet::ParticleSet(int num_particles, int dim,
const Array<int> &field_vdims, int num_tags,
Ordering::Type all_ordering, bool use_device)
Ordering::Type all_ordering)
: ParticleSet(1, 0, num_particles, dim, all_ordering, field_vdims,
GetOrderingArray(all_ordering, field_vdims.Size()),
GetEmptyNameArray(field_vdims.Size()), num_tags,
GetEmptyNameArray(num_tags), use_device)
GetEmptyNameArray(num_tags))
{
}
@@ -870,11 +602,11 @@ ParticleSet::ParticleSet(int num_particles, int dim,
const Array<int> &field_vdims, const Array<const
char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_,
Ordering::Type all_ordering, bool use_device)
Ordering::Type all_ordering)
: ParticleSet(1, 0, num_particles, dim, all_ordering, field_vdims,
GetOrderingArray(all_ordering, field_vdims.Size()),
field_names_, num_tags,
tag_names_, use_device)
tag_names_)
{
}
@@ -884,9 +616,9 @@ ParticleSet::ParticleSet(int num_particles, int dim,
const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_, bool use_device)
const Array<const char*> &tag_names_)
: ParticleSet(1, 0, num_particles, dim, coords_ordering, field_vdims,
field_orderings, field_names_, num_tags, tag_names_, use_device)
field_orderings, field_names_, num_tags, tag_names_)
{
}
@@ -895,21 +627,21 @@ ParticleSet::ParticleSet(int num_particles, int dim,
#ifdef MFEM_USE_MPI
ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
Ordering::Type coords_ordering, bool use_device)
Ordering::Type coords_ordering)
: ParticleSet(comm_, rank_num_particles, dim, coords_ordering, Array<int>(),
Array<Ordering::Type>(), Array<const char*>(), 0,
Array<const char*>(), use_device)
Array<const char*>())
{
};
ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
const Array<int> &field_vdims, int num_tags,
Ordering::Type all_ordering, bool use_device)
Ordering::Type all_ordering)
: ParticleSet(comm_, rank_num_particles, dim, all_ordering, field_vdims,
GetOrderingArray(all_ordering, field_vdims.Size()),
GetEmptyNameArray(field_vdims.Size()), num_tags,
GetEmptyNameArray(num_tags), use_device)
GetEmptyNameArray(num_tags))
{
}
@@ -918,11 +650,11 @@ ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
const Array<int> &field_vdims, const Array<const
char*> &field_names_,
int num_tags, const Array<const char*> &tag_names_,
Ordering::Type all_ordering, bool use_device)
Ordering::Type all_ordering)
: ParticleSet(comm_, rank_num_particles, dim, all_ordering, field_vdims,
GetOrderingArray(all_ordering, field_vdims.Size()),
field_names_, num_tags,
tag_names_, use_device)
tag_names_)
{
}
@@ -932,7 +664,7 @@ ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_, bool use_device)
const Array<const char*> &tag_names_)
: ParticleSet(GetSize(comm_), (IDType)GetRank(comm_),
rank_num_particles,
dim,
@@ -941,7 +673,7 @@ ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
field_orderings,
field_names_,
num_tags,
tag_names_, use_device)
tag_names_)
{
comm = comm_;
#ifdef MFEM_USE_GSLIB
@@ -973,7 +705,6 @@ int ParticleSet::AddField(int vdim, Ordering::Type field_ordering,
}
fields.emplace_back(std::make_unique<ParticleVector>(vdim, field_ordering,
GetNParticles()));
if (coords.UseDevice()) { fields.back()->UseDevice(true); }
field_names.emplace_back(field_name_str);
return GetNFields() - 1;
@@ -987,7 +718,6 @@ int ParticleSet::AddTag(const char* tag_name)
tag_name_str = GetDefaultTagName(tag_names.size());
}
tags.emplace_back(std::make_unique<Array<int>>(GetNParticles()));
if (coords.UseDevice()) { tags.back()->GetMemory().UseDevice(true); }
tag_names.emplace_back(tag_name_str);
return GetNTags() - 1;
@@ -1052,7 +782,7 @@ Particle ParticleSet::GetParticle(int i) const
for (int t = 0; t < GetNTags(); t++)
{
p.Tag(t) = Tag(t).HostRead()[i];
p.Tag(t) = Tag(t)[i];
}
return p;
@@ -1060,21 +790,13 @@ Particle ParticleSet::GetParticle(int i) const
bool ParticleSet::IsParticleRefValid() const
{
if (coords.GetOrdering() == Ordering::byNODES || coords.UseDevice())
if (coords.GetOrdering() == Ordering::byNODES)
{
return false;
}
for (int f = 0; f < GetNFields(); f++)
{
if (fields[f]->GetOrdering() == Ordering::byNODES ||
fields[f]->UseDevice())
{
return false;
}
}
for (int t = 0; t < GetNTags(); t++)
{
if (tags[t]->UseDevice())
if (fields[f]->GetOrdering() == Ordering::byNODES)
{
return false;
}
@@ -1084,10 +806,6 @@ bool ParticleSet::IsParticleRefValid() const
Particle ParticleSet::GetParticleRef(int i)
{
MFEM_ASSERT(IsParticleRefValid(),
"GetParticleRef is only valid when coordinates and fields are "
"ordered byVDIM and particle data is host-resident.");
Particle p = CreateParticle();
Coords().GetValuesRef(i, p.Coords());
@@ -1121,7 +839,7 @@ void ParticleSet::SetParticle(int i, const Particle &p)
for (int t = 0; t < GetNTags(); t++)
{
Tag(t).HostReadWrite()[i] = p.Tag(t);
Tag(t)[i] = p.Tag(t);
}
}
@@ -1182,15 +900,6 @@ void ParticleSet::PrintCSV(const char *fname, const Array<int> &field_idxs,
#ifdef MFEM_USE_MPI
int rank = GetRank(comm);
#endif // MFEM_USE_MPI
// make sure we can read tag data on host. fields and coords will be read as
// needed in the loop below, so we don't need to pre-read them here.
for (int i = 0; i < GetNTags(); i++)
{
tags[i]->HostRead();
}
ids.HostRead();
// Write particle data
for (int i = 0; i < GetNParticles(); i++)
{
ss_data << ids[i];
+12 -49
View File
@@ -211,12 +211,6 @@ public:
* byVDIM). The unique_ptrs to all the ParticleVectors are stored in the
* std::vector \ref fields.
*
* @par Device Behavior:
* When a ParticleSet is constructed with \p use_device=true, \ref coords and
* all ParticleVector fields are marked to use device memory. Fields added
* later through \ref AddField inherit the current device mode (through
* \ref coords).
*
* @par Tags:
* Tags represent integers associated with each particle. For a given tag,
* all particle data are stored in a single Array<int>. The unique_ptrs to all
@@ -375,10 +369,7 @@ protected:
* ID of a particle.
*/
void UpdateID(int local_idx, IDType new_global_id)
{
ids.HostReadWrite();
ids[local_idx] = new_global_id;
}
{ ids[local_idx] = new_global_id; }
/** @brief Create a Particle object with the same spatial dimension,
* number of fields and field vdims, and number of tags as this ParticleSet.
@@ -408,14 +399,12 @@ protected:
* @param[in] field_names_ Array of field names.
* @param[in] num_tags Number of tags to register.
* @param[in] tag_names_ Array of tag names.
* @param[in] use_device Use device memory for particle fields.
*/
ParticleSet(int id_stride_, IDType id_counter_, int num_particles, int dim,
Ordering::Type coords_ordering, const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_,
bool use_device);
const Array<const char*> &tag_names_);
public:
@@ -424,12 +413,9 @@ public:
* @param[in] num_particles Number of particles to initialize.
* @param[in] dim Particle spatial dimension.
* @param[in] coords_ordering Ordering of coordinates.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(int num_particles, int dim,
Ordering::Type coords_ordering=Ordering::byVDIM,
bool use_device=false);
Ordering::Type coords_ordering=Ordering::byVDIM);
/** @brief Construct a serial ParticleSet with specified fields and tags at
* construction.
@@ -440,12 +426,9 @@ public:
* @param[in] num_tags Number of tags to register.
* @param[in] all_ordering (Optional) Ordering of coordinates and
* field ParticleVector.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(int num_particles, int dim, const Array<int> &field_vdims,
int num_tags, Ordering::Type all_ordering=Ordering::byVDIM,
bool use_device=false);
int num_tags, Ordering::Type all_ordering=Ordering::byVDIM);
/** @brief Construct a serial ParticleSet with specified fields and tags at
* construction, with names.
@@ -458,14 +441,11 @@ public:
* @param[in] tag_names_ Array of tag names.
* @param[in] all_ordering (Optional) Ordering of coordinates and
* field ParticleVector.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(int num_particles, int dim, const Array<int> &field_vdims,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_,
Ordering::Type all_ordering=Ordering::byVDIM,
bool use_device=false);
Ordering::Type all_ordering=Ordering::byVDIM);
/** @brief Comprehensive serial constructor of ParticleSet.
*
@@ -477,15 +457,12 @@ public:
* @param[in] field_names_ Array of field names.
* @param[in] num_tags Number of tags to register.
* @param[in] tag_names_ Array of tag names.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(int num_particles, int dim, Ordering::Type coords_ordering,
const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_,
bool use_device=false);
const Array<const char*> &tag_names_);
#ifdef MFEM_USE_MPI
/** @brief Construct a parallel ParticleSet.
@@ -494,12 +471,9 @@ public:
* @param[in] rank_num_particles Number of particles to initialize.
* @param[in] dim Particle spatial dimension.
* @param[in] coords_ordering (Optional) Ordering of coordinates.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
Ordering::Type coords_ordering=Ordering::byVDIM,
bool use_device=false);
Ordering::Type coords_ordering=Ordering::byVDIM);
/** @brief Construct a parallel ParticleSet with specified fields and tags
* at construction.
@@ -511,13 +485,10 @@ public:
* @param[in] num_tags Number of tags to register.
* @param[in] all_ordering (Optional) Ordering of coordinates and
* field ParticleVector.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
const Array<int> &field_vdims, int num_tags,
Ordering::Type all_ordering=Ordering::byVDIM,
bool use_device=false);
Ordering::Type all_ordering=Ordering::byVDIM);
/** @brief Construct a parallel ParticleSet with specified fields and tags
* at construction, with names (for PrintCSV()).
@@ -531,15 +502,12 @@ public:
* @param[in] tag_names_ Array of tag names.
* @param[in] all_ordering (Optional) Ordering of coordinates and
* field ParticleVector.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
const Array<int> &field_vdims,
const Array<const char*> &field_names_,
int num_tags, const Array<const char*> &tag_names_,
Ordering::Type all_ordering=Ordering::byVDIM,
bool use_device=false);
Ordering::Type all_ordering=Ordering::byVDIM);
/** @brief Comprehensive parallel constructor of ParticleSet.
*
@@ -552,15 +520,12 @@ public:
* @param[in] field_names_ Array of field names.
* @param[in] num_tags Number of tags to register.
* @param[in] tag_names_ Array of tag names.
* @param[in] use_device (Optional) Use device memory for particle
* fields.
*/
ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
Ordering::Type coords_ordering, const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_,
bool use_device=false);
const Array<const char*> &tag_names_);
/// Get the MPI communicator for this ParticleSet.
MPI_Comm GetComm() const { return comm; };
@@ -580,8 +545,6 @@ public:
* @param[in] field_ordering (Optional) Ordering::Type of the field.
* @param[in] field_name (Optional) Name of the field.
*
* @note New fields inherit the current device mode of \ref coords.
*
* @return Index of the newly-added field.
*/
int AddField(int vdim, Ordering::Type field_ordering=Ordering::byVDIM,
@@ -674,8 +637,8 @@ public:
/** @brief Determine if GetParticleRef is valid.
*
* Returns true when coordinates and all fields are ordered byVDIM and
* particle data is host-resident. Otherwise, false.
* If coordinates and all fields are ordered byVDIM, then returns true.
* Otherwise, false.
*/
bool IsParticleRefValid() const;
+219 -79
View File
@@ -1030,12 +1030,42 @@ void L2ProjectionGridTransfer::L2ProjectionL2Space::EAProlongateTranspose(
BatchedLinAlg::MultTranspose(P_dt, x, y);
}
L2ProjectionGridTransfer::L2ProjectionH1Space::H1ConsistentMassOperator::
H1ConsistentMassOperator(const Operator &M_LH_, const Solver &M_L_solver_)
: Operator(M_LH_.Height(), M_LH_.Width()),
M_LH(M_LH_),
M_L_solver(M_L_solver_)
{
MFEM_VERIFY(M_LH.Height() == M_L_solver.Height() &&
M_LH.Height() == M_L_solver.Width(),
"incompatible consistent mass operator dimensions");
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::H1ConsistentMassOperator::
Mult(const Vector &x, Vector &y) const
{
Vector tmp(M_LH.Height());
M_LH.Mult(x, tmp);
M_L_solver.Mult(tmp, y);
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::H1ConsistentMassOperator::
MultTranspose(const Vector &x, Vector &y) const
{
Vector tmp(M_LH.Height());
M_L_solver.Mult(x, tmp);
M_LH.MultTranspose(tmp, y);
}
L2ProjectionGridTransfer::L2ProjectionH1Space::L2ProjectionH1Space(
const FiniteElementSpace& fes_ho_, const FiniteElementSpace& fes_lor_,
const bool use_ea_, MemoryType d_mt_)
const bool use_ea_, const bool use_consistent_mass_, MemoryType d_mt_)
: L2Projection(fes_ho_, fes_lor_, d_mt_),
use_ea(use_ea_)
use_ea(use_ea_),
use_consistent_mass(use_consistent_mass_)
{
MFEM_VERIFY(!(use_ea && use_consistent_mass),
"consistent mass is not supported with element assembly");
// need scalar to keep dimensions matching (operators are built to apply
// individually on each vdim)
@@ -1053,7 +1083,7 @@ L2ProjectionGridTransfer::L2ProjectionH1Space::L2ProjectionH1Space(
std::unique_ptr<SparseMatrix> R_mat, M_LH_mat;
std::tie(R_mat, M_LH_mat) = ComputeSparseRAndM_LH();
std::tie(R_mat, M_LH_mat) = ComputeSparseRAndM_LH(!use_consistent_mass);
const SparseMatrix *P_ho = fes_ho_scalar->GetConformingProlongation();
const SparseMatrix *P_lor = fes_lor_scalar->GetConformingProlongation();
@@ -1062,40 +1092,71 @@ L2ProjectionGridTransfer::L2ProjectionH1Space::L2ProjectionH1Space(
{
if (P_ho && P_lor)
{
R_mat.reset(RAP(*P_lor, *R_mat, *P_ho));
if (R_mat) { R_mat.reset(RAP(*P_lor, *R_mat, *P_ho)); }
M_LH_mat.reset(RAP(*P_lor, *M_LH_mat, *P_ho));
}
else if (P_ho)
{
R_mat.reset(mfem::Mult(*R_mat, *P_ho));
if (R_mat) { R_mat.reset(mfem::Mult(*R_mat, *P_ho)); }
M_LH_mat.reset(mfem::Mult(*M_LH_mat, *P_ho));
}
else // P_lor != nullptr
{
R_mat.reset(mfem::Mult(*P_lor, *R_mat));
if (R_mat) { R_mat.reset(mfem::Mult(*P_lor, *R_mat)); }
M_LH_mat.reset(mfem::Mult(*P_lor, *M_LH_mat));
}
}
SparseMatrix *RTxM_LH_mat = TransposeMult(*R_mat, *M_LH_mat);
precon.reset(new DSmoother(*RTxM_LH_mat));
if (use_consistent_mass)
{
BilinearForm M_lor(fes_lor_scalar.get());
M_lor.AddDomainIntegrator(new MassIntegrator);
M_lor.Assemble();
M_lor.Finalize();
SparseMatrix *M_L_mat = M_lor.LoseMat();
// Set ownership
RTxM_LH.reset(RTxM_LH_mat);
R = std::move(R_mat);
M_LH = std::move(M_LH_mat);
ML_precon.reset(new DSmoother(*M_L_mat));
ML_pcg.SetPrintLevel(0);
ML_pcg.SetMaxIter(1000);
ML_pcg.SetRelTol(1e-13);
ML_pcg.SetAbsTol(1e-13);
ML_pcg.SetPreconditioner(*ML_precon);
ML_pcg.SetOperator(*M_L_mat);
// Start each solve from zero so repeated Operator::Mult() calls do not
// depend on the output vector contents supplied by the caller.
ML_pcg.iterative_mode = false;
SetupPCG();
M_L.reset(M_L_mat);
M_LH = std::move(M_LH_mat);
R.reset(new H1ConsistentMassOperator(*M_LH, ML_pcg));
}
else
{
SparseMatrix *RTxM_LH_mat = TransposeMult(*R_mat, *M_LH_mat);
precon.reset(new DSmoother(*RTxM_LH_mat));
// Set ownership
RTxM_LH.reset(RTxM_LH_mat);
R = std::move(R_mat);
M_LH = std::move(M_LH_mat);
SetupPCG();
}
}
#ifdef MFEM_USE_MPI
L2ProjectionGridTransfer::L2ProjectionH1Space::L2ProjectionH1Space(
const ParFiniteElementSpace& pfes_ho, const ParFiniteElementSpace& pfes_lor,
const bool use_ea_, MemoryType d_mt_)
const bool use_ea_, const bool use_consistent_mass_, MemoryType d_mt_)
: L2Projection(pfes_ho, pfes_lor, d_mt_),
use_ea(use_ea_), pcg(pfes_ho.GetComm())
use_ea(use_ea_),
use_consistent_mass(use_consistent_mass_),
ML_pcg(pfes_ho.GetComm()),
pcg(pfes_ho.GetComm())
{
MFEM_VERIFY(!(use_ea && use_consistent_mass),
"consistent mass is not supported with element assembly");
// need scalar to keep dimensions matching (operators are built to apply
// individually on each vdim)
@@ -1111,8 +1172,42 @@ L2ProjectionGridTransfer::L2ProjectionH1Space::L2ProjectionH1Space(
return;
}
std::tie(R, M_LH) = ComputeSparseRAndM_LH();
std::tie(R, M_LH) = ComputeSparseRAndM_LH(!use_consistent_mass);
HypreParMatrix M_LH_local = HypreParMatrix(pfes_ho.GetComm(),
pfes_lor_scalar->GlobalVSize(),
pfes_ho_scalar->GlobalVSize(),
pfes_lor_scalar->GetDofOffsets(),
pfes_ho_scalar->GetDofOffsets(),
static_cast<SparseMatrix*>(M_LH.get()));
HypreParMatrix *M_LH_mat = RAP(pfes_lor_scalar->Dof_TrueDof_Matrix(),
&M_LH_local, pfes_ho_scalar->Dof_TrueDof_Matrix());
if (use_consistent_mass)
{
ParBilinearForm M_lor(pfes_lor_scalar.get());
M_lor.AddDomainIntegrator(new MassIntegrator);
M_lor.Assemble();
M_lor.Finalize();
HypreParMatrix *M_L_mat = M_lor.ParallelAssemble();
M_L.reset(M_L_mat);
M_LH.reset(M_LH_mat);
HypreDiagScale *ML_hypre_precon = new HypreDiagScale(*M_L_mat);
HyprePCG *ML_hypre_pcg = new HyprePCG(*M_L_mat);
ML_hypre_pcg->SetPrintLevel(0);
ML_hypre_pcg->SetMaxIter(1000);
ML_hypre_pcg->SetTol(1e-13);
ML_hypre_pcg->SetAbsTol(1e-13);
ML_hypre_pcg->SetPreconditioner(*ML_hypre_precon);
// Start each solve from zero so repeated Operator::Mult() calls do not
// depend on the output vector contents supplied by the caller.
ML_hypre_pcg->SetZeroInitialIterate();
ML_precon.reset(ML_hypre_precon);
ML_solver.reset(ML_hypre_pcg);
R.reset(new H1ConsistentMassOperator(*M_LH, *ML_solver));
return;
}
HypreParMatrix R_local = HypreParMatrix(pfes_ho.GetComm(),
pfes_lor_scalar->GlobalVSize(),
@@ -1120,17 +1215,9 @@ L2ProjectionGridTransfer::L2ProjectionH1Space::L2ProjectionH1Space(
pfes_lor_scalar->GetDofOffsets(),
pfes_ho_scalar->GetDofOffsets(),
static_cast<SparseMatrix*>(R.get()));
HypreParMatrix M_LH_local = HypreParMatrix(pfes_ho.GetComm(),
pfes_lor_scalar->GlobalVSize(),
pfes_ho_scalar->GlobalVSize(),
pfes_lor_scalar->GetDofOffsets(),
pfes_ho_scalar->GetDofOffsets(),
static_cast<SparseMatrix*>(M_LH.get()));
HypreParMatrix *R_mat = RAP(pfes_lor_scalar->Dof_TrueDof_Matrix(),
&R_local, pfes_ho_scalar->Dof_TrueDof_Matrix());
HypreParMatrix *M_LH_mat = RAP(pfes_lor_scalar->Dof_TrueDof_Matrix(),
&M_LH_local, pfes_ho_scalar->Dof_TrueDof_Matrix());
std::unique_ptr<HypreParMatrix> R_T(R_mat->Transpose());
HypreParMatrix *RTxM_LH_mat = ParMult(R_T.get(), M_LH_mat, true);
@@ -1438,6 +1525,8 @@ void L2ProjectionGridTransfer::L2ProjectionH1Space::MultTranspose(
void L2ProjectionGridTransfer::L2ProjectionH1Space::Prolongate(
const Vector& x, Vector& y) const
{
MFEM_VERIFY(!use_consistent_mass,
"BackwardOperator is not supported with consistent mass");
Vector X(fes_lor.GetTrueVSize());
Vector X_dim(M_LH->Height());
@@ -1469,6 +1558,9 @@ void L2ProjectionGridTransfer::L2ProjectionH1Space::Prolongate(
void L2ProjectionGridTransfer::L2ProjectionH1Space::ProlongateTranspose(
const Vector& x, Vector& y) const
{
MFEM_VERIFY(!use_consistent_mass,
"BackwardOperator is not supported with consistent mass");
Vector X(fes_ho.GetTrueVSize());
Vector X_dim(pcg.Width());
Vector Xbar(pcg.Height());
@@ -1499,17 +1591,34 @@ void L2ProjectionGridTransfer::L2ProjectionH1Space::ProlongateTranspose(
void L2ProjectionGridTransfer::L2ProjectionH1Space::SetRelTol(real_t p_rtol_)
{
pcg.SetRelTol(p_rtol_);
ML_pcg.SetRelTol(p_rtol_);
#ifdef MFEM_USE_MPI
if (ML_solver)
{
HyprePCG *hypre_pcg = dynamic_cast<HyprePCG*>(ML_solver.get());
if (hypre_pcg) { hypre_pcg->SetTol(p_rtol_); }
}
#endif
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::SetAbsTol(real_t p_atol_)
{
pcg.SetAbsTol(p_atol_);
ML_pcg.SetAbsTol(p_atol_);
#ifdef MFEM_USE_MPI
if (ML_solver)
{
HyprePCG *hypre_pcg = dynamic_cast<HyprePCG*>(ML_solver.get());
if (hypre_pcg) { hypre_pcg->SetAbsTol(p_atol_); }
}
#endif
}
std::pair<
std::unique_ptr<SparseMatrix>,
std::unique_ptr<SparseMatrix>>
L2ProjectionGridTransfer::L2ProjectionH1Space::ComputeSparseRAndM_LH()
L2ProjectionGridTransfer::L2ProjectionH1Space::ComputeSparseRAndM_LH(
bool build_R)
{
std::pair<std::unique_ptr<SparseMatrix>,
std::unique_ptr<SparseMatrix>> r_and_mlh;
@@ -1523,10 +1632,10 @@ std::unique_ptr<SparseMatrix>>
// If the local mesh is empty, skip all computations
if (nel_ho == 0)
{
return std::make_pair(
std::unique_ptr<SparseMatrix>(new SparseMatrix),
std::unique_ptr<SparseMatrix>(new SparseMatrix)
);
std::unique_ptr<SparseMatrix> R_empty;
if (build_R) { R_empty.reset(new SparseMatrix); }
std::unique_ptr<SparseMatrix> M_LH_empty(new SparseMatrix);
return std::make_pair(std::move(R_empty), std::move(M_LH_empty));
}
const CoarseFineTransformations& cf_tr = mesh_lor->GetRefinementTransforms();
@@ -1542,69 +1651,76 @@ std::unique_ptr<SparseMatrix>>
BuildHo2Lor(nel_ho, nel_lor, cf_tr);
// ML_inv contains the inverse lumped (row sum) mass matrix. Note that the
// method will also work with a full (consistent) mass matrix, though this is
// not implemented here. L refers to the low-order refined mesh
Vector ML_inv(ndof_lor);
ML_inv = 0.0;
// Compute ML_inv
for (int iho = 0; iho < nel_ho; ++iho)
if (build_R)
{
Array<int> lor_els;
ho2lor.GetRow(iho, lor_els);
int nref = ho2lor.RowSize(iho);
// ML_inv contains the inverse lumped (row sum) mass matrix. L refers to
// the low-order refined mesh.
ML_inv = 0.0;
Geometry::Type geom = mesh_ho->GetElementBaseGeometry(iho);
const FiniteElement& fe_lor = *fes_lor.GetFE(lor_els[0]);
int nedof_lor = fe_lor.GetDof();
// Instead of using a MassIntegrator, manually loop over integration
// points so we can row sum and store the diagonal as a Vector.
Vector ML_el(nedof_lor);
Vector shape_lor(nedof_lor);
Array<int> dofs_lor(nedof_lor);
for (int iref = 0; iref < nref; ++iref)
// Compute ML_inv
for (int iho = 0; iho < nel_ho; ++iho)
{
int ilor = lor_els[iref];
ElementTransformation* el_tr = fes_lor.GetElementTransformation(ilor);
Array<int> lor_els;
ho2lor.GetRow(iho, lor_els);
int nref = ho2lor.RowSize(iho);
int order = 2 * fe_lor.GetOrder() + el_tr->OrderW();
const IntegrationRule* ir = &IntRules.Get(geom, order);
ML_el = 0.0;
for (int i = 0; i < ir->GetNPoints(); ++i)
Geometry::Type geom = mesh_ho->GetElementBaseGeometry(iho);
const FiniteElement& fe_lor = *fes_lor.GetFE(lor_els[0]);
int nedof_lor = fe_lor.GetDof();
// Instead of using a MassIntegrator, manually loop over integration
// points so we can row sum and store the diagonal as a Vector.
Vector ML_el(nedof_lor);
Vector shape_lor(nedof_lor);
Array<int> dofs_lor(nedof_lor);
for (int iref = 0; iref < nref; ++iref)
{
const IntegrationPoint& ip_lor = ir->IntPoint(i);
fe_lor.CalcShape(ip_lor, shape_lor);
el_tr->SetIntPoint(&ip_lor);
ML_el += (shape_lor *= (el_tr->Weight() * ip_lor.weight));
int ilor = lor_els[iref];
ElementTransformation* el_tr = fes_lor.GetElementTransformation(ilor);
int order = 2 * fe_lor.GetOrder() + el_tr->OrderW();
const IntegrationRule* ir = &IntRules.Get(geom, order);
ML_el = 0.0;
for (int i = 0; i < ir->GetNPoints(); ++i)
{
const IntegrationPoint& ip_lor = ir->IntPoint(i);
fe_lor.CalcShape(ip_lor, shape_lor);
el_tr->SetIntPoint(&ip_lor);
ML_el += (shape_lor *= (el_tr->Weight() * ip_lor.weight));
}
fes_lor.GetElementDofs(ilor, dofs_lor);
ML_inv.AddElementVector(dofs_lor, ML_el);
}
fes_lor.GetElementDofs(ilor, dofs_lor);
ML_inv.AddElementVector(dofs_lor, ML_el);
}
// DOF by DOF inverse of non-zero entries
LumpedMassInverse(ML_inv);
}
// DOF by DOF inverse of non-zero entries
LumpedMassInverse(ML_inv);
// Compute sparsity pattern for R = M_L^(-1) M_LH and allocate
r_and_mlh.first = AllocR();
std::unique_ptr<SparseMatrix> pattern = AllocR();
if (build_R)
{
r_and_mlh.first = std::move(pattern);
}
// Allocate M_LH (same sparsity pattern as R)
// L refers to the low-order refined mesh (DOFs correspond to rows)
// H refers to the higher-order mesh (DOFs correspond to columns)
Memory<int> I(r_and_mlh.first->Height() + 1);
for (int icol = 0; icol < r_and_mlh.first->Height() + 1; ++icol)
SparseMatrix &pattern_mat = build_R ? *r_and_mlh.first : *pattern;
Memory<int> I(pattern_mat.Height() + 1);
for (int icol = 0; icol < pattern_mat.Height() + 1; ++icol)
{
I[icol] = r_and_mlh.first->GetI()[icol];
I[icol] = pattern_mat.GetI()[icol];
}
Memory<int> J(r_and_mlh.first->NumNonZeroElems());
for (int jcol = 0; jcol < r_and_mlh.first->NumNonZeroElems(); ++jcol)
Memory<int> J(pattern_mat.NumNonZeroElems());
for (int jcol = 0; jcol < pattern_mat.NumNonZeroElems(); ++jcol)
{
J[jcol] = r_and_mlh.first->GetJ()[jcol];
J[jcol] = pattern_mat.GetJ()[jcol];
}
r_and_mlh.second = std::unique_ptr<SparseMatrix>(
new SparseMatrix(I, J, NULL, r_and_mlh.first->Height(),
r_and_mlh.first->Width(), true, true, true));
new SparseMatrix(I, J, NULL, pattern_mat.Height(),
pattern_mat.Width(), true, true, true));
IntegrationPointTransformation ip_tr;
IsoparametricTransformation& emb_tr = ip_tr.Transf;
@@ -1647,15 +1763,21 @@ std::unique_ptr<SparseMatrix>>
Array<int> dofs_lor(nedof_lor);
fes_lor.GetElementDofs(ilor, dofs_lor);
Vector R_row;
for (int i = 0; i < nedof_lor; ++i)
if (build_R)
{
M_LH_el.GetRow(i, R_row);
R_el.SetRow(i, R_row.Set(ML_inv[dofs_lor[i]], R_row));
for (int i = 0; i < nedof_lor; ++i)
{
M_LH_el.GetRow(i, R_row);
R_el.SetRow(i, R_row.Set(ML_inv[dofs_lor[i]], R_row));
}
}
Array<int> dofs_ho(nedof_ho);
fes_ho.GetElementDofs(iho, dofs_ho);
r_and_mlh.second->AddSubMatrix(dofs_lor, dofs_ho, M_LH_el);
r_and_mlh.first->AddSubMatrix(dofs_lor, dofs_ho, R_el);
if (build_R)
{
r_and_mlh.first->AddSubMatrix(dofs_lor, dofs_ho, R_el);
}
}
}
@@ -2009,6 +2131,8 @@ const Operator &L2ProjectionGridTransfer::ForwardOperator()
const Operator &L2ProjectionGridTransfer::BackwardOperator()
{
MFEM_VERIFY(!UsesH1ConsistentMass(),
"BackwardOperator is not supported with consistent mass");
if (!B)
{
if (!F) { BuildF(); }
@@ -2017,15 +2141,30 @@ const Operator &L2ProjectionGridTransfer::BackwardOperator()
return *B;
}
void L2ProjectionGridTransfer::UseConsistentMass(bool use_consistent_mass_)
{
MFEM_VERIFY(!F && !B,
"UseConsistentMass must be called before constructing operators");
use_consistent_mass = use_consistent_mass_;
}
bool L2ProjectionGridTransfer::UsesH1ConsistentMass() const
{
return use_consistent_mass && !force_l2_space &&
dom_fes.FEColl()->GetContType() == FiniteElementCollection::CONTINUOUS;
}
void L2ProjectionGridTransfer::BuildF()
{
if (!force_l2_space &&
dom_fes.FEColl()->GetContType() == FiniteElementCollection::CONTINUOUS)
{
MFEM_VERIFY(!(use_ea && use_consistent_mass),
"consistent mass is not supported with element assembly");
if (!Parallel())
{
F = new L2ProjectionH1Space(dom_fes, ran_fes,
use_ea, d_mt);
use_ea, use_consistent_mass, d_mt);
}
else
{
@@ -2035,7 +2174,7 @@ void L2ProjectionGridTransfer::BuildF()
const mfem::ParFiniteElementSpace& ran_pfes =
static_cast<mfem::ParFiniteElementSpace&>(ran_fes);
F = new L2ProjectionH1Space(dom_pfes, ran_pfes,
use_ea, d_mt);
use_ea, use_consistent_mass, d_mt);
#endif
}
}
@@ -2048,6 +2187,7 @@ void L2ProjectionGridTransfer::BuildF()
bool L2ProjectionGridTransfer::SupportsBackwardsOperator() const
{
if (UsesH1ConsistentMass()) { return false; }
return ran_fes.GetTrueVSize() >= dom_fes.GetTrueVSize();
}
+65 -16
View File
@@ -169,10 +169,12 @@ public:
is the forward transfer matrix, and M_f is the mass matrix on the coarse
element. For L2 spaces, M_f is the mass matrix on the union of all fine
elements comprising the coarse element. For H1 spaces, M_f is a diagonal
(lumped) mass matrix computed through row-summation. Note that the backward
transfer operator, B, is a left inverse of the forward transfer operator, F,
i.e. B F = I. Both F and B are defined in physical space and, generally for
L2 spaces, vary between different mesh elements.
(lumped) mass matrix computed through row-summation, unless
UseConsistentMass() is enabled for the forward H1 operator. When the
backward transfer operator, B, is supported, it is a left inverse of the
forward transfer operator, F, i.e. B F = I. Both F and B are defined in
physical space and, generally for L2 spaces, vary between different mesh
elements.
This class supports H1 and L2 finite element spaces. Fine meshes are a
uniform refinement of the coarse mesh, usually created through
@@ -352,16 +354,21 @@ public:
class L2ProjectionH1Space : public L2Projection
{
const bool use_ea;
/// Use the consistent low-order mass matrix in non-EA H1 Mult() and
/// MultTranspose().
const bool use_consistent_mass;
public:
L2ProjectionH1Space(const FiniteElementSpace &fes_ho_,
const FiniteElementSpace &fes_lor_,
const bool use_ea_,
const bool use_consistent_mass_,
MemoryType d_mt_ = Device::GetHostMemoryType());
#ifdef MFEM_USE_MPI
L2ProjectionH1Space(const ParFiniteElementSpace &pfes_ho_,
const ParFiniteElementSpace &pfes_lor_,
const bool use_ea_,
const bool use_consistent_mass_,
MemoryType d_mt_ = Device::GetHostMemoryType());
#endif
/// Same as above but assembles action of R through 4 parts:
@@ -417,13 +424,33 @@ public:
void SetAbsTol(real_t p_atol_) override;
protected:
/// Applies the H1 transfer R = M_L^{-1} M_LH and its transpose, where
/// M_L is the consistent low-order mass matrix.
class H1ConsistentMassOperator : public Operator
{
private:
const Operator &M_LH;
const Solver &M_L_solver;
public:
H1ConsistentMassOperator(const Operator &M_LH_,
const Solver &M_L_solver_);
void Mult(const Vector &x, Vector &y) const override;
void MultTranspose(const Vector &x, Vector &y) const override;
};
/// Sets up the PCG solver (sets parameters, operator, and preconditioner)
void SetupPCG();
/// @brief Computes on-rank R and M_LH matrices. If true, computes mixed mass and/or
/// inverse lumped mass matrix error when compared to device implementation.
/** @brief Computes on-rank R and M_LH matrices.
If build_R is true, the returned pair contains both R and M_LH. If
build_R is false, the first pointer is null and only M_LH is built. */
std::pair<std::unique_ptr<SparseMatrix>,
std::unique_ptr<SparseMatrix>> ComputeSparseRAndM_LH();
std::unique_ptr<SparseMatrix>> ComputeSparseRAndM_LH(
bool build_R = true);
/// @brief Recovers vector of tdofs given a vector of dofs and a finite
/// element space
@@ -453,20 +480,30 @@ public:
/// elements and refined LOR elements.
std::unique_ptr<SparseMatrix> AllocR();
CGSolver pcg;
std::unique_ptr<Solver> precon;
/// Consistent low-order mass matrix used when use_consistent_mass is true.
std::unique_ptr<Operator> M_L;
// Used to compute P = (RT*M_LH)^(-1) M_LH^T
std::unique_ptr<Operator> M_LH;
// Lumped M_L inverse operator built via EA. Wrapped with restriction maps
// to multiply with scalar TDof LOR vectors.
std::unique_ptr<Operator> ML_inv_vea;
/// Preconditioner for applying the inverse consistent low-order mass
/// matrix.
std::unique_ptr<Solver> ML_precon;
/// Serial PCG solver for applying the inverse consistent low-order mass
/// matrix in H1 Mult() and MultTranspose().
CGSolver ML_pcg;
/// Solver used by H1ConsistentMassOperator to apply M_L^{-1}.
std::unique_ptr<Solver> ML_solver;
// The restriction operator is represented as an Operator R. The
// prolongation operator is a dense matrix computed as the inverse of (R^T
// M_L R), and hence, is not stored.
// If element assembly is enabled
std::unique_ptr<Operator> R;
// Used to compute P = (RT*M_LH)^(-1) M_LH^T
std::unique_ptr<Operator> M_LH;
// Inverted operator in P = (RT*M_LH)^(-1) M_LH^T. Used to compute P via PCG.
std::unique_ptr<Operator> RTxM_LH;
// Lumped M_L inverse operator built via EA. Wrapped with restriction maps
// to multiply with scalar TDof LOR vectors.
std::unique_ptr<Operator> ML_inv_vea;
std::unique_ptr<Solver> precon;
CGSolver pcg;
// LDof Mixed mass operator built via EA. Wrapped with restriction maps to send
// scalar LDof HO vectors to LDof LOR vectors.
Operator *M_LH_local_op;
@@ -478,7 +515,6 @@ public:
Vector M_LH_ea;
// Element Assembled lumped M_L inverse built via EA. Stores diagonal as a Ldof vector.
Vector ML_inv_ea;
#ifdef MFEM_USE_MPI
std::unique_ptr<ParFiniteElementSpace> pfes_ho_scalar;
std::unique_ptr<ParFiniteElementSpace> pfes_lor_scalar;
@@ -511,6 +547,9 @@ public:
L2Projection *F; ///< Forward, coarse-to-fine, operator
L2Prolongation *B; ///< Backward, fine-to-coarse, operator
bool force_l2_space;
/// Use the consistent low-order mass matrix for non-EA H1 Mult() and
/// MultTranspose().
bool use_consistent_mass;
public:
L2ProjectionGridTransfer(FiniteElementSpace &coarse_fes_,
@@ -518,16 +557,26 @@ public:
bool force_l2_space_ = false,
MemoryType d_mt_ = Device::GetHostMemoryType()) // move to method
: GridTransfer(coarse_fes_, fine_fes_),
F(NULL), B(NULL), force_l2_space(force_l2_space_)
F(NULL), B(NULL), force_l2_space(force_l2_space_),
use_consistent_mass(false)
{ }
virtual ~L2ProjectionGridTransfer();
/** @brief Use the consistent low-order mass matrix in H1 non-EA Mult() and
MultTranspose().
This option must be set before constructing the transfer operators. It
only affects H1 transfer, is not supported with element assembly, and
disables BackwardOperator(). */
void UseConsistentMass(bool use_consistent_mass_ = true);
const Operator &ForwardOperator() override;
const Operator &BackwardOperator() override;
bool SupportsBackwardsOperator() const override;
private:
bool UsesH1ConsistentMass() const;
void BuildF();
};
+9
View File
@@ -38,6 +38,15 @@
#define CUB_IGNORE_DEPRECATED_CPP_DIALECT
#define THRUST_IGNORE_DEPRECATED_CPP_DIALECT
// MFEM only supports using RAJA/CAMP backends in default stream mode because
// memory calls are performed outside of the RAJA ecosystem
#ifndef CAMP_USE_PLATFORM_DEFAULT_STREAM
#define CAMP_USE_PLATFORM_DEFAULT_STREAM 1
#else
#if !CAMP_USE_PLATFORM_DEFAULT_STREAM
#error "MFEM only supports RAJA/CAMP with the default platform stream."
#endif
#endif
#include "RAJA/RAJA.hpp"
#if defined(RAJA_ENABLE_CUDA) && !defined(MFEM_USE_CUDA)
#error When RAJA is built with CUDA, MFEM_USE_CUDA=YES is required
+1 -3
View File
@@ -581,9 +581,7 @@ void Device::Setup(const std::string &device_option, const int device_id)
if (Allows(Backend::CUDA)) { CudaDeviceSetup(dev, ngpu); }
if (Allows(Backend::HIP)) { HipDeviceSetup(dev, ngpu); }
if (Allows(Backend::RAJA_CUDA) || Allows(Backend::RAJA_HIP))
{
RajaDeviceSetup(dev, ngpu);
}
{ RajaDeviceSetup(dev, ngpu); }
// The check for MFEM_USE_OCCA is in the function OccaDeviceSetup().
if (Allows(Backend::OCCA_MASK)) { OccaDeviceSetup(dev); }
if (Allows(Backend::CEED_MASK))
-17
View File
@@ -16,11 +16,6 @@
#include "globals.hpp"
#include "mem_manager.hpp"
#ifdef MFEM_USE_RAJA
#include "RAJA/RAJA.hpp"
#endif
#include <memory>
#include <string>
namespace mfem
@@ -271,18 +266,6 @@ public:
static inline bool Allows(unsigned long b_mask)
{ return Get().backends & b_mask; }
#if defined(MFEM_USE_RAJA) && \
(defined(RAJA_ENABLE_CUDA) || defined(RAJA_ENABLE_HIP))
static inline auto GetRajaResource()
{
#if defined(RAJA_ENABLE_CUDA)
return RAJA::resources::Cuda::CudaFromStream(0, Get().GetId());
#elif defined(RAJA_ENABLE_HIP)
return RAJA::resources::Hip::HipFromStream(0, Get().GetId());
#endif
}
#endif
/** @brief Get the current Host MemoryType. This is the MemoryType used by
most MFEM classes when allocating memory used on the host.
*/
+38 -30
View File
@@ -317,8 +317,8 @@ template <typename DBODY>
void RajaCuWrap1D(const int N, DBODY &&d_body)
{
//true denotes asynchronous kernel
RAJA::forall<RAJA::cuda_exec<MFEM_CUDA_BLOCKS, true> >(
Device::GetRajaResource(), RAJA::RangeSegment(0, N), d_body);
RAJA::forall<RAJA::cuda_exec<MFEM_CUDA_BLOCKS,true>>(RAJA::RangeSegment(0,N),
d_body);
}
template <typename DBODY>
@@ -331,9 +331,9 @@ void RajaCuWrap2D(const int N, DBODY &&d_body,
using namespace RAJA;
using RAJA::RangeSegment;
launch<cuda_launch_policy>(Device::GetRajaResource(),
LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE(LaunchContext ctx)
launch<cuda_launch_policy>
(LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE (LaunchContext ctx)
{
loop<cuda_teams_x>(ctx, RangeSegment(0, G), [&] (const int n)
@@ -349,6 +349,7 @@ void RajaCuWrap2D(const int N, DBODY &&d_body,
});
});
});
MFEM_GPU_CHECK(cudaGetLastError());
@@ -364,9 +365,9 @@ void RajaCuWrap2DLaunchBounds(const int N, DBODY &&d_body, const int X,
using namespace RAJA;
using RAJA::RangeSegment;
launch<cuda_launch_bounds_policy<LB> >(
Device::GetRajaResource(), LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE(LaunchContext ctx)
launch<cuda_launch_bounds_policy<LB> >
(LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE(LaunchContext ctx)
{
loop<cuda_teams_x>(ctx, RangeSegment(0, G), [&] (const int n)
{
@@ -389,12 +390,13 @@ void RajaCuWrap3D(const int N, DBODY &&d_body,
using namespace RAJA;
using RAJA::RangeSegment;
launch<cuda_launch_policy>(Device::GetRajaResource(),
LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE(LaunchContext ctx)
launch<cuda_launch_policy>
(LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE (LaunchContext ctx)
{
loop<cuda_teams_x>(ctx, RangeSegment(0, N), d_body);
});
MFEM_GPU_CHECK(cudaGetLastError());
@@ -408,10 +410,12 @@ void RajaCuWrap3DLaunchBounds(const int N, DBODY &&d_body,
using namespace RAJA;
using RAJA::RangeSegment;
launch<cuda_launch_bounds_policy<LB> >(
Device::GetRajaResource(), LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE(LaunchContext ctx)
{ loop<cuda_teams_x>(ctx, RangeSegment(0, N), d_body); });
launch<cuda_launch_bounds_policy<LB> >
(LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE(LaunchContext ctx)
{
loop<cuda_teams_x>(ctx, RangeSegment(0, N), d_body);
});
MFEM_GPU_CHECK(cudaGetLastError());
}
@@ -480,8 +484,8 @@ template <typename DBODY>
void RajaHipWrap1D(const int N, DBODY &&d_body)
{
//true denotes asynchronous kernel
RAJA::forall<RAJA::hip_exec<MFEM_HIP_BLOCKS,true> >(RAJA::RangeSegment(0,N),
d_body);
RAJA::forall<RAJA::hip_exec<MFEM_HIP_BLOCKS,true>>(RAJA::RangeSegment(0,N),
d_body);
}
template <typename DBODY>
@@ -494,9 +498,9 @@ void RajaHipWrap2D(const int N, DBODY &&d_body,
using namespace RAJA;
using RAJA::RangeSegment;
launch<hip_launch_policy>(Device::GetRajaResource(),
LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE(LaunchContext ctx)
launch<hip_launch_policy>
(LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE (LaunchContext ctx)
{
loop<hip_teams_x>(ctx, RangeSegment(0, G), [&] (const int n)
@@ -512,6 +516,7 @@ void RajaHipWrap2D(const int N, DBODY &&d_body,
});
});
});
MFEM_GPU_CHECK(hipGetLastError());
@@ -527,9 +532,9 @@ void RajaHipWrap2DLaunchBounds(const int N, DBODY &&d_body, const int X,
using namespace RAJA;
using RAJA::RangeSegment;
launch<hip_launch_bounds_policy<LB> >(
Device::GetRajaResource(), LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE(LaunchContext ctx)
launch<hip_launch_bounds_policy<LB> >
(LaunchParams(Teams(G), Threads(X, Y, BZ)),
[=] RAJA_DEVICE(LaunchContext ctx)
{
loop<hip_teams_x>(ctx, RangeSegment(0, G), [&] (const int n)
{
@@ -552,12 +557,13 @@ void RajaHipWrap3D(const int N, DBODY &&d_body,
using namespace RAJA;
using RAJA::RangeSegment;
launch<hip_launch_policy>(Device::GetRajaResource(),
LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE(LaunchContext ctx)
launch<hip_launch_policy>
(LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE (LaunchContext ctx)
{
loop<hip_teams_x>(ctx, RangeSegment(0, N), d_body);
});
MFEM_GPU_CHECK(hipGetLastError());
@@ -571,10 +577,12 @@ void RajaHipWrap3DLaunchBounds(const int N, DBODY &&d_body, const int X,
using namespace RAJA;
using RAJA::RangeSegment;
launch<hip_launch_bounds_policy<LB> >(
Device::GetRajaResource(), LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE(LaunchContext ctx)
{ loop<hip_teams_x>(ctx, RangeSegment(0, N), d_body); });
launch<hip_launch_bounds_policy<LB> >
(LaunchParams(Teams(GRID), Threads(X, Y, Z)),
[=] RAJA_DEVICE(LaunchContext ctx)
{
loop<hip_teams_x>(ctx, RangeSegment(0, N), d_body);
});
MFEM_GPU_CHECK(hipGetLastError());
}
-2
View File
@@ -27,7 +27,6 @@ list(APPEND SRCS
handle.cpp
matrix.cpp
mma.cpp
multivector.cpp
ode.cpp
operator.cpp
ordering.cpp
@@ -64,7 +63,6 @@ list(APPEND HDRS
linalg.hpp
matrix.hpp
mma.hpp
multivector.hpp
ode.hpp
operator.hpp
ordering.hpp
+9 -40
View File
@@ -15,20 +15,10 @@
#ifdef MFEM_USE_CUDSS
#if CUDSS_VERSION >= 800
#ifdef MFEM_USE_SINGLE
#define CUDSS_REAL_T CUDSS_R_32F
#define CUDA_REAL_T CUDA_R_32F
#else
#define CUDSS_REAL_T CUDSS_R_64F
#endif
#define CUDSS_INT_T CUDSS_R_32I
#else
#ifdef MFEM_USE_SINGLE
#define CUDSS_REAL_T CUDA_R_32F
#else
#define CUDSS_REAL_T CUDA_R_64F
#endif
#define CUDSS_INT_T CUDA_R_32I
#define CUDA_REAL_T CUDA_R_64F
#endif
// Define a cuDSS error check macro, MFEM_CUDSS_CHECK(x), where x returns/is of
@@ -75,13 +65,8 @@ CuDSSSolver::CuDSSSolver(MPI_Comm comm_) : mpi_comm(comm_)
#endif
MFEM_CUDSS_CHECK(cudssSetCommLayer(handle, comm_lib));
#if CUDSS_VERSION >= 800
MFEM_CUDSS_CHECK(cudssDataSet(handle, solverData, CUDSS_DATA_COMM_HOST,
&mpi_comm, sizeof(MPI_Comm *)));
#else
MFEM_CUDSS_CHECK(cudssDataSet(handle, solverData, CUDSS_DATA_COMM,
&mpi_comm, sizeof(MPI_Comm *)));
#endif
}
#endif // MFEM_USE_MPI
@@ -272,19 +257,11 @@ void CuDSSSolver::SetMatrixCuDSS(int *csr_offsets, int *csr_columns,
CuMemcpyDtoD(csr_offsets_d, csr_offsets, (n_loc + 1) * sizeof(int));
CuMemcpyDtoD(csr_columns_d, csr_columns, nnz * sizeof(int));
#if CUDSS_VERSION >= 800
MFEM_CUDSS_CHECK(
cudssMatrixCreateCsr(
Ac.get(), n_global, n_global, nnz, csr_offsets_d, NULL,
csr_columns_d, csr_values_d, CUDSS_INT_T, CUDSS_INT_T, CUDSS_REAL_T,
mat_type, mview, CUDSS_BASE_ZERO));
#else
MFEM_CUDSS_CHECK(
cudssMatrixCreateCsr(
Ac.get(), n_global, n_global, nnz, csr_offsets_d, NULL,
csr_columns_d, csr_values_d, CUDSS_INT_T, CUDSS_REAL_T,
mat_type, mview, CUDSS_BASE_ZERO));
#endif
csr_columns_d, csr_values_d, CUDA_R_32I, CUDA_REAL_T, mat_type, mview,
CUDSS_BASE_ZERO));
}
else // !reorder_reuse
{
@@ -292,19 +269,11 @@ void CuDSSSolver::SetMatrixCuDSS(int *csr_offsets, int *csr_columns,
{
MFEM_CUDSS_CHECK(cudssMatrixDestroy(*Ac));
}
#if CUDSS_VERSION >= 800
MFEM_CUDSS_CHECK(
cudssMatrixCreateCsr(
Ac.get(), n_global, n_global, nnz, csr_offsets, NULL,
csr_columns, csr_values_d, CUDSS_INT_T, CUDSS_INT_T, CUDSS_REAL_T,
mat_type, mview, CUDSS_BASE_ZERO));
#else
MFEM_CUDSS_CHECK(
cudssMatrixCreateCsr(
Ac.get(), n_global, n_global, nnz, csr_offsets, NULL,
csr_columns, csr_values_d, CUDSS_INT_T, CUDSS_REAL_T,
mat_type, mview, CUDSS_BASE_ZERO));
#endif
Ac.get(), n_global, n_global, nnz, csr_offsets, NULL, csr_columns,
csr_values_d, CUDA_R_32I, CUDA_REAL_T, mat_type, mview,
CUDSS_BASE_ZERO));
}
#ifdef MFEM_USE_MPI
if (Mpi::IsInitialized())
@@ -365,10 +334,10 @@ void CuDSSSolver::SetNumRHS(int nrhs_) const
}
// Create empty RHS and solution vectors
MFEM_CUDSS_CHECK(cudssMatrixCreateDn(&xc, n_global, nrhs_, n_global, NULL,
CUDSS_REAL_T, CUDSS_LAYOUT_COL_MAJOR));
CUDA_REAL_T, CUDSS_LAYOUT_COL_MAJOR));
MFEM_CUDSS_CHECK(cudssMatrixCreateDn(&yc, n_global, nrhs_, n_global, NULL,
CUDSS_REAL_T, CUDSS_LAYOUT_COL_MAJOR));
CUDA_REAL_T, CUDSS_LAYOUT_COL_MAJOR));
#ifdef MFEM_USE_MPI
MFEM_CUDSS_CHECK(cudssMatrixSetDistributionRow1d(xc, row_start, row_end));
-2
View File
@@ -39,7 +39,6 @@ namespace Ginkgo
{
template <typename T> using gko_array = gko::array<T>;
#if defined(MFEM_USE_MPI) && GINKGO_BUILD_MPI
// for inter-operability with hypre integer types
using gko_hypre_int =
std::conditional_t<sizeof(HYPRE_Int) == sizeof(std::int32_t), std::int32_t,
@@ -51,7 +50,6 @@ static_assert(!std::is_void_v<gko_hypre_int>,
"HYPRE_Int type is incompatible with Ginkgo");
static_assert(!std::is_void_v<gko_hypre_bigint>,
"HYPRE_BigInt type is incompatible with Ginkgo");
#endif
/**
* Helper class for a case where a wrapped MFEM Vector
+3 -29
View File
@@ -2872,8 +2872,8 @@ void HypreParMatrix::Destroy()
if (HypreUsingGPU() && ParCSROwner && (diagOwner < 0 || offdOwner < 0))
{
// Put the "host" or "hypre" pointers in {i,j,data} of A->{diag,offd}, so
// that they can be destroyed by mfem_hypre_TFree_host() or hypre when
// hypre_ParCSRMatrixDestroy(A) is called below, respectively.
// that they can be destroyed by hypre when hypre_ParCSRMatrixDestroy(A)
// is called below.
// Check that if both diagOwner and offdOwner are negative then they have
// the same value.
@@ -2882,33 +2882,7 @@ void HypreParMatrix::Destroy()
MemoryClass mc = (diagOwner == -1 || offdOwner == -1) ?
Device::GetHostMemoryClass() : GetHypreMemoryClass();
Write(mc, diagOwner < 0, offdOwner < 0);
if (diagOwner == -1)
{
// Note: mfem_hypre_TFree_host() sets the pointer to NULL.
mfem_hypre_TFree_host(hypre_CSRMatrixI(A->diag));
if (hypre_CSRMatrixOwnsData(A->diag))
{
mfem_hypre_TFree_host(hypre_CSRMatrixJ(A->diag));
mfem_hypre_TFree_host(hypre_CSRMatrixData(A->diag));
}
#if MFEM_HYPRE_VERSION >= 21800
hypre_CSRMatrixMemoryLocation(A->diag) = GetHypreMemoryLocation();
#endif
}
if (offdOwner == -1)
{
// Note: mfem_hypre_TFree_host() sets the pointer to NULL.
mfem_hypre_TFree_host(hypre_CSRMatrixI(A->offd));
if (hypre_CSRMatrixOwnsData(A->offd))
{
mfem_hypre_TFree_host(hypre_CSRMatrixJ(A->offd));
mfem_hypre_TFree_host(hypre_CSRMatrixData(A->offd));
}
#if MFEM_HYPRE_VERSION >= 21800
hypre_CSRMatrixMemoryLocation(A->offd) = GetHypreMemoryLocation();
#endif
}
Write(mc, diagOwner < 0, offdOwner <0);
}
#endif
+4 -5
View File
@@ -432,11 +432,10 @@ private:
// and A->col_map_offd.
// The possible values for diagOwner are:
// -1: no special treatment of A->diag (default)
// when hypre is using GPU, A->diag owns the "host" pointers (according
// to A->diag->owns_data); these host pointers are freed by MFEM using
// hypre's host deallocation macros
// -2: used when hypre is using GPU, A->diag owns the "hypre" pointers
// (according to A->diag->owns_data)
// when hypre is built with CUDA support, A->diag owns the "host"
// pointers (according to A->diag->owns_data)
// -2: used when hypre is built with CUDA support, A->diag owns the "hypre"
// pointers (according to A->diag->owns_data)
// 0: prevent hypre from destroying A->diag->{i,j,data}
// 1: same as 0, plus own the "host" A->diag->{i,j}
// 2: same as 0, plus own the "host" A->diag->data
-1
View File
@@ -15,7 +15,6 @@
// Linear algebra header file
#include "vector.hpp"
#include "multivector.hpp"
#include "operator.hpp"
#include "matrix.hpp"
#include "sparsemat.hpp"
-60
View File
@@ -1,60 +0,0 @@
// 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 "multivector.hpp"
namespace mfem
{
MultiVector::MultiVector(const Array<int> &vector_sizes)
{
SetSizes(vector_sizes);
}
MultiVector::MultiVector(const Array<int> &vector_sizes, MemoryType mt)
{
SetSizes(vector_sizes, mt);
}
MultiVector::MultiVector(Vector &base, const Array<int> &vector_sizes)
{
MakeRef(base, vector_sizes);
}
void MultiVector::SetSizes(const Array<int> &vector_sizes)
{
blocks.resize(vector_sizes.Size());
for (int i = 0; i < vector_sizes.Size(); i++)
{
operator[](i).SetSize(vector_sizes[i]);
}
}
void MultiVector::SetSizes(const Array<int> &vector_sizes, MemoryType mt)
{
blocks.resize(vector_sizes.Size());
for (int i = 0; i < vector_sizes.Size(); i++)
{
operator[](i).SetSize(vector_sizes[i], mt);
}
}
void MultiVector::MakeRef(Vector &base, const Array<int> &vector_sizes)
{
blocks.resize(vector_sizes.Size());
for (int offset = 0, i = 0; i < vector_sizes.Size(); i++)
{
blocks[i].emplace<0>(base, offset, vector_sizes[i]);
offset += vector_sizes[i];
}
}
} // namespace mfem
-198
View File
@@ -1,198 +0,0 @@
// 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.
#ifndef MFEM_MULTIVECTOR_HPP
#define MFEM_MULTIVECTOR_HPP
#include "../general/array.hpp"
#include "vector.hpp"
#include <vector>
#include <array>
#include <variant>
namespace mfem
{
/// Class representing an array of Vectors with generally different sizes.
/** This class is similar to BlockVector with the following two main
differences:
- the data for the individual Vector blocks does not need to be part of one
big contiguous memory allocation;
- this class does not inherit from class Vector (as a consequence of the
first bullet).
Internally, each Vector block is represented as either:
- (default) a Vector object constructed and owned by this class; this
object, in turn, as any Vector object, can own its Memory allocation or
refer to a sub-Memory of another Memory object; or
- a pointer to an externally allocated Vector or classes derived from
Vector. */
class MultiVector
{
private:
std::vector<std::variant<Vector,Vector*>> blocks;
public:
/// Create an empty MultiVector with zero blocks.
MultiVector() = default;
/** @brief Create a MultiVector with @a num_blocks blocks. The individual
Vector blocks are default initialized, i.e. they all have size zero. */
MultiVector(int num_blocks)
: blocks(num_blocks) { }
/** @brief Construct a MultiVector with number of blocks and individual block
Vector sizes given by @a vector_sizes.
@note The memory of the individual Vector blocks is NOT initialized. */
MultiVector(const Array<int> &vector_sizes);
/** @brief Construct a MultiVector with number of blocks and individual block
Vector sizes given by @a vector_sizes. All Vector blocks use the
MemoryType @a mt.
@note The memory of the individual Vector blocks is NOT initialized. */
MultiVector(const Array<int> &vector_sizes, MemoryType mt);
/** @brief Construct a MultiVector referencing data within a given monolithic
Vector @a base.
With this constructor, the Memory flags of @a base and of the individual
Vector blocks may need to be explicitly synchronized when data is moved
between host and device. */
MultiVector(Vector &base, const Array<int> &vector_sizes);
/** @brief Construct a MultiVector referencing multiple Vectors given as
arguments.
The VectorTypes reference arguments are expected to be static_cast-able
to (Vector &) which is the case if the types are derived from Vector,
e.g. HypreParVector, GridFunction, etc.
With this constructor, operations on individual Vector blocks are
performed directly on the objects @a vs. In particular, there is no need
to synchronize the Memory flags of @a vs and the ones of the individual
Vector blocks when data is moved between host and device. */
template <typename... VectorTypes,
std::enable_if_t<
std::conjunction_v<
std::is_convertible<VectorTypes&,Vector&>...>, bool> = true>
MultiVector(VectorTypes &...vs) { MakeRef(vs...); }
/// Return the number of Vectors in the MultiVector.
int NumBlocks() const { return blocks.size(); }
/** @brief Set the number of Vectors in the MultiVector. Existing Vector
blocks will remain unmodified. New Vector blocks will be default
initialized, i.e. they all have size zero. */
void SetNumBlocks(int num_blocks) { blocks.resize(num_blocks); }
/// Read-write access to the i-th Vector.
inline Vector &operator[](int i);
/// Read-only access to the i-th Vector.
inline const Vector &operator[](int i) const;
/** @brief Update the MultiVector according to the given @a vector_sizes.
This method can be used to add or remove blocks. The individual Vector
sizes are updated using the method Vector::SetSize(int). */
void SetSizes(const Array<int> &vector_sizes);
/** @brief Update the MultiVector according to the given @a vector_sizes and
MemoryType @a mt.
This method can be used to add or remove blocks. The individual Vector
sizes and MemoryType are updated using the method
Vector::SetSize(int, MemoryType). */
void SetSizes(const Array<int> &vector_sizes, MemoryType mt);
/** @brief Update the MultiVector to reference data within a given monolithic
Vector @a base.
After calling this method, the Memory flags of @a base and of the
individual Vector blocks may need to be explicitly synchronized when data
is moved between host and device.*/
void MakeRef(Vector &base, const Array<int> &vector_sizes);
/** @brief Update the @a i-th MultiVector block to reference data within the
given monolithic Vector @a base at the given @a offset and with the given
@a size.
After calling this method, the Memory flags of @a base and of the @a i-th
Vector block may need to be explicitly synchronized when data is moved
between host and device.*/
inline void MakeRef(int i, Vector &base, int offset, int size)
{
blocks[i].emplace<0>(base, offset, size);
}
/** @brief Update the MultiVector to reference multiple Vectors given as
arguments.
The VectorTypes reference arguments are expected to be static_cast-able
to (Vector &) which is the case if the types are derived from Vector,
e.g. HypreParVector, GridFunction, etc.
After calling this method, operations on individual Vector blocks are
performed directly on the objects @a vs. In particular, there is no need
to synchronize the Memory flags of @a vs and the ones of the individual
Vector blocks when data is moved between host and device. */
template <typename... VectorTypes,
std::enable_if_t<
std::conjunction_v<
std::is_convertible<VectorTypes&,Vector&>...>, bool> = true>
inline void MakeRef(VectorTypes &...vs);
/** @brief Update the @a i-th MultiVector block to reference the given
Vector @a v.
After calling this method, operations on the @a i-th Vector block are
performed directly on the Vector @a v. In particular, there is no need
to synchronize the Memory flags of @a v and the ones of the @a i-th
Vector blocks when data is moved between host and device. */
inline void MakeRef(int i, Vector &v) { blocks[i] = &v; }
};
// Inline and template methods
inline Vector &MultiVector::operator[](int i)
{
auto &bi = blocks[i];
return (bi.index() == 0) ? std::get<0>(bi) : *std::get<1>(bi);
}
inline const Vector &MultiVector::operator[](int i) const
{
auto &bi = blocks[i];
return (bi.index() == 0) ? std::get<0>(bi) : *std::get<1>(bi);
}
template <typename... VectorTypes,
std::enable_if_t<
std::conjunction_v<
std::is_convertible<VectorTypes&,Vector&>...>, bool>>
inline void MultiVector::MakeRef(VectorTypes &...vs)
{
blocks.resize(sizeof...(vs));
if constexpr (sizeof...(vs) > 0)
{
const std::array vs_p{&static_cast<Vector&>(vs)...};
for (std::size_t i = 0; i < sizeof...(vs); i++)
{
blocks[i] = vs_p[i];
}
}
}
} // namespace mfem
#endif // MFEM_MULTIVECTOR_HPP
-10
View File
@@ -111,16 +111,6 @@ void Operator::ArrayAddMultTranspose(const Array<const Vector *> &X,
}
}
void Operator::Mult(const MultiVector &, MultiVector &)
{
MFEM_ABORT("this method is not overriden for this class!");
}
Operator &Operator::GetGradient(const MultiVector &) const
{
MFEM_ABORT("this method is not overriden for this class!");
}
void Operator::FormLinearSystem(const Array<int> &ess_tdof_list,
Vector &x, Vector &b,
Operator* &Aout, Vector &X, Vector &B,
-21
View File
@@ -13,7 +13,6 @@
#define MFEM_OPERATOR
#include "vector.hpp"
#include "multivector.hpp"
namespace mfem
{
@@ -130,16 +129,6 @@ public:
virtual void ArrayAddMultTranspose(const Array<const Vector *> &X,
Array<Vector *> &Y, const real_t a = 1.0) const;
/** @brief Operator application, y = A(x), where the input @a x and the
output @a y are MultiVector objects, i.e. they generally use
non-contiguous memory representation.
The typical use case for this method are block operators like
DifferentiableOperator.
The base class implementation for the method is to generate an error. */
virtual void Mult(const MultiVector &x, MultiVector &y);
/** @brief Evaluate the gradient operator at the point @a x. The default
behavior in class Operator is to generate an error. */
virtual Operator &GetGradient(const Vector &x) const
@@ -148,16 +137,6 @@ public:
return const_cast<Operator &>(*this);
}
/** @brief Evaluate the gradient operator at the point @a x. The input @a x
is provided as a MultiVector, i.e. it generally uses non-contiguous
memory representation.
The typical use case for this method are block operators like
DifferentiableOperator.
The base class implementation for the method is to generate an error. */
virtual Operator &GetGradient(const MultiVector &x) const;
/** @brief Computes the diagonal entries into @a diag. Typically, this
operation only makes sense for linear Operator%s. In some cases, only an
approximation of the diagonal is computed. */
+37 -110
View File
@@ -10,7 +10,6 @@
// CONTRIBUTING.md for details.
#include "particlevector.hpp"
#include "../general/forall.hpp"
namespace mfem
{
@@ -47,38 +46,20 @@ void ParticleVector::GetValues(int i, Vector &nvals) const
{
nvals.SetSize(vdim);
const bool nvals_use_dev = nvals.UseDevice();
// Use ParticleVector's device flag to minimize movement from large source
const bool use_dev = UseDevice();
const auto d_src = Read(use_dev);
auto d_dest = nvals.Write(use_dev);
const int vdim_ = vdim;
const int ordering_ = (int)ordering;
const int nv = (ordering == Ordering::byNODES) ? size / vdim : 0;
mfem::forall_switch(use_dev, vdim_, [=] MFEM_HOST_DEVICE (int c)
if (ordering == Ordering::byNODES)
{
if (ordering_ == Ordering::byNODES)
int nv = GetNumParticles();
for (int c = 0; c < vdim; c++)
{
d_dest[c] = d_src[i + nv*c];
nvals[c] = Vector::operator[](i+nv*c);
}
else
{
d_dest[c] = d_src[c + vdim_*i];
}
});
// If nvals was not using device but ParticleVector is, copy back to host
if (!nvals_use_dev && use_dev)
{
nvals.HostRead();
nvals.UseDevice(false);
}
// If nvals was using device but ParticleVector is not, copy back to device
if (!use_dev && nvals_use_dev)
else
{
nvals.Read();
for (int c = 0; c < vdim; c++)
{
nvals[c] = Vector::operator[](c+vdim*i);
}
}
}
@@ -118,27 +99,21 @@ void ParticleVector::GetComponentsRef(int vd, Vector &nref)
void ParticleVector::SetValues(int i, const Vector &nvals)
{
const bool use_dev = UseDevice(); // use ParticleVector's device flag
const auto mc = use_dev ? Device::GetDeviceMemoryClass()
: Device::GetHostMemoryClass();
auto d_dest = ReadWrite(use_dev);
const auto d_src = nvals.GetMemory().Read(mc, nvals.Size());
const int vdim_ = vdim;
const int ordering_ = (int)ordering;
const int nv = (ordering == Ordering::byNODES) ? size / vdim : 0;
mfem::forall_switch(use_dev, vdim_, [=] MFEM_HOST_DEVICE (int c)
if (ordering == Ordering::byNODES)
{
if (ordering_ == Ordering::byNODES)
int nv = GetNumParticles();
for (int c = 0; c < vdim; c++)
{
d_dest[i + c*nv] = d_src[c];
Vector::operator[](i + c*nv) = nvals[c];
}
else
}
else
{
for (int c = 0; c < vdim; c++)
{
d_dest[c + i*vdim_] = d_src[c];
Vector::operator[](c + i*vdim) = nvals[c];
}
});
}
}
void ParticleVector::SetComponents(int vd, const Vector &comp)
@@ -169,9 +144,6 @@ real_t& ParticleVector::operator()(int i, int comp)
"Component index " << comp <<
" is invalid for vector dimension " << vdim);
// non-const so we make host flag valid in case user modifies data
HostReadWrite();
if (ordering == Ordering::byNODES)
{
return Vector::operator[](i + comp*GetNumParticles());
@@ -191,8 +163,6 @@ const real_t& ParticleVector::operator()(int i, int comp) const
"Component index " << comp <<
" is invalid for vector dimension " << vdim);
HostRead();
if (ordering == Ordering::byNODES)
{
return Vector::operator[](i + comp*GetNumParticles());
@@ -270,37 +240,9 @@ void ParticleVector::SetVDim(int vdim_, bool keep_data)
void ParticleVector::SetOrdering(Ordering::Type ordering_, bool keep_data)
{
if (keep_data && ordering != ordering_)
if (keep_data)
{
int num_particles = GetNumParticles();
// create deep copy of old data that will be copied
Vector old_data(*this);
const bool use_dev = UseDevice();
const auto d_src = old_data.Read(use_dev);
auto d_dest = Write(use_dev);
const int vdim_ = vdim;
const int size_ = size;
if (ordering_ == Ordering::byNODES) // byVDIM -> byNODES
{
mfem::forall_switch(use_dev, size_, [=] MFEM_HOST_DEVICE (int k)
{
int i = k / vdim_; // src particle index
int d = k % vdim_; // src component index
d_dest[i + d * num_particles] = d_src[k];
});
}
else // byNODES -> byVDIM
{
mfem::forall_switch(use_dev, size_, [=] MFEM_HOST_DEVICE (int k)
{
int d = k / num_particles; // src component index
int i = k % num_particles; // src particle index
d_dest[d + i * vdim_] = d_src[k];
});
}
Ordering::Reorder(*this, vdim, ordering, ordering_);
}
ordering = ordering_;
}
@@ -328,47 +270,32 @@ void ParticleVector::SetNumParticles(int num_vectors, bool keep_data)
if (!keep_data) { return; }
const bool use_dev = UseDevice();
auto d_dest = this->ReadWrite(use_dev);
if (ordering == Ordering::byNODES)
{
// create deep copy of old data that will be copied
Vector old_slice;
old_slice.MakeRef(*this, 0, old_nv * vdim);
Vector old_copy(old_slice);
const auto d_src = old_copy.Read(use_dev);
const int vdim_ = vdim;
// Shift entries for byNODES
mfem::forall_switch(use_dev, old_nv * vdim_,
[=] MFEM_HOST_DEVICE (int k)
for (int c = vdim-1; c > 0; c--)
{
const int d = k / old_nv;
const int i = k % old_nv;
d_dest[i + d*num_vectors] = d_src[k];
});
for (int i = old_nv-1; i >= 0; i--)
{
Vector::operator[](i+c*num_vectors) = Vector::operator[](i+c*old_nv);
}
}
// Zero-out new data slots
const int diff = num_vectors - old_nv;
mfem::forall_switch(use_dev, diff * vdim,
[=] MFEM_HOST_DEVICE (int k)
// Zero-out data now associated with new Vectors
for (int c = 0; c < vdim; c++)
{
const int d = k / diff;
const int i = k % diff;
d_dest[d * num_vectors + old_nv + i] = 0.0;
});
for (int i = old_nv; i < num_vectors; i++)
{
Vector::operator[](i+c*num_vectors) = 0.0;
}
}
}
else // byVDIM
{
const int start_idx = old_nv * vdim;
const int end_idx = num_vectors * vdim;
const int diff = end_idx - start_idx;
mfem::forall_switch(use_dev, diff, [=] MFEM_HOST_DEVICE (int i)
for (int i = old_nv*vdim; i < num_vectors*vdim; i++)
{
d_dest[start_idx + i] = 0.0;
});
data[i] = 0.0;
}
}
}
else // Else just remove the trailing vector data
-1
View File
@@ -22,7 +22,6 @@ add_subdirectory(common)
add_subdirectory(contact)
add_subdirectory(dfem)
add_subdirectory(diag-smoothers)
add_subdirectory(multiapp)
add_subdirectory(dpg)
add_subdirectory(electromagnetics)
add_subdirectory(fluids/navier)
-4
View File
@@ -126,7 +126,6 @@ void VisualizeParticles(socketstream &sock, const char* vishost, int visport,
{
Vector pcoords;
pset.Coords().GetValues(i, pcoords);
pcoords.HostRead();
if (dim == 2)
{
Add2DPoint(pcoords, particles_mesh, psize);
@@ -140,7 +139,6 @@ void VisualizeParticles(socketstream &sock, const char* vishost, int visport,
FiniteElementSpace fes(&particles_mesh, &l2fec, 1);
GridFunction gf(&fes);
gf.HostWrite();
for (int i = 0; i < pset.GetNParticles(); i++)
{
@@ -195,7 +193,6 @@ void ParticleTrajectories::AddSegmentStart()
{
Vector pcoords;
pset.Coords().GetValues(i, pcoords);
pcoords.HostRead();
segment_meshes.front().AddVertex(pcoords);
}
}
@@ -216,7 +213,6 @@ void ParticleTrajectories::SetSegmentEnd()
{
Vector pcoords;
pset.Coords().GetValues(pidx, pcoords);
pcoords.HostRead();
segment_meshes.front().AddVertex(pcoords);
}
else // Otherwise set its end vertex == start vertex
+7 -147
View File
@@ -91,7 +91,6 @@ struct LorentzContext
int nt = 1000; // number of timesteps
int redist_interval = 5; // redistribution interval
int redist_mesh = 0; // redistribution mesh: 0: E mesh, 1: B mesh
std::string device_config = "cpu";
} ctx;
/// This class implements the Boris algorithm as described in the article
@@ -131,7 +130,7 @@ protected:
public:
Boris(MPI_Comm comm, GridFunction *E_gf_, GridFunction *B_gf_,
int nparticles, Ordering::Type pdata_ordering, bool use_device);
int nparticles, Ordering::Type pdata_ordering);
/// Find Particles in mesh corresponding to E and B fields
void FindParticles();
@@ -140,12 +139,9 @@ public:
/// right after FindParticles has been called.
void EvaluateFieldsAtParticles();
/// Advance particles one time step using Boris algorithm. Host version.
/// Advance particles one time step using Boris algorithm
void Step(real_t &t, real_t &dt);
/// Advance particles one time step using Boris algorithm. Device version.
void StepDevice(real_t &t, real_t &dt);
/// Remove lost particles and return their indices
Array<int> RemoveLostParticles();
@@ -239,8 +235,6 @@ int main(int argc, char *argv[])
args.AddOption(&vis_interval, "-vf", "--vis-interval",
"GLVis visualization update after this many timesteps. "
"0 means no visualization.");
args.AddOption(&ctx.device_config, "-d", "--device",
"Device configuration definition string.");
args.Parse();
if (!args.Good())
@@ -257,10 +251,6 @@ int main(int argc, char *argv[])
args.PrintOptions(cout);
}
Device device(ctx.device_config);
if (Mpi::Root()) { device.Print(); }
bool use_device = (ctx.device_config != "cpu") && Device::IsEnabled();
std::unique_ptr<VisItDataCollection> E_dc, B_dc;
ParGridFunction *E_gf = nullptr, *B_gf = nullptr;
Vector bb_xmin, bb_xmax;
@@ -276,7 +266,6 @@ int main(int argc, char *argv[])
return 1;
}
E_gf->ParFESpace()->GetParMesh()->GetBoundingBox(bb_xmin, bb_xmax, 2);
E_gf->UseDevice(use_device);
}
// Read B field if provided
@@ -291,7 +280,6 @@ int main(int argc, char *argv[])
}
Vector bb_xmint, bb_xmaxt;
B_gf->ParFESpace()->GetParMesh()->GetBoundingBox(bb_xmint, bb_xmaxt, 2);
B_gf->UseDevice(use_device);
if (ctx.E.coll_name != "")
{
// compute intersection of bounding boxes
@@ -314,14 +302,10 @@ int main(int argc, char *argv[])
// Initialize particles
int num_particles = ctx.npt/num_ranks +
(rank < (ctx.npt % num_ranks) ? 1 : 0);
Boris boris(MPI_COMM_WORLD, E_gf, B_gf, num_particles, ordering_type,
use_device);
Boris boris(MPI_COMM_WORLD, E_gf, B_gf, num_particles, ordering_type);
InitializeChargedParticles(boris.GetParticles(), ctx.x_min, ctx.x_max,
ctx.p_min, ctx.p_max, ctx.m, ctx.q);
Array<int> removed_idxs_dummy;
boris.FindParticles();
boris.Redistribute(ctx.redist_mesh, removed_idxs_dummy);
boris.EvaluateFieldsAtParticles();
real_t t = 0.0;
@@ -345,14 +329,7 @@ int main(int argc, char *argv[])
for (int step = 1; step <= ctx.nt; step++)
{
// Step the Boris algorithm
if (use_device)
{
boris.StepDevice(t, dt);
}
else
{
boris.Step(t, dt);
}
boris.Step(t, dt);
if (Mpi::Root())
{
mfem::out << "Step: " << step << " | Time: " << t << endl;
@@ -420,7 +397,7 @@ void Boris::ParticleStep(Particle &part, real_t &dt)
}
Boris::Boris(MPI_Comm comm, GridFunction *E_gf_, GridFunction *B_gf_,
int nparticles, Ordering::Type pdata_ordering, bool use_device)
int nparticles, Ordering::Type pdata_ordering)
: E_gf(E_gf_),
B_gf(B_gf_),
E_finder(comm),
@@ -449,7 +426,6 @@ Boris::Boris(MPI_Comm comm, GridFunction *E_gf_, GridFunction *B_gf_,
}
int dim = E_mesh ? E_mesh->SpaceDimension() : B_mesh->SpaceDimension();
MFEM_VERIFY(dim == 3, "Only 3D meshes are currently supported.");
pxB_.SetSize(dim); pm_.SetSize(dim); pp_.SetSize(dim);
@@ -459,8 +435,7 @@ Boris::Boris(MPI_Comm comm, GridFunction *E_gf_, GridFunction *B_gf_,
Array<int> field_vdims({1, 1, dim, dim, dim});
charged_particles = std::make_unique<ParticleSet>
(comm, nparticles, dim, field_vdims, 0, pdata_ordering,
use_device);
(comm, nparticles, dim, field_vdims, 0, pdata_ordering);
}
void Boris::FindParticles()
@@ -506,6 +481,7 @@ void Boris::Step(real_t &t, real_t &dt)
{
// Interpolate E and B fields onto particles
EvaluateFieldsAtParticles();
// Individually step each particle. If all ParticleSet fields are ordered
// byVDIM, we can use GetParticleRef for better performance.
if (charged_particles->IsParticleRefValid())
@@ -533,112 +509,6 @@ void Boris::Step(real_t &t, real_t &dt)
t += dt;
}
void Boris::StepDevice(real_t &t, real_t &dt)
{
// Interpolate E and B fields onto particles
EvaluateFieldsAtParticles();
const int N = charged_particles->GetNParticles();
auto &X = charged_particles->Coords();
auto &M = charged_particles->Field(MASS);
auto &Q = charged_particles->Field(CHARGE);
auto &P = charged_particles->Field(MOM);
auto &E = charged_particles->Field(EFIELD);
auto &B = charged_particles->Field(BFIELD);
const int dim = X.GetVDim();
// Capture orderings for each field to ensure correct access
const bool byVDIM_X = (X.GetOrdering() == Ordering::byVDIM);
const bool byVDIM_P = (P.GetOrdering() == Ordering::byVDIM);
const bool byVDIM_E = (E.GetOrdering() == Ordering::byVDIM);
const bool byVDIM_B = (B.GetOrdering() == Ordering::byVDIM);
auto d_x = X.ReadWrite();
auto d_m = M.Read();
auto d_q = Q.Read();
auto d_p = P.ReadWrite();
auto d_e = E.Read();
auto d_b = B.Read();
mfem::forall(N, [=] MFEM_HOST_DEVICE (int i)
{
const real_t m = d_m[i];
const real_t q = d_q[i];
real_t x[3], p[3], e[3], b[3];
// Load data
for (int d = 0; d < dim; d++)
{
x[d] = d_x[byVDIM_X ? i * dim + d : i + d * N];
p[d] = d_p[byVDIM_P ? i * dim + d : i + d * N];
e[d] = d_e[byVDIM_E ? i * dim + d : i + d * N];
b[d] = d_b[byVDIM_B ? i * dim + d : i + d * N];
}
// Boris algorithm implementation
real_t pm[3], pxB[3], pp[3];
// Compute half of the contribution from q E
// pm = p + 0.5 * dt * q * e
for (int d = 0; d < dim; d++)
{
pm[d] = p[d] + (0.5 * dt * q) * e[d];
}
// Compute the contribution from q p x B
real_t B2 = 0.0;
for (int d = 0; d < dim; d++) { B2 += b[d] * b[d]; }
// ... along pm x B
// pxB = pm x b
pxB[0] = pm[1] * b[2] - pm[2] * b[1];
pxB[1] = pm[2] * b[0] - pm[0] * b[2];
pxB[2] = pm[0] * b[1] - pm[1] * b[0];
// pp = a1 * pxB
const real_t a1 = 4.0 * dt * q * m;
for (int d = 0; d < dim; d++) { pp[d] = a1 * pxB[d]; }
// ... along pm
// pp += a2 * pm
const real_t a2 = 4.0 * m * m - dt * dt * q * q * B2;
for (int d = 0; d < dim; d++) { pp[d] += a2 * pm[d]; }
// ... along B
real_t b_dot_pm = 0.0;
for (int d = 0; d < dim; d++) { b_dot_pm += b[d] * pm[d]; }
const real_t a3 = 2.0 * dt * dt * q * q * b_dot_pm;
// pp += a3 * b
for (int d = 0; d < dim; d++) { pp[d] += a3 * b[d]; }
// scale by common denominator
const real_t a4 = 4.0 * m * m + dt * dt * q * q * B2;
for (int d = 0; d < dim; d++) { pp[d] /= a4; }
// Update the momentum
// p = pp + 0.5 * dt * q * e
for (int d = 0; d < dim; d++)
{
p[d] = pp[d] + (0.5 * dt * q) * e[d];
}
// Update the position
// x += (dt / m) * p
// Store back to global arrays
for (int d = 0; d < dim; d++)
{
d_p[byVDIM_P ? i * dim + d : i + d * N] = p[d];
d_x[byVDIM_X ? i * dim + d : i + d * N] = x[d] + (dt / m) * p[d];
}
});
// Find updated particle locations in E and B field meshes
FindParticles();
// Update time
t += dt;
}
Array<int> Boris::RemoveLostParticles()
{
Array<int> lost_idxs;
@@ -747,11 +617,6 @@ void InitializeChargedParticles(ParticleSet &charged_particles,
ParticleVector &M = charged_particles.Field(Boris::MASS);
ParticleVector &Q = charged_particles.Field(Boris::CHARGE);
X.HostWrite();
P.HostWrite();
M.HostWrite();
Q.HostWrite();
for (int i = 0; i < charged_particles.GetNParticles(); i++)
{
for (int d = 0; d < dim; d++)
@@ -778,9 +643,4 @@ void InitializeChargedParticles(ParticleSet &charged_particles,
M(i) = m;
Q(i) = q;
}
X.Read();
P.Read();
M.Read();
Q.Read();
}
+3 -19
View File
@@ -49,9 +49,6 @@
// findpts -m ../../data/ref-square.mesh -o 2 -mo 1 -random 1 -surf
// findpts -m ../../data/ref-cube.mesh -o 2 -mo 1 -random 1 -surf
// findpts -m ../../data/square-disc-p2.mesh -o 4 -mo 2 -random 1 -surf
// Surface meshes + bounding box size increase:
// findpts -m ../../data/square-disc-p2.mesh -o 4 -mo 2 -random 1 -surf -sabs 0.1
// findpts -m ../../data/tinyzoo-3d.mesh -o 4 -mo 2 -random 1 -surf -sabs 0.1
#include "mfem.hpp"
#include "../common/mfem-common.hpp"
@@ -112,7 +109,6 @@ int main (int argc, char *argv[])
int randomization = 0;
int npt = 100;
bool surface = false;
double surf_aabb_sz_inc = 0.0;
// Parse command-line options.
OptionsParser args(argc, argv);
@@ -154,9 +150,6 @@ int main (int argc, char *argv[])
args.AddOption(&surface, "-surf", "--surface", "-no-surf",
"--no-surface",
"Extract surface mesh from volume mesh.");
args.AddOption(&surf_aabb_sz_inc, "-sabs", "--surface-aabb-size-inc",
"Absolute AABB expansion applied to surface-search "
"axis-aligned bounding boxes in FindPointsGSLIB surface meshes.");
args.Parse();
if (!args.Good())
@@ -391,17 +384,8 @@ int main (int argc, char *argv[])
// Find and Interpolate FE function values on the desired points.
Vector interp_vals(pts_cnt*vec_dim);
FindPointsGSLIB finder;
if (surface && surf_aabb_sz_inc > 0.0)
{
Vector bb_size({surf_aabb_sz_inc});
finder.SetupSurfWithAABBExpansion(*mesh, bb_size);
}
else
{
finder.Setup(*mesh);
// finder.SetDistanceToleranceForPointsFoundOnBoundary(10);
}
FindPointsGSLIB finder(*mesh);
finder.SetDistanceToleranceForPointsFoundOnBoundary(10);
finder.SetL2AvgType(FindPointsGSLIB::NONE);
finder.Interpolate(vxyz, field_vals, interp_vals, point_ordering);
Array<unsigned int> code_out = finder.GetCode();
@@ -440,7 +424,7 @@ int main (int argc, char *argv[])
<< "Searched points: " << pts_cnt
<< "\nFound points: " << found
<< "\nMax interp error: " << max_err
<< "\nMax dist^2 (of found): " << max_dist
<< "\nMax dist (of found): " << max_dist
<< "\nPoints not found: " << not_found;
if (randomization == 1)
{
+6 -22
View File
@@ -48,14 +48,11 @@
// Device runs:
// mpirun -np 2 pfindpts -m ../../data/inline-quad.mesh -o 3 -mo 2 -random 1 -d debug
// mpirun -np 2 pfindpts -m ../../data/amr-quad.mesh -rs 1 -o 4 -mo 2 -random 1 -npt 100 -d debug
// mpirun -np 2 pfindpts -m ../../data/inline-hex.mesh -o 3 -mo 2 -random 1 -d debug -ft 1
// mpirun -np 2 pfindpts -m ../../data/inline-hex.mesh -o 3 -mo 2 -random 1 -d debug
// Surface meshes:
// mpirun -np 4 pfindpts -m ../../data/square-disc-p2.mesh -o 4 -mo 2 -vis -random 1 -surf
// mpirun -np 4 pfindpts -m ../../data/star-q3.mesh -o 6 -mo 3 -vis -random 1 -surf
// mpirun -np 4 pfindpts -m ../../data/fichera-q2.mesh -o 6 -mo 3 -vis -random 1 -surf
// Surface meshes + bounding box size increase:
// mpirun -np 4 pfindpts -m ../../data/square-disc-p2.mesh -o 4 -mo 2 -vis -random 1 -surf -sabs 0.1
// mpirun -np 4 pfindpts -m ../../data/tinyzoo-3d.mesh -o 4 -mo 2 -vis -random 1 -surf -sabs 0.1
#include "mfem.hpp"
#include "../common/mfem-common.hpp"
@@ -105,7 +102,6 @@ int main (int argc, char *argv[])
int randomization = 0;
int npt = 100; //points per proc
bool surface = false;
double surf_aabb_sz_inc = 0.0;
// Parse command-line options.
OptionsParser args(argc, argv);
@@ -149,9 +145,7 @@ int main (int argc, char *argv[])
args.AddOption(&surface, "-surf", "--surface", "-no-surf",
"--no-surface",
"Extract surface mesh from volume mesh.");
args.AddOption(&surf_aabb_sz_inc, "-sabs", "--surface-aabb-size-inc",
"Absolute AABB expansion applied to surface-search "
"axis-aligned bounding boxes in FindPointsGSLIB surface meshes.");
args.Parse();
if (!args.Good())
{
@@ -349,7 +343,7 @@ int main (int argc, char *argv[])
Geometry::GetRandomPoint(geom, ip);
if (j < npt_face_per_elem)
{
ip.x = 0.0; // force point to be on a face
ip.x = 0.0; // force point to be on the face
npt_total_face++;
}
Vector pos_i(sdim);
@@ -379,17 +373,8 @@ int main (int argc, char *argv[])
// Find and Interpolate FE function values on the desired points.
Vector interp_vals(pts_cnt*vec_dim);
FindPointsGSLIB finder;
if (surface && surf_aabb_sz_inc > 0.0)
{
Vector bb_size({surf_aabb_sz_inc});
finder.SetupSurfWithAABBExpansion(pmesh, bb_size);
}
else
{
finder.Setup(pmesh);
}
// finder.SetDistanceToleranceForPointsFoundOnBoundary(1e-10);
FindPointsGSLIB finder(pmesh);
finder.SetDistanceToleranceForPointsFoundOnBoundary(10);
// Enable GPU to CPU fallback for GPUData only if you are using an older
// version of GSLIB.
// finder.SetGPUtoCPUFallback(true);
@@ -471,11 +456,10 @@ int main (int argc, char *argv[])
<< "\nPoints on faces: " << face_pts << " out of "
<< npt_total_face
<< "\nMax interp error: " << max_error
<< "\nMax dist^2 (of found): " << max_dist
<< "\nMax dist (of found): " << max_dist
<< endl;
}
delete fec;
if (randomization != 0)
@@ -80,8 +80,6 @@ public:
// limitations
void MultRT_2D(const Vector &x, Vector &y, Mode mode) const;
void MultRT_3D(const Vector &x, Vector &y, Mode mode) const;
// suppress warning about hiding overloaded virtual function:
using Operator::Mult;
};
} // namespace mfem
-52
View File
@@ -1,52 +0,0 @@
# 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.
set(MESH_FILES
backward-facing-step.msh
channel-cylinder.msh
)
# Add a target to copy the mesh files from the source directory; used by sample
# runs.
set(SRC_MESH_FILES)
foreach(MESH_FILE ${MESH_FILES})
list(APPEND SRC_MESH_FILES ${CMAKE_CURRENT_SOURCE_DIR}/${MESH_FILE})
endforeach()
add_custom_command(OUTPUT data_is_copied
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SRC_MESH_FILES} .
COMMAND ${CMAKE_COMMAND} -E touch data_is_copied
COMMENT "Copying multiapp miniapps data files ...")
add_custom_target(copy_miniapps_multiapp_data DEPENDS data_is_copied)
list(APPEND MULTIAPP_COMMON_SOURCES
multiapp.cpp)
list(APPEND MULTIAPP_COMMON_HEADERS
multiapp.hpp)
set(MULTIAPP_COMMON_FILES
EXTRA_SOURCES ${MULTIAPP_COMMON_SOURCES}
EXTRA_HEADERS ${MULTIAPP_COMMON_HEADERS})
# Parallel apps.
if (MFEM_USE_MPI)
add_mfem_miniapp(coupled-diffusion
MAIN coupled-diffusion.cpp
${MFEM_MINIAPPS_COMMON_HEADERS} ${MULTIAPP_COMMON_FILES}
LIBRARIES mfem-common)
add_dependencies(coupled-diffusion copy_miniapps_multiapp_data)
# Add parallel tests.
# if (MFEM_ENABLE_TESTING)
# endif()
endif()
-895
View File
@@ -1,895 +0,0 @@
#include "mfem.hpp"
#include "multiapp.hpp"
#include <fstream>
using namespace mfem;
using namespace std;
struct CaseContext
{
int ser_ref = 1; // Serial mesh refinement
int order = 3; // Finite element order
bool visualization = true;// Visualization on/off
int grad_mode = 1; // Gradient mode for the coupled operator - 0: finite difference,
// 1: back/forward propagation
bool coupled = true; // Coupled (true) vs. uncoupled (false) solves
int nl_iter = 50; // Maximum number of nonlinear iterations
int lin_iter = 2000; // Maximum number of linear iterations
#if defined(MFEM_USE_DOUBLE)
real_t tol_nsolve = 1e-4;
real_t tol_lsolve = 1e-6;
#elif defined(MFEM_USE_SINGLE)
real_t tol_nsolve = 1e-3;
real_t tol_lsolve = 1e-3;
#else
#error "Only single and double precision are supported!"
real_t tol_nsolve = 0;
real_t tol_lsolve = 0;
#endif
} ctx;
void SetSolverParameters(IterativeSolver *solver, real_t rtol, real_t atol , int max_it,
int print_level, bool iterative_mode);
/// A functional diffusion coefficient (i.e., k(T))
class FunctionalCoefficient : public Coefficient
{
public:
enum Mode { FUNC = 0, GRAD = 1};
protected:
ParGridFunction *T_gf = nullptr;
real_t kref = 1.0;
real_t a0 = 0.0, a1 = 0.0, a2 = 0.0;
int findex = 0;
Mode mode = Mode::FUNC; // otherwise, grad
public:
FunctionalCoefficient(ParGridFunction *T_gf, real_t kref):
T_gf(T_gf), kref(kref) { }
FunctionalCoefficient(ParGridFunction *T_gf, real_t kref, real_t a0):
T_gf(T_gf), kref(kref), a0(a0) { findex = 1; }
FunctionalCoefficient(ParGridFunction *T_gf, real_t kref,
real_t a0, real_t a1, real_t a2): T_gf(T_gf),
kref(kref), a0(a0), a1(a1), a2(a2) { findex = 2; }
real_t Exponential(real_t x, bool eval_f) const
{
real_t f = kref*exp(a0*x);
return (eval_f ? f : a0*f);
}
real_t Polynomial(real_t x, bool eval_f) const
{
return (eval_f ? kref*(a0 + a1*x + a2*x*x) : kref*(a1 + 2*a2*x));
}
void SetMode(Mode mode) { this->mode = mode; }
Mode GetMode() const { return mode; }
void UpdateGridFunction(ParGridFunction *gf) { T_gf = gf; }
real_t Eval(real_t x, bool eval_f) const
{
switch (findex)
{
case 1:
return Exponential(x, eval_f);
case 2:
return Polynomial(x, eval_f);
default:
return kref;
}
}
real_t Eval(ElementTransformation &Tr,
const IntegrationPoint &ip) override
{
real_t T = T_gf ? T_gf->GetValue(Tr, ip) : 0.0;
bool eval_f = (mode == Mode::FUNC);
return Eval(T, eval_f);
}
};
/// A coefficient defined by the product of grid functions, e.g. k(T) = prod_i x_i
class GridFunctionProductCoefficient : public Coefficient
{
protected:
std::vector<ParGridFunction*> &x;
public:
GridFunctionProductCoefficient(std::vector<ParGridFunction*> &x) : x(x) { }
real_t Eval(ElementTransformation &Tr, const IntegrationPoint &ip) override
{
real_t prod = 1.0;
for(size_t i = 0; i < x.size(); i++)
{
real_t val = x[i]->GetValue(Tr, ip);
prod *= val;
}
return prod;
}
};
class CoefficientIntegrator : public NonlinearFormIntegrator
{
protected:
FunctionalCoefficient *func = nullptr;
Vector shape;
public:
CoefficientIntegrator(FunctionalCoefficient *func) : func(func) { }
void SetCoefficient(FunctionalCoefficient *f) { func = f; }
void AssembleElementVector(const FiniteElement &el,
ElementTransformation &Tr,
const Vector &elfun, Vector &elvect)
{
int dof = el.GetDof();
shape.SetSize(dof);
elvect.SetSize(dof);
elvect = 0.0;
const IntegrationRule *ir = &el.GetNodes();
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
el.CalcShape(ip, shape);
Tr.SetIntPoint(&ip);
real_t x = elfun * shape; // Evaluate the function at the integration point
real_t fval = func->Eval(x, true);
for (int j = 0; j < dof; j++)
{
elvect(j) += fval * shape(j);
}
}
}
void AssembleElementGrad(const FiniteElement &el, ElementTransformation &Tr,
const Vector &elfun, DenseMatrix &elmat)
{
int dof = el.GetDof();
shape.SetSize(dof);
elmat.SetSize(dof);
elmat = 0.0;
const IntegrationRule *ir = &el.GetNodes();
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
el.CalcShape(ip, shape);
Tr.SetIntPoint(&ip);
real_t x = elfun * shape; // Evaluate the function at the integration point
real_t dfdx = func->Eval(x, false); // Evaluate the derivative of the function at the integration point
for (int j = 0; j < dof; j++)
{
elmat(j,j) += dfdx * shape(j); // Diagonal contribution to the Jacobian
}
}
}
};
class NonlinearDiffusionIntegrator : public NonlinearFormIntegrator
{
protected:
Coefficient *k;
Coefficient *dk;
Vector u, vec, shape;
DenseMatrix dshape, dshapedxt, adjJ;
public:
NonlinearDiffusionIntegrator(Coefficient *kappa, Coefficient *dkappa) :
k(kappa), dk(dkappa) { }
virtual void AssembleElementVector(const FiniteElement &el,
ElementTransformation &Tr,
const Vector &elfun, Vector &elvec)
{
int dim = el.GetDim();
int dof = el.GetDof();
real_t w;
elvec.SetSize(dof);
elvec = 0.0;
const IntegrationRule *ir = IntRule ? IntRule : &IntRules.Get(el.GetGeomType(), 2*el.GetOrder());
u.SetSize(dim);
vec.SetSize(dim);
dshape.SetSize(dof, dim);
adjJ.SetSize(dim, dim);
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
el.CalcDShape(ip, dshape);
Tr.SetIntPoint(&ip);
CalcAdjugate(Tr.Jacobian(), adjJ);
w = ip.weight / Tr.Weight();
dshape.MultTranspose(elfun, u);
adjJ.MultTranspose(u, vec);
if(k)
{
w *= k->Eval(Tr, ip);
}
vec *= w;
adjJ.Mult(vec, u);
dshape.AddMult(u, elvec);
}
}
void AssembleElementGrad(const FiniteElement &el, ElementTransformation &Tr,
const Vector &elfun, DenseMatrix &elmat)
{
int dim = el.GetDim();
int dof = el.GetDof();
real_t w, k0 = 0.0, dk0 = 0.0;
elmat.SetSize(dof);
elmat = 0.0;
const IntegrationRule *ir = IntRule ? IntRule : &IntRules.Get(el.GetGeomType(), 2*el.GetOrder());
u.SetSize(dim);
shape.SetSize(dof);
vec.SetSize(dof);
dshape.SetSize(dof, dim);
dshapedxt.SetSize(dof, dim);
// f = grad(psi) * k(u) * grad(T)
// df/dT = grad(psi) ( k(u0) * grad(T) + k'(u0) * grad(u0) * T )
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint &ip = ir->IntPoint(i);
el.CalcShape(ip, shape);
el.CalcDShape(ip, dshape);
Tr.SetIntPoint(&ip);
w = ip.weight / Tr.Weight();
Mult(dshape, Tr.AdjugateJacobian(), dshapedxt);
k0 = k ? k->Eval(Tr, ip) : 0.0;
dk0 = dk ? dk->Eval(Tr, ip) : 0.0;
if(k0 != 0.0) // grad(psi) * k(u0) * grad(T)
{
real_t kdT = w*k0;
AddMult_a_AAt(kdT, dshapedxt, elmat);
}
if(dk0 != 0.0) // grad(psi) * (k'(T0) * grad(T0)) * T
{
dk0 = w*dk->Eval(Tr, ip);
dshapedxt.MultTranspose(elfun, u); // grad(T0) in physical space
u *= dk0; // k'(T0) * grad(T0)
dshapedxt.Mult(u, vec); // grad(psi) * k'(T0) * grad(T0)
AddMultVWt(vec, shape, elmat); // grad(psi) * k'(T0) * grad(T0) * T
}
}
}
};
/// An application that takes an input field T, and computes an output field k(T)
// represented by the FunctionalCoefficient class.
class DiffusionCoefficient : public GraphNode
{
public:
using Mode = FunctionalCoefficient::Mode;
protected:
ParFiniteElementSpace &fes;
mutable ParGridFunction T, k;
mutable FunctionalCoefficient *kc;
// mutable Vector tdof, kdof, dk_dof, dT_dof;
mutable Mode mode = Mode::FUNC;
mutable ParNonlinearForm Nform;
mutable Operator *J = nullptr; // Jacobian for the nonlinear form
CoefficientIntegrator *coeff_integrator = nullptr;
public:
DiffusionCoefficient(ParFiniteElementSpace &fes) :
GraphNode(fes.GetTrueVSize()), fes(fes), T(&fes), k(&fes),
kc(new FunctionalCoefficient(&T, 1.0, 5.0e-2)),
Nform(&fes),
coeff_integrator(new CoefficientIntegrator(kc))
{
k = 0.0;
T = 0.0;
k.ProjectCoefficient(*kc);
// Testing with the nonlinear form framework to compute k(T) and dk/dT
Nform.AddDomainIntegrator(coeff_integrator); // Transfer ownership
Nform.SetGradientType(Operator::Type::Hypre_ParCSR);
Nform.Setup();
SetInputOffsets(Array<int>({0, fes.GetTrueVSize()}));
SetOutputOffsets(Array<int>({0, fes.GetTrueVSize()}));
}
void SetMode(Mode mode) { this->mode = mode; }
FunctionalCoefficient* GetCoefficient() { return kc; }
void SetCoefficient(FunctionalCoefficient *fc)
{
if(kc) delete kc;
kc = fc;
kc->SetMode(mode);
kc->UpdateGridFunction(&T);
coeff_integrator->SetCoefficient(kc);
}
void Mult(const Vector &x, Vector &y) const override
{
BlockVector xb(x.GetData(), InputOffsets());
BlockVector yb(y.GetData(), OutputOffsets());
MultiVector xmv(1), ymv(1);
xmv.MakeRef(0, xb.GetBlock(0));
ymv.MakeRef(0, yb.GetBlock(0));
const_cast<DiffusionCoefficient*>(this)->Mult(xmv, ymv);
}
void Mult(const MultiVector &x, MultiVector &y) override
{
const Vector &tdof = x[0];
Vector &kdof = y[0];
Nform.Mult(tdof, kdof);
if(exec_mode == GraphNode::GRADIENT_MODE)
{
J = &Nform.GetGradient(tdof); // Store jacobian for JVP
}
else
{
J = nullptr; // Clear the Jacobian if not in gradient mode
}
}
// TODO: Possibly delete and only support MultiVector version of GradientMult
void GradientMult(const Vector &x, const Vector &dx, Vector &dy) const override
{
BlockVector xb(x.GetData(), InputOffsets());
BlockVector dxb(dx.GetData(), InputOffsets());
BlockVector dyb(dy.GetData(), OutputOffsets());
MultiVector xmv(1), dxmv(1), dymv(1);
xmv.MakeRef(0, xb.GetBlock(0));
dxmv.MakeRef(0, dxb.GetBlock(0));
dymv.MakeRef(0, dyb.GetBlock(0));
const_cast<DiffusionCoefficient*>(this)->GradientMult(xmv, dxmv, dymv);
}
void GradientMult(const MultiVector &x, const MultiVector &dx, MultiVector &dy) const override
{
const Vector &tdof = x[0];
const Vector &xadj = dx[0];
Vector &yadj = dy[0];
if(J)
{
J->Mult(xadj, yadj);
}
else
{
J = &Nform.GetGradient(tdof); // Store jacobian for JVP
J->Mult(xadj, yadj);
}
}
~DiffusionCoefficient() override
{
if(kc) delete kc;
}
};
/// An application that takes n input fields x_i, and computes an output
/// field prod(x) := y = prod_i x_i.
/// Also provides the derivative dy/dx_i = prod_{j!=i} x_j * dx_i/dx for i = 0,...,n-1.
class ProductGridFunctions : public GraphNode
{
protected:
ParFiniteElementSpace &fes;
mutable std::vector<ParGridFunction*> x_gf;
mutable Vector dfdx;
mutable ParGridFunction y_gf;
mutable GridFunctionProductCoefficient prod_coeff;
public:
ProductGridFunctions(ParFiniteElementSpace &fes, int n) :
// GraphNode(fes.GetTrueVSize()),
GraphNode(fes.GetTrueVSize(), fes.GetTrueVSize() * n),
fes(fes), x_gf(n),
y_gf(&fes), prod_coeff(x_gf)
{
Array<int> offsets(n+1);
offsets[0] = 0;
for (int i = 0; i < n; i++)
{
x_gf[i] = new ParGridFunction(&fes);
*x_gf[i] = 0.0;
offsets[i+1] = offsets[i] + fes.GetTrueVSize();
}
y_gf = 0.0;
y_gf.ProjectCoefficient(prod_coeff);
SetInputOffsets(offsets);
SetOutputOffsets(Array<int>({0, fes.GetTrueVSize()}));
}
void Mult(const Vector &x, Vector &y) const override
{
BlockVector xb(x.GetData(), InputOffsets());
BlockVector yb(y.GetData(), OutputOffsets());
MultiVector xmv(x_gf.size()), ymv(1);
for (size_t i = 0; i < x_gf.size(); i++)
{
xmv.MakeRef(i, xb.GetBlock(i));
}
ymv.MakeRef(0, yb.GetBlock(0));
const_cast<ProductGridFunctions*>(this)->Mult(xmv, ymv);
}
void Mult(const MultiVector &x, MultiVector &y) override
{
for (size_t i = 0; i < x_gf.size(); i++)
{
const Vector &x_dof = x[i];
x_gf[i]->SetFromTrueDofs(x_dof);
}
Field *out_field = OutputField(0);
Vector &y_dof = y[0];
y_gf.ProjectCoefficient(prod_coeff);
y_gf.GetTrueDofs(y_dof);
}
// TODO: Possibly delete and only support MultiVector version of GradientMult
void GradientMult(const Vector &x, const Vector &dx, Vector &dy) const override
{
BlockVector xb(x.GetData(), InputOffsets());
BlockVector dxb(dx.GetData(), InputOffsets());
BlockVector dyb(dy.GetData(), OutputOffsets());
MultiVector xmv(x_gf.size()), dxmv(x_gf.size()), dymv(1);
for (size_t i = 0; i < x_gf.size(); i++)
{
xmv.MakeRef(i, xb.GetBlock(i));
dxmv.MakeRef(i, dxb.GetBlock(i));
}
dymv.MakeRef(0, dyb.GetBlock(0));
const_cast<ProductGridFunctions*>(this)->GradientMult(xmv, dxmv, dymv);
}
void GradientMult(const MultiVector &x, const MultiVector &dx, MultiVector &dy) const override
{
// Jacobian vector product for y = prod_i x_i is:
// dy/dx = sum_i (prod_{j!=i} x_j * dx_i/dx)
for (size_t i = 0; i < x_gf.size(); i++)
{
const Vector &x_dof = x[i];
x_gf[i]->SetFromTrueDofs(x_dof); // Set all x_i
}
Vector &jvp = dy[0];
jvp = 0.0;
for (size_t i = 0; i < x_gf.size(); i++)
{
const Vector &x_dof = x[i];
const Vector &dx_dof = dx[i]; // Get dx_i/dx
x_gf[i]->SetFromTrueDofs(dx_dof); // Set x_i = dx_i/dx for i-th term in the product
y_gf.ProjectCoefficient(prod_coeff); // Recompute product with x_i replaced by dx_i/dx
y_gf.GetTrueDofs(dfdx); // Get prod_{j!=i} x_j * dx_i/dx for i-th term
jvp += dfdx; // Accumulate contribution from i-th term
x_gf[i]->SetFromTrueDofs(x_dof); // reset to original value for next iteration
}
}
~ProductGridFunctions() override
{
for (size_t i = 0; i < x_gf.size(); i++)
{
if(x_gf[i]) delete x_gf[i];
}
}
};
/// An application that represents the nonlinear diffusion operator: f(T) = -Div(k(u) grad(T))
/// with input field T and k, and output field f(T).
class DiffusionOperator : public GraphNode
{
public:
// Mesh and finite element space
ParMesh &mesh;
ParFiniteElementSpace &fes;
/// Essential dof array.
Array<int> ess_tdofs;
/// Grid functions for the temperature and heat flux
mutable ParGridFunction T, k, dk;
mutable GridFunctionCoefficient k_gfc, dk_gfc;
mutable ParNonlinearForm Nform;
mutable ParLinearForm bform;
mutable Vector b;
ConstantCoefficient zero_coeff, one_coeff;
mutable FunctionalCoefficient *kc = nullptr;
mutable HypreParMatrix *dfdk_mat = nullptr, *dfdT_mat = nullptr;
public:
DiffusionOperator(ParFiniteElementSpace &fes_) :
// GraphNode(fes_.GetTrueVSize()),
GraphNode(fes_.GetTrueVSize(),2*fes_.GetTrueVSize()),
mesh(*fes_.GetParMesh()), fes(fes_),
T(&fes), k(&fes), dk(&fes),
k_gfc(&k), dk_gfc(&dk),
Nform(&fes), bform(&fes),
zero_coeff(0.0), one_coeff(1.0)
{
fes.GetBoundaryTrueDofs(ess_tdofs);
T = 0.0;
k = 0.0;
dk = 0.0;
bform.AddDomainIntegrator(new DomainLFIntegrator(one_coeff));
Nform.AddDomainIntegrator(new NonlinearDiffusionIntegrator(&k_gfc, &dk_gfc));
Nform.SetGradientType(Operator::Type::Hypre_ParCSR);
b.SetSize(fes.GetTrueVSize()); b = 0.0;
Assemble();
SetInputOffsets(Array<int>({0, fes.GetTrueVSize(), 2*fes.GetTrueVSize()}));
SetOutputOffsets(Array<int>({0, fes.GetTrueVSize()}));
}
void SetCoefficient(FunctionalCoefficient *fc) { kc = fc; }
void Assemble()
{
AssembleLinearForms();
AssembleBilinearForms();
AssembleNonlinearForms();
}
void AssembleBilinearForms()
{}
void AssembleNonlinearForms()
{
Nform.SetEssentialTrueDofs(ess_tdofs);
Nform.Setup();
}
void AssembleLinearForms()
{
bform.Assemble();
bform.ParallelAssemble(b);
}
void Mult(const Vector &x, Vector &y) const override
{
BlockVector xb(x.GetData(), InputOffsets());
BlockVector yb(y.GetData(), OutputOffsets());
MultiVector xmv(2), ymv(1);
xmv.MakeRef(0, xb.GetBlock(0));
xmv.MakeRef(1, xb.GetBlock(1));
ymv.MakeRef(0, yb.GetBlock(0));
const_cast<DiffusionOperator*>(this)->Mult(xmv, ymv);
}
void Mult(const MultiVector &x, MultiVector &y) override
{
const Vector &tdofs = x[0];
const Vector &kdofs = x[1];
Vector &fdofs = y[0];
k.SetFromTrueDofs(kdofs); // update for use in k_gfc
if(exec_mode == GraphNode::GRADIENT_MODE)
{
if(dfdT_mat) delete dfdT_mat;
if(dfdk_mat) delete dfdk_mat;
dk = 0.0;
k.SetFromTrueDofs(kdofs);
Operator* grad = &Nform.GetGradient(tdofs);
dfdT_mat = new HypreParMatrix(dynamic_cast<const HypreParMatrix&>(*grad)); // deep copy
dk = 1.0;
k = 0.0;
grad = &Nform.GetGradient(tdofs);
dfdk_mat = new HypreParMatrix(dynamic_cast<const HypreParMatrix&>(*grad)); // deep copy
}
else
{
if(dfdT_mat) { delete dfdT_mat; dfdT_mat = nullptr; }
if(dfdk_mat) { delete dfdk_mat; dfdk_mat = nullptr; }
}
Nform.Mult(tdofs, fdofs);
fdofs.SetSubVector(ess_tdofs, 0.0);
}
// Exact block jacobian [df/dT, df/dk]
Operator& GetGradient(const Vector &x) const override
{
MFEM_ABORT("GetGradient not implemented for DiffusionOperator");
}
// TODO: Possibly delete and only support MultiVector version of GradientMult
void GradientMult(const Vector &x, const Vector &dx, Vector &dy) const override
{
BlockVector xb(x.GetData(), InputOffsets());
BlockVector dxb(dx.GetData(), InputOffsets());
BlockVector dyb(dy.GetData(), OutputOffsets());
Vector &Tadj = dxb.GetBlock(0);
Vector &kadj = dxb.GetBlock(1);
Vector &yadj = dyb.GetBlock(0);
Vector &tdofs = xb.GetBlock(0);
Vector &kdofs = xb.GetBlock(1);
dfdT_mat->Mult(Tadj, yadj);
dfdk_mat->AddMult(kadj, yadj);
}
void GradientMult(const MultiVector &x, const MultiVector &dx, MultiVector &dy) const override
{
const Vector &Tadj = dx[0];
const Vector &kadj = dx[1];
Vector &yadj = dy[0];
const Vector &tdofs = x[0];
const Vector &kdofs = x[1];
dfdT_mat->Mult(Tadj, yadj);
dfdk_mat->AddMult(kadj, yadj);
}
/// @brief Destroy the DiffusionOperator object
~DiffusionOperator() override
{
if(dfdT_mat) delete dfdT_mat;
if(dfdk_mat) delete dfdk_mat;
}
};
int main(int argc, char *argv[])
{
Mpi::Init();
Hypre::Init();
using GradMode = DAGraph::GradMode;
OptionsParser args(argc, argv);
args.AddOption(&ctx.order, "-o", "--order",
"Finite element order (polynomial degree).");
args.AddOption(&ctx.visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&ctx.ser_ref, "-rs", "--serial-refine",
"Number of times to refine the mesh in serial.");
args.AddOption(&ctx.grad_mode, "-gm", "--grad-mode",
"Gradient mode for the coupled operator (0: exact, 1: finite difference, 2: algorithmic differentiation)");
args.AddOption(&ctx.coupled, "-cp", "--coupled", "-ucp", "--uncoupled",
"Coupled (true) vs. uncoupled (false) solves.");
args.ParseCheck();
int order = ctx.order;
std::string mesh_file = "../../data/star.mesh";
Mesh *serial_mesh = new Mesh(mesh_file);
int dim = serial_mesh->Dimension();
for (int i = 0; i < ctx.ser_ref; ++i) { serial_mesh->UniformRefinement(); }
serial_mesh->SetCurvature(order, false, dim, Ordering::byNODES);
ParMesh pmesh(MPI_COMM_WORLD, *serial_mesh);
delete serial_mesh;
pmesh.UniformRefinement();
// Finite element spaces
H1_FECollection fec(order, dim);
ParFiniteElementSpace fes(&pmesh, &fec);
// Build all operator nodes
DiffusionCoefficient diff_coeff_1(fes);
diff_coeff_1.SetName("k(T1)");
diff_coeff_1.SetCoefficient(new FunctionalCoefficient(nullptr, 1.0, 3.5e-2));
// diff_coeff_1.SetCoefficient(new FunctionalCoefficient(nullptr, 1.0, 1.0, 0.1, 0.0));
DiffusionCoefficient diff_coeff_2(fes);
diff_coeff_2.SetName("k(T2)");
diff_coeff_2.SetCoefficient(new FunctionalCoefficient(nullptr, 1.0, 1.0, 2.0, 0.0));
// diff_coeff_2.SetCoefficient(new FunctionalCoefficient(nullptr, 1.5, 2.5e-2));
ProductGridFunctions prod_coeff(fes, 2);
prod_coeff.SetName("k(T1,T2)");
DiffusionOperator diff_op1(fes);
diff_op1.SetName("Div(k(T1,T2) grad(T1))");
diff_op1.SetCoefficient(diff_coeff_1.GetCoefficient());
DiffusionOperator diff_op2(fes);
diff_op2.SetName("Div(k(T1,T2) grad(T2))");
diff_op2.SetCoefficient(diff_coeff_2.GetCoefficient());
// Build the DAG in any order, and then sort it to ensure the correct execution order
DAGraph dag(5);
dag.AddOperator(&diff_coeff_1);
dag.AddOperator(&diff_op1, fes.GetTrueVSize());
dag.AddOperator(&diff_op2, fes.GetTrueVSize());
dag.AddOperator(&diff_coeff_2);
dag.AddOperator(&prod_coeff);
Vector k1vec(fes.GetTrueVSize()); k1vec = 0.0;
Vector k2vec(fes.GetTrueVSize()); k2vec = 0.0;
Vector kpvec(fes.GetTrueVSize()); kpvec = 0.0;
Vector k1adj(fes.GetTrueVSize()); k1adj = 0.0;
Vector k2adj(fes.GetTrueVSize()); k2adj = 0.0;
// Vector kpadj(fes.GetTrueVSize()); kpadj = 0.0;
// Input fields get data from 'x' in DAGraph::Mult(x, y)
Field T1_field(nullptr, nullptr);
Field T2_field(nullptr, nullptr);
// Write space for data and adjoint only needed
// for the intermediate fields k1, k2, and k_prod
Field k1_field(&k1vec, &k1adj);
Field k2_field(&k2vec, &k2adj);
Field kp_field(&kpvec, &kpvec); // can use same space for data & adjoint
// Output fields get data from 'y' in DAGraph::Mult(x, y)
Field f1_field(nullptr, nullptr);
Field f2_field(nullptr, nullptr);
// Add input and output to the DAG
int sz = fes.GetTrueVSize();
dag.AddInput(&T1_field, sz);
dag.AddInput(&T2_field, sz);
dag.AddOutput(&f1_field, sz);
dag.AddOutput(&f2_field, sz);
// Form connections between the nodes in the DAG
diff_coeff_1.AddInput(&T1_field);
diff_coeff_1.AddOutput(&k1_field);
diff_coeff_2.AddInput(&T2_field);
diff_coeff_2.AddOutput(&k2_field);
prod_coeff.AddInputs(&k1_field, &k2_field);
prod_coeff.AddOutput(&kp_field);
diff_op1.AddInput(&T1_field);
diff_op1.AddOutput(&f1_field);
diff_op2.AddInput(&T2_field);
diff_op2.AddOutput(&f2_field);
if(ctx.coupled)
{
diff_op1.AddInput(&kp_field); // kp_field
diff_op2.AddInput(prod_coeff.OutputField(0)); // Can also use kp_field directly
}
else
{
diff_op1.AddInput(&k1_field); // Can also use diff_coeff_1.OutputField(0)
diff_op2.AddInput(&k2_field); // Can also use diff_coeff_2.OutputField(0)
}
// Assemble DAG: topological sort, validate nodes, etc.
dag.Assemble();
std::string output_prefix = ctx.coupled ? "Coupled_Diffusion" : "Uncoupled_Diffusion";
if(Mpi::Root())
{
std::ofstream fout(output_prefix+"-dag.txt");
fout << "{\n";
dag.Save(fout);
fout << "}\n";
fout << std::flush;
fout.close();
}
// Set initial guess and boundary conditions for T1 and T2
Array<int> ess_tdofs;
fes.GetBoundaryTrueDofs(ess_tdofs);
int T1_idx = 0;
int T2_idx = 1;
BlockVector xb(dag.InputOffsets());
BlockVector yb(dag.OutputOffsets());
xb.GetBlock(T1_idx).Randomize();
xb.GetBlock(T2_idx).Randomize();
xb.GetBlock(T1_idx).SetSubVector(ess_tdofs, 0.0);
xb.GetBlock(T2_idx).SetSubVector(ess_tdofs, 0.0);
// Build the nonlinear solver and linear solver for the DAG
NewtonSolver newton_solver(pmesh.GetComm());
GMRESSolver linear_solver(pmesh.GetComm());
linear_solver.SetKDim(500);
SetSolverParameters(&newton_solver, ctx.tol_nsolve, 0.0, ctx.nl_iter, 1, true);
SetSolverParameters(&linear_solver, ctx.tol_lsolve, 0.0, ctx.lin_iter, 1, false);
newton_solver.SetPreconditioner(linear_solver);
linear_solver.SetPrintLevel(1);
// Set the gradient mode for the DAG and solve the coupled system
GradMode gm = static_cast<GradMode>(ctx.grad_mode);
dag.SetGradientMode(gm);
newton_solver.SetOperator(dag);
newton_solver.Mult(xb, yb);
ParaViewDataCollection *pv = nullptr;
if (ctx.visualization)
{
std::string pv_prefix;
switch (ctx.grad_mode)
{
case 0: pv_prefix = "FD"; break;
case 1: pv_prefix = "MF"; break;
default: pv_prefix = "Unknown"; break;
}
pv = new ParaViewDataCollection(output_prefix+"-"+pv_prefix, &pmesh);
pv->SetLevelsOfDetail(order);
pv->SetDataFormat(VTKFormat::BINARY);
pv->SetHighOrderOutput(true);
ParGridFunction T1_gf(&fes);
ParGridFunction T2_gf(&fes);
T1_gf.SetFromTrueDofs(yb.GetBlock(T1_idx));
T2_gf.SetFromTrueDofs(yb.GetBlock(T2_idx));
pv->RegisterField("T1", &T1_gf);
pv->RegisterField("T2", &T2_gf);
pv->Save();
delete pv;
}
std::cout << "Finished solving the coupled diffusion problem." << std::endl;
return 0;
}
void SetSolverParameters(IterativeSolver *solver, real_t rtol, real_t atol,
int max_it, int print_level, bool iterative_mode)
{
solver->SetRelTol(rtol);
solver->SetAbsTol(atol);
solver->SetMaxIter(max_it);
solver->SetPrintLevel(print_level);
solver->iterative_mode = iterative_mode;
}
-873
View File
@@ -1,873 +0,0 @@
// 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 "multiapp.hpp"
namespace mfem
{
DAGraph::~DAGraph()
{
for(int i=0; i < nnodes; i++)
{
if(node_owned[i] && nodes[i]) delete nodes[i];
}
if(grad) delete grad;
}
void DAGraph::Assemble()
{
// Sort graph nodes topologically to ensure correct execution order
// Ordering is not unique, hence, id->index maps are needed
TopologicalSort();
// Collect all fields from the nodes into the field map
CollectFieldMaps();
// Compute depth of the graph nodes
ComputeDepth();
// Validate each node
for (auto &node : nodes)
{
ValidateNode(*node);
}
// Update width and height of the DAG from offsets
// Check that the input and output offsets are consistent
ValidateOffsets();
width = input_offsets.Last();
height = output_offsets.Last();
// Delete any existing gradient operator as node ordering may have changed
if (grad) delete grad;
assembled = true;
}
void DAGraph::ValidateOffsets()
{
// Check that the input and output offsets are consistent
// with the number of inputs and outputs
if(InputFields().Size() > 1)
{
MFEM_ASSERT(input_offsets.Size() == InputFields().Size() + 1,
"Input offsets size inconsistent with number of input fields");
}
else
{
input_offsets = Array<int>({0, nodes[0]->Width()});
}
if(OutputFields().Size() > 1)
{
MFEM_ASSERT(output_offsets.Size() == OutputFields().Size() + 1,
"Output offsets size inconsistent with number of output fields");
}
else
{
output_offsets = Array<int>({0, nodes.Last()->Height()});
}
}
void DAGraph::ValidateNode(GraphNode &node)
{
// Validate that the node's input and output fields are consistent with the graph's field map
auto inputs = node.InputFields();
auto outputs = node.OutputFields();
// Check offsets match width and height of the node
MFEM_ASSERT(node.InputOffsets().Last() == node.Width(),
"Node ID: " << node.ID() << " input offsets do not match node width.");
MFEM_ASSERT(node.OutputOffsets().Last() == node.Height(),
"Node ID: " << node.ID() << " output offsets do not match node height.");
// Check number of input and output fields match the offsets
MFEM_ASSERT(node.InputOffsets().Size() == inputs.Size() + 1,
"Node input offsets size inconsistent with number of input fields");
MFEM_ASSERT(node.OutputOffsets().Size() == outputs.Size() + 1,
"Node output offsets size inconsistent with number of output fields");
// Check that all input and output fields are registered in the graph's field map
for(auto input_field : inputs)
{
MFEM_ASSERT(fid_to_index.Has(input_field->ID()),
"Input field ID " << input_field->ID() << " not found in graph's field map");
}
for(auto output_field : outputs)
{
MFEM_ASSERT(fid_to_index.Has(output_field->ID()),
"Output field ID " << output_field->ID() << " not found in graph's field map");
}
}
void DAGraph::TopologicalSort()
{
Array<int> sorted_indices;
sorted_indices.Reserve(nnodes);
Array<bool> visited(nnodes);
visited = false; // Initialize all nodes as unvisited
// Perform a depth-first search to sort the nodes topologically
std::function<void(int)> DepthFirstSearch = [&](int node_index)
{
if(visited[node_index]) return;
visited[node_index] = true;
auto node = nodes[node_index];
// Visit all nodes that this node depends on
for(auto input_field : node->InputFields())
{
for(int j=0; j < nnodes; j++)
{
auto other_node = nodes[j];
if(other_node == node) continue;
for(auto output_field : other_node->OutputFields())
{
if(input_field->ID() == output_field->ID()) // Compare by unique ID
{
DepthFirstSearch(j);
}
}
}
}
sorted_indices.push_back(node_index);
};
for(int i=0; i < nnodes; i++)
{
DepthFirstSearch(i);
}
nodes.Permute(sorted_indices);
node_owned.Permute(sorted_indices);
// Update the node indices after sorting
for(int i=0; i < nnodes; i++)
{
nodes[i]->SetNodeIndex(i);
}
sorted = true;
}
void DAGraph::ComputeDepth()
{
// Compute depth of ordered nodes
node_depth.SetSize(nnodes);
node_depth = 0;
for(int i=0; i < nnodes; i++)
{
int max_depth = 0;
auto node = nodes[i];
for(auto input_field : node->InputFields())
{
for(int j=0; j < i; j++)
{
auto other_node = nodes[j];
if(other_node == node) continue;
for(auto output_field : other_node->OutputFields())
{
if(input_field->ID() == output_field->ID()) // Compare by unique ID
{
max_depth = std::max(max_depth, node_depth[j] + 1);
}
}
}
}
node_depth[i] = max_depth;
}
}
void DAGraph::CollectFieldMaps()
{
MFEM_ASSERT(sorted, "DAGraph must be topologically sorted before collecting fields");
fid_to_index.clear();
fid_to_field.clear();
int nfields = 0;
for (auto f : InputFields())
{
fid_to_index.Register(f->ID(), nfields++);
fid_to_field.Register(f->ID(), f);
}
for (auto &node : nodes)
{
for (auto f : node->OutputFields())
{
if (!fid_to_index.Has(f->ID()))
{
fid_to_index.Register(f->ID(), nfields++);
}
if (!fid_to_field.Has(f->ID()))
{
fid_to_field.Register(f->ID(), f);
}
}
}
// TODO: Possibly add all intermediate fields from nodes to the graph's FieldCollection
}
void DAGraph::Mult(const Vector &x, Vector &y) const
{
MFEM_ASSERT(width == x.Size(), "Input vector size (" << x.Size()
<< ") must match matrix width (" << width << ")");
MFEM_ASSERT(height == y.Size(), "Output vector size (" << y.Size()
<< ") must match matrix height (" << height << ")");
auto inputs = InputFields();
auto outputs = OutputFields();
BlockVector xb(x.GetData(), input_offsets);
BlockVector yb(y.GetData(), output_offsets);
MultiVector xmv(inputs.Size()), ymv(outputs.Size());
// Set the data pointers of the input and output fields
// of the graph to point to the corresponding blocks of
// the input and output vectors
for(int i=0; i < inputs.Size(); i++)
{
xmv.MakeRef(i, xb.GetBlock(i));
}
for(int i=0; i < outputs.Size(); i++)
{
ymv.MakeRef(i, yb.GetBlock(i));
}
const_cast<DAGraph*>(this)->Mult(xmv, ymv);
}
void DAGraph::Mult(const MultiVector &x, MultiVector &y)
{
auto inputs = InputFields();
auto outputs = OutputFields();
MFEM_ASSERT(inputs.Size() == x.NumBlocks(), "Number of input blocks (" << x.NumBlocks()
<< ") must match number of input fields (" << inputs.Size() << ")");
MFEM_ASSERT(outputs.Size() == y.NumBlocks(), "Number of output blocks (" << y.NumBlocks()
<< ") must match number of output fields (" << outputs.Size() << ")");
for(int i=0; i < inputs.Size(); i++)
{
inputs[i]->SetData(const_cast<Vector*>(&x[i]));
}
for (int i=0; i < outputs.Size(); i++)
{
outputs[i]->SetData(&y[i]);
}
auto index_map = GetFieldIdToIndexMap();
auto fld_map = GetFieldIdToFieldMap();
int nfields = index_map.NumFields();
MultiVector ymv(nfields); // TODO: Should this be a member function?
// Assemble the multivector from the individual fields based on their IDs
// This multivector contains all input, output, and intermediate fields in the graph
for (auto const& [id, idx] : index_map)
{
if (fld_map.Has(id))
{
auto field = fld_map.Get(id);
ymv.MakeRef(idx, *field->Data());
}
else
{
MFEM_ABORT("Field ID " << id << " not found in field map");
}
}
Execute(x, ymv);
for(auto &f : inputs)
{
f->SetData(nullptr);
}
for(auto &f : outputs)
{
f->SetData(nullptr);
}
}
void DAGraph::Execute(const MultiVector &x, MultiVector &y) const
{
MFEM_ASSERT(assembled, "DAGraph must be assembled before calling Execute()");
MFEM_ASSERT(x.NumBlocks() == InputFields().Size(),
"Number of input blocks (" << x.NumBlocks()
<< ") must match number of input fields (" << InputFields().Size() << ")");
auto index_map = GetFieldIdToIndexMap();
MFEM_ASSERT(y.NumBlocks() == index_map.NumFields(),
"Number of output blocks (" << y.NumBlocks()
<< ") must match number of fields (" << index_map.NumFields() << ")");
auto inputs = InputFields();
for(int i=0; i < inputs.Size(); i++)
{
int idx = index_map.Get(inputs[i]->ID());
if(&y[idx] != &x[i]) // copy data, if address is different
{
y[idx] = x[i];
}
}
if(input_type == InputType::VECTOR)
{
x_node.SetSize(MaxWidth());
y_node.SetSize(MaxHeight());
for (auto node : nodes)
{
x_node.SetSize(node->Width());
y_node.SetSize(node->Height());
// Assemble input fields into a single vector for the node
auto node_inputs = node->InputFields();
auto ioffsets = node->InputOffsets();
for (int i=0; i < node_inputs.Size(); i++)
{
auto in_field = node_inputs[i];
int idx = index_map.Get(in_field->ID());
x_node.SetVector(y[idx],ioffsets[i]);
}
node->Mult(x_node, y_node);
// Disassemble output vector back
auto node_outputs = node->OutputFields();
BlockVector ynb(y_node.GetData(), node->OutputOffsets());
for (int i=0; i < node_outputs.Size(); i++)
{
auto out_field = node_outputs[i];
int idx = index_map.Get(out_field->ID());
y[idx] = ynb.GetBlock(i);
}
}
}
else if(input_type == InputType::MULTIVECTOR)
{
for (auto node : nodes)
{
auto node_inputs = node->InputFields();
auto node_outputs = node->OutputFields();
xmv_node.SetNumBlocks(node_inputs.Size());
ymv_node.SetNumBlocks(node_outputs.Size());
for (int i=0; i < node_inputs.Size(); i++)
{
int idx = index_map.Get(node_inputs[i]->ID());
xmv_node.MakeRef(i, y[idx]);
}
for (int i=0; i < node_outputs.Size(); i++)
{
int idx = index_map.Get(node_outputs[i]->ID());
ymv_node.MakeRef(i, y[idx]);
}
node->Mult(xmv_node, ymv_node);
}
}
else if(input_type == InputType::NONE)
{
Vector x_unused, y_unused;
for (auto node : nodes)
{
node->Mult(x_unused, y_unused);
}
}
else
{
MFEM_ABORT("DAGraph::Execute() not implemented for input type: "
<< static_cast<int>(input_type));
}
}
Operator& DAGraph::GetGradient(const Vector &x) const
{
// TODO: Should/could be removed
if(grad_mode == GradMode::FINITE_DIFF)
{
if(!grad)
{
grad = new future::FDJacobian(*this, x, 1e-6);
}
else
{
grad->GetGradient(x); // Update the FDJacobian with new point x
}
return *grad;
}
MFEM_ASSERT(static_cast<int>(grad_mode) < static_cast<int>(GradMode::NONE),
"DAGraph::GetGradient() called with invalid grad_mode: "
<< static_cast<int>(grad_mode));
if(!grad)
{
grad = new GraphGradient(const_cast<DAGraph&>(*this));
}
if(grad_mode == GradMode::ASSEMBLED)
{
return grad->GetGradient(x); // Assemble the Jacobian matrix
}
else // GradMode::MATRIX_FREE
{
dynamic_cast<GraphGradient*>(grad)->Update(x); // Update the GraphGradient with new point x
}
return *grad;
}
GraphGradient::GraphGradient(DAGraph &dag) : Operator(dag.Height(), dag.Width()),
graph(&dag)
{
MFEM_ASSERT(graph->IsAssembled(), "GraphGradient requires an assembled DAGraph.");
MFEM_ASSERT(graph->IsSorted(), "GraphGradient requires a topologically sorted DAGraph.");
auto index_map = graph->GetFieldIdToIndexMap();
auto field_map = graph->GetFieldIdToFieldMap();
MFEM_ASSERT(index_map.NumFields() == field_map.NumFields(),
"Mismatch in number of fields between index_map and field_map");
x_work.DeleteAll(); // Clear any existing pointers
x_work.SetSize(index_map.NumFields());
x_work = nullptr; // Initialize all pointers to nullptr
xlin.SetNumBlocks(index_map.NumFields());
for (auto const& [id, idx] : index_map)
{
MFEM_ASSERT(idx >= 0 && idx < x_work.Size(), "Index out of bounds for field ID: " << id);
MFEM_ASSERT(field_map.Has(id), "Field ID not found in field_map: " << id);
if(x_work[idx] == nullptr)
{
x_work[idx] = new Vector(); // Allocate a new Vector for this field
}
xlin.MakeRef(idx, *x_work[idx]); // Make xlin refer to the allocated Vector
}
}
void GraphGradient::Update(const Vector &x)
{
MFEM_ASSERT(graph != nullptr, "GraphGradient operator requires a non-null DAGraph pointer.");
auto set_exec_mode = [&](DAGraph::ExecutionMode mode)
{
for (auto &node : graph->Nodes())
{
node->SetExecutionMode(mode);
}
};
auto inputs = graph->InputFields();
BlockVector xb(x.GetData(), graph->InputOffsets());
MultiVector xmv(inputs.Size());
for(int i=0; i < inputs.Size(); i++)
{
xmv.MakeRef(i, xb.GetBlock(i));
}
set_exec_mode(DAGraph::ExecutionMode::GRADIENT_MODE);
graph->Execute(xmv, xlin); // Forward pass to populate fields for gradient computations
set_exec_mode(DAGraph::ExecutionMode::DEFAULT_MODE); // Reset execution mode for forward pass
}
void GraphGradient::Mult(const Vector &x, Vector &y) const
{
MFEM_ASSERT(x.Size() == graph->Width(), "Input vector size (" << x.Size()
<< ") must match graph width (" << graph->Width() << ")");
MFEM_ASSERT(y.Size() == graph->Height(), "Output vector size (" << y.Size()
<< ") must match graph height (" << graph->Height() << ")");
auto in_offsets = graph->InputOffsets();
auto out_offsets = graph->OutputOffsets();
auto inputs = graph->InputFields();
auto outputs = graph->OutputFields();
BlockVector xb(x.GetData(), in_offsets);
BlockVector yb(y.GetData(), out_offsets);
MultiVector xmv(inputs.Size()), ymv(outputs.Size());
for(int i=0; i < inputs.Size(); i++)
{
xmv.MakeRef(i, xb.GetBlock(i));
}
for(int i=0; i < outputs.Size(); i++)
{
ymv.MakeRef(i, yb.GetBlock(i));
}
const_cast<GraphGradient*>(this)->Mult(xmv, ymv); // Forward mode: compute JVP, y = J(z) * x
}
void GraphGradient::Mult(const MultiVector &x, MultiVector &y)
{
auto inputs = graph->InputFields();
auto outputs = graph->OutputFields();
MFEM_ASSERT(inputs.Size() == x.NumBlocks(), "Number of input blocks (" << x.NumBlocks()
<< ") must match number of input fields (" << inputs.Size() << ")");
MFEM_ASSERT(outputs.Size() == y.NumBlocks(), "Number of output blocks (" << y.NumBlocks()
<< ") must match number of output fields (" << outputs.Size() << ")");
for(int i=0; i < inputs.Size(); i++)
{
inputs[i]->SetAdjoint(const_cast<Vector*>(&x[i]));
}
for (int i=0; i < outputs.Size(); i++)
{
outputs[i]->SetAdjoint(&y[i]);
}
auto index_map = graph->GetFieldIdToIndexMap();
auto fld_map = graph->GetFieldIdToFieldMap();
int nfields = index_map.NumFields();
MultiVector ymv(nfields); // TODO: Should this be a member function?
// Assemble the multivector from the individual fields based on their IDs
// This multivector contains all input, output, and intermediate fields in the graph
for (auto const& [id, idx] : index_map)
{
if (fld_map.Has(id))
{
auto field = fld_map.Get(id);
ymv.MakeRef(idx, *field->Adjoint());
}
else
{
MFEM_ABORT("Field ID " << id << " not found in field map");
}
}
Forward(x, ymv); // Forward mode: compute JVP, y = J(z) * x
for (auto &f : inputs)
{
f->SetAdjoint(nullptr);
}
for (auto &f : outputs)
{
f->SetAdjoint(nullptr);
}
}
void GraphGradient::MultTranspose(const Vector &x, Vector &y) const
{
MFEM_ASSERT(x.Size() == graph->Height(), "Input vector size (" << x.Size()
<< ") must match graph height (" << graph->Height() << ")");
MFEM_ASSERT(y.Size() == graph->Width(), "Output vector size (" << y.Size()
<< ") must match graph width (" << graph->Width() << ")");
auto in_offsets = graph->InputOffsets();
auto out_offsets = graph->OutputOffsets();
auto inputs = graph->InputFields();
auto outputs = graph->OutputFields();
BlockVector xb(x.GetData(), out_offsets);
BlockVector yb(y.GetData(), in_offsets);
MultiVector xmv(outputs.Size()), ymv(inputs.Size());
for(int i=0; i < inputs.Size(); i++)
{
xmv.MakeRef(i, xb.GetBlock(i));
}
for(int i=0; i < outputs.Size(); i++)
{
ymv.MakeRef(i, yb.GetBlock(i));
}
const_cast<GraphGradient*>(this)->MultTranspose(xmv, ymv); // Reverse mode: compute VJP, y = J(z)^T * x
}
void GraphGradient::MultTranspose(const MultiVector &x, MultiVector &y)
{
auto inputs = graph->InputFields();
auto outputs = graph->OutputFields();
MFEM_ASSERT(outputs.Size() == x.NumBlocks(), "Number of input blocks (" << x.NumBlocks()
<< ") must match number of output fields (" << outputs.Size() << ")");
MFEM_ASSERT(inputs.Size() == y.NumBlocks(), "Number of output blocks (" << y.NumBlocks()
<< ") must match number of input fields (" << inputs.Size() << ")");
for(int i=0; i < outputs.Size(); i++)
{
outputs[i]->SetAdjoint(const_cast<Vector*>(&x[i]));
}
for (int i=0; i < inputs.Size(); i++)
{
inputs[i]->SetAdjoint(&y[i]);
}
auto index_map = graph->GetFieldIdToIndexMap();
auto fld_map = graph->GetFieldIdToFieldMap();
int nfields = index_map.NumFields();
MultiVector ymv(nfields); // TODO: Should this be a member function?
for(auto const& [id, idx] : index_map)
{
if (fld_map.Has(id))
{
auto field = fld_map.Get(id);
ymv.MakeRef(idx, *field->Adjoint());
}
else
{
MFEM_ABORT("Field ID " << id << " not found in field map");
}
}
Reverse(x, ymv); // Reverse mode: compute VJP, y = J(z)^T * x
for (auto &f : outputs)
{
f->SetAdjoint(nullptr);
}
for (auto &f : inputs)
{
f->SetAdjoint(nullptr);
}
}
void GraphGradient::Forward(const MultiVector &x, MultiVector &y) const
{
MFEM_ASSERT(x.NumBlocks() == graph->InputFields().Size(),
"Number of input blocks (" << x.NumBlocks()
<< ") must match number of input fields (" << graph->InputFields().Size() << ")");
auto in_type = graph->GetInputType();
auto index_map = graph->GetFieldIdToIndexMap();
auto field_map = graph->GetFieldIdToFieldMap();
MFEM_ASSERT(y.NumBlocks() == index_map.NumFields(),
"Number of output blocks (" << y.NumBlocks()
<< ") must match number of fields (" << index_map.NumFields() << ")");
auto inputs = graph->InputFields();
for(int i=0; i < inputs.Size(); i++)
{
int idx = index_map.Get(inputs[i]->ID());
if(&y[idx] != &x[i]) // copy data, if address is different
{
y[idx] = x[i];
}
}
if(in_type == InputType::VECTOR)
{
x0.SetSize(graph->MaxWidth());
dx.SetSize(graph->MaxWidth());
dy.SetSize(graph->MaxHeight());
auto nodes = graph->Nodes();
for (auto node : nodes)
{
x0.SetSize(node->Width());
dx.SetSize(node->Width());
dy.SetSize(node->Height());
// Assemble input fields into a single vector for the node
auto node_inputs = node->InputFields();
auto ioffsets = node->InputOffsets();
for(int i=0; i < node_inputs.Size(); i++)
{
auto in_field = node_inputs[i];
MFEM_ASSERT(index_map.Has(in_field->ID()), "Input field ID not found in index_map");
int idx = index_map.Get(in_field->ID());
x0.SetVector(xlin[idx], ioffsets[i]);
dx.SetVector(y[idx], ioffsets[i]);
}
node->GradientMult(x0, dx, dy); // Compute JVP for the node
// Disassemble output vector back
auto node_outputs = node->OutputFields();
BlockVector ynb(dy.GetData(), node->OutputOffsets());
for(int i=0; i < node_outputs.Size(); i++)
{
auto out_field = node_outputs[i];
MFEM_ASSERT(index_map.Has(out_field->ID()), "Output field ID not found in index_map");
int idx = index_map.Get(out_field->ID());
y[idx] = ynb.GetBlock(i);
}
}
}
else if(in_type == InputType::MULTIVECTOR)
{
auto nodes = graph->Nodes();
for (auto node : nodes)
{
auto node_inputs = node->InputFields();
auto node_outputs = node->OutputFields();
x0_mv.SetNumBlocks(node_inputs.Size());
dx_mv.SetNumBlocks(node_inputs.Size());
dy_mv.SetNumBlocks(node_outputs.Size());
for(int i=0; i < node_inputs.Size(); i++)
{
int idx = index_map.Get(node_inputs[i]->ID());
x0_mv.MakeRef(i, xlin[idx]);
dx_mv.MakeRef(i, y[idx]);
}
for(int i=0; i < node_outputs.Size(); i++)
{
int idx = index_map.Get(node_outputs[i]->ID());
dy_mv.MakeRef(i, y[idx]);
}
node->GradientMult(x0_mv, dx_mv, dy_mv); // Compute JVP for the node
}
}
else if(in_type == InputType::NONE)
{
Vector x_unused, dx_unused, dy_unused;
auto nodes = graph->Nodes();
for (auto node : nodes)
{
node->GradientMult(x_unused, dx_unused, dy_unused);
}
}
else
{
MFEM_ABORT("GraphGradient::Forward() not implemented for input type: "
<< static_cast<int>(in_type));
}
}
void GraphGradient::Reverse(const MultiVector &x, MultiVector &y) const
{
MFEM_ASSERT(x.NumBlocks() == graph->OutputFields().Size(),
"Number of input blocks (" << x.NumBlocks()
<< ") must match number of output fields (" << graph->OutputFields().Size() << ")");
auto in_type = graph->GetInputType();
auto index_map = graph->GetFieldIdToIndexMap();
auto field_map = graph->GetFieldIdToFieldMap();
int nnodes = graph->Size();
MFEM_ASSERT(y.NumBlocks() == index_map.NumFields(),
"Number of output blocks (" << y.NumBlocks()
<< ") must match number of fields (" << index_map.NumFields() << ")");
auto outputs = graph->OutputFields();
for(int i=0; i < outputs.Size(); i++)
{
int idx = index_map.Get(outputs[i]->ID());
if(&y[idx] != &x[i]) // copy data, if address is different
{
y[idx] = x[i];
}
}
if(in_type == InputType::VECTOR)
{
x0.SetSize(graph->MaxWidth());
dx.SetSize(graph->MaxHeight());
dy.SetSize(graph->MaxWidth());
for (int i=nnodes-1; i >= 0; i--)
{
auto node = graph->GetNode(i);
x0.SetSize(node->Width());
dx.SetSize(node->Height());
dy.SetSize(node->Width());
auto node_inputs = node->InputFields();
auto ioffsets = node->InputOffsets();
for(int i=0; i < node_inputs.Size(); i++)
{
auto in_field = node_inputs[i];
MFEM_ASSERT(index_map.Has(in_field->ID()), "Input field ID not found in index_map");
int idx = index_map.Get(in_field->ID());
x0.SetVector(xlin[idx], ioffsets[i]);
}
auto node_outputs = node->OutputFields();
auto ooffsets = node->OutputOffsets();
for(int i=0; i < node_outputs.Size(); i++)
{
auto out_field = node_outputs[i];
MFEM_ASSERT(index_map.Has(out_field->ID()), "Output field ID not found in index_map");
int idx = index_map.Get(out_field->ID());
dx.SetVector(y[idx], ooffsets[i]);
}
node->GradientMultTranspose(x0, dx, dy); // Compute JVP for the node
BlockVector dynb(dy.GetData(), node->InputOffsets());
for(int i=0; i < node_inputs.Size(); i++)
{
int idx = index_map.Get(node_inputs[i]->ID());
y[idx] = dynb.GetBlock(i);
}
}
}
else if(in_type == InputType::MULTIVECTOR)
{
for (int i=nnodes-1; i >= 0; i--)
{
auto node = graph->GetNode(i);
auto node_inputs = node->InputFields();
auto node_outputs = node->OutputFields();
x0_mv.SetNumBlocks(node_inputs.Size());
dx_mv.SetNumBlocks(node_outputs.Size());
dy_mv.SetNumBlocks(node_inputs.Size());
for(int i=0; i < node_inputs.Size(); i++)
{
int idx = index_map.Get(node_inputs[i]->ID());
x0_mv.MakeRef(i, xlin[idx]);
dy_mv.MakeRef(i, y[idx]);
}
for(int i=0; i < node_outputs.Size(); i++)
{
int idx = index_map.Get(node_outputs[i]->ID());
dx_mv.MakeRef(i, y[idx]);
}
node->GradientMultTranspose(x0_mv, dx_mv, dy_mv); // Compute JVP for the node
}
}
else if(in_type == InputType::NONE)
{
Vector x_unused, dx_unused, dy_unused;
for (int i=nnodes-1; i >= 0; i--)
{
auto node = graph->GetNode(i);
node->GradientMultTranspose(x_unused, dx_unused, dy_unused); // Compute VJP for the node
}
}
else
{
MFEM_ABORT("GraphGradient::Reverse() not implemented for input type: "
<< static_cast<int>(in_type));
}
}
Operator& GraphGradient::GetGradient(const Vector &x) const
{
// Used to build Jacobian matrix
MFEM_ABORT("GraphGradient::GetGradient() not implemented");
}
} // namespace mfem
-838
View File
@@ -1,838 +0,0 @@
// 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.
#ifndef MFEM_MULTIAPP_HPP
#define MFEM_MULTIAPP_HPP
#include "mfem.hpp"
namespace mfem
{
/// Forward declarations needed below
class Field;
class FieldCollection;
class GraphNode;
class DAGraph;
class GraphGradient;
/// @brief Base class for storing data (Vector) and distinguishing
/// fields variables
class Field
{
public:
enum Type ///< Not used for now, but could be used to distinguish between input/output fields
{
INPUT , ///< Input field
OUTPUT, ///< Output field
DEFAULT ///< Any field
};
friend class GraphNode;
private:
Type type = Type::DEFAULT;
inline static int next_id = 0;
protected:
Vector *data = nullptr;
Vector *adjoint = nullptr; // For storing derivative info
int id = -1; // initialized to invalid id
std::string name; // Optional name for the field
Operator *oper = nullptr; // Operator that outputs this field
int GetValidID(int id_, int lb=0, int ub = std::numeric_limits<int>::max())
{
return (id_ >= lb && id_ <= ub) ? id_ : next_id++;
}
public:
///@brief Constructor for a Field of type Type with optional ID
Field(Vector *field, Vector *adjoint, Type type, int id_ = -1) :
type(type), data(field), adjoint(adjoint), id(GetValidID(id_)),
name("Field_" + std::to_string(id)) { }
///@brief Constructor for a Field of Default type with optional ID
Field(Vector *field, Vector *adjoint, int id_ = -1) :
Field(field, adjoint, Type::DEFAULT, id_) { }
///@brief Constructor for an input field
Field(Vector *field, int id_ = -1) :
Field(field, nullptr, Type::DEFAULT, id_) { }
///@brief Constructor for a Field of type Type
Field(Vector *field, Type type, int id_ = -1) :
Field(field, nullptr, type, id_) { }
///@brief Get the stored internally stored data pointer
Vector* Data() const { return data; }
Vector* Adjoint() const { return adjoint; }
Operator* GetOperator() const { return oper; }
///@brief Set the internally stored data pointer
virtual void SetData(Vector *field) { data = field; }
virtual void SetAdjoint(Vector *adj) { adjoint = adj; }
virtual void SetOperator(Operator *op) { oper = op; }
virtual void GetData(Vector &field) const { field = *data; }
virtual void GetAdjoint(Vector &adj) const { adj = *adjoint; }
std::string Name() const { return name; }
void SetName(const std::string &n) { name = n; }
int ID() const { return id; }
void SetID(int i)
{
MFEM_ASSERT(i >= 0, "ID must be non-negative.");
id = i;
}
bool IsInput() const {return (type == Type::INPUT);}
bool IsOutput() const {return (type == Type::OUTPUT);}
bool IsDefault() const {return (type == Type::DEFAULT);}
virtual ~Field() = default;
protected:
///@brief Set the type of the field (prevents changing type of input/output fields)
void SetType(Type t)
{
type = t;
}
};
/// @brief A collection of Fields, each identified by a name
class FieldCollection
{
public:
using FieldMap = GenericFieldMap<std::string, Field*>;
using IndexMap = GenericFieldMap<std::string, int>;
private:
std::string name; /// Name of the collection
Operator *oper = nullptr; /// Operator associated with this collection (not owned)
FieldMap fields; /// Map from field name to Field pointer
IndexMap index_map; /// Map from field name to index in input/output vectors
Array<Field*> input_fields; // Input fields for this node
Array<Field*> output_fields; // Output fields for this node
public:
FieldCollection() = default;
/// @brief Constructor with collection name and optional associated operator
FieldCollection(std::string collection_name, Operator *op = nullptr):
name(collection_name), oper(op) {}
/// @brief Constructor with associated operator and default collection name
FieldCollection(Operator *op) : name("FieldCollection"), oper(op) {}
/// @brief Get the number of fields in the collection
int Size() const { return fields.NumFields(); }
/// @brief Set the name of the collection
void SetName(const std::string &collection_name) { name = collection_name;}
/// @brief Get the name of the collection
std::string Name() const { return name; }
/// @brief Set the operator associated with this collection
void SetOperator(Operator *op){ oper = op; }
/// @brief Get the operator associated with this collection
const Operator* GetOperator() const { return oper; }
/// @brief Get the field associated with the given name, or nullptr if not found
Field* GetField(const std::string &field_name) const
{
return fields.Get(field_name);
}
/// @brief Add a field to the collection with a given name and ownership flag
void AddField(const std::string &field_name, Field *field, bool own = false)
{
if(fields.Has(field_name))
{
MFEM_WARNING("FieldCollection::AddField: Field with name "
<< field_name << " already exists. Replacing existing field.");
}
fields.Register(field_name, field, own);
}
void AddInput(const std::string &field_name,
Field *field, bool own = false)
{
bool has_field = fields.Has(field_name);
bool has_index = index_map.Has(field_name);
if(has_field && has_index)
{
int i = index_map.Get(field_name);
input_fields[i] = field;
}
else
{
input_fields.push_back(field);
index_map.Register(field_name, input_fields.Size() - 1);
}
AddField(field_name, field, own);
}
void AddOutput(const std::string &field_name,
Field *field, bool own = false)
{
bool has_field = fields.Has(field_name);
bool has_index = index_map.Has(field_name);
if(has_field && has_index)
{
int i = index_map.Get(field_name);
output_fields[i] = field;
}
else
{
output_fields.push_back(field);
index_map.Register(field_name, output_fields.Size() - 1);
}
AddField(field_name, field, own);
if(field->GetOperator() == nullptr)
{
field->SetOperator(oper);
}
}
Array<Field*>& InputFields() { return input_fields; }
Array<Field*>& OutputFields() { return output_fields; }
Field* InputField(int i) const { return input_fields[i]; }
Field *InputField(const std::string &field_name) const
{
bool has_index = index_map.Has(field_name);
if(!has_index)
{
MFEM_WARNING("FieldCollection::InputField: Field with name "
<< field_name << " does not exist in the collection.");
return nullptr;
}
int index = index_map.Get(field_name);
MFEM_VERIFY(index >= 0 && index < input_fields.Size(),
"FieldCollection::InputField: Invalid index for field name: "
<< field_name << ".");
return input_fields[index];
}
Field* OutputField(int i) const { return output_fields[i]; }
Field *OutputField(const std::string &field_name) const
{
bool has_index = index_map.Has(field_name);
if(!has_index)
{
MFEM_WARNING("FieldCollection::OutputField: Field with name "
<< field_name << " does not exist in the collection.");
return nullptr;
}
int index = index_map.Get(field_name);
MFEM_VERIFY(index >= 0 && index < output_fields.Size(),
"FieldCollection::OutputField: Invalid index for field name: "
<< field_name << ".");
return output_fields[index];
}
FieldMap &Fields() { return fields; }
FieldMap Fields() const { return fields; }
virtual void Save (std::ostream &out) const
{
out << "\"Fields\":\n";
out << "{\n";
for (auto f = fields.begin(); f != fields.end(); ++f)
{
std::string f_name = f->first;
Field *f_obj = f->second;
// out << " " << f_name << ": ID " << f_obj->ID() << ",\n";
// out << f_obj->ID() << ": " << f_name << ",\n";
out << '\"' << f_obj->ID() << "\": \"" << f_name << "\"";
if(f != std::prev(fields.end())) out << ",";
out << "\n";
}
out << "},\n";
out << "\"Inputs\":\n";
out << "{\n";
for (int i = 0; i < input_fields.Size(); ++i)
{
Field *f_obj = input_fields[i];
out << '\"' << f_obj->ID() << "\": \"" << f_obj->Name() << "\"";
if(i != input_fields.Size() - 1) out << ",";
out << "\n";
}
out << "},\n";
out << "\"Outputs\":\n";
out << "{\n";
for (int i = 0; i < output_fields.Size(); ++i)
{
Field *f_obj = output_fields[i];
out << '\"' << f_obj->ID() << "\": \"" << f_obj->Name() << "\"";
if(i != output_fields.Size() - 1) out << ",";
out << "\n";
}
out << "}\n";
}
Field* HasField(const Field &field) const
{
for (auto f = fields.begin(); f != fields.end(); ++f)
{
if(f->second == &field)
{
return f->second;
}
}
return nullptr;
}
Field* HasField(const std::string &field_name) const
{
return fields.Get(field_name);
}
Field* HasField(const int id) const
{
for (auto f = fields.begin(); f != fields.end(); ++f)
{
if(f->second->ID() == id)
{
return f->second;
}
}
return nullptr;
}
~FieldCollection(){}
};
class GraphNode : public Operator
{
public:
enum ExecutionMode
{
GRADIENT_MODE, ///< Node is being executed as part of a gradient evaluation
DEFAULT_MODE ///< Node is being executed as default, operator evaluation
};
private:
inline static int next_id = 0;
protected:
int id = -1;
int node_index = -1;
mutable ExecutionMode exec_mode = DEFAULT_MODE;
std::string name;
mutable FieldCollection field_collection; ///< Collection of fields associated with this node
// Offsets to be used for operation on BlockVector
Array<int> input_offsets; ///< Offsets for input fields
Array<int> output_offsets; ///< Offsets for output fields
int GetValidID(int id_, int lb=0, int ub = std::numeric_limits<int>::max())
{
return (id_ >= lb && id_ <= ub) ? id_ : next_id++;
}
public:
GraphNode(int h, int w) : Operator(h,w), id(GetValidID(-1)),
name("Node_" + std::to_string(id)),
field_collection(this) { }
GraphNode(int s = 0) : GraphNode(s, s) { }
void SetNodeIndex(int index){ node_index = index; }
int GetNodeIndex() const { return node_index; }
void SetExecutionMode(ExecutionMode mode) { exec_mode = mode; }
ExecutionMode GetExecutionMode() const { return exec_mode; }
void SetName(const std::string &name_) { name = name_; }
std::string Name() const { return name; }
void SetID(int id_) { id = id_; }
int ID() const { return id; }
FieldCollection::FieldMap& Fields() { return field_collection.Fields(); }
Field* Fields(const std::string &f) { return field_collection.GetField(f); }
FieldCollection::FieldMap Fields() const { return field_collection.Fields(); }
Field* Fields(const std::string &f) const { return field_collection.GetField(f); }
Array<Field*>& InputFields() const { return field_collection.InputFields(); }
Array<Field*>& OutputFields() const { return field_collection.OutputFields(); }
Field* InputField(int i) const { return field_collection.InputField(i); }
Field* OutputField(int i) const { return field_collection.OutputField(i); }
virtual void AddInput(const std::string &field_name,
Field *field, bool own = false)
{ field_collection.AddInput(field_name, field, own); }
virtual void AddInput(Field *field, bool own = false)
{ AddInput(field->Name(), field, own); }
template<bool OwnInputs = false,
typename... Args,
bool AreFields = std::conjunction<std::is_base_of<Field, std::remove_pointer_t<Args>> ...>::value,
typename std::enable_if<AreFields, bool>::type = true >
void AddInputs(Args... args)
{
((AddInput(std::forward<Args>(args), OwnInputs)), ...);
}
virtual void AddOutput(const std::string &field_name,
Field *field, bool own = false)
{ field_collection.AddOutput(field_name, field, own); }
virtual void AddOutput(Field *field, bool own = false)
{ AddOutput(field->Name(), field, own); }
template<bool OwnOutputs = false,
typename... Args,
bool AreFields = std::conjunction<std::is_base_of<Field, std::remove_pointer_t<Args>> ...>::value,
typename std::enable_if<AreFields, bool>::type = true >
void AddOutputs(Args... args)
{
((AddOutput(std::forward<Args>(args), OwnOutputs)), ...);
}
virtual void Save (std::ostream &out) const
{
out << "\"Node-" << id << "\" : " << std::endl;
out << "{\n";
out << "\"Name\": \"" << name << "\",\n";
field_collection.Save(out);
out << "}";
}
virtual void Mult(const Vector &x, Vector &y) const override
{
MFEM_ABORT("GraphNode::Mult() not implemented");
}
virtual void Mult(const MultiVector &x, MultiVector &y) override
{
MFEM_ABORT("GraphNode::Mult(MultiVector) not implemented");
}
using Operator::GetGradient;
// TODO: Possibly remove this and only support MultiVector version of GradientMult
virtual void GradientMult(const Vector &x, const Vector &dx, Vector &dy) const
{
MFEM_ABORT("GraphNode::GradientMult() not implemented");
GetGradient(x).Mult(dx, dy);
}
virtual void GradientMult(const MultiVector &x, const MultiVector &dx, MultiVector &dy) const
{
MFEM_ABORT("GraphNode::GradientMult() not implemented");
GetGradient(x).Mult(dx, dy);
}
// TODO: Possibly remove this and only support MultiVector version of GradientMultTranspose
virtual void GradientMultTranspose(const Vector &x, const Vector &dx, Vector &dy) const
{
MFEM_ABORT("GraphNode::GradientMultTranspose() not implemented");
GetGradient(x).MultTranspose(dx, dy);
}
virtual void GradientMultTranspose(const MultiVector &x, const MultiVector &dx, MultiVector &dy) const
{
MFEM_ABORT("GraphNode::GradientMultTranspose() not implemented");
// GetGradient(x).MultTranspose(dx, dy); // Not yet implemented
}
/// @brief Return the input offsets for block starts.
Array<int>& InputOffsets() { return input_offsets; }
/// @brief Read only access to the input offsets for block starts.
const Array<int>& InputOffsets() const { return input_offsets; }
void SetInputOffsets(const Array<int> &offsets) { input_offsets = offsets; }
/// @brief Return the output offsets for block starts.
Array<int>& OutputOffsets() { return output_offsets; }
/// @brief Read only access to the output offsets for block starts.
const Array<int>& OutputOffsets() const { return output_offsets; }
void SetOutputOffsets(const Array<int> &offsets) { output_offsets = offsets; }
virtual ~GraphNode() = default;
};
/**
@brief An abstract, type-erased class to define the interface for
operators, not inherited from @a GraphNode. It performs SFINAE
checks for stored operator's member functions and override the Mult
to call the stored object's functions.
*/
template <typename OpType>
class AbstractOperator : public GraphNode
{
protected:
/// Define a template class 'check' to test for the existence of member functions
template <typename C>
class CheckMember{
private:
/// @brief A type trait to check if the erased class has the function Mult
/// with the needed signatures.
template<class T>
using Mult = decltype(std::declval<T&>().Mult(std::declval<const Vector&>(),
std::declval<Vector&>()));
template<class T>
using MultPtr = decltype(std::declval<T&>().Mult(std::declval<const int>(),
std::declval<const real_t*>(),
std::declval<const int>(),
std::declval<real_t*>()));
// ---------------------------------------------------------------------
template <typename T, template<typename> typename Func, typename R>
static constexpr auto Check(T*) -> typename std::is_same< Func<T>, R>::type;
template <typename, template<typename> typename, typename >
static constexpr std::false_type Check(...);
// --- Check for the existence of the member functions
typedef decltype(Check<C,Mult,void>(0)) Has_Mult;
typedef decltype(Check<C,MultPtr,void>(0)) Has_MultPtr;
public:
static constexpr bool HasMult = Has_Mult::value;
static constexpr bool HasMultPtr = Has_MultPtr::value;
};
OpType *op; ///< Pointer to the operator
public:
constexpr bool HasExecute(){return CheckMember<OpType>::HasStep;}
constexpr bool HasMult(){return CheckMember<OpType>::HasMult;}
/// @brief Constructor for the type-erased AbstractOperator class
AbstractOperator(OpType *op_, int h, int w) : GraphNode(h,w), op(op_)
{ }
/// @brief Constructor for the type-erased AbstractOperator class.
AbstractOperator(OpType *op_, int s = 0) : AbstractOperator(op_,s,s) {}
/**
@brief Perform Mult operation with the stored operator, if it exists.
*/
void Mult(const Vector &x, Vector &y) const override
{
if constexpr (CheckMember<OpType>::HasMult)
{
op->Mult(x,y);
}
else if constexpr (CheckMember<OpType>::HasMultPtr)
{
op->Mult(x.Size(), x.GetData(), y.Size(), y.GetData());
}
else
{
MFEM_ABORT("The AbstractOperator does not have the function, "
"Mult(const Vector&, Vector&) or "
"Mult(int, double*, int, double*).");
}
}
};
/**
@brief A class to store and coupled multiple operators together.
*/
class DAGraph : public GraphNode
{
public:
using IntToIntMap = GenericFieldMap<int, int>;
using IntToFieldMap = GenericFieldMap<int, Field*>;
enum class GradMode
{
FINITE_DIFF = 0, ///< Finite difference Jacobian
MATRIX_FREE = 1, ///< Matrix-free Jacobian
ASSEMBLED = 2, ///< Assembled Jacobian
NONE = 3 ///< Not implemented
};
enum InputType
{
VECTOR, ///< Asemble the input blockvector from individual fields
MULTIVECTOR, ///< Asemble the multivector from individual fields
NONE ///< No input
};
protected:
Array<GraphNode*> nodes; ///< Vector of individual operators
Array<bool> node_owned; ///< Whether the operators are owned
Array<int> node_depth; ///< Depth of each operator in the graph
int max_width = 0; ///< Largest operator width
int max_height = 0; ///< Largest operator height
int nnodes = 0; ///< The number of nodes
bool sorted = false; ///< True if the nodes are topologically sorted
bool assembled = false; ///< True if the graph is assembled
GradMode grad_mode = GradMode::MATRIX_FREE; ///< Gradient mode for the graph
mutable Operator *grad = nullptr; ///< Gradient operator
InputType input_type = InputType::MULTIVECTOR; ///< Input type for the graph
mutable Vector x_node, y_node; ///< Temporary vectors for evaluating nodes
mutable MultiVector xmv_node, ymv_node; ///< Temporary multivectors for evaluating nodes
IntToFieldMap fid_to_field; ///< Map from Field ID to Field pointer
IntToIntMap fid_to_index; ///< Map from ID to index in an array; needed since ordering is not unique
friend class GraphGradient;
public:
/**
@brief Construct a new CoupledOperator object.
@param nop Total number of operators to couple
*/
DAGraph(const int nop) : GraphNode()
{
nodes.Reserve(nop);
node_owned.Reserve(nop);
}
/**
@brief Construct a new CoupledOperator object for an
abstract non/mfem operator.
*/
template <class OpType>
DAGraph(const OpType &op) : DAGraph(1)
{
AddOperator(op);
}
/**
@brief Add an operator to the list of coupled operator and
return pointer to it. Not owned unless it's not derived from GraphNode.
*/
template <class OpType>
GraphNode* AddOperator(OpType *op_, int h, int w)
{
// Add operator to list of operators
if constexpr(std::is_base_of<GraphNode, OpType>::value)
{
nodes.push_back(op_);
node_owned.Append(false);
}
else
{
nodes.push_back(new AbstractOperator<OpType>(op_,h,w));
node_owned.Append(true);
}
nnodes++;
// Update size of the coupled operator and the block offsets
GraphNode* op = nodes.Last();
op->SetNodeIndex(nnodes-1); // Set the index of the operator
int ht = op->Height();
int wt = op->Width();
max_width = std::max(max_width, wt);
max_height = std::max(max_height, ht);
sorted = false;
return op;
}
/// @brief Add an operator to the list of coupled operator and return pointer to it.
template <class OpType>
GraphNode* AddOperator(OpType *op_, int s = 0) { return AddOperator(op_,s,s);}
/// @brief Get the number of coupled operators
int Size(){return nnodes;}
/// @brief Get the size of the largest operator
int MaxWidth() const {return max_width;}
int MaxHeight() const {return max_height;}
IntToIntMap &GetFieldIdToIndexMap() { return fid_to_index; }
IntToIntMap GetFieldIdToIndexMap() const { return fid_to_index; }
IntToFieldMap &GetFieldIdToFieldMap() { return fid_to_field; }
IntToFieldMap GetFieldIdToFieldMap() const { return fid_to_field; }
/// @brief Get the operator at index @a i
GraphNode* GetNode(const int i)
{
MFEM_ASSERT(i >= 0 && i < nnodes,
"index [" << i << "] is out of range [0," << nnodes << ")");
return nodes[i];
}
Array<GraphNode*>& Nodes() { return nodes; }
/// @brief Specify whether the operator at index @a i is owned.
void OwnNode(const int i, bool own = true)
{
MFEM_ASSERT(i >= 0 && i < nnodes,
"index [" << i << "] is out of range [0," << nnodes << ")");
node_owned[i] = own;
}
void Assemble();
bool IsAssembled() const { return assembled; }
void TopologicalSort();
bool IsSorted() const { return sorted; }
void ComputeDepth();
void ValidateOffsets();
void ValidateNode(GraphNode &node);
void CollectFieldMaps();
using GraphNode::AddInput;
void AddInput(Field *field, int sz, bool own = false)
{
if(input_offsets.Size() == 0)
{ // First entry
input_offsets.Append(0);
}
input_offsets.Append(input_offsets.Last() + sz);
AddInput(field, own);
}
using GraphNode::AddOutput;
void AddOutput(Field *field, int sz, bool own = false)
{
if(output_offsets.Size() == 0)
{ // First entry
output_offsets.Append(0);
}
output_offsets.Append(output_offsets.Last() + sz);
AddOutput(field, own);
}
/// @brief Set the gradient mode for the coupled operator
void SetGradientMode(GradMode mode)
{
if(mode != grad_mode)
{
if(grad) { delete grad; grad = nullptr; }
grad_mode = mode;
}
}
void SetInputType(InputType type) { input_type = type; }
InputType GetInputType() const { return input_type; }
/**
@brief Apply the operator to the vector @a x
and return the result in @a y.
*/
virtual void Mult(const Vector &x, Vector &y) const override;
virtual void Mult(const MultiVector &x, MultiVector &y) override;
virtual void Execute(const MultiVector &x, MultiVector &y) const;
virtual void Save (std::ostream &out) const
{
out << "\"DAGraph\":\n";
out << "{\n";
// out << "\"nodes\" : " << nnodes << ",\n";
out << "\"Nodes\":\n";
out << "{\n";
for (int i = 0; i < nodes.Size(); i++)
{
nodes[i]->Save(out);
if(i != nodes.Size()-1) out << ",";
out << "\n";
}
out << "},\n"; // End of Nodes
field_collection.Save(out);
out << "}\n";
}
Operator& GetGradient(const Vector &x) const override;
/// @brief Destroy the Coupled Application object
~DAGraph();
};
class GraphGradient : public Operator
{
public:
using InputType = DAGraph::InputType;
protected:
mutable DAGraph *graph = nullptr; ///< Pointer to the DAGraph for which this is the gradient operator
Array<Vector*> x_work; ///< Array to store linearization point (intermediate fields)
mutable MultiVector xlin;
mutable Vector x0, dx, dy;
mutable MultiVector x0_mv, dx_mv, dy_mv;
public:
GraphGradient(DAGraph &dag);
void Update(const Vector &x);
void Mult(const Vector &x, Vector &y) const override;
void Mult(const MultiVector &x, MultiVector &y) override;
void MultTranspose(const Vector &x, Vector &y) const override;
void MultTranspose(const MultiVector &x, MultiVector &y);
Operator &GetGradient(const Vector &x) const override;
void Forward(const MultiVector &x, MultiVector &y) const;
void Reverse(const MultiVector &x, MultiVector &y) const;
~GraphGradient()
{
for (auto &v : x_work)
{
if(v) { delete v; v = nullptr; }
}
x_work.DeleteAll();
}
};
} //mfem namespace
#endif
+1 -1
View File
@@ -83,7 +83,7 @@ real_t IntegrateBC(const ParGridFunction &x, const Array<int> &bdr,
/// where A is
/// A = div ( Theta(x) grad + Id ) u(x)
/// and alpha is given as
/// alpha = (2 nu + dim) / 4.
/// alpha = (2 nu + dim) / 2.
/// Theta (anisotropy tensor) and nu (smoothness) can be specified in the
/// constructor. Traditionally, the SPDE method requires the specification of
/// a white noise right hands side. SPDESolver accepts arbitrary right hand
+8 -337
View File
@@ -12,8 +12,6 @@
#include "unit_tests.hpp"
#include "mfem.hpp"
#include <random>
using namespace mfem;
#ifdef MFEM_USE_GSLIB
namespace gslib_test
@@ -38,165 +36,6 @@ void F_exact(const Vector &p, Vector &F)
enum class Space { H1, L2 };
enum class SurfaceMeshType { Segment2D, Segment3D, Quad3D, Tri3D };
const char *SurfaceMeshName(const SurfaceMeshType type)
{
switch (type)
{
case SurfaceMeshType::Segment2D: return "segment-2d";
case SurfaceMeshType::Segment3D: return "segment-3d";
case SurfaceMeshType::Quad3D: return "quad-3d";
case SurfaceMeshType::Tri3D: return "tri-3d";
}
return "unknown";
}
int SurfaceSpaceDim(const SurfaceMeshType type)
{
switch (type)
{
case SurfaceMeshType::Segment2D: return 2;
case SurfaceMeshType::Segment3D: return 3;
case SurfaceMeshType::Quad3D: return 3;
case SurfaceMeshType::Tri3D: return 3;
}
return -1;
}
Mesh MakeSurfaceMesh(const SurfaceMeshType type, const int ne)
{
switch (type)
{
case SurfaceMeshType::Segment2D:
return Mesh::MakeCartesian1D(ne);
case SurfaceMeshType::Segment3D:
return Mesh::MakeCartesian1D(ne);
case SurfaceMeshType::Quad3D:
return Mesh::MakeCartesian2D(ne, ne, Element::QUADRILATERAL);
case SurfaceMeshType::Tri3D:
return Mesh::MakeCartesian2D(ne, ne, Element::TRIANGLE);
}
MFEM_ABORT("Unknown surface mesh type.");
return Mesh();
}
void GetSurfaceInteriorPoints(Mesh &mesh, const int npt_per_el,
const int ordering, Vector &xyz,
const int p0 = 0)
{
MFEM_VERIFY(mesh.GetNodes() != nullptr, "Mesh nodes are required.");
const int sdim = mesh.SpaceDimension();
const int npt = xyz.Size()/sdim;
MFEM_VERIFY((p0 + mesh.GetNE()*npt_per_el)*sdim <= xyz.Size(),
"Output vector is too small.");
Vector point(sdim);
std::mt19937 gen(123);
std::uniform_real_distribution<double> uni(0.01, 0.99);
int p = p0;
for (int e = 0; e < mesh.GetNE(); e++)
{
ElementTransformation *T = mesh.GetElementTransformation(e);
const Geometry::Type geom = mesh.GetElementBaseGeometry(e);
for (int j = 0; j < npt_per_el; j++)
{
IntegrationPoint ip;
real_t xv = uni(gen);
if (geom == Geometry::SEGMENT)
{
ip.x = xv;
}
else if (geom == Geometry::SQUARE)
{
ip.Set2(xv, uni(gen));
}
else
{
MFEM_VERIFY(geom == Geometry::TRIANGLE,
"Unsupported surface element geometry.");
ip.Set2(xv, uni(gen)*(1.0 - xv));
}
T->Transform(ip, point);
for (int d = 0; d < sdim; d++)
{
const int idx = (ordering == Ordering::byNODES) ?
d*npt + p :
p*sdim + d;
xyz(idx) = point(d);
}
p++;
}
}
}
void GetSurfaceBoundaryPoints(Mesh &mesh, const int npt_per_el,
const int ordering, Vector &xyz,
const int p0 = 0)
{
MFEM_VERIFY(mesh.GetNodes() != nullptr, "Mesh nodes are required.");
const int sdim = mesh.SpaceDimension();
const int npt = xyz.Size()/sdim;
MFEM_VERIFY((p0 + mesh.GetNE()*npt_per_el)*sdim <= xyz.Size(),
"Output vector is too small.");
Vector point(sdim);
std::mt19937 gen(246);
std::uniform_real_distribution<double> uni(0.01, 0.99);
int p = p0;
for (int e = 0; e < mesh.GetNE(); e++)
{
ElementTransformation *T = mesh.GetElementTransformation(e);
const Geometry::Type geom = mesh.GetElementBaseGeometry(e);
for (int j = 0; j < npt_per_el; j++)
{
IntegrationPoint ip;
if (geom == Geometry::SEGMENT)
{
MFEM_VERIFY(npt_per_el == 2,
"Segment boundary sampling requires npt_per_el = 2.");
ip.x = (j == 0) ? 0.0 : 1.0;
}
else
{
const double t = uni(gen);
if (geom == Geometry::SQUARE)
{
switch (j % 4)
{
case 0: ip.Set2(t, 0.0); break;
case 1: ip.Set2(1.0, t); break;
case 2: ip.Set2(t, 1.0); break;
case 3: ip.Set2(0.0, t); break;
}
}
else
{
MFEM_VERIFY(geom == Geometry::TRIANGLE,
"Unsupported surface element geometry.");
switch (j % 3)
{
case 0: ip.Set2(t, 0.0); break;
case 1: ip.Set2(t, 1.0 - t); break;
case 2: ip.Set2(0.0, t); break;
}
}
}
T->Transform(ip, point);
for (int d = 0; d < sdim; d++)
{
const int idx = (ordering == Ordering::byNODES) ?
d*npt + p :
p*sdim + d;
xyz(idx) = point(d);
}
p++;
}
}
}
TEST_CASE("GSLIBInterpolate", "[GSLIBInterpolate][GSLIB]")
{
auto space = GENERATE(Space::H1, Space::L2);
@@ -351,92 +190,6 @@ TEST_CASE("GSLIBInterpolate", "[GSLIBInterpolate][GSLIB]")
delete c_fec;
}
TEST_CASE("GSLIBSurfInterpolate", "[GSLIBSurfInterpolate][GSLIB]")
{
auto surface_mesh_type = GENERATE(SurfaceMeshType::Segment2D,
SurfaceMeshType::Segment3D,
SurfaceMeshType::Quad3D,
SurfaceMeshType::Tri3D);
func_order = GENERATE(1, 2);
int mesh_order = GENERATE(1, 2);
int mesh_node_ordering = GENERATE(0, 1);
int point_ordering = GENERATE(0, 1);
int ncomp = GENERATE(1, 2);
int gf_ordering = GENERATE(0, 1);
int func_out_ordering = GENERATE(0, 1);
const char *mesh_name = SurfaceMeshName(surface_mesh_type);
CAPTURE(mesh_name, func_order, mesh_order, mesh_node_ordering,
point_ordering, ncomp, gf_ordering, func_out_ordering);
if (ncomp == 1 && gf_ordering == 1)
{
return;
}
Mesh mesh = MakeSurfaceMesh(surface_mesh_type, 4);
const int sdim = SurfaceSpaceDim(surface_mesh_type);
mesh.SetCurvature(mesh_order, false, sdim, mesh_node_ordering);
H1_FECollection c_fec(func_order, mesh.Dimension());
FiniteElementSpace c_fespace(&mesh, &c_fec, ncomp, gf_ordering);
GridFunction field_vals(&c_fespace);
VectorFunctionCoefficient F(ncomp, F_exact);
field_vals.ProjectCoefficient(F);
const int npt_per_el = 8;
const int pts_cnt = mesh.GetNE()*npt_per_el;
Vector vxyz(pts_cnt*sdim);
GetSurfaceInteriorPoints(mesh, npt_per_el, point_ordering, vxyz);
Vector interp_vals(pts_cnt*ncomp);
FindPointsGSLIB finder;
finder.SetupSurf(mesh);
finder.SetL2AvgType(FindPointsGSLIB::NONE);
finder.Interpolate(vxyz, field_vals, interp_vals, point_ordering,
func_out_ordering);
Array<unsigned int> code_out = finder.GetCode();
Vector dist_p_out = finder.GetDist();
int not_found = 0;
double err = 0.0, max_err = 0.0, max_dist = 0.0;
Vector pos(sdim);
Vector exact_val(ncomp);
for (int i = 0; i < pts_cnt; i++)
{
max_dist = std::max(max_dist, dist_p_out(i));
for (int d = 0; d < sdim; d++)
{
const int idx = (point_ordering == Ordering::byNODES) ?
d*pts_cnt + i :
i*sdim + d;
pos(d) = vxyz(idx);
}
F_exact(pos, exact_val);
for (int j = 0; j < ncomp; j++)
{
if (code_out[i] < 2)
{
err = func_out_ordering == Ordering::byNODES ?
fabs(exact_val(j) - interp_vals[i + j*pts_cnt]) :
fabs(exact_val(j) - interp_vals[i*ncomp + j]);
max_err = std::max(max_err, err);
}
else if (j == 0)
{
not_found++;
}
}
}
REQUIRE(max_err < 1e-12);
REQUIRE(max_dist < 1e-10);
REQUIRE(not_found == 0);
}
// Generates meshes with different element types, followed by points at
// element faces and interior, and finally checks to see if these points are
// correctly detected at element boundary or not.
@@ -504,8 +257,9 @@ TEST_CASE("GSLIBFindAtElementBoundary",
int nptface = xyz.Size()/dim;
// Generate points inside each element
L2_FECollection l2_fec(l2_order, dim);
FiniteElementSpace l2_fespace(&mesh, &l2_fec, 1);
FiniteElementCollection *l2_fec = new L2_FECollection(l2_order, dim);
FiniteElementSpace l2_fespace =
FiniteElementSpace(&mesh, l2_fec, 1);
DenseMatrix vals;
DenseMatrix tr;
for (int e = 0; e < mesh.GetNE(); e++)
@@ -541,92 +295,7 @@ TEST_CASE("GSLIBFindAtElementBoundary",
cmax = std::max(code_out[i], cmax);
}
REQUIRE((cmin == 0 && cmax == 0)); // should be found inside element
}
}
TEST_CASE("GSLIBSurfFindAtElementBoundary",
"[GSLIBSurfFindAtElementBoundary][GSLIB]")
{
auto surface_mesh_type = GENERATE(SurfaceMeshType::Segment2D,
SurfaceMeshType::Segment3D,
SurfaceMeshType::Quad3D,
SurfaceMeshType::Tri3D);
const char *mesh_name = SurfaceMeshName(surface_mesh_type);
CAPTURE(mesh_name);
Mesh mesh = MakeSurfaceMesh(surface_mesh_type, 4);
const int sdim = SurfaceSpaceDim(surface_mesh_type);
mesh.SetCurvature(2, false, sdim);
const int nptface_per_el = (mesh.Dimension() == 1) ? 2 : 8;
const int nptint_per_el = 8;
const int nptface = mesh.GetNE()*nptface_per_el;
const int nptint = mesh.GetNE()*nptint_per_el;
Vector xyz((nptface + nptint)*sdim);
GetSurfaceBoundaryPoints(mesh, nptface_per_el, Ordering::byVDIM, xyz, 0);
GetSurfaceInteriorPoints(mesh, nptint_per_el, Ordering::byVDIM, xyz,
nptface);
FindPointsGSLIB finder;
finder.SetupSurf(mesh);
finder.FindPoints(xyz, Ordering::byVDIM);
Array<unsigned int> code_out = finder.GetCode();
for (int i = 0; i < nptface; i++)
{
REQUIRE(code_out[i] == 1);
}
for (int i = nptface; i < nptface + nptint; i++)
{
REQUIRE(code_out[i] == 0);
}
}
TEST_CASE("GSLIBSurfAABBExpansion", "[GSLIBSurfAABBExpansion][GSLIB]")
{
auto surface_mesh_type = GENERATE(SurfaceMeshType::Segment2D,
SurfaceMeshType::Segment3D,
SurfaceMeshType::Quad3D,
SurfaceMeshType::Tri3D);
const char *mesh_name = SurfaceMeshName(surface_mesh_type);
CAPTURE(mesh_name);
constexpr double offset = 1.0e-3;
const int npt_per_el = 8;
Mesh mesh = MakeSurfaceMesh(surface_mesh_type, 4);
const int sdim = SurfaceSpaceDim(surface_mesh_type);
mesh.SetCurvature(2, false, sdim);
const int npt = mesh.GetNE()*npt_per_el;
Vector xyz(npt*sdim);
GetSurfaceInteriorPoints(mesh, npt_per_el, Ordering::byVDIM, xyz);
// offset them to move away from the surface
const int off_d = (surface_mesh_type == SurfaceMeshType::Segment2D) ? 1 : 2;
for (int i = 0; i < npt; i++)
{
xyz(i*sdim + off_d) += offset;
}
FindPointsGSLIB finder;
finder.SetupSurf(mesh, 0.0);
finder.FindPoints(xyz, Ordering::byVDIM);
Array<unsigned int> code_no_pad = finder.GetCode();
for (int i = 0; i < code_no_pad.Size(); i++)
{
REQUIRE(code_no_pad[i] == 2);
}
// make aabb at least big enough to include the offset points
Vector aabb_sz_inc({2.1*offset});
finder.SetupSurfWithAABBExpansion(mesh, aabb_sz_inc);
finder.FindPoints(xyz, Ordering::byVDIM);
Array<unsigned int> code_with_pad = finder.GetCode();
for (int i = 0; i < npt; i++)
{
REQUIRE(code_with_pad[i] == 1);
delete l2_fec;
}
}
@@ -653,8 +322,9 @@ TEST_CASE("GSLIBInterpolateL2ElementBoundary",
mesh.SetCurvature(mesh_order);
// Set GridFunction to be interpolated
L2_FECollection c_fec(3, dim);
FiniteElementSpace c_fespace(&mesh, &c_fec, 1);
FiniteElementCollection *c_fec = new L2_FECollection(3, dim);
FiniteElementSpace c_fespace =
FiniteElementSpace(&mesh, c_fec, 1);
GridFunction field_vals(&c_fespace);
Array<int> dofs;
double leftval = 1.0;
@@ -696,6 +366,7 @@ TEST_CASE("GSLIBInterpolateL2ElementBoundary",
REQUIRE(interp_vals(0) == MFEM_Approx(0.5*(leftval+rightval)));
finder.FreeData();
delete c_fec;
}
#ifdef MFEM_USE_MPI
+16 -34
View File
@@ -328,7 +328,7 @@ TEST_CASE("Linear Form Extension", "[LinearFormExtension], [GPU]")
}
}
TEST_CASE("Vector FE Linear Form Extension", "[LinearFormExtension], [GPU]")
TEST_CASE("H(div) Linear Form Extension", "[LinearFormExtension], [GPU]")
{
const bool all = launch_all_non_regression_tests;
@@ -341,44 +341,26 @@ TEST_CASE("Vector FE Linear Form Extension", "[LinearFormExtension], [GPU]")
Mesh mesh(mesh_file);
const int dim = mesh.Dimension();
{
const auto space_type =
dim == 3 ? GENERATE(FiniteElement::DIV, FiniteElement::CURL)
: FiniteElement::DIV;
CAPTURE(mesh_file, dim, p);
CAPTURE(mesh_file, dim, p, space_type);
RT_FECollection fec(p, dim);
FiniteElementSpace fes(&mesh, &fec);
std::unique_ptr<FiniteElementCollection> fec;
VectorFunctionCoefficient coeff(dim, fvec_dim);
switch (space_type)
{
case FiniteElement::DIV:
fec.reset(new RT_FECollection(p, dim));
break;
case FiniteElement::CURL:
fec.reset(new ND_FECollection(p, dim));
break;
default:
MFEM_ABORT("unsupported space type");
}
FiniteElementSpace fes(&mesh, fec.get());
LinearForm d1(&fes);
d1.AddDomainIntegrator(new VectorFEDomainLFIntegrator(coeff));
d1.UseFastAssembly(true);
d1.Assemble();
VectorFunctionCoefficient coeff(dim, fvec_dim);
LinearForm d2(&fes);
d2.AddDomainIntegrator(new VectorFEDomainLFIntegrator(coeff));
d2.UseFastAssembly(false);
d2.Assemble();
LinearForm d1(&fes);
d1.AddDomainIntegrator(new VectorFEDomainLFIntegrator(coeff));
d1.UseFastAssembly(true);
d1.Assemble();
LinearForm d2(&fes);
d2.AddDomainIntegrator(new VectorFEDomainLFIntegrator(coeff));
d2.UseFastAssembly(false);
d2.Assemble();
CAPTURE(d1.Norml2(), d2.Norml2());
d1 -= d2;
REQUIRE(d1.Norml2() == MFEM_Approx(0.0));
}
CAPTURE(d1.Norml2(), d2.Norml2());
d1 -= d2;
REQUIRE(d1.Norml2() == MFEM_Approx(0.0));
}
#ifdef MFEM_USE_MPI
+4 -79
View File
@@ -200,71 +200,10 @@ int CheckArrayEquality(const Array<T> &arr1, const Array<T> &arr2)
return wrong_ct;
}
// Apply a deterministic perturbation to particle data on host.
void PerturbParticleDataOnHost(std::vector<Particle> &particles)
{
for (auto &p : particles)
{
for (int f = -1; f < p.GetNFields(); f++)
{
Vector &field = f == -1 ? p.Coords() : p.Field(f);
field.HostReadWrite();
const real_t scale = (f == -1) ? 0.001 : 1.0;
for (int c = 0; c < field.Size(); c++)
{
field(c) += scale * (f + c + 2);
}
}
for (int t = 0; t < p.GetNTags(); t++)
{
p.Tag(t) += t + 1;
}
}
}
// Apply a deterministic perturbation to particle data on device.
void PerturbParticleDataOnDevice(ParticleSet &pset)
{
const int np = pset.GetNParticles();
// Shift coordinates and fields using the same per-component formula while
// honoring the ParticleVector ordering selected by the test.
for (int f = -1; f < pset.GetNFields(); f++)
{
ParticleVector &field = f == -1 ? pset.Coords() : pset.Field(f);
const int vdim = field.GetVDim();
const bool by_vdim = (field.GetOrdering() == Ordering::byVDIM);
const real_t scale = (f == -1) ? 0.001 : 1.0;
auto d_field = field.ReadWrite();
mfem::forall(np, [=] MFEM_HOST_DEVICE (int i)
{
for (int c = 0; c < vdim; c++)
{
const int idx = by_vdim ? i * vdim + c : i + c * np;
d_field[idx] += scale * (f + c + 2);
}
});
}
for (int t = 0; t < pset.GetNTags(); t++)
{
Array<int> &tag = pset.Tag(t);
auto d_tag = tag.ReadWrite();
mfem::forall(np, [=] MFEM_HOST_DEVICE (int i)
{
d_tag[i] += t + 1;
});
}
}
void TestRedistribute(Ordering::Type ordering)
{
int size = Mpi::WorldSize();
int rank = Mpi::WorldRank();
const bool use_device = Device::IsEnabled();
// Create a 3D hex mesh
Mesh m = Mesh::MakeCartesian3D(N_e, N_e, N_e, Element::Type::HEXAHEDRON);
@@ -313,22 +252,15 @@ void TestRedistribute(Ordering::Type ordering)
SECTION(std::string("Ordering: ") +
(ordering == Ordering::byNODES ? "byNODES" : "byVDIM"))
{
// Add the particles uniquely to each rank particleset
ParticleSet pset(MPI_COMM_WORLD, 0, SpaceDim, FieldVDims,
NumTags, ordering, use_device);
CHECK(pset.IsParticleRefValid() ==
(!use_device && ordering == Ordering::byVDIM));
NumTags, ordering);
for (int i = 0; i < N_rank; i++)
{
pset.AddParticle(all_particles[i*size+rank]);
}
if (use_device)
{
PerturbParticleDataOnDevice(pset);
PerturbParticleDataOnHost(all_particles);
}
// Find points
FindPointsGSLIB finder(MPI_COMM_WORLD);
finder.Setup(pmesh);
@@ -338,7 +270,6 @@ void TestRedistribute(Ordering::Type ordering)
int code_1_count = 0;
int code_2_count = 0;
const Array<unsigned int> &code = finder.GetCode();
code.HostRead();
for (int i = 0; i < code.Size(); i++)
{
if (code[i] == 1)
@@ -361,7 +292,6 @@ void TestRedistribute(Ordering::Type ordering)
finder.FindPoints(pset.Coords(), ordering);
const Array<unsigned int> &procs = finder.GetProc();
procs.HostRead();
int wrong_proc_count = 0;
for (int i = 0; i < procs.Size(); i++)
@@ -377,11 +307,6 @@ void TestRedistribute(Ordering::Type ordering)
// Check that coordinates + fields + tags are all still correct
int wrong_particle_count = 0;
pset.GetIDs().HostRead();
for (int t = 0; t < pset.GetNTags(); t++)
{
pset.Tag(t).HostRead();
}
for (int i = 0; i < pset.GetNParticles(); i++)
{
Particle &actual_p = all_particles[pset.GetIDs()[i]];
@@ -392,13 +317,13 @@ void TestRedistribute(Ordering::Type ordering)
wrong_particle_count++;
}
}
MPI_Allreduce(MPI_IN_PLACE, &wrong_particle_count, 1, MPI_INT, MPI_SUM,
MPI_Allreduce(MPI_IN_PLACE, &wrong_proc_count, 1, MPI_INT, MPI_SUM,
MPI_COMM_WORLD);
CHECK(wrong_particle_count == 0);
}
}
TEST_CASE("Particle Redistribution", "[ParticleSet][Parallel][GPU]")
TEST_CASE("Particle Redistribution", "[ParticleSet][Parallel]")
{
TestRedistribute(Ordering::byNODES);
TestRedistribute(Ordering::byVDIM);
+2 -2
View File
@@ -124,8 +124,8 @@ TEST_CASE("3D ProjectBdrCoefficientNormal Scalar",
const double tol = 1e-6;
const int bdrs_axis[] = {2, 1, 0, 1, 0, 2};
const int bdrs_sign[] = {-1, -1, +1, +1, -1, +1};
const char bdrs_axis[] = {2, 1, 0, 1, 0, 2};
const char bdrs_sign[] = {-1, -1, +1, +1, -1, +1};
for (int type = (int)Element::TETRAHEDRON;
type <= (int)Element::HEXAHEDRON; type++)
+146
View File
@@ -440,6 +440,99 @@ TEST_CASE("Variable Order True Transfer", "[Transfer][VariableOrder]")
delete c_fec;
}
TEST_CASE("H1 L2 transfer with consistent mass", "[Transfer]")
{
auto vectorspace = GENERATE(VecSpace::H1, VecSpace::VectorH1nodes,
VecSpace::VectorH1vdim);
dimension = GENERATE(2, 3);
const int order = 2;
const int ne = 2;
const int vdim = (vectorspace == VecSpace::VectorH1nodes
|| vectorspace == VecSpace::VectorH1vdim) ? dimension : 1;
Ordering::Type ordering = (vectorspace == VecSpace::VectorH1vdim)
? Ordering::byVDIM : Ordering::byNODES;
CAPTURE(VecSpaceName(vectorspace), dimension, order);
Mesh mesh;
if (dimension == 2)
{
mesh = Mesh::MakeCartesian2D(ne, ne, Element::QUADRILATERAL,
1, 1.0, 1.0);
}
else
{
mesh = Mesh::MakeCartesian3D(ne, ne, ne, Element::HEXAHEDRON,
1.0, 1.0, 1.0);
}
Mesh fineMesh(mesh);
fineMesh.UniformRefinement();
H1_FECollection fec(order, dimension);
FiniteElementSpace c_fespace(&mesh, &fec, vdim, ordering);
FiniteElementSpace f_fespace(&fineMesh, &fec, vdim, ordering);
L2ProjectionGridTransfer transfer(c_fespace, f_fespace);
transfer.UseConsistentMass();
const Operator &R = transfer.ForwardOperator();
GridFunction X(&c_fespace);
GridFunction Y(&f_fespace);
GridFunction Y_ref(&f_fespace);
coeff_order = 1;
LinearForm rhs(&f_fespace);
BilinearForm mass(&f_fespace);
FunctionCoefficient funcCoeff(&coeff);
VectorFunctionCoefficient vecCoeff(dimension, &vectorcoeff);
if (vectorspace == VecSpace::H1)
{
X.ProjectCoefficient(funcCoeff);
rhs.AddDomainIntegrator(new DomainLFIntegrator(funcCoeff));
mass.AddDomainIntegrator(new MassIntegrator);
}
else
{
X.ProjectCoefficient(vecCoeff);
rhs.AddDomainIntegrator(new VectorDomainLFIntegrator(vecCoeff));
mass.AddDomainIntegrator(new VectorMassIntegrator);
}
rhs.Assemble();
mass.Assemble();
SparseMatrix M;
Array<int> empty;
mass.FormSystemMatrix(empty, M);
GSSmoother M_prec(M);
Y_ref = 0.0;
PCG(M, M_prec, rhs, Y_ref, 0, 500, 1e-24, 0.0);
Y = 0.0;
R.Mult(X, Y);
Y -= Y_ref;
REQUIRE(Y.Norml2() < 1e-11 * Y_ref.Norml2());
Vector x(c_fespace.GetVSize());
Vector y(f_fespace.GetVSize());
Vector Ry(f_fespace.GetVSize());
Vector Rtx(c_fespace.GetVSize());
x.Randomize(1);
y.Randomize(2);
R.Mult(x, Ry);
R.MultTranspose(y, Rtx);
const real_t ip1 = InnerProduct(Ry, y);
const real_t ip2 = InnerProduct(x, Rtx);
REQUIRE(std::abs(ip1 - ip2) <
1e-10 * std::max(std::abs(ip1), std::abs(ip2)));
REQUIRE_FALSE(transfer.SupportsBackwardsOperator());
}
TEST_CASE("Restriction Transpose Operator")
{
int order = GENERATE(1, 2);
@@ -797,6 +890,59 @@ TEST_CASE("Parallel Transfer", "[Transfer][Parallel]")
delete pmesh;
}
TEST_CASE("Parallel H1 L2 transfer with consistent mass",
"[Transfer][Parallel]")
{
dimension = GENERATE(2, 3);
const int order = 2;
const int ne = 2;
const int vdim = 1;
CAPTURE(dimension, order);
Mesh mesh;
if (dimension == 2)
{
mesh = Mesh::MakeCartesian2D(ne, ne, Element::QUADRILATERAL,
1, 1.0, 1.0);
}
else
{
mesh = Mesh::MakeCartesian3D(ne, ne, ne, Element::HEXAHEDRON,
1.0, 1.0, 1.0);
}
ParMesh pmesh(MPI_COMM_WORLD, mesh);
ParMesh pfineMesh(MPI_COMM_WORLD, mesh);
pfineMesh.UniformRefinement();
H1_FECollection fec(order, dimension);
ParFiniteElementSpace c_fespace(&pmesh, &fec, vdim);
ParFiniteElementSpace f_fespace(&pfineMesh, &fec, vdim);
L2ProjectionGridTransfer transfer(c_fespace, f_fespace);
transfer.UseConsistentMass();
const Operator &R = transfer.TrueForwardOperator();
Vector x(c_fespace.GetTrueVSize());
Vector y(f_fespace.GetTrueVSize());
Vector Rx(f_fespace.GetTrueVSize());
Vector Rty(c_fespace.GetTrueVSize());
x.Randomize(1);
y.Randomize(2);
R.Mult(x, Rx);
R.MultTranspose(y, Rty);
const real_t ip1 = InnerProduct(MPI_COMM_WORLD, Rx, y);
const real_t ip2 = InnerProduct(MPI_COMM_WORLD, x, Rty);
REQUIRE(std::abs(ip1 - ip2) <
1e-10 * std::max(std::abs(ip1), std::abs(ip2)));
REQUIRE_FALSE(transfer.SupportsBackwardsOperator());
}
TEST_CASE("Trace PRefinement Parallel TrueTransfer", "[Transfer][Parallel]")
{
auto simplex = GENERATE(true, false);
@@ -113,65 +113,3 @@ TEST_CASE("ComplexOperator Quaternion Tests", "[ComplexOperator]")
REQUIRE(qikx.Normlinf() < tol);
}
}
#ifdef MFEM_USE_MPI
TEST_CASE("ComplexHypreParMatrix GetSystemMatrix",
"[ComplexOperator][Parallel][GPU]")
{
// This test reproduces the issue described in PR #5200 on GitHub. See also
// the follow up PR #5346.
// 1. Construct ComplexHypreParMatrix similar to ex25p.
const char mesh_file[] = "../../data/inline-quad.mesh";
Mesh *mesh = new Mesh(mesh_file, 1, 1);
int dim = mesh->Dimension();
int ref_levels = 1;
for (int l = 0; l < ref_levels; l++)
{
mesh->UniformRefinement();
}
ParMesh pmesh(MPI_COMM_WORLD, *mesh);
delete mesh;
int par_ref_levels = 1;
for (int l = 0; l < par_ref_levels; l++)
{
pmesh.UniformRefinement();
}
int order = 1;
ND_FECollection fec(order, dim);
ParFiniteElementSpace fespace(&pmesh, &fec);
Array<int> ess_tdof_list;
Array<int> ess_bdr;
if (pmesh.bdr_attributes.Size())
{
ess_bdr.SetSize(pmesh.bdr_attributes.Max());
ess_bdr = 1;
}
fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
ComplexOperator::Convention conv = ComplexOperator::HERMITIAN;
VectorConstantCoefficient f(Vector{1_r, 2_r});
ParComplexLinearForm b(&fespace, conv);
b.AddDomainIntegrator(NULL, new VectorFEDomainLFIntegrator(f));
b = 0.0;
b.Assemble();
ParComplexGridFunction x(&fespace);
x = 0.0;
ConstantCoefficient one(1_r);
ParSesquilinearForm a(&fespace, conv);
a.AddDomainIntegrator(new CurlCurlIntegrator(one),
new CurlCurlIntegrator(one));
a.AddDomainIntegrator(new VectorFEMassIntegrator(one),
new VectorFEMassIntegrator(one));
a.Assemble();
OperatorPtr Ah;
Vector B, X;
a.FormLinearSystem(ess_tdof_list, x, b, Ah, X, B);
// 2. Test the call to ComplexHypreParMatrix::GetSystemMatrix and destroying
// the returned matrix.
HypreParMatrix *A = Ah.As<ComplexHypreParMatrix>()->GetSystemMatrix();
delete A;
}
#endif // MFEM_USE_MPI