Compare commits

..
Author SHA1 Message Date
Mittal, Ketan adaf2bbec6 minor 2026-05-08 13:11:31 -07:00
Mittal, Ketan b8aa60060b 2D 2026-05-08 09:58:33 -07:00
Mittal, Ketan 38c243ab05 initial commit 2026-05-06 15:05:01 -07:00
Tzanio Kolev bdd36c8982 Merge pull request #5318 from mfem/ai-policy
AI policy
2026-05-05 07:39:53 -07:00
Tzanio Kolev a7988aa845 Merge branch 'master' into ai-policy 2026-05-04 14:30:08 -07:00
Tzanio Kolev 4ec768c82b Merge pull request #5322 from mfem/fix-changelog
Fix CHANGELOG
2026-05-04 14:28:58 -07:00
Mittal, Ketan e32ea54e00 fix changelog 2026-05-04 14:11:52 -07:00
Veselin Dobrev 630a75440f Merge pull request #5299 from mfem/batchmass3d
Add element batching capabilities to 3D MassIntegrator
2026-05-04 13:47:24 -07:00
Veselin Dobrev 3ef3c8e6b4 Merge pull request #5306 from mfem/gslib-gitlab-testing
Include gslib testing on Dane
2026-05-04 13:43:23 -07:00
Tzanio Kolev 145efc313d Merge pull request #5320 from mfem/fix-cmake-libceed-test
Fix a CMake test of libCEED
2026-05-02 12:55:41 -07:00
Veselin Dobrev 26b2aa5cea In .gitlab/scripts/baseline, use srun to run scripts since salloc
does NOT run the script in the allocation as does srun.

Revert the change in the number of build tasks in dane-baseline.yml.
2026-05-01 11:10:09 -07:00
Veselin Dobrev 476c148949 Adjust the number of build tasks in dane-baseline.yml 2026-05-01 09:13:40 -07:00
Tzanio KolevandVeselin Dobrev 8ed259be31 Update CONTRIBUTING.md
Co-authored-by: Veselin Dobrev <v-dobrev@users.noreply.github.com>
2026-04-30 11:56:57 -07:00
Tzanio Kolev 67025d49ff AI policy updates based on feedback 2026-04-30 11:56:57 -07:00
Tzanio Kolev de1dea610e AI policy updates based on feedback 2026-04-30 11:56:57 -07:00
Tzanio Kolev 9f3f5c0372 Suggested AI policy 2026-04-30 11:56:56 -07:00
Tzanio Kolev 9205efab48 Merge pull request #5319 from mfem/gslib-gnu-make-updates
GSLIB related updates to the GNU make build system
2026-04-29 15:04:10 -07:00
Veselin Dobrev 1ccc27226a Fix a CMake test of libCEED 2026-04-29 10:54:49 -07:00
Veselin Dobrev 0d3195e69b Fix issue #5314 and other tweaks.
* 'make style' now checks if all git source files are selected for formatting.
* In examples/makefile, propagate the target 'test-noclean' to subdirectories.
* In miniapps/plasma/makefile, use logic similar to examples/makefile to
  propagate targets to subdirectories.
* Other small fixes.
2026-04-28 06:32:57 -07:00
Veselin Dobrev f37a596173 Fix a build issue: in the top makefile ensure miniapps/common is built
before building miniapps/gslib.
2026-04-27 07:26:12 -07:00
Andrew Ho 04dd962b6d review comments 2026-04-23 15:58:25 -07:00
Mittal, Ketan 383914db9a use MFEM's Mpi class to initialize instead of MPI_Init directly 2026-04-23 14:27:01 -07:00
Andrew HoandJohn Camier f77d238a5d Update fem/dgmassinv_kernels.hpp
Co-authored-by: John Camier <camierjs@gmail.com>
2026-04-23 10:52:43 -07:00
John Camier 84996ce32f Merge branch 'master' into batchmass3d 2026-04-23 06:26:09 -07:00
Mittal, Ketan f2b64de28f Merge branch 'master' of https://github.com/mfem/mfem into gslib-gitlab-testing 2026-04-22 12:27:50 -07:00
Mittal, Ketan 3415b0f3d4 run serial miniapps on 1 run when mfem is built with MPI 2026-04-22 12:26:45 -07:00
Andrew Ho 6ea799e385 Merge branch 'master' into batchmass3d 2026-04-20 09:04:25 -07:00
Andrew Ho f1561e47d1 Merge branch 'master' into batchmass3d 2026-04-17 10:07:18 -07:00
Mittal, Ketan abf5fedc5b include hypre with cuda on matrix 2026-04-16 21:03:11 -07:00
Mittal, Ketan d183f43c96 Merge branch 'gslib-gitlab-testing' of https://github.com/mfem/mfem into gslib-gitlab-testing 2026-04-16 12:56:36 -07:00
Mittal, Ketan a545b94ad7 enable testing on matrix as well 2026-04-16 12:56:08 -07:00
Ketan Mittal 12eefe3c41 Merge branch 'master' into gslib-gitlab-testing 2026-04-14 12:58:08 -07:00
Mittal, Ketan 8e33891c07 initial commit 2026-04-14 12:13:15 -07:00
Andrew Ho 7b4df2d374 Merge branch 'master' into batchmass3d 2026-04-13 09:29:35 -07:00
Andrew Ho 2d7460bde1 fixed bug in how tidz was set
128 seems to offer a slightly better balance for low and high orders
2026-04-11 10:21:10 -07:00
Andrew Ho 3c45d59813 cap CPU version to batch size 1 2026-04-10 17:23:48 -07:00
Andrew Ho 63acbeb8c0 use the same batching pattern as elsewhere, hopefully fixes bugs 2026-04-10 14:39:20 -07:00
Andrew Ho 9bf6819f7a Merge remote-tracking branch 'base/fix-umpire-dep' into batchmass3d 2026-04-10 13:53:32 -07:00
Andrew Ho bed2cc5735 implemented 3D element batching for mass integrator 2026-04-10 13:48:16 -07:00
88 changed files with 2173 additions and 8677 deletions
+2 -2
View File
@@ -451,8 +451,8 @@ miniapps/plasma/pic/*.csv
tests/unit/output_meshes
tests/unit/unit_tests
tests/unit/punit_tests
tests/unit/cunit_tests
tests/unit/pcunit_tests
tests/unit/gpu_unit_tests
tests/unit/pgpu_unit_tests
tests/unit/sedov_tests_*
tests/unit/psedov_tests_*
tests/unit/tmop_pa_tests_*
+5
View File
@@ -85,3 +85,8 @@ opt_par_gcc_10_pumi:
extends: .mfem_job_on_dane
variables:
SPEC: "%gcc@10.3.1 +pumi"
opt_par_gcc_10_gslib:
extends: .mfem_job_on_dane
variables:
SPEC: "%gcc@10.3.1 +gslib"
+5
View File
@@ -63,3 +63,8 @@ opt_mpi_cuda_hypre_cuda_gcc:
extends: .mfem_job_on_matrix
variables:
SPEC: "%gcc@10.3.1 +mpi +cuda cuda_arch=90 ^hypre+cuda"
opt_mpi_cuda_gcc_gslib:
extends: .mfem_job_on_matrix
variables:
SPEC: "%gcc@10.3.1 +mpi +cuda +gslib cuda_arch=90 ^hypre+cuda"
+2 -2
View File
@@ -32,9 +32,9 @@ mkdir _${BASELINE_TEST} && cd _${BASELINE_TEST}
# run
if [[ "${MACHINE_NAME}" == "dane" ]]; then
salloc --nodes=1 -t 60 --exclusive --reservation=ci ../runtest ../../mfem "${BASELINE_TEST} ${TPLS_DIR}"
srun --nodes=1 -t 60 --exclusive --reservation=ci ../runtest ../../mfem "${BASELINE_TEST} ${TPLS_DIR}"
elif [[ ${MACHINE_NAME} == "corona" ]]; then
salloc --nodes=1 -t 60 -p pbatch ../runtest ../../mfem "${BASELINE_TEST} ${TPLS_DIR}"
srun --nodes=1 -t 60 -p pbatch ../runtest ../../mfem "${BASELINE_TEST} ${TPLS_DIR}"
else
echo "Unknown machine: MACHINE_NAME=$MACHINE_NAME"
exit 1
+4 -8
View File
@@ -8,8 +8,10 @@
https://mfem.org
Version 4.10 (development)
==========================
Version 4.9.1 (development)
===========================
- Policy for AI-assisted contribution added to CONTRIBUTING.md
Discretization improvements
---------------------------
@@ -23,12 +25,6 @@ Discretization improvements
Tet rules (d=14-20): Chuluunbaatar et al., Comput. Math. Appl. 124:89-97,
2022.
Version 4.9.1 (development)
===========================
Discretization improvements
---------------------------
- Improved the gridfunction projection routines. Projections work for Scalar,
Vector and VectorFE, also NURBS versions. Optionally different types of
projections can be selected, default behaviour has not changed.
-17
View File
@@ -592,13 +592,6 @@ if (MFEM_USE_ENZYME)
set(ENZYME_INCLUDE_DIRS ${ENZYME_DIR}/include)
endif()
if (MFEM_USE_PROTEUS)
enable_language(C)
find_package(proteus REQUIRED PATHS "${PROTEUS_DIR}")
message(STATUS "${PROTEUS_DIR}/include")
include_directories("${PROTEUS_DIR}/include")
endif()
# MFEM_TIMER_TYPE
if (NOT DEFINED MFEM_TIMER_TYPE)
if (APPLE)
@@ -735,16 +728,6 @@ mfem_add_library(mfem ${SOURCES} ${HEADERS} ${MASTER_HEADERS})
target_compile_features(mfem PUBLIC cxx_std_${CMAKE_CXX_STANDARD})
# message(STATUS "TPL_LIBRARIES = ${TPL_LIBRARIES}")
target_link_libraries(mfem PUBLIC ${TPL_LIBRARIES} ${TPL_TARGETS})
if (MFEM_USE_PROTEUS)
add_library(ClangProteusFlags INTERFACE IMPORTED)
set_target_properties(ClangProteusFlags PROPERTIES
INTERFACE_COMPILE_OPTIONS "-fpass-plugin=$<TARGET_FILE:ProteusPass>"
)
target_link_libraries(mfem PUBLIC ClangProteusFlags)
target_link_libraries(mfem PUBLIC proteus)
endif()
if (TPL_TARGETS)
add_dependencies(mfem ${TPL_TARGETS})
endif()
+8
View File
@@ -24,6 +24,14 @@ must be made under this license.
Note also that MFEM has a [Code of Conduct](CODE_OF_CONDUCT.md). By participating
in the MFEM community, you agree to abide by its rules.
## AI Policy
- Use of AI code generation in MFEM is allowed but must be disclosed, e.g. by
selecting the `AI-assisted` label on the PR.
- By submitting a PR, the author acknowledges that they have reviewed and
understand the changes they are proposing.
- PR authors are still responsible for correctness, licensing, and attribution
of all changes.
If you plan on contributing to MFEM, consider reviewing the
[issue tracker](https://github.com/mfem/mfem/issues) first to check if a thread
already exists for your desired feature or the bug you ran into. Use a pull
-18
View File
@@ -157,22 +157,4 @@ constexpr real_t operator""_r(unsigned long long v)
#endif
#endif // MFEM_USE_MPI not defined
#ifdef NVTX_DBG_HPP
#include NVTX_DBG_HPP
#else
#define db1(...)
#define dbg(...)
#define dbl(...)
#define dba(...)
#define dbc(...)
#define NVTX_MARK_FUNCTION
#define NVTX_MARK_BEGIN(...)
#define NVTX_INI(...)
#define NVTX_END(...)
#define NVTX_MARK_INI(...)
#define NVTX_MARK_END(...)
#define NVTX_MARK(...)
#define NVTX(...)
#endif
#endif // MFEM_CONFIG_HPP
+1 -2
View File
@@ -47,7 +47,6 @@ list(APPEND ALL_EXE_SRCS
ex39.cpp
ex40.cpp
ex41.cpp
jitplayground.cpp
)
if (MFEM_USE_MPI)
@@ -216,7 +215,7 @@ if (MFEM_ENABLE_TESTING)
add_test(NAME ex1p_ceed_np=${MFEM_MPI_NP}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MFEM_MPI_NP}
${MPIEXEC_PREFLAGS}
$<TARGET_FILE:ex1p> "-no-vis" "-d ceed-cpu" "-pa" "-a"
$<TARGET_FILE:ex1p> "-no-vis" "-d" "ceed-cpu" "-pa" "-a"
${MPIEXEC_POSTFLAGS})
endif()
endif()
+1 -1
View File
@@ -64,7 +64,7 @@ PARALLEL_NAME := Parallel AMGX example
$(MFEM_LIB_FILE):
$(error The MFEM library is not build)
clean: clean-build
clean: clean-build clean-exec
clean-build:
rm -f *.o *~ $(SEQ_EXAMPLES) $(PAR_EXAMPLES)
+3 -3
View File
@@ -64,12 +64,12 @@ ex1p-test-par: ex1p
$(MFEM_LIB_FILE):
$(error The MFEM library is not built)
clean: clean-build clean-exec $(SUBDIRS_CLEAN)
clean: clean-build clean-exec
clean-build:
rm -f *.o *~ $(SEQ_EXAMPLES) $(PAR_EXAMPLES)
rm -rf *.dSYM *.TVD.*breakpoints
clean-exec:
@rm -f refined.mesh displaced.mesh mesh.* ex5.mesh
@rm -f sphere_refined.* sol.* sol_u.* sol_p.* sol_r.* sol_i.*
@rm -f refined.mesh mesh.*
@rm -f sol.*
+1 -1
View File
@@ -76,4 +76,4 @@ clean-build:
rm -rf *.dSYM *.TVD.*breakpoints
clean-exec:
@rm -f refined.mesh sol.gf
@rm -f refined.mesh sol.gf mesh.* sol.*
-536
View File
@@ -1,536 +0,0 @@
#include <mfem.hpp>
#include "../fem/dfem/util.hpp"
#include <proteus/CppJitModule.h>
#include "jitplayground.hpp"
#include <algorithm>
#include <array>
#include <cctype>
#include <cmath>
#include <fstream>
#include <initializer_list>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
namespace util
{
constexpr std::string_view Dirname(std::string_view path)
{
const size_t last_sep = path.find_last_of("/\\");
if (last_sep == std::string_view::npos) { return {}; }
return path.substr(0, last_sep);
}
constexpr std::string_view thisFileDir = Dirname(__FILE__);
}
template <typename T>
static std::string TypeNameString()
{
return std::string(mfem::future::get_type_name<T>());
}
template <typename Tuple, size_t... Is>
static auto ParamTypeStringsImpl(std::index_sequence<Is...>)
{
return std::array<std::string, sizeof...(Is)>
{
TypeNameString<std::remove_reference_t<decltype(mfem::future::get<Is>(std::declval<Tuple&>()))>>()...
};
}
template <typename Tuple>
static auto ParamTypeStrings()
{
return ParamTypeStringsImpl<Tuple>(
std::make_index_sequence<mfem::future::tuple_size<Tuple>::value> {});
}
static std::string_view Trim(std::string_view s)
{
size_t begin = 0;
while (begin < s.size() && std::isspace(static_cast<unsigned char>(s[begin])))
{
++begin;
}
size_t end = s.size();
while (end > begin &&
std::isspace(static_cast<unsigned char>(s[end - 1])))
{
--end;
}
return s.substr(begin, end - begin);
}
static bool IsValidIdentifier(std::string_view s)
{
if (s.empty()) { return false; }
const unsigned char c0 = static_cast<unsigned char>(s[0]);
if (!(std::isalpha(c0) || c0 == '_')) { return false; }
for (size_t i = 1; i < s.size(); ++i)
{
const unsigned char c = static_cast<unsigned char>(s[i]);
if (!(std::isalnum(c) || c == '_')) { return false; }
}
return true;
}
static bool ParseJitDirective(std::string_view line,
std::string &type,
std::string &var,
std::string &kind)
{
const size_t jit_pos = line.find("$JIT");
if (jit_pos == std::string_view::npos) { return false; }
const size_t open = line.find('[', jit_pos);
const size_t close = line.find(']', jit_pos);
MFEM_VERIFY(open != std::string_view::npos &&
close != std::string_view::npos &&
close > open,
"malformed $JIT directive (expected brackets): " << line);
const std::string_view payload = line.substr(open + 1, close - open - 1);
const size_t comma1 = payload.find(',');
const size_t comma2 = (comma1 == std::string_view::npos)
? std::string_view::npos
: payload.find(',', comma1 + 1);
MFEM_VERIFY(comma1 != std::string_view::npos &&
comma2 != std::string_view::npos,
"malformed $JIT directive (expected 3 comma-separated fields): "
<< line);
const std::string_view f0 = Trim(payload.substr(0, comma1));
const std::string_view f1 = Trim(payload.substr(comma1 + 1,
comma2 - comma1 - 1));
const std::string_view f2 = Trim(payload.substr(comma2 + 1));
MFEM_VERIFY(!f0.empty() && !f1.empty() && !f2.empty(),
"malformed $JIT directive (empty field): " << line);
type.assign(f0);
var.assign(f1);
kind.assign(f2);
return true;
}
static std::string ReadFileOrEmpty(const std::string &fn)
{
std::ifstream file(fn);
if (!file.is_open())
{
std::cerr << "could not open file " << fn << "\n";
return {};
}
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
static std::vector<std::string> ExtractJitVarNames(const std::string
&kernel_code)
{
std::stringstream ss(kernel_code);
std::string line;
std::vector<std::string> var_names;
std::unordered_set<std::string> seen_vars;
while (std::getline(ss, line))
{
std::string type, var, kind;
if (ParseJitDirective(line, type, var, kind))
{
MFEM_VERIFY(IsValidIdentifier(var),
"$JIT variable must be a valid identifier: " << var);
MFEM_VERIFY(seen_vars.insert(var).second,
"duplicate $JIT variable name: " << var);
var_names.push_back(var);
}
}
return var_names;
}
static std::string RewriteKernelForJit(std::string kernel_code,
const std::vector<std::string> &jit_values)
{
std::stringstream ss(kernel_code);
std::string line;
std::string out;
out.reserve(kernel_code.size() + 128);
bool have_pending = false;
size_t pending_index = 0;
std::string pending_type;
std::string pending_var;
std::unordered_set<std::string> seen_vars;
while (std::getline(ss, line))
{
line.push_back('\n');
if (have_pending)
{
MFEM_VERIFY(pending_index < jit_values.size(),
"not enough JIT values provided");
const size_t indent_end = line.find_first_not_of(" \t");
const std::string indent =
(indent_end == std::string::npos) ? std::string() :
line.substr(0, indent_end);
out += indent + "const " + pending_type + " " + pending_var + " = " +
jit_values[pending_index] + ";\n";
have_pending = false;
++pending_index;
continue;
}
std::string type, var, kind;
if (ParseJitDirective(line, type, var, kind))
{
MFEM_VERIFY(IsValidIdentifier(var),
"$JIT variable must be a valid identifier: " << var);
MFEM_VERIFY(kind == "generic",
"unsupported $JIT kind: " << kind);
MFEM_VERIFY(seen_vars.insert(var).second,
"duplicate $JIT variable name: " << var);
pending_type = std::move(type);
pending_var = std::move(var);
have_pending = true;
continue; // drop directive line
}
out += line;
}
MFEM_VERIFY(!have_pending,
"$JIT directive must annotate a following line");
MFEM_VERIFY(jit_values.size() == pending_index,
"JIT value count must match number of $JIT directives");
return out;
}
static std::string GeneratedOutputPath(std::string_view original_path)
{
const size_t last_sep = original_path.find_last_of("/\\");
const size_t dot = original_path.find_last_of('.');
const bool dot_in_filename =
(dot != std::string_view::npos) &&
(last_sep == std::string_view::npos || dot > last_sep);
const std::string_view base =
dot_in_filename ? original_path.substr(0, dot) : original_path;
return std::string(base) + "_generated.hpp";
}
static void WriteFileOrWarn(const std::string &path,
const std::string &contents)
{
std::ofstream out(path);
if (!out.is_open())
{
std::cerr << "could not write generated file " << path << "\n";
return;
}
out << contents;
}
class JitQFunction
{
public:
template <typename ImplT, size_t N>
JitQFunction(ImplT, const std::string &fn,
const std::array<bool, N> &activity_map)
{
using qf_signature = typename
mfem::future::get_function_signature<
decltype(&ImplT::operator())>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
constexpr size_t nparams = mfem::future::tuple_size<qf_param_ts>::value;
static_assert(N == nparams, "activity_map size must match qfunc arity");
this->fn = fn;
this->nparams = nparams;
this->activity_map.reserve(N);
for (size_t i = 0; i < N; ++i)
{
this->activity_map.push_back(activity_map[i]);
}
{
const auto param_types_arr = ParamTypeStrings<qf_param_ts>();
this->param_types.assign(param_types_arr.begin(), param_types_arr.end());
}
this->return_type = TypeNameString<typename qf_signature::return_t>();
this->return_is_void = std::is_same_v<typename qf_signature::return_t, void>;
this->impl_type_name = TypeNameString<ImplT>();
this->jit_var_names = ExtractJitVarNames(ReadFileOrEmpty(fn));
}
template <typename ReturnT, typename... Args>
ReturnT run(std::string_view name,
std::initializer_list<std::pair<std::string_view, std::string_view>> jit_values,
Args&&... args)
{
auto ordered_values = MatchJitValues(jit_values);
auto &mod = GetOrCreateModule(ordered_values);
auto &instance = mod.instantiate(std::string(name), std::string());
return instance.template run<ReturnT>(std::forward<Args>(args)...);
}
template <typename ReturnT, typename... Args>
ReturnT run_primal(
std::initializer_list<std::pair<std::string_view, std::string_view>> jit_values,
Args&&... args)
{
return run<ReturnT>(qfunc_name, jit_values,
std::forward<Args>(args)...);
}
template <typename ReturnT, typename... Args>
ReturnT run_derivative(
std::initializer_list<std::pair<std::string_view, std::string_view>> jit_values,
Args&&... args)
{
return run<ReturnT>(qfunc_name + "_fwddiff", jit_values,
std::forward<Args>(args)...);
}
private:
std::vector<std::string_view> MatchJitValues(
std::initializer_list<std::pair<std::string_view, std::string_view>>
named_values) const
{
std::unordered_map<std::string_view, std::string_view> value_map;
for (const auto &[name, value] : named_values)
{
value_map[name] = value;
}
std::vector<std::string_view> ordered_values;
ordered_values.reserve(jit_var_names.size());
for (const auto &var_name : jit_var_names)
{
auto it = value_map.find(var_name);
MFEM_VERIFY(it != value_map.end(),
"missing JIT value for variable: " << var_name);
ordered_values.push_back(it->second);
}
MFEM_VERIFY(ordered_values.size() == named_values.size(),
"provided " << named_values.size() << " JIT values but expected "
<< jit_var_names.size());
return ordered_values;
}
std::string BuildModuleCode(const std::vector<std::string> &jit_values) const
{
std::string module_code =
RewriteKernelForJit(ReadFileOrEmpty(fn), jit_values);
module_code += "\n\n";
module_code += "// --- generated ---\n";
module_code +=
"template <typename return_type, typename... Args>\n"
"return_type __enzyme_fwddiff(Args...);\n"
"\n"
"extern int enzyme_const;\n"
"extern int enzyme_dup;\n"
"\n";
// Generate a primal wrapper with the requested symbol name, so the kernel
// header can just define the qfunc as a functor.
//
// Note: Proteus instantiates entrypoints via `qfunc_wrapper<>(...)` even
// when there are no user template args, so keep the wrapper itself a
// template (with a default parameter) while still doing literal `$JIT`
// replacements in the kernel code.
module_code += "template <typename = void>\n";
module_code += return_type + " " +
std::string(qfunc_name) + "(";
bool first = true;
for (size_t i = 0; i < nparams; ++i)
{
if (!first) { module_code += ", "; }
first = false;
module_code += param_types[i] + " Arg" + std::to_string(i);
}
module_code += ")\n";
module_code += "{\n";
module_code += " " + impl_type_name + " qf;\n";
if (return_is_void)
{
module_code += " ";
}
else
{
module_code += " return ";
}
module_code += "qf(";
for (size_t i = 0; i < nparams; ++i)
{
if (i) { module_code += ", "; }
module_code += "Arg" + std::to_string(i);
}
module_code += ");\n";
module_code += "}\n\n";
module_code += "template <typename = void>\n";
module_code += return_type + " " +
std::string(qfunc_name) + "_fwddiff(";
first = true;
for (size_t i = 0; i < nparams; ++i)
{
if (!first) { module_code += ", "; }
first = false;
module_code += param_types[i] + " Arg" + std::to_string(i);
if (activity_map[i])
{
module_code += ", " + param_types[i] + " dArg" + std::to_string(i);
}
}
module_code += ")\n";
module_code += "{\n";
if (return_is_void)
{
module_code += " __enzyme_fwddiff<void>(\n";
}
else
{
module_code += " return __enzyme_fwddiff<" +
return_type + ">(\n";
}
module_code += " (void*)" + std::string(qfunc_name) + "<>";
module_code += ",\n";
for (size_t i = 0; i < nparams; ++i)
{
if (activity_map[i])
{
module_code += " enzyme_dup, Arg" + std::to_string(i) +
", dArg" + std::to_string(i);
}
else
{
module_code += " enzyme_const, Arg" + std::to_string(i);
}
module_code += (i + 1 == nparams) ? ");\n" : ",\n";
}
module_code += "}\n";
WriteFileOrWarn(GeneratedOutputPath(fn), module_code);
return module_code;
}
proteus::CppJitModule &GetOrCreateModule(
const std::vector<std::string_view> &jit_values)
{
std::string key;
for (const auto &val : jit_values)
{
if (!key.empty()) { key += ","; }
key += val;
}
auto it = modules.find(key);
if (it != modules.end())
{
return *it->second;
}
std::vector<std::string> values(jit_values.begin(), jit_values.end());
std::string code = BuildModuleCode(values);
auto mod = std::make_unique<proteus::CppJitModule>("host", code,
DefaultExtraArgs());
auto [inserted, ok] = modules.emplace(key, std::move(mod));
MFEM_VERIFY(ok, "failed to cache JIT module");
return *inserted->second;
}
static std::vector<std::string> DefaultExtraArgs()
{
return {"-fplugin=/Users/andrej1/local/enzyme/lib/ClangEnzyme-20.dylib"};
}
std::string qfunc_name = "qfunc_wrapper";
std::string fn;
size_t nparams = 0;
std::vector<bool> activity_map;
std::vector<std::string> param_types;
std::string return_type;
bool return_is_void = false;
std::string impl_type_name;
std::vector<std::string> jit_var_names;
std::unordered_map<std::string, std::unique_ptr<proteus::CppJitModule>> modules;
};
int main()
{
const size_t N = 4;
const size_t M = 5;
const double A = 123.4;
std::vector<double> X(N);
std::vector<double> Y(N);
for (size_t i = 0; i < N; ++i)
{
X[i] = static_cast<double>(i + 1);
Y[i] = static_cast<double>(N - i);
}
// // >>> user interface calls
// const std::string kernel_path = std::string(util::thisFileDir) +
// "/jitplayground.hpp";
// JitQFunction qf(daxpy_op{}, kernel_path, std::array{false, true, false});
// // <<< user interface calls
// // this will happen internally in dFEM
daxpy_op op;
printf("\n\nfunction call\n");
op(&A, X.data(), Y.data(), &N);
// reset X for the derivative test
for (size_t i = 0; i < N; ++i)
{
X[i] = static_cast<double>(i + 1);
Y[i] = static_cast<double>(N - i);
}
std::vector<double> dX(N, 1.0);
printf("\n\nforward diff call\n");
daxpy_op_fwddiff(&A, X.data(), dX.data(), Y.data(), &N);
std::vector<double> dX_manual(N, A);
printf("\n\nderivative checks\n");
std::cout << "dX: ";
for (size_t i = 0; i < N; ++i)
{
std::cout << dX[i] << (i + 1 == N ? '\n' : ' ');
}
std::cout << "dX_manual: ";
for (size_t i = 0; i < N; ++i)
{
std::cout << dX_manual[i] << (i + 1 == N ? '\n' : ' ');
}
double max_abs_err = 0.0;
for (size_t i = 0; i < N; ++i)
{
max_abs_err = std::max(max_abs_err, std::abs(dX[i] - dX_manual[i]));
}
std::cout << "max |dX - dX_manual| = " << max_abs_err << "\n";
return 0;
}
-58
View File
@@ -1,58 +0,0 @@
#pragma once
#include <cstddef>
#include <vector>
#include <type_traits>
#include "proteus/JitInterface.h"
struct daxpy_op
{
void operator()(
const double *a,
double *x,
const double *y,
const size_t *N) const
{
const size_t n = *N;
auto lam = [=, n = proteus::jit_variable(n)]
() __attribute__((annotate("jit")))
{
printf("N = %zu\n", n);
for (size_t i = 0; i < n; ++i)
{
printf("x[%zu] = %f, y[%zu] = %f\n", i, x[i], i, y[i]);
x[i] = *a * x[i] + y[i];
printf("updated x[%zu] = %f\n", i, x[i]);
}
};
proteus::register_lambda(lam);
lam();
}
};
template <typename return_type, typename... Args>
return_type __enzyme_fwddiff(Args...);
extern int enzyme_const;
extern int enzyme_dup;
void daxpy_op_wrapper(const double * Arg0, double * Arg1,
const double * Arg2, const size_t *Arg3)
{
daxpy_op qf;
qf(Arg0, Arg1, Arg2, Arg3);
}
void daxpy_op_fwddiff(const double * Arg0, double * Arg1,
double * dArg1, const double * Arg2, const size_t *Arg3)
{
__enzyme_fwddiff<void>(
(void*)daxpy_op_wrapper,
enzyme_const, Arg0,
enzyme_dup, Arg1, dArg1,
enzyme_const, Arg2,
enzyme_const, Arg3);
}
+5 -2
View File
@@ -71,6 +71,7 @@ endif
SUBDIRS_ALL = $(addsuffix /all,$(SUBDIRS))
SUBDIRS_TEST = $(addsuffix /test,$(SUBDIRS))
SUBDIRS_TEST_NOCLEAN = $(addsuffix /test-noclean,$(SUBDIRS))
SUBDIRS_CLEAN = $(addsuffix /clean,$(SUBDIRS))
SUBDIRS_TPRINT = $(addsuffix /test-print,$(SUBDIRS))
@@ -87,8 +88,9 @@ SUBDIRS_TPRINT = $(addsuffix /test-print,$(SUBDIRS))
all: $(EXAMPLES) $(SUBDIRS_ALL)
.PHONY: $(SUBDIRS_ALL) $(SUBDIRS_TEST) $(SUBDIRS_CLEAN) $(SUBDIRS_TPRINT)
$(SUBDIRS_ALL) $(SUBDIRS_TEST) $(SUBDIRS_CLEAN):
.PHONY: $(SUBDIRS_ALL) $(SUBDIRS_TEST) $(SUBDIRS_TEST_NOCLEAN) \
$(SUBDIRS_CLEAN) $(SUBDIRS_TPRINT)
$(SUBDIRS_ALL) $(SUBDIRS_TEST) $(SUBDIRS_TEST_NOCLEAN) $(SUBDIRS_CLEAN):
$(MAKE) -C $(@D) $(@F)
$(SUBDIRS_TPRINT):
@$(MAKE) -C $(@D) $(@F)
@@ -107,6 +109,7 @@ endif
MFEM_TESTS = EXAMPLES
include $(MFEM_TEST_MK)
test: $(SUBDIRS_TEST)
test-noclean: $(SUBDIRS_TEST_NOCLEAN)
test-print: $(SUBDIRS_TPRINT)
# Testing: Parallel vs. serial runs
+13 -20
View File
@@ -121,11 +121,6 @@ set(SRCS
qinterp/eval_hdiv.cpp
qinterp/grad_by_nodes.cpp
qinterp/grad_by_vdim.cpp
qinterp/grad_transpose.cpp
qinterp/grad_transpose_by_nodes.cpp
qinterp/grad_transpose_by_vdim.cpp
qinterp/eval_transpose.cpp
qinterp/eval_transpose_by_vdim.cpp
qspace.cpp
quadinterpolator.cpp
quadinterpolator_face.cpp
@@ -138,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
@@ -283,10 +278,8 @@ set(HDRS
qfunction.hpp
qinterp/det.hpp
qinterp/eval.hpp
qinterp/eval_transpose.hpp
qinterp/eval_hdiv.hpp
qinterp/grad.hpp
qinterp/grad_transpose.hpp
qspace.hpp
quadinterpolator.hpp
quadinterpolator_face.hpp
@@ -320,36 +313,36 @@ set(HDRS
)
if (MFEM_USE_SIDRE)
list(APPEND SRCS sidredatacollection.cpp)
list(APPEND HDRS sidredatacollection.hpp)
list(APPEND SRCS sidredatacollection.cpp)
list(APPEND HDRS sidredatacollection.hpp)
endif()
if (MFEM_USE_CONDUIT)
list(APPEND SRCS conduitdatacollection.cpp)
list(APPEND HDRS conduitdatacollection.hpp)
list(APPEND SRCS conduitdatacollection.cpp)
list(APPEND HDRS conduitdatacollection.hpp)
endif()
if (MFEM_USE_ADIOS2)
list(APPEND SRCS adios2datacollection.cpp)
list(APPEND HDRS adios2datacollection.hpp)
list(APPEND SRCS adios2datacollection.cpp)
list(APPEND HDRS adios2datacollection.hpp)
endif()
if (MFEM_USE_FMS)
list(APPEND SRCS fmsdatacollection.cpp fmsconvert.cpp)
list(APPEND HDRS fmsdatacollection.hpp fmsconvert.hpp)
list(APPEND SRCS fmsdatacollection.cpp fmsconvert.cpp)
list(APPEND HDRS fmsdatacollection.hpp fmsconvert.hpp)
endif()
if (MFEM_USE_MPI)
list(APPEND SRCS
list(APPEND SRCS
pbilinearform.cpp
pfespace.cpp
pgridfunc.cpp
plinearform.cpp
pnonlinearform.cpp
prestriction.cpp)
# If this list (HDRS -> HEADERS) is used for install, we probably want the
# headers added all the time.
list(APPEND HDRS
# If this list (HDRS -> HEADERS) is used for install, we probably want the
# headers added all the time.
list(APPEND HDRS
pbilinearform.hpp
pfespace.hpp
pgridfunc.hpp
+12 -17
View File
@@ -2178,22 +2178,18 @@ class DiffusionIntegrator: public BilinearFormIntegrator
{
public:
using DiffusionApplyKernelType = void(*)(const int, const bool,
const Array<real_t>&,
const Array<real_t>&, const Array<real_t>&,
const Array<real_t>&,
const Vector&, const Vector&,
Vector&, const int, const int);
using ApplyKernelType = void(*)(const int, const bool, const Array<real_t>&,
const Array<real_t>&, const Array<real_t>&,
const Array<real_t>&,
const Vector&, const Vector&,
Vector&, const int, const int);
using DiffusionDiagonalKernelType = void(*)(const int, const bool,
const Array<real_t>&,
const Array<real_t>&, const Vector&, Vector&,
const int, const int);
using DiagonalKernelType = void(*)(const int, const bool, const Array<real_t>&,
const Array<real_t>&, const Vector&, Vector&,
const int, const int);
MFEM_REGISTER_KERNELS(DiffusionApplyPAKernel, DiffusionApplyKernelType,
(int, int, int));
MFEM_REGISTER_KERNELS(DiffusionDiagonalPAKernel, DiffusionDiagonalKernelType,
(int, int, int));
MFEM_REGISTER_KERNELS(ApplyPAKernels, ApplyKernelType, (int, int, int));
MFEM_REGISTER_KERNELS(DiagonalPAKernels, DiagonalKernelType, (int, int, int));
struct Kernels { Kernels(); };
protected:
@@ -2213,7 +2209,6 @@ private:
const FiniteElementSpace *fespace;
const DofToQuad *maps; ///< Not owned
const GeometricFactors *geom; ///< Not owned
public:
int dim, ne, dofs1D, quad1D;
Vector pa_data;
bool symmetric = true; ///< False if using a nonsymmetric matrix coefficient
@@ -2355,8 +2350,8 @@ public:
template <int DIM, int D1D, int Q1D>
static void AddSpecialization()
{
DiffusionApplyPAKernel::Specialization<DIM,D1D,Q1D>::Add();
DiffusionDiagonalPAKernel::Specialization<DIM,D1D,Q1D>::Add();
ApplyPAKernels::Specialization<DIM,D1D,Q1D>::Add();
DiagonalPAKernels::Specialization<DIM,D1D,Q1D>::Add();
}
protected:
const IntegrationRule* GetDefaultIntegrationRule(
+17 -1
View File
@@ -41,9 +41,14 @@ void PLBound::Setup(const int nb_i, const int ncp_i,
tol = tol_i;
lbound.SetSize(ncp, nb);
ubound.SetSize(ncp, nb);
lbound_t.SetSize(nb, ncp);
ubound_t.SetSize(nb, ncp);
nodes.SetSize(nb);
weights.SetSize(nb);
control_points.SetSize(ncp);
xhat.SetSize(nb);
what.SetSize(nb);
cphat.SetSize(ncp);
auto scalenodes = [](const Vector &in, const real_t a, const real_t b) -> Vector
{
@@ -90,6 +95,10 @@ void PLBound::Setup(const int nb_i, const int ncp_i,
MFEM_ABORT("Unsupported interval points. Use [0,1].\n");
}
control_points = scalenodes(control_points, 0.0, 1.0); // rescale to [0,1]
for (int i = 0; i < ncp; i++)
{
cphat(i) = 2.0*control_points(i) - 1.0;
}
Poly_1D::Basis &basis1d(poly1d.GetBasis(nb-1, b_type));
@@ -145,6 +154,8 @@ void PLBound::Setup(const int nb_i, const int ncp_i,
lbound(j,i) = std::max(lbound(j,i),0_r);
}
}
lbound_t(i,j) = lbound(j,i);
ubound_t(i,j) = ubound(j,i);
}
}
@@ -176,6 +187,11 @@ void PLBound::Setup(const int nb_i, const int ncp_i,
nodes(i) = irule.IntPoint(i).x;
}
}
for (int i = 0; i < nb; i++)
{
xhat(i) = 2.0*nodes(i) - 1.0;
what(i) = 2.0*weights(i);
}
if (b_type == 2)
{
@@ -755,4 +771,4 @@ void PLBound::Print(std::ostream &outp) const
ubound.Print(outp);
}
}
}
+615 -1
View File
@@ -13,6 +13,7 @@
#define MFEM_BOUNDS
#include "../config/config.hpp"
#include "../general/forall.hpp"
#include "fespace.hpp"
namespace mfem
@@ -60,7 +61,9 @@ private:
bool proj = true; // Use linear projection to compute bounds.
real_t tol = 0.0; // offset bounds to avoid round-off errors
Vector nodes, weights, control_points;
Vector xhat, what, cphat;
DenseMatrix lbound, ubound; // ncp x nb matrices with bounds of all bases
DenseMatrix lbound_t, ubound_t; // nb x ncp transposes for device kernel
// Some auxillary storage for computing the bounds with Bernstein
DenseMatrix basisMatNodes; // Bernstein bases at equispaced nodes
DenseMatrix basisMatInt; // Bernstein bases at GLL nodes
@@ -113,7 +116,10 @@ public:
* @details This projection increases the computational cost but results in
* tighter bounds.
*/
void SetProjectionFlagForBounding(bool proj_) { proj = proj_; }
void SetProjectionFlagForBounding(bool proj_)
{
proj = proj_;
}
/** @brief Compute piecewise linear bounds for the lexicographically-ordered
* nodal coefficients in @a coeff in 1D/2D/3D.
@@ -137,9 +143,23 @@ public:
/// Get number of control points used to compute the bounds.
int GetNControlPoints() const { return ncp; }
/// Get the underlying 1D basis type.
int GetBasisType() const { return b_type; }
/// Get 1D control point locations (lexicographic order) in [0,1].
const Vector &GetControlPoints() const { return control_points; }
/** @brief Compute element-wise bounds from a lexicographic E-vector.
*
* @details The expected layout of @a e_vec is `ND x VDIM x NE`, where
* `ND = nb^rdim`, `VDIM = fes_vdim`, and `NE` is the number of elements.
* The output layout matches GridFunction::GetElementBounds:
* `NE x active_vdim`, with the element index varying fastest.
*/
void GetElementBoundsKernel(const int rdim, const int fes_vdim,
const Vector &e_vec, Vector &lower,
Vector &upper, const int vdim = 0) const;
/** @brief Get lower and upper bounding matrix (ncp^dim x nb^dim)
*
* @details The matrices can be used to compute the bounds at control points
@@ -183,6 +203,600 @@ private:
const int cp_type_i, const real_t tol_i);
};
namespace internal
{
struct PLBoundDeviceData
{
int nb;
int ncp;
const real_t *xhat;
const real_t *what;
const real_t *cphat;
const real_t *lbound;
const real_t *ubound;
};
template<int T_NB = 0, bool T_PROJ = true>
inline void GetElementBoundsKernel1D(const PLBoundDeviceData &data,
const int fes_vdim,
const int ne,
const Vector &e_vec,
Vector &lower,
Vector &upper,
const int comp0,
const int ncomp)
{
constexpr int GENERIC_MAX_ND = 32;
constexpr int MAX_ND = T_NB ? T_NB : GENERIC_MAX_ND;
constexpr int BLOCK_X = 2*MAX_ND;
const int nd = T_NB ? T_NB : data.nb;
MFEM_VERIFY(nd <= MAX_ND,
"Device element bounds kernel supports up to 32 "
"1D degrees of freedom.");
const auto E = Reshape(e_vec.Read(), nd, fes_vdim, ne);
auto L = Reshape(lower.Write(), ne, ncomp);
auto U = Reshape(upper.Write(), ne, ncomp);
mfem::forall_2D<BLOCK_X>(ne*ncomp, BLOCK_X, 1,
[=] MFEM_HOST_DEVICE (int ec)
{
const int e = ec % ne;
const int c = ec / ne;
const int vc = comp0 + c;
const real_t *coeff = &E(0, vc, e);
const int tid = MFEM_THREAD_ID(x);
MFEM_SHARED real_t sproj[MAX_ND];
MFEM_SHARED real_t ssum0[MAX_ND];
MFEM_SHARED real_t ssum1[MAX_ND];
MFEM_SHARED real_t smin[BLOCK_X];
MFEM_SHARED real_t smax[BLOCK_X];
MFEM_SHARED real_t sa0;
MFEM_SHARED real_t sa1;
MFEM_FOREACH_THREAD(i, x, nd)
{
if constexpr (T_PROJ)
{
const real_t x = data.xhat[i];
const real_t w = data.what[i];
ssum0[i] = 0.5*coeff[i]*w;
ssum1[i] = 1.5*coeff[i]*w*x;
}
else
{
ssum0[i] = 0.0;
ssum1[i] = 0.0;
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(ii, x, 1)
{
sa0 = 0.0;
sa1 = 0.0;
for (int i = 0; i < nd; i++)
{
sa0 += ssum0[i];
sa1 += ssum1[i];
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(i, x, nd)
{
if constexpr (T_PROJ)
{
const real_t x = data.xhat[i];
sproj[i] = coeff[i] - sa0 - sa1*x;
}
else
{
sproj[i] = coeff[i];
}
}
MFEM_SYNC_THREAD;
real_t lower_local = HUGE_VAL;
real_t upper_local = -HUGE_VAL;
MFEM_FOREACH_THREAD(j, x, data.ncp)
{
real_t lo = 0.0;
real_t hi = 0.0;
if constexpr (T_PROJ)
{
const real_t xcp = data.cphat[j];
lo = sa0 + sa1*xcp;
hi = lo;
}
for (int i = 0; i < nd; i++)
{
const real_t val = sproj[i];
const real_t lv = data.lbound[j + i*data.ncp]*val;
const real_t uv = data.ubound[j + i*data.ncp]*val;
lo += lv < uv ? lv : uv;
hi += lv > uv ? lv : uv;
}
lower_local = lower_local < lo ? lower_local : lo;
upper_local = upper_local > hi ? upper_local : hi;
}
smin[tid] = lower_local;
smax[tid] = upper_local;
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(ii, x, 1)
{
real_t lower_ec = smin[0];
real_t upper_ec = smax[0];
const int nthreads = MFEM_THREAD_SIZE(x);
const int nactive = data.ncp < nthreads ? data.ncp : nthreads;
for (int t = 1; t < nactive; t++)
{
lower_ec = lower_ec < smin[t] ? lower_ec : smin[t];
upper_ec = upper_ec > smax[t] ? upper_ec : smax[t];
}
L(e, c) = lower_ec;
U(e, c) = upper_ec;
}
});
}
template<int T_NB = 0, int T_NCP = 0, bool T_PROJ = true>
inline void GetElementBoundsKernel2D(const PLBoundDeviceData &data,
const int fes_vdim,
const int ne,
const Vector &e_vec,
Vector &lower,
Vector &upper,
const int comp0,
const int ncomp)
{
constexpr int DEFAULT_MAX_NB = 8;
constexpr int DEFAULT_MAX_CP = 3*DEFAULT_MAX_NB;
constexpr int MAX_NB = T_NB ? T_NB : DEFAULT_MAX_NB;
constexpr int MAX_CP = T_NCP ? T_NCP : DEFAULT_MAX_CP;
constexpr int MAX_THREADS = MAX_CP*MAX_CP;
const int nb = data.nb;
const int ncp = data.ncp;
const int nd = nb*nb;
MFEM_VERIFY(nb <= MAX_NB,
"Device 2D element bounds kernel exceeds its compile-time "
"1D degree bound.");
MFEM_VERIFY(ncp <= MAX_CP,
"Device 2D element bounds kernel exceeds its compile-time "
"control-point bound.");
MFEM_VERIFY(ncp*ncp <= MAX_THREADS,
"Device 2D element bounds kernel exceeds its compile-time "
"thread-block bound.");
const auto E = Reshape(e_vec.Read(), nd, fes_vdim, ne);
auto L = Reshape(lower.Write(), ne, ncomp);
auto U = Reshape(upper.Write(), ne, ncomp);
mfem::forall_2D<MAX_THREADS>(ne*ncomp, ncp, ncp,
[=] MFEM_HOST_DEVICE (int ec)
{
const int e = ec % ne;
const int c = ec / ne;
const int vc = comp0 + c;
const real_t *coeff = &E(0, vc, e);
const int tx = MFEM_THREAD_ID(x);
const int ty = MFEM_THREAD_ID(y);
MFEM_SHARED real_t sproj[MAX_NB*MAX_NB];
MFEM_SHARED real_t srow_min[MAX_NB*MAX_CP];
MFEM_SHARED real_t srow_max[MAX_NB*MAX_CP];
MFEM_SHARED real_t srow_a0[MAX_NB];
MFEM_SHARED real_t srow_a1[MAX_NB];
MFEM_SHARED real_t sa0[MAX_CP];
MFEM_SHARED real_t sa1[MAX_CP];
MFEM_SHARED real_t smin[MAX_THREADS];
MFEM_SHARED real_t smax[MAX_THREADS];
// Stage 1a: for each nodal row, form the per-node contributions to the
// row-wise linear fit used by the first 1D bounding solve.
MFEM_FOREACH_THREAD(jrow, y, nb)
{
const real_t *row_coeff = coeff + jrow*nb;
const int row_ncp_off = jrow*MAX_CP;
MFEM_FOREACH_THREAD(i, x, nb)
{
if constexpr (T_PROJ)
{
const real_t x = data.xhat[i];
const real_t w = data.what[i];
srow_min[row_ncp_off + i] = 0.5*row_coeff[i]*w;
srow_max[row_ncp_off + i] = 1.5*row_coeff[i]*w*x;
}
else
{
srow_min[row_ncp_off + i] = 0.0;
srow_max[row_ncp_off + i] = 0.0;
}
}
}
MFEM_SYNC_THREAD;
// Stage 1b: reduce the row-wise projection coefficients a0/a1.
if constexpr (T_PROJ)
{
MFEM_FOREACH_THREAD(jrow, y, nb)
{
const int row_ncp_off = jrow*MAX_CP;
real_t a0 = 0.0;
real_t a1 = 0.0;
MFEM_FOREACH_THREAD(ii, x, 1)
{
for (int i = 0; i < nb; i++)
{
a0 += srow_min[row_ncp_off + i];
a1 += srow_max[row_ncp_off + i];
}
srow_a0[jrow] = a0;
srow_a1[jrow] = a1;
}
}
MFEM_SYNC_THREAD;
}
// Stage 1c: subtract the row-wise linear fit once and cache the
// projected row coefficients for reuse across all x-control points.
MFEM_FOREACH_THREAD(jrow, y, nb)
{
const real_t *row_coeff = coeff + jrow*nb;
MFEM_FOREACH_THREAD(i, x, nb)
{
if constexpr (T_PROJ)
{
const real_t x = data.xhat[i];
sproj[jrow*MAX_NB + i] = row_coeff[i]
- srow_a0[jrow] - srow_a1[jrow]*x;
}
else
{
sproj[jrow*MAX_NB + i] = row_coeff[i];
}
}
}
MFEM_SYNC_THREAD;
// Stage 1d: solve the first 1D bounding problem along each nodal row and
// store bounds at every x-direction control point.
MFEM_FOREACH_THREAD(icp, x, ncp)
{
MFEM_FOREACH_THREAD(jrow, y, nb)
{
const int row_cp_off = jrow*ncp;
real_t lo = 0.0;
real_t hi = 0.0;
if constexpr (T_PROJ)
{
const real_t xcp = data.cphat[icp];
lo = srow_a0[jrow] + srow_a1[jrow]*xcp;
hi = lo;
}
for (int i = 0; i < nb; i++)
{
const real_t val = sproj[jrow*MAX_NB + i];
const real_t lv = data.lbound[icp + i*data.ncp]*val;
const real_t uv = data.ubound[icp + i*data.ncp]*val;
lo += lv < uv ? lv : uv;
hi += lv > uv ? lv : uv;
}
srow_min[row_cp_off + icp] = lo;
srow_max[row_cp_off + icp] = hi;
}
}
MFEM_SYNC_THREAD;
// Stage 2a: from the row bounds, form the per-row contributions to the
// second 1D projection solve in the y-direction.
MFEM_FOREACH_THREAD(icp, x, ncp)
{
MFEM_FOREACH_THREAD(jrow, y, nb)
{
const int row_cp_off = jrow*ncp;
if constexpr (T_PROJ)
{
const real_t x = data.xhat[jrow];
const real_t w = data.what[jrow];
const real_t t = 0.5*(srow_min[row_cp_off + icp] +
srow_max[row_cp_off + icp]);
smin[row_cp_off + icp] = 0.5*t*w;
smax[row_cp_off + icp] = 1.5*t*w*x;
}
else
{
smin[row_cp_off + icp] = 0.0;
smax[row_cp_off + icp] = 0.0;
}
}
}
MFEM_SYNC_THREAD;
// Stage 2b: reduce the y-direction projection coefficients for each
// x-control-point column.
MFEM_FOREACH_THREAD(jj, y, 1)
{
MFEM_FOREACH_THREAD(icp, x, ncp)
{
real_t a0 = 0.0;
real_t a1 = 0.0;
for (int jrow = 0; jrow < nb; jrow++)
{
a0 += smin[jrow*ncp + icp];
a1 += smax[jrow*ncp + icp];
}
sa0[icp] = a0;
sa1[icp] = a1;
}
}
MFEM_SYNC_THREAD;
// Stage 2c: subtract the y-direction linear fit from the intermediate
// row bounds so the final tensor-product bound uses the perturbation.
if constexpr (T_PROJ)
{
MFEM_FOREACH_THREAD(icp, x, ncp)
{
MFEM_FOREACH_THREAD(jrow, y, nb)
{
const int row_cp_off = jrow*ncp;
const real_t x = data.xhat[jrow];
const real_t t = sa0[icp] + sa1[icp]*x;
srow_min[row_cp_off + icp] -= t;
srow_max[row_cp_off + icp] -= t;
}
}
}
MFEM_SYNC_THREAD;
// Stage 3: each thread now owns one 2D control point (icp, kcp) and
// accumulates its final lower/upper bound from the row-bound data.
MFEM_FOREACH_THREAD(icp, x, ncp)
{
MFEM_FOREACH_THREAD(kcp, y, ncp)
{
real_t lo = 0.0;
real_t hi = 0.0;
if constexpr (T_PROJ)
{
const real_t xcp = data.cphat[kcp];
lo = sa0[icp] + sa1[icp]*xcp;
hi = lo;
}
for (int jrow = 0; jrow < nb; jrow++)
{
const real_t w0 = srow_min[jrow*ncp + icp];
const real_t w1 = srow_max[jrow*ncp + icp];
const real_t lb = data.lbound[kcp + jrow*data.ncp];
const real_t ub = data.ubound[kcp + jrow*data.ncp];
const real_t v0 = lb*w0;
const real_t v1 = ub*w0;
const real_t v2 = lb*w1;
const real_t v3 = ub*w1;
real_t vlo = v0 < v1 ? v0 : v1;
real_t vhi = v0 > v1 ? v0 : v1;
vlo = vlo < v2 ? vlo : v2;
vlo = vlo < v3 ? vlo : v3;
vhi = vhi > v2 ? vhi : v2;
vhi = vhi > v3 ? vhi : v3;
lo += vlo;
hi += vhi;
}
const int slot = kcp*ncp + icp;
smin[slot] = lo;
smax[slot] = hi;
}
}
MFEM_SYNC_THREAD;
const int lane = ty*ncp + tx;
const int nactive = ncp*ncp;
const int nthreads = MFEM_THREAD_SIZE(x)*MFEM_THREAD_SIZE(y);
// Reduce all 2D control-point bounds to one lower/upper pair per
// (element, component).
if (nthreads == 1)
{
if (tx == 0 && ty == 0)
{
real_t lower_ec = smin[0];
real_t upper_ec = smax[0];
for (int t = 1; t < nactive; t++)
{
lower_ec = lower_ec < smin[t] ? lower_ec : smin[t];
upper_ec = upper_ec > smax[t] ? upper_ec : smax[t];
}
L(e, c) = lower_ec;
U(e, c) = upper_ec;
}
}
else
{
for (int stride = (nactive + 1)/2; stride > 0;
stride = (stride + 1)/2)
{
if (lane < stride && lane + stride < nactive)
{
smin[lane] = smin[lane] < smin[lane + stride] ?
smin[lane] : smin[lane + stride];
smax[lane] = smax[lane] > smax[lane + stride] ?
smax[lane] : smax[lane + stride];
}
MFEM_SYNC_THREAD;
if (stride == 1) { break; }
}
if (lane == 0)
{
L(e, c) = smin[0];
U(e, c) = smax[0];
}
}
});
}
} // namespace internal
inline void PLBound::GetElementBoundsKernel(const int rdim, const int fes_vdim,
const Vector &e_vec,
Vector &lower, Vector &upper,
const int vdim) const
{
MFEM_VERIFY(b_type != BasisType::Positive,
"Bernstein device bounds are not implemented.");
if (rdim == 3)
{
MFEM_ABORT("Device element bounds kernel currently only supports 1D/2D.");
}
MFEM_VERIFY(rdim == 1 || rdim == 2, "Invalid element dimension.");
MFEM_VERIFY(vdim >= -1 && vdim <= fes_vdim, "Invalid vector component.");
const int nd = static_cast<int>(std::pow(nb, rdim));
const int ne = e_vec.Size()/(nd*fes_vdim);
const int ncomp = (vdim > 0) ? 1 : fes_vdim;
lower.SetSize(ne*ncomp, e_vec);
upper.SetSize(ne*ncomp, e_vec);
lower.UseDevice(true);
upper.UseDevice(true);
if (!proj)
{
MFEM_ABORT("Device element bounds kernel currently requires projection "
"enabled.");
}
const real_t *dxhat = xhat.Read();
const real_t *dwhat = what.Read();
const real_t *dcphat = cphat.Read();
const real_t *dlbound = lbound.Read();
const real_t *dubound = ubound.Read();
internal::PLBoundDeviceData data
{
nb,
ncp,
dxhat,
dwhat,
dcphat,
dlbound,
dubound
};
const int comp0 = (vdim > 0) ? (vdim - 1) : 0;
if (rdim == 1)
{
switch (nb)
{
case 2: return internal::GetElementBoundsKernel1D<2, true>(data, fes_vdim, ne,
e_vec, lower, upper,
comp0, ncomp);
case 3: return internal::GetElementBoundsKernel1D<3, true>(data, fes_vdim, ne,
e_vec, lower, upper,
comp0, ncomp);
case 4: return internal::GetElementBoundsKernel1D<4, true>(data, fes_vdim, ne,
e_vec, lower, upper,
comp0, ncomp);
case 5: return internal::GetElementBoundsKernel1D<5, true>(data, fes_vdim, ne,
e_vec, lower, upper,
comp0, ncomp);
case 6: return internal::GetElementBoundsKernel1D<6, true>(data, fes_vdim, ne,
e_vec, lower, upper,
comp0, ncomp);
case 7: return internal::GetElementBoundsKernel1D<7, true>(data, fes_vdim, ne,
e_vec, lower, upper,
comp0, ncomp);
case 8: return internal::GetElementBoundsKernel1D<8, true>(data, fes_vdim, ne,
e_vec, lower, upper,
comp0, ncomp);
case 9: return internal::GetElementBoundsKernel1D<9, true>(data, fes_vdim, ne,
e_vec, lower, upper,
comp0, ncomp);
case 10: return internal::GetElementBoundsKernel1D<10, true>(data, fes_vdim, ne,
e_vec, lower, upper,
comp0, ncomp);
default: return internal::GetElementBoundsKernel1D<0, true>(data, fes_vdim, ne,
e_vec, lower, upper,
comp0, ncomp);
}
}
#define MFEM_PLBOUND_2D_DISPATCH(NB, NCP) \
return internal::GetElementBoundsKernel2D<NB, NCP, true>(data, fes_vdim, ne, \
e_vec, lower, upper, \
comp0, ncomp)
switch (nb)
{
case 2:
switch (ncp)
{
case 4: MFEM_PLBOUND_2D_DISPATCH(2, 4);
case 6: MFEM_PLBOUND_2D_DISPATCH(2, 6);
case 8: MFEM_PLBOUND_2D_DISPATCH(2, 8);
}
break;
case 3:
switch (ncp)
{
case 6: MFEM_PLBOUND_2D_DISPATCH(3, 6);
case 9: MFEM_PLBOUND_2D_DISPATCH(3, 9);
case 12: MFEM_PLBOUND_2D_DISPATCH(3, 12);
}
break;
case 4:
switch (ncp)
{
case 8: MFEM_PLBOUND_2D_DISPATCH(4, 8);
case 12: MFEM_PLBOUND_2D_DISPATCH(4, 12);
case 16: MFEM_PLBOUND_2D_DISPATCH(4, 16);
}
break;
case 5:
switch (ncp)
{
case 10: MFEM_PLBOUND_2D_DISPATCH(5, 10);
case 15: MFEM_PLBOUND_2D_DISPATCH(5, 15);
case 20: MFEM_PLBOUND_2D_DISPATCH(5, 20);
}
break;
case 6:
switch (ncp)
{
case 12: MFEM_PLBOUND_2D_DISPATCH(6, 12);
case 18: MFEM_PLBOUND_2D_DISPATCH(6, 18);
case 24: MFEM_PLBOUND_2D_DISPATCH(6, 24);
}
break;
case 7:
switch (ncp)
{
case 14: MFEM_PLBOUND_2D_DISPATCH(7, 14);
case 21: MFEM_PLBOUND_2D_DISPATCH(7, 21);
case 28: MFEM_PLBOUND_2D_DISPATCH(7, 28);
}
break;
case 8:
switch (ncp)
{
case 16: MFEM_PLBOUND_2D_DISPATCH(8, 16);
case 24: MFEM_PLBOUND_2D_DISPATCH(8, 24);
case 32: MFEM_PLBOUND_2D_DISPATCH(8, 32);
}
break;
}
#undef MFEM_PLBOUND_2D_DISPATCH
return internal::GetElementBoundsKernel2D<0, 0, true>(data, fes_vdim, ne,
e_vec, lower, upper,
comp0, ncomp);
}
} // namespace mfem
#endif // MFEM_BOUNDS
-587
View File
@@ -1,587 +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.
#pragma once
#include <cassert>
#include <cstddef>
// #include "fem/kernels.hpp"
#include "fem/kernels3d.hpp"
namespace ker = mfem::kernels::internal;
namespace low = mfem::kernels::internal::low;
#include "fem/kernel_dispatch.hpp"
// #include "linalg/kernels.hpp"
#include "util.hpp"
#undef NVTX_COLOR
#define NVTX_COLOR ::nvtx::kOrchid
namespace mfem::future
{
/** @brief Zero-copy view of a contiguous block as a `tensor<T, n1>` */
template<typename T, int n1>
MFEM_HOST_DEVICE
const tensor<T, n1>& as_tensor(const T* ptr)
{
// std::launder makes this defined behavior under strict aliasing rules
return *std::launder(reinterpret_cast<const tensor<T, n1>*>(ptr));
}
// convenience overload if you prefer a mutable view
template<typename T, int n1>
MFEM_HOST_DEVICE
tensor<T, n1>& as_tensor(T* ptr)
{
return *std::launder(reinterpret_cast<tensor<T, n1>*>(ptr));
}
/** @brief Zero-copy view of a contiguous block as a `tensor<T, n1, n2>` */
template<typename T, int n1, int n2>
MFEM_HOST_DEVICE
const tensor<T, n1, n2>& as_tensor(const T* ptr)
{
// std::launder makes this defined behavior under strict aliasing rules
return *std::launder(reinterpret_cast<const tensor<T, n1, n2>*>(ptr));
}
// convenience overload if you prefer a mutable view
template<typename T, int n1, int n2>
MFEM_HOST_DEVICE
tensor<T, n1, n2>& as_tensor(T* ptr)
{
return *std::launder(reinterpret_cast<tensor<T, n1, n2>*>(ptr));
}
/** @brief Zero-copy view of a contiguous block as a `tensor<T, n1, n2, n3>` */
template<typename T, int n1, int n2, int n3>
MFEM_HOST_DEVICE
const tensor<T, n1, n2, n3>& as_tensor(const T* ptr)
{
// std::launder makes this defined behavior under strict aliasing rules
return *std::launder(reinterpret_cast<const tensor<T, n1, n2, n3>*>(ptr));
}
// convenience overload if you prefer a mutable view
template<typename T, int n1, int n2, int n3>
MFEM_HOST_DEVICE
tensor<T, n1, n2, n3>& as_tensor(T* ptr)
{
return *std::launder(reinterpret_cast<tensor<T, n1, n2, n3>*>(ptr));
}
/** @brief Zero-copy view of a contiguous block as a `tensor<T, n1, n2, n3, n4>` */
template<typename T, int n1, int n2, int n3, int n4>
MFEM_HOST_DEVICE
const tensor<T, n1, n2, n3, n4>& as_tensor(const T* ptr)
{
// std::launder makes this defined behavior under strict aliasing rules
return *std::launder(reinterpret_cast<const tensor<T, n1, n2, n3, n4>*>(ptr));
}
// convenience overload if you prefer a mutable view
template<typename T, int n1, int n2, int n3, int n4>
MFEM_HOST_DEVICE
tensor<T, n1, n2, n3, n4>& as_tensor(T* ptr)
{
return *std::launder(reinterpret_cast<tensor<T, n1, n2, n3, n4>*>(ptr));
}
template <std::size_t N>
MFEM_HOST_DEVICE inline
std::array<real_t*, N>
load_field_e_ptr(const std::array<DeviceTensor<2>, N> &fields_e,
const int e)
{
std::array<real_t*, N> f;
for_constexpr<N>([&](auto i) { f[i] = &fields_e[i](0, e); });
return f;
}
namespace qf
{
template <int T_Q1D,
size_t num_args,
typename reg_t,
typename qfunc_t,
typename args_ts>
MFEM_HOST_DEVICE inline
void apply_kernel(reg_t &res /*output*/,
reg_t &reg,
const real_t *rd,
const int qx, const int qy, const int qz,
const qfunc_t &qfunc, args_ts &args)
{
if constexpr (num_args == 2) // PAApply
{
// ∇u
tensor<real_t, 3> &arg_0 = get<0>(args);
arg_0[0] = reg[qz][qy][qx][0];
arg_0[1] = reg[qz][qy][qx][1];
arg_0[2] = reg[qz][qy][qx][2];
// D (PA data)
tensor<real_t, 3, 3> &arg_1 = get<1>(args);
if constexpr (T_Q1D > 0)
{
const auto *D = (const real_t (*)[T_Q1D][T_Q1D][3][3]) rd;
for (int k = 0; k < 3; k++)
{
for (int j = 0; j < 3; j++)
{
arg_1[k][j] = D[qx][qy][qz][k][j];
}
}
}
else
{
static_assert(false);
// const auto D = Reshape(r2, 3, 3, Q1D, Q1D, Q1D);
// for (int j = 0; j < 3; j++)
// {
// for (int k = 0; k < 3; k++)
// {
// arg_1[k][j] = D(j, k, qz, qy, qx);
// }
// }
}
}
else
{
// MFApply comes here
assert(false);
// MFEM_ABORT("Only two arguments (∇u and D) are supported in apply_kernel for now");
}
const auto r = get<0>(apply(qfunc, args));
if constexpr (decltype(r)::ndim == 1)
{
// process_qf_result_from_reg(r0, qx, qy, qz, r);
as_tensor<real_t, 3>(&res[qz][qy][qx][0]) = r;
}
else
{
static_assert(false);
}
}
} // namespace qf
#define MFEM_D2Q_MAX_SIZE 4
static MFEM_CONSTANT real_t Bi[MFEM_D2Q_MAX_SIZE][8*8], Bo[8*8];
static MFEM_CONSTANT real_t Gi[MFEM_D2Q_MAX_SIZE][8*8], Go[8*8];
template<size_t num_fields,
size_t num_inputs,
size_t num_outputs,
typename restriction_cb_t,
typename qfunc_t,
typename input_t,
typename output_fop_t>
class NewActionCallback
{
restriction_cb_t &restriction_cb;
qfunc_t &qfunc;
input_t &inputs;
const std::array<size_t, num_inputs> &input_to_field;
const std::array<DofToQuadMap, num_inputs> &input_dtq_maps;
const std::array<DofToQuadMap, num_outputs> &output_dtq_maps;
const int num_entities;
const int test_vdim;
const int num_test_dof;
const int dimension;
const ThreadBlocks &thread_blocks;
SharedMemoryInfo<num_fields, num_inputs, num_outputs> &shmem_info;
const Array<int> &attributes;
const output_fop_t &output_fop;
const Array<int> *elem_attributes;
// refs
std::vector<Vector> &fields_e;
Vector &residual_e;
std::function<void(Vector &, Vector &)> &output_restriction_transpose;
// args
std::vector<Vector> &solutions_l;
const std::vector<Vector> &parameters_l;
Vector &residual_l;
public:
NewActionCallback() = delete;
NewActionCallback(const bool use_kernels_specialization,
restriction_cb_t &restriction_cb,
qfunc_t &qfunc,
input_t &inputs,
const std::array<size_t, num_inputs> &input_to_field,
const std::array<DofToQuadMap, num_inputs> &input_dtq_maps,
const std::array<DofToQuadMap, num_outputs> &output_dtq_maps,
const int num_entities,
const int test_vdim,
const int num_test_dof,
const int dimension,
const ThreadBlocks &thread_blocks,
SharedMemoryInfo<num_fields, num_inputs, num_outputs> &shmem_info,
const Array<int> &attributes,
const output_fop_t &output_fop,
const Array<int> *elem_attributes,
// refs
std::vector<Vector> &fields_e,
Vector &residual_e,
std::function<void(Vector &, Vector &)> &output_restriction_transpose,
// args
std::vector<Vector> &solutions_l,
const std::vector<Vector> &parameters_l,
Vector &residual_l):
restriction_cb(restriction_cb),
qfunc(qfunc),
inputs(inputs),
input_to_field(input_to_field),
input_dtq_maps(input_dtq_maps),
output_dtq_maps(output_dtq_maps),
num_entities(num_entities),
test_vdim(test_vdim),
num_test_dof(num_test_dof),
dimension(dimension),
thread_blocks(thread_blocks),
shmem_info(shmem_info),
attributes(attributes),
output_fop(output_fop),
elem_attributes(elem_attributes),
fields_e(fields_e),
residual_e(residual_e),
output_restriction_transpose(output_restriction_transpose),
solutions_l(solutions_l),
parameters_l(parameters_l),
residual_l(residual_l)
{
if (!use_kernels_specialization) { return; }
NewActionCallbackKernels::template Specialization<3>::Add(); // 1
NewActionCallbackKernels::template Specialization<4>::Add(); // 2
NewActionCallbackKernels::template Specialization<5>::Add(); // 3
NewActionCallbackKernels::template Specialization<6>::Add(); // 4
NewActionCallbackKernels::template Specialization<7>::Add(); // 5
NewActionCallbackKernels::template Specialization<8>::Add(); // 6
}
template<int T_Q1D = 0>
static void action_callback_new(const int d1d,
restriction_cb_t &restriction_cb,
qfunc_t &qfunc,
[[maybe_unused]] input_t &inputs,
[[maybe_unused]] const std::array<size_t, num_inputs> &input_to_field,
const std::array<DofToQuadMap, num_inputs> &input_dtq_maps,
const std::array<DofToQuadMap, num_outputs> &output_dtq_maps,
[[maybe_unused]] const int dimension,
const int num_entities,
[[maybe_unused]] const int test_vdim,
[[maybe_unused]] const int num_test_dof,
const ThreadBlocks &thread_blocks,
[[maybe_unused]] SharedMemoryInfo<num_fields, num_inputs, num_outputs>
&shmem_info,
[[maybe_unused]] const Array<int> &attributes,
[[maybe_unused]] const output_fop_t &output_fop,
[[maybe_unused]] const Array<int> *elem_attributes,
// refs
std::vector<Vector> &fields_e,
Vector &residual_e,
std::function<void(Vector &, Vector &)> &output_restriction_transpose,
// args
std::vector<Vector> &solutions_l,
const std::vector<Vector> &parameters_l,
Vector &residual_l,
// fallback arguments
const int q1d)
{
NVTX_MARK_FUNCTION;
assert(dimension == 3);
static_assert(MFEM_D2Q_MAX_SIZE >= num_inputs, "MFEM_D2Q_MAX_SIZE error");
constexpr int DIM = 3;
[[maybe_unused]] static bool ini = (for_constexpr<num_inputs>([&](auto i)
{
const auto dtq = input_dtq_maps[i];
{
const auto [q, _, p] = dtq.B.GetShape();
const auto B = (const real_t*)input_dtq_maps[i].B;
dbg("Loading Bi[{}]: q={} p={}", i.value, q, p);
if (B) { Gpu(MemcpyToSymbol)(Bi[i], B, (p*q)*sizeof(real_t)); }
}
{
const auto [q, _, p] = dtq.G.GetShape();
const auto G = (const real_t*)input_dtq_maps[i].G;
if (G) { Gpu(MemcpyToSymbol)(Gi[i], G, (p*q)*sizeof(real_t)); }
}
if constexpr (i == 0) // output B
{
const auto dtq_o = output_dtq_maps[0];
const auto [q, _, p] = dtq_o.B.GetShape();
const auto B = (const real_t*)dtq_o.B;
if (B) { Gpu(MemcpyToSymbol)(Bo, B, (p*q)*sizeof(real_t)); }
}
if constexpr (i == 0) // output G
{
const auto dtq_o = output_dtq_maps[0];
const auto [q, _, p] = dtq_o.G.GetShape();
const auto G = (const real_t*)dtq_o.G;
if (G) { Gpu(MemcpyToSymbol)(Go, G, (p*q)*sizeof(real_t)); }
dbg("Loaded B and G to constant memory");
}
}), true);
// types
using qf_signature =
typename create_function_signature<decltype(&qfunc_t::operator())>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
restriction_cb(solutions_l, parameters_l, fields_e);
NVTX_INI("res=0");
residual_e = 0.0;
NVTX_END("res=0");
// auto wrapped_fields_e =
// wrap_fields(fields_e, shmem_info.field_sizes, num_entities);
const bool has_attr = attributes.Size() > 0;
const auto d_attr = attributes.Read();
const auto d_elem_attr = elem_attributes->Read();
// const int vdim = input.vdim;
// const auto fields_e_ptr = load_field_e_ptr(wrapped_fields_e, e);
// const real_t *field_e_r = fields_e_ptr[input_to_field[i]];
// const auto fields_e_ptr = load_field_e_ptr(wrapped_fields_e, e);
const int NE = num_entities;
constexpr int VDIM = 1;
const auto XE = Reshape(fields_e[0].Read(), d1d, d1d, d1d, VDIM, NE);
const real_t *dx_ptr = fields_e[1].Read();
auto YE = Reshape(residual_e.ReadWrite(), d1d, d1d, d1d, VDIM, NE);
const auto B = (const real_t*)input_dtq_maps[0/*i*/].B;
const auto G = (const real_t*)input_dtq_maps[0/*i*/].G;
NVTX_INI("forall");
dfem::forall<T_Q1D*T_Q1D*T_Q1D>([=] MFEM_HOST_DEVICE (int e, void *)
{
if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
constexpr int MQ1 = T_Q1D > 0 ? T_Q1D : 8;
MFEM_SHARED real_t sm0[MQ1][MQ1][MQ1][3];
MFEM_SHARED real_t sm1[MQ1][MQ1][MQ1][3];
// real_t (&sm0_ptr)[MQ1][MQ1][MQ1][3] = sm0;
// real_t (&sm1_ptr)[MQ1][MQ1][MQ1][3] = sm1;
low::regs3d_t<DIM, MQ1> reg;
const real_t *rd = dx_ptr;
// const auto fields_e_ptr = load_field_e_ptr(wrapped_fields_e, e);
MFEM_SHARED real_t sB[MQ1][MQ1], sG[MQ1][MQ1];
// real_t (&sB_ptr)[MD1][MQ1] = sB;
// real_t (&sG_ptr)[MD1][MQ1] = sG;
// Interpolate
// for_constexpr<num_inputs>(
// [ D1D, Q1D, MQ1, e,
// &input_dtq_maps,
// &sm0_ptr, &sm1_ptr,
// &sB = sB_ptr, &sG = sG_ptr,
// &inputs,
// // &fields_e_ptr,
// &reg, &rd,
// &input_to_field ] (auto i)
{
// const auto input = get<0/*i*/>(inputs);
// using field_operator_t = std::decay_t<decltype(input)>;
// if constexpr (is_gradient_fop<field_operator_t>::value) // Grad
{
// const int vdim = input.vdim;
// const real_t *field_e_r = fields_e_ptr[input_to_field[i]];
// const auto XE = Reshape(field_e_r, D1D, D1D, D1D, vdim);
// const auto sB = reinterpret_cast<const real_t (*)[MQ1]>(Bi[i]);
// const auto sG = reinterpret_cast<const real_t (*)[MQ1]>(Gi[i]);
low::LoadMatrix(d1d, q1d, B, sB);
low::LoadMatrix(d1d, q1d, G, sG);
// for (int c = 0; c < vdim; c++)
// constexpr int c = 0;
{
low::LoadDofs3d(e, d1d, XE, sm0);
low::Grad3d(d1d, q1d, sB, sG, sm0, sm1, reg);
}
}
// else if constexpr (is_identity_fop<field_operator_t>::value) // Identity
{
// db1("Identity");
// rd = fields_e_ptr[input_to_field[i]];
// rd = dx_ptr;
}
// else if constexpr (is_weight_fop<field_operator_t>::value) // Weight
// {
// dbg("Weight");
// rw = fields_e_ptr[input_to_field[i]]; // 🔥
// }
// else
{
// MFApply comes here
// assert(false);
// MFEM_ABORT("Only Grad and Identity field operators are supported");
}
}//); // for_constexpr<num_inputs>
MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
{
#if 0
auto qf_args = decay_tuple<qf_param_ts> {};
qf::apply_kernel<T_Q1D, num_inputs>
(reg, reg, rd, qx, qy, qz, qfunc, qf_args);
#elif 0
real_t v[3], u[3] = { reg[qz][qy][qx][0],
reg[qz][qy][qx][1],
reg[qz][qy][qx][2]
};
const auto *D = (real_t (*)[T_Q1D][T_Q1D][3][3]) rd;
kernels::Mult(3, 3, &D[qx][qy][qz][0][0], u, v);
reg[qz][qy][qx][0] = v[0];
reg[qz][qy][qx][1] = v[1];
reg[qz][qy][qx][2] = v[2];
#elif 0
const auto *D = (real_t (*)[T_Q1D][T_Q1D][3][3]) rd;
const auto args = decay_tuple<qf_param_ts>
{
{{ reg[qz][qy][qx][0], reg[qz][qy][qx][1], reg[qz][qy][qx][2] }},
{{
{{ D[qx][qy][qz][0][0], D[qx][qy][qz][0][1], D[qx][qy][qz][0][2] }},
{{ D[qx][qy][qz][1][0], D[qx][qy][qz][1][1], D[qx][qy][qz][1][2] }},
{{ D[qx][qy][qz][2][0], D[qx][qy][qz][2][1], D[qx][qy][qz][2][2] }}
}
}
};
const auto r = get<0>(apply(qfunc, args));
reg[qz][qy][qx][0] = r[0];
reg[qz][qy][qx][1] = r[1];
reg[qz][qy][qx][2] = r[2];
#elif 0
auto u = as_tensor<real_t, 3>(&reg[qz][qy][qx][0]);
const auto *d = (real_t (*)[T_Q1D][T_Q1D][3][3]) rd;
auto D = as_tensor<real_t, 3, 3>(&d[qx][qy][qz][0][0]);
auto r = D * u;
reg[qz][qy][qx][0] = r[0];
reg[qz][qy][qx][1] = r[1];
reg[qz][qy][qx][2] = r[2];
#else
auto args = decay_tuple<qf_param_ts> {};
get<0>(args) = as_tensor<real_t, 3>(&reg[qz][qy][qx][0]);
if constexpr (T_Q1D > 0)
{
get<1>(args) = as_tensor<real_t, 3, 3>(rd + 9*(qx*T_Q1D*T_Q1D + qy*T_Q1D + qz));
}
else
{
get<1>(args) = as_tensor<real_t, 3, 3>(rd + 9*(qx*q1d*q1d + qy*q1d + qz));
}
auto r = get<0>(apply(qfunc, args));
if constexpr (decltype(r)::ndim == 1)
{
as_tensor<real_t, 3>(&reg[qz][qy][qx][0]) = r;
}
else { static_assert(false); }
#endif
}
}
}
MFEM_SYNC_THREAD;
// Integrate
// if constexpr (is_gradient_fop<std::decay_t<output_fop_t>>::value) // Gradient
{
// const auto sB = reinterpret_cast<const real_t (*)[MQ1]>(Bo);
// const auto sG = reinterpret_cast<const real_t (*)[MQ1]>(Go);
low::GradTranspose3d(d1d, q1d, sB, sG, reg, sm1, sm0);
low::WriteDofs3d(d1d, 0, e, reg, YE);
}
},
num_entities, thread_blocks, 0, nullptr);
NVTX_END("forall");
NVTX_INI("out^T");
output_restriction_transpose(residual_e, residual_l);
NVTX_END("out^T");
}
using NewActionKernelType = decltype(&NewActionCallback::action_callback_new<>);
MFEM_REGISTER_KERNELS(NewActionCallbackKernels, NewActionKernelType, (int));
void Apply(const int d1d, const int q1d)
{
db1();
NewActionCallbackKernels::Run(q1d,
// args
d1d,
restriction_cb,
qfunc,
inputs,
input_to_field,
input_dtq_maps,
output_dtq_maps,
dimension,
num_entities,
test_vdim,
num_test_dof,
thread_blocks,
shmem_info,
attributes,
output_fop,
elem_attributes,
fields_e,
residual_e,
output_restriction_transpose,
solutions_l,
parameters_l,
residual_l,
// fallback arguments
q1d);
}
};
template<size_t num_fields, size_t num_inputs, size_t num_outputs,
typename restriction_cb_t, typename qfunc_t, typename input_t, typename output_fop_t>
template<int T_Q1D>
typename NewActionCallback<num_fields, num_inputs, num_outputs, restriction_cb_t, qfunc_t, input_t, output_fop_t>::NewActionKernelType
NewActionCallback<num_fields, num_inputs, num_outputs, restriction_cb_t, qfunc_t, input_t, output_fop_t>::NewActionCallbackKernels::Kernel()
{
return action_callback_new<T_Q1D>;
}
template<size_t num_fields, size_t num_inputs, size_t num_outputs,
typename restriction_cb_t, typename qfunc_t, typename input_t, typename output_fop_t>
typename NewActionCallback<num_fields, num_inputs, num_outputs, restriction_cb_t, qfunc_t, input_t, output_fop_t>::NewActionKernelType
NewActionCallback<num_fields, num_inputs, num_outputs, restriction_cb_t, qfunc_t, input_t, output_fop_t>::NewActionCallbackKernels::Fallback
(int q1d)
{
dbg("\x1b[33mFallback q1d:{}", q1d);
// MFEM_ABORT("No kernel for q1d=" << q1d);
// return nullptr;
return action_callback_new<>;
}
} // namespace mfem::future
-111
View File
@@ -1,111 +0,0 @@
#pragma once
#include "../util.hpp"
#include "../../integrator_ctx.hpp"
#include <utility>
namespace mfem::future
{
namespace GlobalQFImpl
{
template<
typename qfunc_t,
typename inputs_t,
typename outputs_t,
size_t ninputs = tuple_size<inputs_t>::value,
size_t noutputs = tuple_size<outputs_t>::value>
struct Action
{
Action(
IntegratorContext ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs) :
ctx(ctx),
qfunc(std::move(qfunc)),
inputs(inputs),
outputs(outputs)
{
create_fop_to_fd(inputs, ctx.infds, input_to_infd);
create_fop_to_fd(outputs, ctx.outfds, output_to_outfd);
check_consistency(inputs, input_to_infd, ctx.infds);
check_consistency(outputs, output_to_outfd, ctx.outfds);
create_fieldbases(inputs, input_to_infd, ctx.infds, ctx.ir, input_bases);
create_fieldbases(outputs, output_to_outfd, ctx.outfds, ctx.ir, output_bases);
create_qlayouts(inputs, ctx.in_qlayouts, input_qlayouts);
create_qlayouts(outputs, ctx.out_qlayouts, output_qlayouts);
const int nqp = ctx.ir.GetNPoints();
gnqp = nqp * ctx.nentities;
xq_offsets.SetSize(ninputs + 1);
xq_offsets[0] = 0;
constexpr_for<0, ninputs>([&](auto i)
{
const auto input = get<i>(inputs);
xq_offsets[i + 1] = nqp * input.size_on_qp * ctx.nentities;
});
xq_offsets.PartialSum();
xq.Update(xq_offsets);
yq_offsets.SetSize(noutputs + 1);
yq_offsets[0] = 0;
constexpr_for<0, noutputs>([&](auto i)
{
const auto output = get<i>(outputs);
yq_offsets[i + 1] = nqp * output.size_on_qp * ctx.nentities;
});
yq_offsets.PartialSum();
yq.Update(yq_offsets);
}
void operator()(
const std::vector<Vector *> &xe,
std::vector<Vector *> &ye) const
{
if (ctx.attr.Size() == 0) { return; }
// E -> Q
interpolate(input_to_infd, input_bases, xe, xq);
// Q -> Q
static_assert(
detail::supports_tensor_array_qfunc<qfunc_t, inputs_t, outputs_t>::value,
"qfunc signature not supported by default backend Action");
detail::call_qfunc(
qfunc, xq, yq, gnqp, input_qlayouts, output_qlayouts,
std::make_index_sequence<ninputs> {},
std::make_index_sequence<noutputs> {});
// Q -> E
integrate(output_to_outfd, output_bases, yq, ye);
}
IntegratorContext ctx;
qfunc_t qfunc;
inputs_t inputs;
outputs_t outputs;
std::array<size_t, ninputs> input_to_infd;
std::array<size_t, noutputs> output_to_outfd;
std::array<FieldBasis, ninputs> input_bases;
std::array<FieldBasis, noutputs> output_bases;
std::array<std::vector<int>, ninputs> input_qlayouts;
std::array<std::vector<int>, noutputs> output_qlayouts;
int gnqp = 0;
Array<int> xq_offsets, yq_offsets;
mutable BlockVector xq, yq;
};
}
}
@@ -1,131 +0,0 @@
#pragma once
#include "../fem/quadinterpolator.hpp"
#include "../../integrator_ctx.hpp"
#include "../util.hpp"
#include <utility>
namespace mfem::future
{
namespace GlobalQFImpl
{
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t,
size_t ninputs = tuple_size<inputs_t>::value,
size_t noutputs = tuple_size<outputs_t>::value>
struct DerivativeActionEnzyme
{
DerivativeActionEnzyme(
IntegratorContext ctx,
qfunc_t &qfunc,
inputs_t inputs,
outputs_t outputs) :
ctx(ctx),
qfunc(qfunc),
inputs(inputs),
outputs(outputs)
{
create_fop_to_fd(inputs, ctx.infds, input_to_infd);
create_fop_to_fd(outputs, ctx.outfds, output_to_outfd);
check_consistency(inputs, input_to_infd, ctx.infds);
check_consistency(outputs, output_to_outfd, ctx.outfds);
create_fieldbases(inputs, input_to_infd, ctx.infds, ctx.ir, input_bases);
create_fieldbases(outputs, output_to_outfd, ctx.outfds, ctx.ir, output_bases);
create_qlayouts(inputs, ctx.in_qlayouts, input_qlayouts);
create_qlayouts(outputs, ctx.out_qlayouts, output_qlayouts);
const int nqp = ctx.ir.GetNPoints();
gnqp = nqp * ctx.nentities;
xq_offsets.SetSize(ninputs + 1);
xq_offsets[0] = 0;
constexpr_for<0, ninputs>([&](auto i)
{
const auto input = get<i>(inputs);
xq_offsets[i + 1] = nqp * input.size_on_qp * ctx.nentities;
});
xq_offsets.PartialSum();
xq.Update(xq_offsets);
yq_offsets.SetSize(noutputs + 1);
yq_offsets[0] = 0;
constexpr_for<0, noutputs>([&](auto i)
{
const auto output = get<i>(outputs);
yq_offsets[i + 1] = nqp * output.size_on_qp * ctx.nentities;
});
yq_offsets.PartialSum();
yq.Update(yq_offsets);
// For each dependent input in the dependency map we create a shadow
// memory variable at the quadrature point level.
const auto activity_map = detail::make_activity_map<derivative_id>(inputs);
shadow_xq_offsets.SetSize(ninputs + 1);
shadow_xq_offsets = 0;
constexpr_for<0, ninputs>([&](auto i)
{
if (activity_map[i])
{
shadow_xq_offsets[i + 1] =
xq_offsets[i + 1] - xq_offsets[i];;
}
});
shadow_xq_offsets.PartialSum();
shadow_xq.Update(shadow_xq_offsets);
}
void operator()(
const std::vector<Vector *> &xe,
const Vector *de,
std::vector<Vector *> &ye) const
{
if (ctx.attr.Size() == 0) { return; }
// E -> Q
interpolate(input_to_infd, input_bases, xe, xq);
const auto activity_map = detail::make_activity_map<derivative_id>(inputs);
interpolate(input_to_infd, input_bases, xe, shadow_xq, activity_map);
// Q -> Q
static_assert(
detail::supports_tensor_array_qfunc<qfunc_t, inputs_t, outputs_t>::value,
"qfunc signature not supported by default backend Action");
detail::enzyme_fwddiff<derivative_id, qfunc_t, inputs_t, outputs_t>(
qfunc, xq, shadow_xq, yq, gnqp, input_qlayouts, output_qlayouts,
std::make_index_sequence<ninputs> {},
std::make_index_sequence<noutputs> {});
// Q -> E
integrate(output_to_outfd, output_bases, yq, ye);
}
IntegratorContext ctx;
qfunc_t &qfunc;
inputs_t inputs;
outputs_t outputs;
std::array<size_t, ninputs> input_to_infd;
std::array<size_t, noutputs> output_to_outfd;
std::array<FieldBasis, ninputs> input_bases;
std::array<FieldBasis, noutputs> output_bases;
std::array<std::vector<int>, ninputs> input_qlayouts;
std::array<std::vector<int>, noutputs> output_qlayouts;
int gnqp = 0;
Array<int> xq_offsets, shadow_xq_offsets, yq_offsets;
mutable BlockVector xq, shadow_xq, yq;
};
}
}
-42
View File
@@ -1,42 +0,0 @@
#pragma once
#include "action.hpp"
#include "derivative_action_enzyme.hpp"
namespace mfem::future
{
struct GlobalQFBackend
{
template<
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeAction(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs)
{
return GlobalQFImpl::Action(ctx, qfunc, inputs, outputs);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeDerivativeAction(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs)
{
return GlobalQFImpl::DerivativeActionEnzyme<
derivative_id, qfunc_t, inputs_t, outputs_t>(
ctx, qfunc, inputs, outputs);
}
};
}
-166
View File
@@ -1,166 +0,0 @@
#pragma once
#include "../util.hpp"
#include "../../integrator_ctx.hpp"
#include <utility>
namespace mfem::future
{
namespace LocalQFImpl
{
template<
typename qfunc_t,
typename inputs_t,
typename outputs_t,
size_t ninputs = tuple_size<inputs_t>::value,
size_t noutputs = tuple_size<outputs_t>::value>
struct Action
{
Action(
IntegratorContext ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs) :
ctx(ctx),
qfunc(std::move(qfunc)),
inputs(inputs),
outputs(outputs)
{
create_fop_to_fd(inputs, ctx.infds, input_to_infd);
create_fop_to_fd(outputs, ctx.outfds, output_to_outfd);
check_consistency(inputs, input_to_infd, ctx.infds);
check_consistency(outputs, output_to_outfd, ctx.outfds);
const int nqp = ctx.ir.GetNPoints();
// Initialize DofToQuad maps for inputs
for_constexpr<ninputs>([&](auto i)
{
const auto &fd = ctx.infds[input_to_infd[i]];
std::visit([&](auto* space_ptr)
{
using T = std::decay_t<decltype(*space_ptr)>;
if constexpr (std::is_same_v<T, FiniteElementSpace> ||
std::is_same_v<T, ParFiniteElementSpace>)
{
const auto *fe = space_ptr->GetTypicalFE();
input_dtq_maps[i] = &fe->GetDofToQuad(ctx.ir, DofToQuad::TENSOR);
}
}, fd.data);
});
// Initialize DofToQuad maps for outputs
for_constexpr<noutputs>([&](auto i)
{
const auto &fd = ctx.outfds[output_to_outfd[i]];
std::visit([&](auto* space_ptr)
{
using T = std::decay_t<decltype(*space_ptr)>;
if constexpr (std::is_same_v<T, FiniteElementSpace> ||
std::is_same_v<T, ParFiniteElementSpace>)
{
const auto *fe = space_ptr->GetTypicalFE();
output_dtq_maps[i] = &fe->GetDofToQuad(ctx.ir, DofToQuad::TENSOR);
}
}, fd.data);
});
}
void operator()(
const std::vector<Vector *> &xe,
std::vector<Vector *> &ye) const
{
if (ctx.attr.Size() == 0) { return; }
// input_dtq_maps
// const auto B = (const real_t*)input_dtq_maps[0/*i*/].B;
// const auto G = (const real_t*)input_dtq_maps[0/*i*/].G;
// dfem::forall<T_Q1D*T_Q1D*T_Q1D>([=] MFEM_HOST_DEVICE (int e, void *)
// {
// if (has_attr && !d_attr[d_elem_attr[e] - 1]) { return; }
// constexpr int MQ1 = T_Q1D > 0 ? T_Q1D : 8;
// MFEM_SHARED real_t sm0[MQ1][MQ1][MQ1][3];
// MFEM_SHARED real_t sm1[MQ1][MQ1][MQ1][3];
// low::regs3d_t<DIM, MQ1> reg;
// const real_t *rd = dx_ptr;
// MFEM_SHARED real_t sB[MQ1][MQ1], sG[MQ1][MQ1];
// {
// low::LoadMatrix(d1d, q1d, B, sB);
// low::LoadMatrix(d1d, q1d, G, sG);
// {
// low::LoadDofs3d(e, d1d, XE, sm0);
// low::Grad3d(d1d, q1d, sB, sG, sm0, sm1, reg);
// }
// }
// // else if constexpr (is_identity_fop<field_operator_t>::value) // Identity
// {
// // db1("Identity");
// // rd = fields_e_ptr[input_to_field[i]];
// // rd = dx_ptr;
// }
// }
// MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
// {
// MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
// {
// MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
// {
// auto args = decay_tuple<qf_param_ts> {};
// get<0>(args) = as_tensor<real_t, 3>(&reg[qz][qy][qx][0]);
// if constexpr (T_Q1D > 0)
// {
// get<1>(args) = as_tensor<real_t, 3, 3>(rd + 9*(qx*T_Q1D*T_Q1D + qy*T_Q1D + qz));
// }
// else
// {
// get<1>(args) = as_tensor<real_t, 3, 3>(rd + 9*(qx*q1d*q1d + qy*q1d + qz));
// }
// auto r = get<0>(apply(qfunc, args));
// if constexpr (decltype(r)::ndim == 1)
// {
// as_tensor<real_t, 3>(&reg[qz][qy][qx][0]) = r;
// }
// else { static_assert(false); }
// }
// }
// }
// MFEM_SYNC_THREAD;
// // Integrate
// // if constexpr (is_gradient_fop<std::decay_t<output_fop_t>>::value) // Gradient
// {
// // const auto sB = reinterpret_cast<const real_t (*)[MQ1]>(Bo);
// // const auto sG = reinterpret_cast<const real_t (*)[MQ1]>(Go);
// low::GradTranspose3d(d1d, q1d, sB, sG, reg, sm1, sm0);
// low::WriteDofs3d(d1d, 0, e, reg, YE);
// }
// },
// num_entities, thread_blocks, 0, nullptr);
}
IntegratorContext ctx;
qfunc_t qfunc;
inputs_t inputs;
outputs_t outputs;
std::array<size_t, ninputs> input_to_infd;
std::array<size_t, noutputs> output_to_outfd;
std::array<const DofToQuad*, ninputs> input_dtq_maps;
std::array<const DofToQuad*, noutputs> output_dtq_maps;
};
}
}
-39
View File
@@ -1,39 +0,0 @@
#pragma once
#include "../../integrator_ctx.hpp"
#include "action.hpp"
namespace mfem::future
{
struct LocalQFBackend
{
template<
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeAction(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs)
{
return LocalQFImpl::Action(ctx, qfunc, inputs, outputs);
}
template<
int derivative_id,
typename qfunc_t,
typename inputs_t,
typename outputs_t>
auto static MakeDerivativeAction(
const IntegratorContext &ctx,
qfunc_t qfunc,
inputs_t inputs,
outputs_t outputs)
{
MFEM_ABORT("LocalQFBackend does not support derivative actions.");
}
};
}
-659
View File
@@ -1,659 +0,0 @@
#pragma once
#include "../fem/quadinterpolator.hpp"
#include "../util.hpp"
#include "general/enzyme.hpp"
namespace mfem::future
{
template <size_t N, size_t... Is>
constexpr std::array<bool, N> all_true_impl(std::index_sequence<Is...>)
{
return {{((void)Is, true)...}};
}
template <size_t N>
constexpr std::array<bool, N> all_true()
{
return all_true_impl<N>(std::make_index_sequence<N> {});
}
struct FieldBasis
{
// E-vector -> Q-vector
std::function<void(const Vector &, Vector &)> forward;
// Q-vector -> E-vector
std::function<void(const Vector &, Vector &)> transpose;
};
inline FieldBasis FromQI(const QuadratureInterpolator *qi,
QuadratureInterpolator::EvalFlags mode)
{
return
{
[qi, mode](const Vector &xe, Vector &xq)
{
qi->SetOutputLayout(QVectorLayout::byVDIM);
if (mode == QuadratureInterpolator::VALUES)
{
qi->Values(xe, xq);
}
else
{
qi->Derivatives(xe, xq);
}
},
[qi, mode](const Vector &yq, Vector &ye)
{
Vector empty;
qi->SetOutputLayout(QVectorLayout::byVDIM);
if (mode == QuadratureInterpolator::VALUES)
{
qi->AddMultTranspose(QuadratureInterpolator::VALUES, yq, empty, ye);
}
else
{
qi->AddMultTranspose(QuadratureInterpolator::DERIVATIVES, empty, yq, ye);
}
}
};
}
// QuadratureFunction identity copy
inline FieldBasis FromQF()
{
return
{
[](const Vector &xe, Vector &xq) { xq = xe; },
[](const Vector &yq, Vector &ye) { ye = yq; }
};
}
// User-defined parameter space B
inline FieldBasis FromPS(const Operator *B, const Operator *Bt)
{
return
{
[B](const Vector &xe, Vector &xq) { B->Mult(xe, xq); },
[Bt](const Vector &yq, Vector &ye) { Bt->Mult(yq, ye); }
};
}
inline FieldBasis FieldBasisFromWeight(const IntegrationRule &ir)
{
return
{
[&ir](const Vector &, Vector &xq)
{
const int nqp = ir.GetNPoints();
MFEM_ASSERT(xq.Size() % nqp == 0, "weight block has unexpected size");
const int ne = xq.Size() / nqp;
const real_t *wref = ir.GetWeights().Read();
for (int e = 0; e < ne; e++)
{
std::memcpy(xq.GetData() + e*nqp, wref, nqp*sizeof(real_t));
}
},
[](const Vector &, Vector &) {}
};
}
inline const FieldBasis GetFieldBasis(const FieldDescriptor &f,
const IntegrationRule &ir,
QuadratureInterpolator::EvalFlags mode)
{
return std::visit([&ir, &mode](auto && arg) -> FieldBasis
{
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, const FiniteElementSpace *>)
{
return FromQI(arg->GetQuadratureInterpolator(ir), mode);
}
else if constexpr (std::is_same_v<T, const ParFiniteElementSpace *>)
{
return FromQI(arg->GetQuadratureInterpolator(ir), mode);
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return FromQF();
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return FromPS(arg->GetB(), arg->GetBt());
}
else if constexpr (std::is_same_v<T, const IntegrationRule *>)
{
return FieldBasis{};
}
else
{
static_assert(dfem::always_false<T>, "internal error");
}
}, f.data);
}
template <typename fops_t, size_t nfops>
void create_fieldbases(
fops_t &fops,
const std::array<size_t, nfops> &fop_to_fd,
const std::vector<FieldDescriptor> &fds,
const IntegrationRule &ir,
std::array<FieldBasis, nfops> &bases)
{
constexpr_for<0, nfops>([&](auto i)
{
const auto fop = get<i>(fops);
using fop_t = std::decay_t<decltype(fop)>;
const auto fd = fds[fop_to_fd[i]];
constexpr QuadratureInterpolator::EvalFlags dummy_mode =
QuadratureInterpolator::VALUES;
if constexpr (is_identity_fop<fop_t>::value)
{
bases[i] = GetFieldBasis(fd, ir, dummy_mode);
}
else if constexpr (is_weight_fop<fop_t>::value)
{
bases[i] = FieldBasisFromWeight(ir);
}
else if constexpr (is_value_fop<fop_t>::value)
{
bases[i] = GetFieldBasis(fd, ir, QuadratureInterpolator::VALUES);
}
else if constexpr (is_gradient_fop<fop_t>::value)
{
bases[i] = GetFieldBasis(fd, ir, QuadratureInterpolator::DERIVATIVES);
}
});
}
template <typename fops_t, size_t nfops>
void check_consistency(
fops_t &fops,
const std::array<size_t, nfops> &fop_to_fd,
const std::vector<FieldDescriptor> &fields)
{
constexpr_for<0, nfops>([&](auto i)
{
const auto input = get<i>(fops);
using input_t = std::decay_t<decltype(input)>;
const auto fd = fields[fop_to_fd[i]];
if constexpr (is_identity_fop<input_t>::value)
{
MFEM_ASSERT(std::holds_alternative<const QuadratureFunction *>(fd.data),
"Identity FieldOperator requested on non "
"QuadratureFunction");
}
else if constexpr (is_weight_fop<input_t>::value)
{
}
else if constexpr (is_value_fop<input_t>::value)
{
MFEM_ASSERT(std::holds_alternative<const FiniteElementSpace *>(fd.data) ||
std::holds_alternative<const ParFiniteElementSpace *>(fd.data) ||
std::holds_alternative<const ParameterSpace *>(fd.data),
"Value FieldOperator requested on non "
"QuadratureFunction");
}
else if constexpr (is_gradient_fop<input_t>::value)
{
MFEM_ASSERT(std::holds_alternative<const FiniteElementSpace *>(fd.data) ||
std::holds_alternative<const ParFiniteElementSpace *>(fd.data),
"Value FieldOperator requested on non "
"QuadratureFunction");
}
});
}
template <size_t ninputs>
void interpolate(
const std::array<size_t, ninputs> &input_to_infd,
const std::array<FieldBasis, ninputs> &input_bases,
const std::vector<Vector *> &xe,
BlockVector &xq,
const std::array<bool, ninputs> &conditional = all_true<ninputs>())
{
constexpr_for<0, ninputs>([&](auto i)
{
if (!conditional.empty() && !conditional[i]) { return; }
input_bases[i].forward(*xe[input_to_infd[i]], xq.GetBlock(i));
});
}
template <size_t noutputs>
void integrate(
const std::array<size_t, noutputs> &output_to_outfd,
const std::array<FieldBasis, noutputs> &output_bases,
const BlockVector &yq,
std::vector<Vector *> &ye)
{
for (auto v : ye) { *v = 0.0; }
constexpr_for<0, noutputs>([&](auto i)
{
output_bases[i].transpose(yq.GetBlock(i), *ye[output_to_outfd[i]]);
});
}
namespace detail
{
template <typename T>
struct is_tensor_array : std::false_type {};
template <typename scalar_t, int... Dims>
struct is_tensor_array<tensor_array<scalar_t, Dims...>> : std::true_type {};
template <typename T>
struct is_tensor_array_mut : std::false_type {};
template <typename scalar_t, int... Dims>
struct is_tensor_array_mut<tensor_array<scalar_t, Dims...>> :
std::bool_constant<!std::is_const_v<scalar_t>> {};
template <typename ndarray_t>
inline void set_layout_default(ndarray_t &a)
{
if constexpr (ndarray_t::tensor_rank() == 0) { return; }
constexpr std::size_t nd = ndarray_t::rank();
constexpr std::size_t td = ndarray_t::tensor_rank();
std::array<std::size_t, nd + td> perm{};
for (std::size_t i = 0; i < td; i++) { perm[i] = nd + i; }
for (std::size_t i = 0; i < nd; i++) { perm[td + i] = i; }
a.set_layout(perm);
}
template <typename ndarray_t>
inline void set_layout(ndarray_t& a, const std::vector<int>& layout)
{
if constexpr (ndarray_t::tensor_rank() == 0) { return; }
constexpr std::size_t nd = ndarray_t::rank();
constexpr std::size_t td = ndarray_t::tensor_rank();
constexpr std::size_t N = nd + td;
// missing means default
if (layout.empty()) { set_layout_default(a); return; }
MFEM_VERIFY(layout.size() == N,
"layout size mismatch: expected " << N << " got " << layout.size());
// TODO: make a version of set_layout that takes `std::vector<int>`
std::array<std::size_t, N> perm{};
for (std::size_t i = 0; i < N; i++)
{
MFEM_VERIFY(layout[i] >= 0, "layout index must be >=0");
perm[i] = static_cast<std::size_t>(layout[i]);
}
a.set_layout(perm);
}
/// Primary template: intentionally undefined — gives a clear error for unsupported types.
template <typename T>
struct tensor_array_traits;
/// Matches tensor<scalar_t, sizes...>
template <typename scalar_t, int... sizes>
struct tensor_array_traits<tensor<scalar_t, sizes...>>
{
using scalar_type = scalar_t;
template <std::size_t ndims>
using array_type = tensor_ndarray<scalar_t, ndims, sizes...>;
};
/// Matches tensor_ndarray<scalar_t, ndims, tensor_sizes...>
template <typename scalar_t, int ndims, int... tensor_sizes>
struct tensor_array_traits<tensor_ndarray<scalar_t, ndims, tensor_sizes...>>
{
using scalar_type = scalar_t;
template <std::size_t N>
using array_type = tensor_ndarray<scalar_t, N, tensor_sizes...>;
};
/// Entry point: explicit tensor type T as template argument.
template <typename T, typename ptr_scalar_t, typename... dyn_sizes_t>
decltype(auto) make_tensor_array(ptr_scalar_t *ptr,
const std::vector<int>* layout,
dyn_sizes_t... dynamic_sizes)
{
using traits = tensor_array_traits<T>;
using array_t = typename traits::template array_type<sizeof...(dynamic_sizes)>;
auto a = array_t(ptr, {std::size_t(dynamic_sizes)...});
if (layout) { set_layout(a, *layout); }
else { set_layout_default(a); }
return a;
}
template <typename qfunc_t, typename inputs_t, typename outputs_t>
struct supports_tensor_array_qfunc
{
using qf_signature = typename get_function_signature<qfunc_t>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
static constexpr int ninputs = tuple_size<inputs_t>::value;
static constexpr int noutputs = tuple_size<outputs_t>::value;
static constexpr int nparams = tuple_size<qf_param_ts>::value;
template <std::size_t... Is>
static constexpr bool InputsOk(std::index_sequence<Is...>)
{
return (is_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<Is, qf_param_ts>::type>>>::value && ...);
}
template <std::size_t... Is>
static constexpr bool OutputsOk(std::index_sequence<Is...>)
{
return (is_tensor_array_mut<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<ninputs + Is, qf_param_ts>::type>>>::value && ...);
}
static constexpr bool value =
(nparams == ninputs + noutputs) &&
InputsOk(std::make_index_sequence<ninputs> {}) &&
OutputsOk(std::make_index_sequence<noutputs> {});
};
template <typename qfunc_t, std::size_t... Is, std::size_t... Os>
inline void call_qfunc(
const qfunc_t &qfunc,
const BlockVector &xq,
BlockVector &yq,
int gnqp,
const std::array<std::vector<int>, sizeof...(Is)>& in_layouts,
const std::array<std::vector<int>, sizeof...(Os)>& out_layouts,
std::index_sequence<Is...>,
std::index_sequence<Os...>)
{
constexpr std::size_t ninputs = sizeof...(Is);
using qf_signature = typename get_function_signature<qfunc_t>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
auto inputs = std::make_tuple(
make_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<Is, qf_param_ts>::type>>>(
xq.GetBlock(Is).Read(), &in_layouts[Is], gnqp)...);
auto outputs = std::make_tuple(
make_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<ninputs + Os, qf_param_ts>::type>>>(
yq.GetBlock(Os).ReadWrite(), &out_layouts[Os], gnqp)...);
std::apply([&](auto&&... args)
{
qfunc(args...);
}, std::tuple_cat(inputs, outputs));
}
template <typename func_t, typename... arg_ts>
MFEM_HOST_DEVICE inline
auto qfunction_wrapper(const func_t &f, arg_ts...args)
{
return f(args...);
}
template <std::size_t derivative_id, std::size_t I, typename Tuple, std::size_t... Is>
constexpr std::array<bool, sizeof...(Is)>
make_activity_array(std::index_sequence<Is...>)
{
return { (std::decay_t<typename tuple_element<Is, Tuple>::type>::GetFieldId() == derivative_id)... };
}
template <std::size_t derivative_id, typename inputs_t, std::size_t... Is>
constexpr auto make_activity_map_impl(std::index_sequence<Is...>)
{
constexpr std::size_t N = sizeof...(Is);
if constexpr (N == 0)
return std::array<bool, 0> {};
return make_activity_array<derivative_id, 0, inputs_t>
(std::make_index_sequence<N> {});
}
template <std::size_t derivative_id, typename inputs_t>
constexpr auto make_activity_map(inputs_t)
{
return make_activity_map_impl<derivative_id, inputs_t>(
std::make_index_sequence<tuple_size<inputs_t>::value> {});
}
namespace enzyme_detail
{
template <auto wrapper_fn, typename qf_return_t, typename... AccArgs>
__attribute__((always_inline)) inline void
do_enzyme_call(AccArgs... acc)
{
__enzyme_fwddiff<qf_return_t>(wrapper_fn, acc...);
}
template <auto wrapper_fn, typename qf_return_t,
size_t CurO, size_t NO,
typename primals_t, typename derivs_t,
typename... AccArgs>
__attribute__((always_inline)) inline void
process_outputs(primals_t &primals, derivs_t &derivs, AccArgs... acc)
{
if constexpr (CurO == NO)
{
do_enzyme_call<wrapper_fn, qf_return_t>(acc...);
}
else
{
process_outputs<wrapper_fn, qf_return_t, CurO + 1, NO>(
primals, derivs,
acc...,
enzyme_dupnoneed,
&std::get<CurO>(primals),
&std::get<CurO>(derivs));
}
}
template <auto wrapper_fn, typename qf_return_t,
size_t CurI, size_t NI, bool... ActivityMap,
typename inputs_t, typename shadows_t,
typename primals_t, typename derivs_t,
typename... AccArgs>
__attribute__((always_inline)) inline void
process_inputs(inputs_t &inputs, shadows_t &shadows,
primals_t &primals, derivs_t &derivs,
AccArgs... acc)
{
if constexpr (CurI == NI)
{
constexpr size_t NO = std::tuple_size_v<primals_t>;
process_outputs<wrapper_fn, qf_return_t, 0, NO>(
primals, derivs, acc...);
}
else
{
constexpr bool active =
std::array<bool, sizeof...(ActivityMap)> {ActivityMap...} [CurI];
if constexpr (active)
{
std::cout << "Input[" << CurI << "]: ACTIVE (enzyme_dup)\n"
<< " primal ptr type: "
<< get_type_name<decltype(&std::get<CurI>(inputs))>() << "\n"
<< " shadow ptr type: "
<< get_type_name<decltype(&std::get<CurI>(shadows))>() << "\n";
}
else
{
std::cout << "Input[" << CurI << "]: INACTIVE (enzyme_const)\n"
<< " primal ptr type: "
<< get_type_name<decltype(&std::get<CurI>(inputs))>() << "\n";
}
if constexpr (active)
{
process_inputs<wrapper_fn, qf_return_t, CurI + 1, NI, ActivityMap...>(
inputs, shadows, primals, derivs,
acc...,
enzyme_dup,
&std::get<CurI>(inputs),
&std::get<CurI>(shadows));
}
else
{
process_inputs<wrapper_fn, qf_return_t, CurI + 1, NI, ActivityMap...>(
inputs, shadows, primals, derivs,
acc...,
enzyme_const,
&std::get<CurI>(inputs));
}
}
}
} // namespace enzyme_detail
template <size_t derivative_id, typename qfunc_t, typename inputs_t, typename outputs_t,
std::size_t... Is, std::size_t... Os>
inline void enzyme_fwddiff(
qfunc_t &qfunc,
const BlockVector &xq,
const BlockVector &shadow_xq,
BlockVector &yq,
const int &gnqp,
const std::array<std::vector<int>, sizeof...(Is)>& in_layouts,
const std::array<std::vector<int>, sizeof...(Os)>& out_layouts,
std::index_sequence<Is...>,
std::index_sequence<Os...>)
{
#ifdef MFEM_USE_ENZYME
constexpr std::size_t ninputs = sizeof...(Is);
constexpr std::size_t noutputs = sizeof...(Os);
using qf_signature = typename get_function_signature<qfunc_t>::type;
using qf_param_ts = typename qf_signature::parameter_ts;
using qf_return_t = typename qf_signature::return_t;
constexpr auto activity_map = make_activity_map<derivative_id>(inputs_t{});
static_assert(activity_map.size() == ninputs, "activity map size mismatch");
std::cout << "activity_map: ";
for (const auto &v : activity_map)
{
std::cout << v << " ";
}
std::cout << "\n";
auto inputs = std::make_tuple(
make_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<Is, qf_param_ts>::type>>>(
xq.GetBlock(Is).Read(), &in_layouts[Is], gnqp)...);
auto shadows = std::make_tuple(
make_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<Is, qf_param_ts>::type>>>(
shadow_xq.GetBlock(Is).Read(), &in_layouts[Is], gnqp)...);
std::array<Vector, noutputs> primal_storage;
((primal_storage[Os].SetSize(yq.GetBlock(Os).Size())), ...);
auto primals_out = std::make_tuple(
make_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<ninputs + Os, qf_param_ts>::type>>>(
primal_storage[Os].ReadWrite(), &out_layouts[Os], gnqp)...);
auto derivs_out = std::make_tuple(
make_tensor_array<std::remove_cv_t<std::remove_reference_t<
typename tuple_element<ninputs + Os, qf_param_ts>::type>>>(
yq.GetBlock(Os).ReadWrite(), &out_layouts[Os], gnqp)...);
using wrapper_fn_t = qf_return_t (*)(
const qfunc_t &,
std::remove_reference_t<decltype(std::get<Is>(inputs))>...,
std::remove_reference_t<decltype(std::get<Os>(primals_out))>...);
constexpr wrapper_fn_t wrapper_fn =
qfunction_wrapper<qfunc_t,
std::remove_reference_t<decltype(std::get<Is>(inputs))>...,
std::remove_reference_t<decltype(std::get<Os>(primals_out))>...>;
// wrapper_fn travels as a non-type template parameter throughout without
// being stored.
enzyme_detail::process_inputs<
wrapper_fn,
qf_return_t,
0,
ninputs,
activity_map[Is]...
>(inputs, shadows,
primals_out, derivs_out,
enzyme_const, &qfunc // seed: qfunc is always inactive
);
#else
MFEM_ABORT("enzyme_fwddiff requires MFEM_USE_ENZYME");
#endif
}
} // namespace detail
// Create quadrature function fop to fields map
template <typename fops_t, size_t N = tuple_size<fops_t>::value, size_t M>
void create_fop_to_fd(const fops_t &fops,
const std::vector<FieldDescriptor> &fields,
std::array<size_t, M> &fop_to_fd)
{
static_assert(N == M, "sizes must match");
constexpr_for<0, N>([&](auto i)
{
const auto fop = get<i>(fops);
fop_to_fd[i] = std::numeric_limits<size_t>::max();
for (size_t j = 0; j < fields.size(); j++)
{
// TODO: output.GetFieldId() should probably store/return size_t
if (static_cast<int>(fields[j].id) == fop.GetFieldId())
{
fop_to_fd[i] = j;
}
}
// Handle Weight type. There is no FieldDescriptor for the weight.
// TODO: Create weight descriptor for the weight for internal use?
// TODO: this is a hack...
if (is_weight_fop<std::remove_cv_t<decltype(fop)>>::value)
{
fop_to_fd[i] = 0;
}
else if (fop_to_fd[i] == std::numeric_limits<size_t>::max())
{
MFEM_ABORT("not found");
}
});
}
template <typename fops_t, size_t nfops>
void create_qlayouts(const fops_t &fops,
const std::unordered_map<std::type_index, std::vector<int>> &a,
std::array<std::vector<int>, nfops> &b)
{
constexpr_for<0, nfops>([&](auto i)
{
using fop_t =
std::remove_cv_t<std::remove_reference_t<decltype(get<i>(fops))>>;
auto it = a.find(std::type_index(typeid(fop_t)));
if (it != a.end()) { b[i] = it->second; }
else { b[i].clear(); }
});
}
}
+23 -98
View File
@@ -11,119 +11,44 @@
#include "doperator.hpp"
#include <algorithm>
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
void DifferentiableOperator::SetParameters(std::vector<Vector *> p) const
{
MFEM_ASSERT(parameters.size() == p.size(),
"number of parameters doesn't match descriptors");
for (size_t i = 0; i < parameters.size(); i++)
{
p[i]->Read();
parameters_l[i] = *p[i];
}
}
DifferentiableOperator::DifferentiableOperator(
const std::vector<FieldDescriptor> &infds,
const std::vector<FieldDescriptor> &outfds,
const std::vector<FieldDescriptor> &solutions,
const std::vector<FieldDescriptor> &parameters,
const ParMesh &mesh) :
Operator(),
mesh(mesh),
infds(infds),
outfds(outfds)
solutions(solutions),
parameters(parameters)
{
unionfds.clear();
unionfds.insert(unionfds.end(), infds.begin(), infds.end());
unionfds.insert(unionfds.end(), outfds.begin(), outfds.end());
std::sort(unionfds.begin(), unionfds.end());
auto last = std::unique(unionfds.begin(), unionfds.end());
unionfds.erase(last, unionfds.end());
fields.resize(solutions.size() + parameters.size());
fields_e.resize(fields.size());
solutions_l.resize(solutions.size());
parameters_l.resize(parameters.size());
infields_l.resize(infds.size());
for (size_t i = 0; i < infds.size(); i++)
for (size_t i = 0; i < solutions.size(); i++)
{
infields_l[i] = new Vector(GetVSize(infds[i]));
fields[i] = solutions[i];
}
infields_e.resize(infds.size());
}
void DifferentiableOperator::SetMultLevel(MultLevel level)
{
mult_level = level;
}
void DifferentiableOperator::Mult(const Vector &x, Vector &y) const
{
MFEM_ASSERT(!action_callbacks.empty(),
"no integrators have been set");
MFEM_ASSERT(dynamic_cast<const BlockVector*>(&x),
"x needs to be a BlockVector");
MFEM_ASSERT(dynamic_cast<const BlockVector*>(&y),
"y needs to be a BlockVector");
const auto &bx = static_cast<const BlockVector &>(x);
auto &by = static_cast<BlockVector &>(y);
Mult(bx, by);
}
void DifferentiableOperator::DisableTensorProductStructure(bool disable)
{
use_tensor_product_structure = !disable;
}
std::shared_ptr<DerivativeOperator> DifferentiableOperator::GetDerivative(
size_t derivative_id, const Vector &x)
{
MFEM_ASSERT(derivative_action_callbacks.find(derivative_id) !=
derivative_action_callbacks.end(),
"no derivative action has been found for ID " << derivative_id);
const size_t dfidx = FindIdx(derivative_id, infds);
// Get transpose callbacks if available, otherwise pass empty vector
std::vector<derivative_action_t> transpose_callbacks;
auto it = daction_transpose_callbacks.find(derivative_id);
if (it != daction_transpose_callbacks.end())
for (size_t i = 0; i < parameters.size(); i++)
{
transpose_callbacks = it->second;
fields[i + solutions.size()] = parameters[i];
}
return std::make_shared<DerivativeOperator>(
height,
GetTrueVSize(infds[dfidx]),
derivative_action_callbacks[derivative_id],
transpose_callbacks,
infds[dfidx],
x,
infds,
outfds);
}
std::shared_ptr<DerivativeOperator> DifferentiableOperator::GetDerivative(
size_t derivative_id, const MultiVector &x)
{
MFEM_ASSERT(derivative_action_callbacks.find(derivative_id) !=
derivative_action_callbacks.end(),
"no derivative action has been found for ID " << derivative_id);
const size_t dfidx = FindIdx(derivative_id, infds);
// Get transpose callbacks if available, otherwise pass empty vector
std::vector<derivative_action_t> transpose_callbacks;
auto it = daction_transpose_callbacks.find(derivative_id);
if (it != daction_transpose_callbacks.end())
{
transpose_callbacks = it->second;
}
return std::make_shared<DerivativeOperator>(
height,
GetTrueVSize(infds[dfidx]),
derivative_action_callbacks[derivative_id],
transpose_callbacks,
infds[dfidx],
x,
infds,
outfds);
}
#endif // MFEM_USE_MPI
+897 -237
View File
File diff suppressed because it is too large Load Diff
-63
View File
@@ -1,63 +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.
#pragma once
#include "../fespace.hpp"
#include "parameterspace.hpp"
namespace mfem::future
{
/// @brief FieldDescriptor struct
///
/// This struct is used to store information about a field.
struct FieldDescriptor
{
using data_variant_t =
std::variant<const FiniteElementSpace *,
const ParFiniteElementSpace *,
const QuadratureFunction *,
const ParameterSpace *>;
/// Field ID
std::size_t id;
/// Field variant
data_variant_t data;
/// Default constructor
FieldDescriptor() :
id(SIZE_MAX), data(data_variant_t{}) {}
/// Constructor
template <typename T>
FieldDescriptor(std::size_t field_id, const T* v) :
id(field_id), data(v) {}
bool operator==(const FieldDescriptor& other) const
{
return id == other.id;
}
bool operator<(const FieldDescriptor& other) const
{
return id < other.id;
}
friend void swap(FieldDescriptor& a, FieldDescriptor& b)
{
using std::swap;
swap(a.id, b.id);
swap(a.data, b.data);
}
};
}
-22
View File
@@ -1,22 +0,0 @@
#pragma once
#include "util.hpp"
namespace mfem::future
{
struct IntegratorContext
{
const ParMesh &mesh;
const Array<int> *elem_attr;
Array<int> attr;
int nentities;
const std::vector<FieldDescriptor> &infds;
const std::vector<FieldDescriptor> &outfds;
const std::vector<FieldDescriptor> &unionfds;
const IntegrationRule &ir;
std::unordered_map<std::type_index, std::vector<int>> &in_qlayouts;
std::unordered_map<std::type_index, std::vector<int>> &out_qlayouts;
};
}
+9 -94
View File
@@ -9,23 +9,8 @@
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#pragma once
// #define NVTX_COLOR nvtx::kPeru
#include "util.hpp"
#include "fem/kernels.hpp"
///////////////////////////////////////////////////////////////////////////////
template <class T>
inline std::enable_if_t<!std::numeric_limits<T>::is_integer, bool>
AlmostEq(T x, T y, T tolerance = 15.0 * std::numeric_limits<T>::epsilon())
{
const T neg = std::abs(x - y);
constexpr T min = std::numeric_limits<T>::min();
constexpr T eps = std::numeric_limits<T>::epsilon();
const T min_abs = std::min(std::abs(x), std::abs(y));
if (std::abs(min_abs) == 0.0) { return neg < eps; }
return (neg / (1.0 + std::max(min, min_abs))) < tolerance;
}
namespace mfem::future
{
@@ -45,7 +30,6 @@ void map_field_to_quadrature_data_tensor_product_3d(
if constexpr (is_value_fop<std::decay_t<field_operator_t>>::value)
{
dbg("Value");
auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const auto field = Reshape(&field_e[0], d1d, d1d, d1d, vdim);
@@ -110,11 +94,10 @@ void map_field_to_quadrature_data_tensor_product_3d(
else if constexpr (
is_gradient_fop<std::decay_t<field_operator_t>>::value)
{
// dbg("Gradient");
const auto [q1d, B_dim, d1d] = B.GetShape();
const auto [q1d, unused, d1d] = B.GetShape();
const int vdim = input.vdim;
const int dim = input.dim;
const auto field = Reshape(&std::as_const(field_e[0]), d1d, d1d, d1d, vdim);
const auto field = Reshape(&field_e[0], d1d, d1d, d1d, vdim);
auto fqp = Reshape(&field_qp[0], vdim, dim, q1d, q1d, q1d);
auto s0 = Reshape(&scratch_mem[0](0), d1d, d1d, q1d);
@@ -123,30 +106,7 @@ void map_field_to_quadrature_data_tensor_product_3d(
auto s3 = Reshape(&scratch_mem[3](0), d1d, q1d, q1d);
auto s4 = Reshape(&scratch_mem[4](0), d1d, q1d, q1d);
// constexpr int MQ1 = T_Q1D > 0 ? T_Q1D : 8;
// static constexpr int DIM = 3;
// MFEM_VERIFY(q1d <= MQ1, "q1d > MQ1");
// MFEM_SHARED real_t smem[MQ1][MQ1];
// kernels::internal::d_regs3d_t<DIM, MQ1> r0, r1;
// real_t sB[MQ1][MQ1], sG[MQ1][MQ1];
/*
{
assert(B_dim == 1 && "1D B required!");
kernels::internal::LoadMatrix(d1d, q1d, B, sB);
kernels::internal::LoadMatrix(d1d, q1d, G, sG);
for (int qx = 0; qx < q1d; qx++)
{
for (int dx = 0; dx < d1d; dx++)
{
assert(AlmostEq(B(qx, 0, dx), sB[dx][qx]));
assert(AlmostEq(G(qx, 0, dx), sG[dx][qx]));
}
}
}*/
for (int c = 0; c < vdim; c++)
for (int vd = 0; vd < vdim; vd++)
{
MFEM_FOREACH_THREAD(dz, z, d1d)
{
@@ -157,7 +117,7 @@ void map_field_to_quadrature_data_tensor_product_3d(
real_t uv[2] = {0.0, 0.0};
for (int dx = 0; dx < d1d; dx++)
{
const real_t f = field(dx, dy, dz, c);
const real_t f = field(dx, dy, dz, vd);
uv[0] += f * B(qx, 0, dx);
uv[1] += f * G(qx, 0, dx);
}
@@ -203,59 +163,19 @@ void map_field_to_quadrature_data_tensor_product_3d(
uvw[1] += s3(dz, qy, qx) * B(qz, 0, dz);
uvw[2] += s4(dz, qy, qx) * G(qz, 0, dz);
}
fqp(c, 0, qx, qy, qz) = uvw[0];
fqp(c, 1, qx, qy, qz) = uvw[1];
fqp(c, 2, qx, qy, qz) = uvw[2];
fqp(vd, 0, qx, qy, qz) = uvw[0];
fqp(vd, 1, qx, qy, qz) = uvw[1];
fqp(vd, 2, qx, qy, qz) = uvw[2];
}
}
}
MFEM_SYNC_THREAD;
}
/*
{
for (int c = 0; c < vdim; c++)
{
kernels::internal::LoadDofs3d(d1d, c, field, r0);
for (int d = 0; d < DIM; d++)
{
for (int dz = 0; dz < d1d; dz++)
{
for (int dy = 0; dy < d1d; dy++)
{
for (int dx = 0; dx < d1d; dx++)
{
const real_t f = field(dx, dy, dz, c);
assert(AlmostEq(f, r0[d][dz][dy][dx]));
}
}
}
}
kernels::internal::Grad3d(d1d, q1d, smem, sB, sG, r0, r1, c);
for (int qz = 0; qz < q1d; qz++)
{
for (int qy = 0; qy < q1d; qy++)
{
for (int qx = 0; qx < q1d; qx++)
{
if (!AlmostEq(fqp(c, d, qx, qy, qz), r1[d][qz][qy][qx]))
{
dbg("\x1b[31m[{}:d] {} {}", c, fqp(c, d, qx, qy, qz), r1[d][qz][qy][qx]);
dbg("❌❌❌"), std::exit(EXIT_FAILURE);
}
}
}
}
}
// dbg("✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅");//, std::exit(EXIT_SUCCESS);
}*/
}
// TODO: Create separate function for clarity
else if constexpr (
std::is_same_v<std::decay_t<field_operator_t>, Weight>)
{
// dbg("None");
const int num_qp = integration_weights.GetShape()[0];
// TODO: eeek
const int q1d = (int)floor(std::pow(num_qp, 1.0/input.dim) + 0.5);
@@ -598,9 +518,6 @@ void map_fields_to_quadrature_data(
const int &dimension,
const bool &use_sum_factorization = false)
{
// dbg();
assert(use_sum_factorization && "❌ use_sum_factorization required");
// When the input_to_field map returns -1, this means the requested input
// is the integration weight. Weights don't have a user defined field
// attached to them and we create a dummy field which is not accessed
@@ -661,7 +578,6 @@ void map_field_to_quadrature_data_conditional(
const int &dimension,
const bool &use_sum_factorization = false)
{
assert(false && "❌ condition not implemented");
if (condition)
{
if (use_sum_factorization)
@@ -703,7 +619,6 @@ void map_fields_to_quadrature_data_conditional(
const std::array<bool, num_inputs> &conditions,
const bool &use_sum_factorization = false)
{
assert(false && "❌ condition not implemented");
for_constexpr<num_inputs>([&](auto i)
{
map_field_to_quadrature_data_conditional(
@@ -712,7 +627,7 @@ void map_fields_to_quadrature_data_conditional(
});
}
template <int T_Q1D, size_t num_inputs, typename field_operator_ts>
template <size_t num_inputs, typename field_operator_ts>
MFEM_HOST_DEVICE
void map_direction_to_quadrature_data_conditional(
std::array<DeviceTensor<2>, num_inputs> &directions_qp,
@@ -745,7 +660,7 @@ void map_direction_to_quadrature_data_conditional(
}
else if (dimension == 3)
{
map_field_to_quadrature_data_tensor_product_3d<T_Q1D>(
map_field_to_quadrature_data_tensor_product_3d(
directions_qp[i], dtqmaps[i], direction_e, get<i>(fops),
integration_weights, scratch_mem);
}
+3 -18
View File
@@ -43,7 +43,7 @@ public:
/// Get spatial dimension
///
/// returns always 1.
constexpr int Dimension() const
int Dimension() const
{
return 1;
}
@@ -74,14 +74,11 @@ public:
return elem_restr.get();
}
virtual const Operator* GetB() const = 0;
virtual const Operator* GetBt() const = 0;
protected:
int vdim;
DofToQuad dtq;
mutable std::unique_ptr<Operator> prolongation, elem_restr, B, Bt;
mutable std::unique_ptr<Operator> prolongation;
mutable std::unique_ptr<Operator> elem_restr;
};
/// @brief Uniform parameter space
@@ -125,18 +122,6 @@ public:
return lsize;
}
const Operator* GetB() const override
{
MFEM_ABORT("UniformParameterSpace does not support GetB");
return nullptr;
}
const Operator* GetBt() const override
{
MFEM_ABORT("UniformParameterSpace does not support GetBt");
return nullptr;
}
private:
/// T-vector size
int tsize;
-2
View File
@@ -243,8 +243,6 @@ void process_qf_arg(
}
}
// const tensor<real_t, DIM> ∇u
// const tensor<real_t, DIM, DIM> D (PA_DATA)
template <typename arg_type>
MFEM_HOST_DEVICE inline
void process_qf_arg(const DeviceTensor<2> &u, arg_type &arg, int qp)
-76
View File
@@ -1,76 +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.
#pragma once
#include "tuple.hpp"
#include "../linalg/tensor.hpp"
using namespace mfem::future;
using mfem::future::tensor;
// Helper to add dimension to tensor type
template<typename T, int qp>
struct AddQPDimension;
// Specialization for tensor<real_t, dim>
template<typename real_t, int dim, int qp>
struct AddQPDimension<tensor<real_t, dim>, qp>
{
using type = tensor<real_t, dim, qp>;
};
// Specialization for tensor<real_t, dim, dim>
template<typename real_t, int dim, int qp>
struct AddQPDimension<tensor<real_t, dim, dim>, qp>
{
using type = tensor<real_t, dim, dim, qp>;
};
// Specialization for real_t (transforms to tensor<real_t, qp>)
template<typename real_t, int qp>
struct AddQPDimension
{
using type = tensor<real_t, qp>;
};
// Helper to transform tuple
template<typename Tuple, int qp>
struct TransformTupleQP {};
// Specialization for mfem::future::tuple
template<int qp, typename... Types>
struct TransformTupleQP<mfem::future::tuple<Types...>, qp>
{
using type = mfem::future::tuple<typename AddQPDimension<Types, qp>::type...>;
};
template<int qp, typename... Types>
struct TransformTupleQP<std::tuple<Types...>, qp>
{
using type = std::tuple<typename AddQPDimension<Types, qp>::type...>;
};
// Function to transform tuple type with qp dimension
template<int qp, typename qf_param_ts>
struct add_qp_dimension
{
using type = typename TransformTupleQP<qf_param_ts, qp>::type;
};
// Helper alias template for cleaner usage
template<int qp, typename qf_param_ts>
using add_qp_dimension_t = typename add_qp_dimension<qp, qf_param_ts>::type;
// ...AddDomainIntegrator...
// {
// constexpr int Q1D = 4;
// using qf_param_augmentd_ts = add_qp_dimension_t<Q1D, decay_tuple<qf_param_ts>>;
// }
+49 -532
View File
@@ -21,7 +21,6 @@
#include <type_traits>
#include <numeric>
#include <iomanip>
#include <typeindex>
#include "../../general/communication.hpp"
#include "../../general/forall.hpp"
@@ -29,19 +28,13 @@
#include "../fe/fe_base.hpp"
#include "../fespace.hpp"
#include "../pfespace.hpp"
#include "../qfunction.hpp"
#include "../../mesh/mesh.hpp"
#include "../../linalg/dtensor.hpp"
#include "../quadinterpolator.hpp"
#include "fielddescriptor.hpp"
#include "fieldoperator.hpp"
#include "parameterspace.hpp"
#include "tuple.hpp"
#undef NVTX_COLOR
#define NVTX_COLOR ::nvtx::kLightBlue
namespace mfem::future
{
@@ -82,7 +75,7 @@ constexpr void for_constexpr(lambda&& f,
}
template <typename lambda>
constexpr void for_constexpr(lambda&&, std::integer_sequence<std::size_t>) {}
constexpr void for_constexpr(lambda&& f, std::integer_sequence<std::size_t>) {}
template <int... n, typename lambda>
constexpr void for_constexpr(lambda&& f)
@@ -91,7 +84,7 @@ constexpr void for_constexpr(lambda&& f)
}
template <typename lambda, typename arg_t>
constexpr void for_constexpr_with_arg(lambda&&, arg_t&&,
constexpr void for_constexpr_with_arg(lambda&& f, arg_t&& arg,
std::integer_sequence<std::size_t>)
{
// Base case - do nothing for empty sequence
@@ -115,16 +108,6 @@ constexpr void for_constexpr_with_arg(lambda&& f, arg_t&& arg)
indices{});
}
template <auto start, auto end, auto inc = 1, typename F>
constexpr void constexpr_for(F&& f)
{
if constexpr (start < end)
{
f(std::integral_constant<decltype(start), start>());
constexpr_for<start + inc, end, inc>(f);
}
}
template <std::size_t I, typename Tuple, std::size_t... Is>
std::array<bool, sizeof...(Is)>
make_dependency_array(const Tuple& inputs, std::index_sequence<Is...>)
@@ -461,21 +444,6 @@ struct create_function_signature<output_t (*)(input_ts...)>
using type = FunctionSignature<output_t(input_ts...)>;
};
template <typename...>
using void_t = void;
template <typename T, typename = void>
struct get_function_signature
{
using type = typename create_function_signature<T>::type;
};
template <typename T>
struct get_function_signature<T, void_t<decltype(&T::operator())>>
{
using type = typename create_function_signature<decltype(&T::operator())>::type;
};
template <typename T>
constexpr int GetFieldId()
{
@@ -570,12 +538,38 @@ auto get_marked_entries(
/// @param t the tuple to filter fields from.
/// @returns a tuple containing only the fields with field IDs not equal to -1.
template <typename... Ts>
constexpr auto filter_fields(const std::tuple<Ts...>&)
constexpr auto filter_fields(const std::tuple<Ts...>& t)
{
return std::tuple_cat(
std::conditional_t<Ts::GetFieldId() != -1, std::tuple<Ts>, std::tuple<>> {}...);
}
/// @brief FieldDescriptor struct
///
/// This struct is used to store information about a field.
struct FieldDescriptor
{
using data_variant_t =
std::variant<const FiniteElementSpace *,
const ParFiniteElementSpace *,
const ParameterSpace *>;
/// Field ID
std::size_t id;
/// Field variant
data_variant_t data;
/// Default constructor
FieldDescriptor() :
id(SIZE_MAX), data(data_variant_t{}) {}
/// Constructor
template <typename T>
FieldDescriptor(std::size_t field_id, const T* v) :
id(field_id), data(v) {}
};
namespace dfem
{
template <class... T> constexpr bool always_false = false;
@@ -605,7 +599,7 @@ struct ThreadBlocks
#if defined(MFEM_USE_CUDA_OR_HIP)
template <typename func_t>
__global__ void forall_kernel_extern_shmem(func_t f, int n)
__global__ void forall_kernel_shmem(func_t f, int n)
{
int i = blockIdx.x;
extern __shared__ real_t shmem[];
@@ -614,48 +608,23 @@ __global__ void forall_kernel_extern_shmem(func_t f, int n)
f(i, shmem);
}
}
template <typename func_t>
__global__ void forall_kernel_static_smem(func_t f, int n)
{
int i = blockIdx.x;
if (i >= n) { return; }
f(i, nullptr);
}
template <int MAX_THREADS_PER_BLOCK, typename func_t>
__global__
MFEM_LAUNCH_BOUNDS(MAX_THREADS_PER_BLOCK)
static void forall_kernel_static_smem_launch_bounds(func_t f, int n)
{
for (int k = blockIdx.x; k < n; k += gridDim.x) { f(k, nullptr); }
}
#endif
template </*typename kernel_tag,*/ typename func_t>
template <typename func_t>
void forall(func_t f,
const int &N,
[[maybe_unused]] const ThreadBlocks &blocks,
[[maybe_unused]] int num_shmem = 0,
const ThreadBlocks &blocks,
int num_shmem = 0,
real_t *shmem = nullptr)
{
db1();
if (Device::Allows(Backend::CUDA_MASK) ||
Device::Allows(Backend::HIP_MASK))
{
#if defined(MFEM_USE_CUDA_OR_HIP)
// int gridsize = (N + Z - 1) / Z;
int num_bytes = num_shmem * sizeof(decltype(shmem));
db1("num_bytes:{}", num_bytes);
db1("block: {}x{}x{}", blocks.x, blocks.y, blocks.z);
dim3 block_size(blocks.x, blocks.y, blocks.z);
// ForallKernel<kernel_tag>::run<<<N, block_size, num_bytes>>>(f, N);
if (num_bytes > 0)
{
forall_kernel_extern_shmem<<<N, block_size, num_bytes>>>(f, N);
}
else
{
forall_kernel_static_smem<<<N, block_size>>>(f, N);
}
forall_kernel_shmem<<<N, block_size, num_bytes>>>(f, N);
#if defined(MFEM_USE_CUDA)
MFEM_GPU_CHECK(cudaGetLastError());
#elif defined(MFEM_USE_HIP)
@@ -666,7 +635,6 @@ void forall(func_t f,
}
else if (Device::Allows(Backend::CPU_MASK))
{
db1("CPU_MASK");
MFEM_ASSERT(!((bool)num_shmem != (bool)shmem),
"Backend::CPU needs a pre-allocated shared memory block");
for (int i = 0; i < N; i++)
@@ -680,69 +648,6 @@ void forall(func_t f,
}
}
namespace dfem
{
template <int MAX_THREADS_PER_BLOCK = 0, typename func_t>
void forall(func_t f,
const int &N,
[[maybe_unused]] const ThreadBlocks &blocks,
[[maybe_unused]] int num_shmem = 0,
real_t *shmem = nullptr)
{
db1();
if (Device::Allows(Backend::CUDA_MASK) ||
Device::Allows(Backend::HIP_MASK))
{
#if defined(MFEM_USE_CUDA_OR_HIP)
int num_bytes = num_shmem * sizeof(decltype(shmem));
db1("num_bytes:{}", num_bytes);
db1("block: {}x{}x{}", blocks.x, blocks.y, blocks.z);
db1("MAX_THREADS_PER_BLOCK:{}", MAX_THREADS_PER_BLOCK);
dim3 block_size(blocks.x, blocks.y, blocks.z);
if constexpr (MAX_THREADS_PER_BLOCK > 0)
{
assert(num_bytes == 0);
forall_kernel_static_smem_launch_bounds
<MAX_THREADS_PER_BLOCK><<<N, block_size>>> (f, N);
}
else
{
static_assert(MAX_THREADS_PER_BLOCK == 0);
if (num_bytes == 0)
{
forall_kernel_static_smem<<<N, block_size>>>(f, N);
}
else
{
forall_kernel_extern_shmem<<<N, block_size, num_bytes>>>(f, N);
}
}
#if defined(MFEM_USE_CUDA)
MFEM_GPU_CHECK(cudaGetLastError());
#elif defined(MFEM_USE_HIP)
MFEM_GPU_CHECK(hipGetLastError());
#endif
// MFEM_DEVICE_SYNC; // ⚠️
#endif
}
else if (Device::Allows(Backend::CPU_MASK))
{
db1("CPU_MASK");
MFEM_ASSERT(!((bool)num_shmem != (bool)shmem),
"Backend::CPU needs a pre-allocated shared memory block");
for (int i = 0; i < N; i++)
{
f(i, shmem);
}
}
else
{
MFEM_ABORT("no compute backend available");
}
}
}
/// @todo To be removed.
class FDJacobian : public Operator
{
@@ -867,10 +772,6 @@ int GetVSize(const FieldDescriptor &f)
{
return arg->GetVSize();
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return arg->Size();
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return arg->GetVSize();
@@ -909,10 +810,6 @@ void GetElementVDofs(const FieldDescriptor &f, int el, Array<int> &vdofs)
{
arg->GetElementVDofs(el, vdofs);
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
MFEM_ABORT("internal error");
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
MFEM_ABORT("internal error");
@@ -947,10 +844,6 @@ int GetTrueVSize(const FieldDescriptor &f)
{
return arg->GetTrueVSize();
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return arg->Size();
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return arg->GetTrueVSize();
@@ -981,10 +874,6 @@ int GetVDim(const FieldDescriptor &f)
{
return arg->GetVDim();
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return arg->GetVDim();
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return arg->GetVDim();
@@ -1020,10 +909,6 @@ int GetDimension(const FieldDescriptor &f)
return arg->GetMesh()->Dimension() - 1;
}
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return arg->GetSpace()->GetMesh()->Dimension();
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return arg->Dimension();
@@ -1036,36 +921,6 @@ int GetDimension(const FieldDescriptor &f)
}, f.data);
}
inline
std::variant<const QuadratureInterpolator *, const Operator *>get_qinterp(
const FieldDescriptor &f,
const IntegrationRule &ir)
{
return std::visit([&ir](auto && arg) -> const QuadratureInterpolator*
{
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, const FiniteElementSpace *> ||
std::is_same_v<T, const ParFiniteElementSpace *>)
{
return arg->GetQuadratureInterpolator(ir);
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
// QuadratureFunction doesn't need a QuadratureInterpolator
return nullptr;
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return nullptr;
}
else
{
static_assert(dfem::always_false<T>, "internal error");
}
return nullptr; // Unreachable, but avoids compiler warning
}, f.data);
}
/// @brief Get the prolongation operator for a field descriptor.
///
@@ -1074,7 +929,6 @@ std::variant<const QuadratureInterpolator *, const Operator *>get_qinterp(
inline
const Operator *get_prolongation(const FieldDescriptor &f)
{
NVTX("get P");
return std::visit([](auto&& arg) -> const Operator*
{
using T = std::decay_t<decltype(arg)>;
@@ -1083,10 +937,6 @@ const Operator *get_prolongation(const FieldDescriptor &f)
{
return arg->GetProlongationMatrix();
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return nullptr;
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return arg->GetProlongationMatrix();
@@ -1109,7 +959,6 @@ inline
const Operator *get_element_restriction(const FieldDescriptor &f,
ElementDofOrdering o)
{
NVTX("get ER");
return std::visit([&o](auto&& arg) -> const Operator*
{
using T = std::decay_t<decltype(arg)>;
@@ -1118,10 +967,6 @@ const Operator *get_element_restriction(const FieldDescriptor &f,
{
return arg->GetElementRestriction(o);
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return nullptr;
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return arg->GetElementRestriction(o);
@@ -1149,7 +994,6 @@ const Operator *get_face_restriction(const FieldDescriptor &f,
FaceType ft,
L2FaceValues m)
{
NVTX("get FR");
return std::visit([&o, &ft, &m](auto&& arg) -> const Operator*
{
using T = std::decay_t<decltype(arg)>;
@@ -1158,11 +1002,6 @@ const Operator *get_face_restriction(const FieldDescriptor &f,
{
return arg->GetFaceRestriction(o, ft, m);
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
// QuadratureFunction does not support face restrictions
MFEM_ABORT("internal error");
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
// ParameterSpace does not support face restrictions
@@ -1188,7 +1027,6 @@ inline
const Operator *get_restriction(const FieldDescriptor &f,
const ElementDofOrdering &o)
{
NVTX("get R");
if constexpr (std::is_same_v<entity_t, Entity::Element>)
{
return get_element_restriction(f, o);
@@ -1214,14 +1052,12 @@ inline std::tuple<std::function<void(const Vector&, Vector&)>, int>
get_restriction_transpose(
const FieldDescriptor &f,
const ElementDofOrdering &o,
[[maybe_unused]] const fop_t &fop)
const fop_t &fop)
{
NVTX("get R^T");
if constexpr (is_sum_fop<fop_t>::value)
{
auto RT = [=](const Vector &v_e, Vector &v_l)
{
NVTX("R^T sum");
v_l += v_e;
};
return std::make_tuple(RT, 1);
@@ -1231,7 +1067,6 @@ get_restriction_transpose(
const Operator *R = get_restriction<entity_t>(f, o);
std::function<void(const Vector&, Vector&)> RT = [=](const Vector &x, Vector &y)
{
NVTX("R^T+");
R->AddMultTranspose(x, y);
};
return std::make_tuple(RT, R->Height());
@@ -1251,26 +1086,11 @@ get_restriction_transpose(
inline
void prolongation(const FieldDescriptor field, const Vector &x, Vector &field_l)
{
NVTX("P");
const auto P = get_prolongation(field);
NVTX_INI("SetSize");
field_l.SetSize(P->Height());
NVTX_END("SetSize");
NVTX_INI("P->Mult");
P->Mult(x, field_l);
}
inline
void prolongation_transpose(
const FieldDescriptor &field, const Vector &field_l, Vector &x)
{
const auto P = get_prolongation(field);
x.SetSize(P->Width());
P->MultTranspose(field_l, x);
}
/// @brief Apply the prolongation operator to a vector of fields.
///
/// x is a long vector containing the data for all fields on tdofs and
@@ -1287,7 +1107,6 @@ void prolongation(const std::array<FieldDescriptor, N> fields,
const Vector &x,
std::array<Vector, M> &fields_l)
{
NVTX("P");
int data_offset = 0;
for (int i = 0; i < N; i++)
{
@@ -1295,14 +1114,9 @@ void prolongation(const std::array<FieldDescriptor, N> fields,
const int width = P->Width();
// const Vector x_i(x.GetData() + data_offset, width);
const Vector x_i(const_cast<Vector&>(x), data_offset, width);
NVTX_INI("SetSize");
fields_l[i].SetSize(P->Height());
NVTX_END("SetSize");
NVTX_INI("P->Mult");
P->Mult(x_i, fields_l[i]);
NVTX_END("P->Mult");
data_offset += width;
}
}
@@ -1316,259 +1130,20 @@ void prolongation(const std::array<FieldDescriptor, N> fields,
/// @param fields the array of field descriptors.
/// @param x the input vector in tdofs.
/// @param fields_l the array of output vectors in vdofs.
// inline
// void prolongation(const std::vector<FieldDescriptor> fields,
// const Vector &x,
// std::vector<Vector> &fields_l)
// {
// int data_offset = 0;
// for (std::size_t i = 0; i < fields.size(); i++)
// {
// const auto P = get_prolongation(fields[i]);
// const int width = P->Width();
// const Vector x_i(const_cast<Vector&>(x), data_offset, width);
// fields_l[i].SetSize(P->Height());
// P->Mult(x_i, fields_l[i]);
// data_offset += width;
// }
// }
inline
void prolongation(
const std::vector<FieldDescriptor> fields,
const BlockVector &x,
std::vector<Vector *> &x_l)
void prolongation(const std::vector<FieldDescriptor> fields,
const Vector &x,
std::vector<Vector> &fields_l)
{
MFEM_ASSERT(x.NumBlocks() == static_cast<int>(x_l.size()),
"error " << x.NumBlocks() << " vs " << x_l.size());
for (int i = 0; i < x.NumBlocks(); i++)
int data_offset = 0;
for (std::size_t i = 0; i < fields.size(); i++)
{
const auto P = get_prolongation(fields[i]);
// If nullptr, assume Identity.
if (P == nullptr)
{
*x_l[i] = x.GetBlock(i);
}
else
{
const auto P = get_prolongation(fields[i]);
MFEM_ASSERT(P->Width() == x.GetBlock(i).Size(),
"prolongation not applicable to given input data size " <<
P->Width() << " vs " << x.GetBlock(i).Size());
MFEM_ASSERT(P->Height() == x_l[i]->Size(),
"prolongation not applicable to given output data size " <<
P->Height() << " vs " << x_l[i]->Size());
P->Mult(x.GetBlock(i), *x_l[i]);
}
}
}
inline
void prolongation(
const std::vector<FieldDescriptor> fields,
const MultiVector &x,
std::vector<Vector *> &x_l)
{
MFEM_ASSERT(x.NumBlocks() == static_cast<int>(x_l.size()),
"error " << x.NumBlocks() << " vs " << x_l.size());
for (int i = 0; i < x.NumBlocks(); i++)
{
const auto P = get_prolongation(fields[i]);
// If nullptr, assume Identity.
if (P == nullptr)
{
*x_l[i] = x[i];
}
else
{
const auto P = get_prolongation(fields[i]);
MFEM_ASSERT(P->Width() == x[i].Size(),
"prolongation not applicable to given input data size " <<
P->Width() << " vs " << x[i].Size());
MFEM_ASSERT(P->Height() == x_l[i]->Size(),
"prolongation not applicable to given output data size " <<
P->Height() << " vs " << x_l[i]->Size());
P->Mult(x[i], *x_l[i]);
}
}
}
inline
void prolongation_transpose(
const std::vector<FieldDescriptor> fields,
const std::vector<Vector *> &x_l,
BlockVector &x)
{
MFEM_ASSERT(static_cast<int>(x_l.size()) == x.NumBlocks(),
"error " << x_l.size() << " vs " << x.NumBlocks());
for (size_t i = 0; i < x_l.size(); i++)
{
const auto P = get_prolongation(fields[i]);
// If nullptr, assume Identity.
if (P == nullptr)
{
x.GetBlock(i) = *x_l[i];
}
else
{
MFEM_ASSERT(P->Height() == x_l[i]->Size(),
"prolongation not applicable to given input data size " <<
P->Height() << " vs " << x_l[i]->Size());
MFEM_ASSERT(P->Width() == x.GetBlock(i).Size(),
"prolongation not applicable to given output data size " <<
P->Width() << " vs " << x.GetBlock(i).Size());
P->MultTranspose(*x_l[i], x.GetBlock(i));
}
}
}
inline
void prolongation_transpose(
const std::vector<FieldDescriptor> fields,
const std::vector<Vector *> &x_l,
MultiVector &x)
{
MFEM_ASSERT(static_cast<int>(x_l.size()) == x.NumBlocks(),
"error " << x_l.size() << " vs " << x.NumBlocks());
for (size_t i = 0; i < x_l.size(); i++)
{
const auto P = get_prolongation(fields[i]);
// If nullptr, assume Identity.
if (P == nullptr)
{
x[i] = *x_l[i];
}
else
{
MFEM_ASSERT(P->Height() == x_l[i]->Size(),
"prolongation not applicable to given input data size " <<
P->Height() << " vs " << x_l[i]->Size());
MFEM_ASSERT(P->Width() == x[i].Size(),
"prolongation not applicable to given output data size " <<
P->Width() << " vs " << x[i].Size());
P->MultTranspose(*x_l[i], x[i]);
}
}
}
template <typename entity_t>
void restriction(
const std::vector<FieldDescriptor> fields,
const std::vector<Vector *> &x_l,
std::vector<Vector *> &x_e)
{
MFEM_ASSERT(x_l.size() == x_e.size(),
"internal error " << x_l.size() << " vs " << x_e.size());
for (size_t i = 0; i < fields.size(); i++)
{
int s = 0;
const auto R = get_restriction<entity_t>(
fields[i], ElementDofOrdering::LEXICOGRAPHIC);
// If nullptr, assume Identity.
if (R == nullptr)
{
s = x_l[i]->Size();
}
else
{
s = R->Height();
}
// TODO
if (x_e[i] == nullptr)
{
x_e[i] = new Vector(s);
}
x_e[i]->SetSize(s);
if (R == nullptr)
{
x_e[i] = x_l[i];
}
else
{
MFEM_ASSERT(R->Width() == x_l[i]->Size(),
"restriction not applicable to given input data size " <<
R->Width() << " vs " << x_l[i]->Size());
R->Mult(*x_l[i], *x_e[i]);
}
}
}
template <typename entity_t>
void prepare_residual(
const std::vector<FieldDescriptor> &fields,
std::vector<Vector *> &r_e)
{
for (size_t i = 0; i < fields.size(); i++)
{
int s = 0;
if (std::holds_alternative<const QuadratureFunction *>(fields[i].data))
{
const auto fd = std::get<const QuadratureFunction *>(fields[i].data);
s = fd->Size();
}
else
{
const auto R = get_restriction<entity_t>(
fields[i], ElementDofOrdering::LEXICOGRAPHIC);
s = R->Height();
}
// TODO
if (r_e[i] == nullptr)
{
r_e[i] = new Vector(s);
}
else
{
r_e[i]->SetSize(s);
}
}
}
template <typename entity_t>
void restriction_transpose(
const std::vector<FieldDescriptor> &fields,
const std::vector<Vector *> &x_e,
std::vector<Vector *> &x_l)
{
for (size_t i = 0; i < fields.size(); i++)
{
int s = 0;
const auto R = get_restriction<entity_t>(
fields[i], ElementDofOrdering::LEXICOGRAPHIC);
// TODO: if nullptr, assume Identity
if (R == nullptr)
{
s = x_e[i]->Size();
}
else
{
s = R->Width();
}
// TODO
if (x_l[i] == nullptr)
{
x_l[i] = new Vector(s);
}
x_l[i]->SetSize(s);
// TODO: if nullptr, assume Identity
if (R == nullptr)
{
x_l[i] = x_e[i];
}
else
{
R->MultTranspose(*x_e[i], *x_l[i]);
}
const int width = P->Width();
const Vector x_i(const_cast<Vector&>(x), data_offset, width);
fields_l[i].SetSize(P->Height());
P->Mult(x_i, fields_l[i]);
data_offset += width;
}
}
@@ -1577,7 +1152,6 @@ void get_lvectors(const std::vector<FieldDescriptor> fields,
const Vector &x,
std::vector<Vector> &fields_l)
{
NVTX("get_lvectors");
int data_offset = 0;
for (std::size_t i = 0; i < fields.size(); i++)
{
@@ -1604,15 +1178,13 @@ template <typename fop_t>
inline
std::function<void(const Vector&, Vector&)> get_prolongation_transpose(
const FieldDescriptor &f,
[[maybe_unused]] const fop_t &fop,
const fop_t &fop,
MPI_Comm mpi_comm)
{
NVTX("get P^T");
if constexpr (is_sum_fop<fop_t>::value)
{
auto PT = [=](const Vector &r_local, Vector &y)
{
NVTX("P^T sum");
MFEM_ASSERT(y.Size() == 1, "output size doesn't match kernel description");
real_t local_sum = r_local.Sum();
MPI_Allreduce(&local_sum, y.GetData(), 1, MPI_DOUBLE, MPI_SUM, mpi_comm);
@@ -1623,7 +1195,6 @@ std::function<void(const Vector&, Vector&)> get_prolongation_transpose(
{
auto PT = [=](const Vector &r_local, Vector &y)
{
NVTX("P^T Identity");
y = r_local;
};
return PT;
@@ -1631,7 +1202,6 @@ std::function<void(const Vector&, Vector&)> get_prolongation_transpose(
const Operator *P = get_prolongation(f);
auto PT = [=](const Vector &r_local, Vector &y)
{
NVTX("P^T");
P->MultTranspose(r_local, y);
};
return PT;
@@ -1650,19 +1220,12 @@ void restriction(const FieldDescriptor u,
Vector &field_e,
ElementDofOrdering ordering)
{
NVTX("R");
const auto R = get_restriction<entity_t>(u, ordering);
MFEM_ASSERT(R->Width() == u_l.Size(),
"restriction not applicable to given data size");
const int height = R->Height();
NVTX_INI("SetSize");
field_e.SetSize(height);
NVTX_END("SetSize");
NVTX_INI("R->Mult");
R->Mult(u_l, field_e);
NVTX_END("R->Mult");
}
/// @brief Apply the restriction operator to a vector of fields.
@@ -1680,29 +1243,14 @@ void restriction(const std::vector<FieldDescriptor> u,
ElementDofOrdering ordering,
const int offset = 0)
{
NVTX("R");
for (std::size_t i = 0; i < u.size(); i++)
{
const auto R = get_restriction<entity_t>(u[i], ordering);
MFEM_ASSERT(R->Width() == u_l[i].Size(),
"restriction not applicable to given data size");
const int height = R->Height();
// NVTX_INI("SetSize");
fields_e[i + offset].SetSize(height);
// NVTX_END("SetSize");
// NVTX_INI("R->Mult");
if (dynamic_cast<const IdentityOperator*>(R))
{
NVTX("Identity");
fields_e[i + offset].NewMemoryAndSize(u_l[i].GetMemory(), u_l[i].Size(), false);
}
else
{
R->Mult(u_l[i], fields_e[i + offset]);
}
// NVTX_END("R->Mult");
R->Mult(u_l[i], fields_e[i + offset]);
}
}
@@ -1714,21 +1262,14 @@ void element_restriction(const std::array<FieldDescriptor, N> u,
ElementDofOrdering ordering,
const int offset = 0)
{
NVTX("ER");
for (int i = 0; i < N; i++)
{
const auto R = get_element_restriction(u[i], ordering);
MFEM_ASSERT(R->Width() == u_l[i].Size(),
"element restriction not applicable to given data size");
const int height = R->Height();
NVTX_INI("SetSize");
fields_e[i + offset].SetSize(height);
NVTX_END("SetSize");
NVTX_INI("R->Mult");
R->Mult(u_l[i], fields_e[i + offset]);
NVTX_END("R->Mult");
}
}
@@ -1785,10 +1326,6 @@ const DofToQuad *GetDofToQuad(const FieldDescriptor &f,
return &arg->GetTypicalTraceElement()->GetDofToQuad(ir, mode);
}
}
else if constexpr (std::is_same_v<T, const QuadratureFunction *>)
{
return nullptr;
}
else if constexpr (std::is_same_v<T, const ParameterSpace *>)
{
return &arg->GetDofToQuad();
@@ -1920,7 +1457,7 @@ create_descriptors_to_fields_map(
auto f = [&](auto &fop, auto &map)
{
if constexpr (is_weight_fop<std::decay_t<decltype(fop)>>::value)
if constexpr (std::is_same_v<std::decay_t<decltype(fop)>, Weight>)
{
// TODO-bug: stealing dimension from the first field
fop.dim = GetDimension<entity_t>(fields[0]);
@@ -2050,7 +1587,7 @@ get_shmem_info(
const std::array<DofToQuadMap, num_outputs> &output_dtq_maps,
const std::vector<FieldDescriptor> &fields,
const int &num_entities,
[[maybe_unused]] const input_t &inputs,
const input_t &inputs,
const int &num_qp,
const std::vector<int> &input_size_on_qp,
const int &residual_size_on_qp,
@@ -2805,25 +2342,5 @@ std::array<DofToQuadMap, num_fields> create_dtq_maps(
std::make_index_sequence<num_fields> {});
}
struct QLayoutEntry
{
std::type_index type;
std::vector<int> layout;
template <class Fop>
QLayoutEntry(Fop, std::initializer_list<int> idx) :
type(typeid(Fop)), layout(idx) {}
};
static void ExtractQLayouts(
const std::initializer_list<QLayoutEntry> entries,
std::unordered_map<std::type_index, std::vector<int>>& out)
{
for (const auto& e : entries)
{
out[e.type] = e.layout;
}
}
} // namespace mfem::future
#endif
+1 -1
View File
@@ -57,7 +57,7 @@ void DGMassApply(const int e,
}
else if (DIM == 3)
{
SmemPAMassApply3D_Element<TD1D,TQ1D,ACCUM>(e, NE, B, pa_data, x, y);
SmemPAMassApply3D_Element<TD1D,TQ1D,NBZ,ACCUM>(e, NE, B, pa_data, x, y);
}
else
{
+1 -1
View File
@@ -52,7 +52,7 @@
#include "bounds.hpp"
#include "particleset.hpp"
// #include "dfem/doperator.hpp"
#include "dfem/doperator.hpp"
#ifdef MFEM_USE_MPI
#include "pfespace.hpp"
+24
View File
@@ -5252,6 +5252,30 @@ void GridFunction::GetElementBounds(const PLBound &plb,
Vector &lower, Vector &upper,
const int vdim) const
{
if (UseDevice() && Device::Allows(Backend::DEVICE_MASK) &&
plb.GetBasisType() != BasisType::Positive &&
UsesTensorBasis(*fes))
{
const FiniteElement &fe = *fes->GetTypicalFE();
const int rdim = fe.GetDim();
const int fes_dim = fes->GetVDim();
const int nel = fes->GetNE();
const int nd = fe.GetDof();
Vector e_vec(nd*fes_dim*nel, Device::GetDeviceMemoryType());
e_vec.UseDevice(true);
const ElementRestrictionOperator *elem_restr =
fes->GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
MFEM_VERIFY(elem_restr != nullptr,
"Element restriction is required for device bounds.");
elem_restr->Mult(*this, e_vec);
plb.GetElementBoundsKernel(rdim, fes_dim, e_vec, lower, upper, vdim);
lower.HostRead();
upper.HostRead();
return;
}
int nel = fes->GetNE();
int fes_dim = fes->GetVDim();
lower.SetSize(nel*(vdim > 0 ? 1 :fes_dim));
+2 -6
View File
@@ -106,9 +106,7 @@ FindPointsGSLIB::FindPointsGSLIB()
gsl_comm = new gslib::comm;
cr = new gslib::crystal;
#ifdef MFEM_USE_MPI
int initialized = 0;
MPI_Initialized(&initialized);
if (!initialized) { MPI_Init(NULL, NULL); }
if (!Mpi::IsInitialized()) { Mpi::Init(); }
MPI_Comm comm = MPI_COMM_WORLD;
comm_init(gsl_comm, comm);
#else
@@ -2624,9 +2622,7 @@ GSOPGSLIB::GSOPGSLIB(Array<long long> &ids)
gsl_comm = new gslib::comm;
cr = new gslib::crystal;
#ifdef MFEM_USE_MPI
int initialized;
MPI_Initialized(&initialized);
if (!initialized) { MPI_Init(NULL, NULL); }
if (!Mpi::IsInitialized()) { Mpi::Init(); }
MPI_Comm comm = MPI_COMM_WORLD;
comm_init(gsl_comm, comm);
#else
+8 -18
View File
@@ -1064,8 +1064,6 @@ inline void SmemPADiffusionApply3D(const int NE,
}
}
MFEM_SYNC_THREAD;
// Grad X
MFEM_FOREACH_THREAD_DIRECT(dz,z,D1D)
{
MFEM_FOREACH_THREAD_DIRECT(dy,y,D1D)
@@ -1086,8 +1084,6 @@ inline void SmemPADiffusionApply3D(const int NE,
}
}
MFEM_SYNC_THREAD;
// Grad Y
MFEM_FOREACH_THREAD_DIRECT(dz,z,D1D)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,Q1D)
@@ -1109,8 +1105,6 @@ inline void SmemPADiffusionApply3D(const int NE,
}
}
MFEM_SYNC_THREAD;
// Grad Z + Q-function
MFEM_FOREACH_THREAD_DIRECT(qz,z,Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,Q1D)
@@ -1223,23 +1217,20 @@ inline void SmemPADiffusionApply3D(const int NE,
namespace
{
using DiffusionApplyKernelType =
DiffusionIntegrator::DiffusionApplyKernelType;
using DiffusionDiagonalKernelType =
DiffusionIntegrator::DiffusionDiagonalKernelType;
using ApplyKernelType = DiffusionIntegrator::ApplyKernelType;
using DiagonalKernelType = DiffusionIntegrator::DiagonalKernelType;
}
template<int DIM, int T_D1D, int T_Q1D>
DiffusionApplyKernelType DiffusionIntegrator::DiffusionApplyPAKernel::Kernel()
ApplyKernelType DiffusionIntegrator::ApplyPAKernels::Kernel()
{
if constexpr (DIM == 2) { return internal::SmemPADiffusionApply2D<T_D1D,T_Q1D>; }
else if constexpr (DIM == 3) { return internal::SmemPADiffusionApply3D<T_D1D, T_Q1D>; }
MFEM_ABORT("");
}
inline DiffusionApplyKernelType
DiffusionIntegrator::DiffusionApplyPAKernel::Fallback(int DIM, int, int)
inline
ApplyKernelType DiffusionIntegrator::ApplyPAKernels::Fallback(int DIM, int, int)
{
if (DIM == 2) { return internal::PADiffusionApply2D; }
else if (DIM == 3) { return internal::PADiffusionApply3D; }
@@ -1247,16 +1238,15 @@ DiffusionIntegrator::DiffusionApplyPAKernel::Fallback(int DIM, int, int)
}
template<int DIM, int D1D, int Q1D>
DiffusionDiagonalKernelType
DiffusionIntegrator::DiffusionDiagonalPAKernel::Kernel()
DiagonalKernelType DiffusionIntegrator::DiagonalPAKernels::Kernel()
{
if constexpr (DIM == 2) { return internal::SmemPADiffusionDiagonal2D<D1D,Q1D>; }
else if constexpr (DIM == 3) { return internal::SmemPADiffusionDiagonal3D<D1D, Q1D>; }
MFEM_ABORT("");
}
inline DiffusionDiagonalKernelType
DiffusionIntegrator::DiffusionDiagonalPAKernel::Fallback(int DIM, int, int)
inline DiagonalKernelType
DiffusionIntegrator::DiagonalPAKernels::Fallback(int DIM, int, int)
{
if (DIM == 2) { return internal::PADiffusionDiagonal2D; }
else if (DIM == 3) { return internal::PADiffusionDiagonal3D; }
+7 -7
View File
@@ -31,8 +31,8 @@ void DiffusionIntegrator::AssembleDiagonalPA(Vector &diag)
const Array<real_t> &B = maps->B;
const Array<real_t> &G = maps->G;
const Vector &Dv = pa_data;
DiffusionDiagonalPAKernel::Run(dim, dofs1D, quad1D, ne, symmetric, B, G, Dv,
diag, dofs1D, quad1D);
DiagonalPAKernels::Run(dim, dofs1D, quad1D, ne, symmetric, B, G, Dv,
diag, dofs1D, quad1D);
}
}
@@ -68,8 +68,8 @@ void DiffusionIntegrator::AddMultPA(const Vector &x, Vector &y) const
}
#endif // MFEM_USE_OCCA
DiffusionApplyPAKernel::Run(dim, dofs1D, quad1D, ne, symmetric, B, G, Bt,
Gt, Dv, x, y, dofs1D, quad1D);
ApplyPAKernels::Run(dim, dofs1D, quad1D, ne, symmetric, B, G, Bt,
Gt, Dv, x, y, dofs1D, quad1D);
}
}
@@ -174,9 +174,9 @@ void DiffusionIntegrator::AddAbsMultPA(const Vector &x, Vector &y) const
abs_pa_data.Abs();
auto abs_maps = maps->Abs();
DiffusionApplyPAKernel::Run(dim, dofs1D, quad1D, ne, symmetric,
abs_maps.B, abs_maps.G, abs_maps.Bt, abs_maps.Gt,
abs_pa_data, x, y, dofs1D, quad1D);
ApplyPAKernels::Run(dim, dofs1D, quad1D, ne, symmetric,
abs_maps.B, abs_maps.G, abs_maps.Bt, abs_maps.Gt,
abs_pa_data, x, y, dofs1D, quad1D);
}
void DiffusionIntegrator::AddAbsMultTransposePA(const Vector &x,
+76 -48
View File
@@ -181,6 +181,12 @@ constexpr int NBZ(int D1D)
{
return ipow(2, D(D1D) >= 0 ? D(D1D) : 0);
}
constexpr int NBZ3D(int MDQ)
{
return MDQ > 0 ? std::min<int>(
(128 + MDQ * MDQ * MDQ - 1) / (MDQ * MDQ * MDQ), 64)
: 1;
}
}
// Shared memory PA Mass Diagonal 2D kernel
@@ -804,19 +810,23 @@ void PAMassApply3D_Element(const int e,
}
}
template<int T_D1D, int T_Q1D, bool ACCUMULATE = true>
MFEM_HOST_DEVICE inline
void SmemPAMassApply3D_Element(const int e,
const int NE,
const real_t *b_,
const real_t *d_,
const real_t *x_,
real_t *y_,
const int d1d = 0,
const int q1d = 0)
template <int T_D1D, int T_Q1D, int TBATCH, bool ACCUMULATE = true>
MFEM_HOST_DEVICE inline void
SmemPAMassApply3D_Element(const int e, const int NE, const real_t *b_,
const real_t *d_, const real_t *x_, real_t *y_,
int d1d = 0, int q1d = 0)
{
constexpr int D1D = T_D1D ? T_D1D : d1d;
constexpr int Q1D = T_Q1D ? T_Q1D : q1d;
static_assert(TBATCH > 0, "TBATCH must be positive");
#if defined(__CUDA_ARCH__) || defined(__HIP_DEVICE_COMPILE__)
constexpr int tbatch = TBATCH;
const int tidz = MFEM_THREAD_ID(z);
#else
// host always batch size 1
constexpr int tbatch = 1;
constexpr int tidz = 0;
#endif
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
constexpr int MQ1 = T_Q1D ? T_Q1D : DofQuadLimits::MAX_Q1D;
constexpr int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
constexpr int MDQ = (MQ1 > MD1) ? MQ1 : MD1;
@@ -829,33 +839,37 @@ void SmemPAMassApply3D_Element(const int e,
MFEM_SHARED real_t sDQ[MQ1*MD1];
real_t (*B)[MD1] = (real_t (*)[MD1]) sDQ;
real_t (*Bt)[MQ1] = (real_t (*)[MQ1]) sDQ;
MFEM_SHARED real_t sm0[MDQ*MDQ*MDQ];
MFEM_SHARED real_t sm1[MDQ*MDQ*MDQ];
real_t (*X)[MD1][MD1] = (real_t (*)[MD1][MD1]) sm0;
real_t (*DDQ)[MD1][MQ1] = (real_t (*)[MD1][MQ1]) sm1;
real_t (*DQQ)[MQ1][MQ1] = (real_t (*)[MQ1][MQ1]) sm0;
real_t (*QQQ)[MQ1][MQ1] = (real_t (*)[MQ1][MQ1]) sm1;
real_t (*QQD)[MQ1][MD1] = (real_t (*)[MQ1][MD1]) sm0;
real_t (*QDD)[MD1][MD1] = (real_t (*)[MD1][MD1]) sm1;
MFEM_FOREACH_THREAD(dy,y,D1D)
MFEM_SHARED real_t sm0[tbatch][MDQ*MDQ*MDQ];
MFEM_SHARED real_t sm1[tbatch][MDQ*MDQ*MDQ];
real_t (*X)[MD1][MD1] = (real_t (*)[MD1][MD1]) (sm0+tidz);
real_t (*DDQ)[MD1][MQ1] = (real_t (*)[MD1][MQ1]) (sm1+tidz);
real_t (*DQQ)[MQ1][MQ1] = (real_t (*)[MQ1][MQ1]) (sm0+tidz);
real_t (*QQQ)[MQ1][MQ1] = (real_t (*)[MQ1][MQ1]) (sm1+tidz);
real_t (*QQD)[MQ1][MD1] = (real_t (*)[MQ1][MD1]) (sm0+tidz);
real_t (*QDD)[MD1][MD1] = (real_t (*)[MD1][MD1]) (sm1+tidz);
MFEM_FOREACH_THREAD(dy, y, D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
MFEM_FOREACH_THREAD(dx, x, D1D)
{
MFEM_UNROLL(MD1)
for (int dz = 0; dz < D1D; ++dz)
{
X[dz][dy][dx] = x(dx,dy,dz,e);
X[dz][dy][dx] = x(dx, dy, dz, e);
}
}
MFEM_FOREACH_THREAD(dx,x,Q1D)
MFEM_FOREACH_THREAD(dx, x, Q1D) { B[dx][dy] = b(dx, dy); }
}
if (tidz == 0)
{
MFEM_FOREACH_THREAD(dy, y, D1D)
{
B[dx][dy] = b(dx,dy);
MFEM_FOREACH_THREAD(dx, x, Q1D) { B[dx][dy] = b(dx, dy); }
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dy,y,D1D)
MFEM_FOREACH_THREAD(dy, y, D1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
MFEM_FOREACH_THREAD(qx, x, Q1D)
{
real_t u[D1D];
MFEM_UNROLL(MD1)
@@ -880,9 +894,9 @@ void SmemPAMassApply3D_Element(const int e,
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qy,y,Q1D)
MFEM_FOREACH_THREAD(qy, y, Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
MFEM_FOREACH_THREAD(qx, x, Q1D)
{
real_t u[D1D];
MFEM_UNROLL(MD1)
@@ -907,9 +921,9 @@ void SmemPAMassApply3D_Element(const int e,
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qy,y,Q1D)
MFEM_FOREACH_THREAD(qy, y, Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
MFEM_FOREACH_THREAD(qx, x, Q1D)
{
real_t u[Q1D];
MFEM_UNROLL(MQ1)
@@ -929,22 +943,22 @@ void SmemPAMassApply3D_Element(const int e,
MFEM_UNROLL(MQ1)
for (int qz = 0; qz < Q1D; qz++)
{
QQQ[qz][qy][qx] = u[qz] * d(qx,qy,qz,e);
QQQ[qz][qy][qx] = u[qz] * d(qx, qy, qz, e);
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(di,y,D1D)
if (tidz == 0)
{
MFEM_FOREACH_THREAD(q,x,Q1D)
MFEM_FOREACH_THREAD(di, y, D1D)
{
Bt[di][q] = b(q,di);
MFEM_FOREACH_THREAD(q, x, Q1D) { Bt[di][q] = b(q, di); }
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(qy,y,Q1D)
MFEM_FOREACH_THREAD(qy, y, Q1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
MFEM_FOREACH_THREAD(dx, x, D1D)
{
real_t u[Q1D];
MFEM_UNROLL(MQ1)
@@ -969,9 +983,9 @@ void SmemPAMassApply3D_Element(const int e,
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dy,y,D1D)
MFEM_FOREACH_THREAD(dy, y, D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
MFEM_FOREACH_THREAD(dx, x, D1D)
{
real_t u[Q1D];
MFEM_UNROLL(MQ1)
@@ -996,9 +1010,9 @@ void SmemPAMassApply3D_Element(const int e,
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dy,y,D1D)
MFEM_FOREACH_THREAD(dy, y, D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
MFEM_FOREACH_THREAD(dx, x, D1D)
{
real_t u[D1D];
MFEM_UNROLL(MD1)
@@ -1020,11 +1034,11 @@ void SmemPAMassApply3D_Element(const int e,
{
if (ACCUMULATE)
{
y(dx,dy,dz,e) += u[dz];
y(dx, dy, dz, e) += u[dz];
}
else
{
y(dx,dy,dz,e) = u[dz];
y(dx, dy, dz, e) = u[dz];
}
}
}
@@ -1115,8 +1129,8 @@ inline void PAMassApply3D(const int NE,
});
}
// Shared memory PA Mass Apply 2D kernel
template<int T_D1D = 0, int T_Q1D = 0>
// Shared memory PA Mass Apply 3D kernel
template<int T_D1D = 0, int T_Q1D = 0, int TBATCH=1>
inline void SmemPAMassApply3D(const int NE,
const Array<real_t> &b_,
const Array<real_t> &bt_,
@@ -1126,6 +1140,9 @@ inline void SmemPAMassApply3D(const int NE,
const int d1d = 0,
const int q1d = 0)
{
static_assert(T_D1D > 0, "T_D1D must be positive");
static_assert(T_Q1D > 0, "T_Q1D must be positive");
static_assert(TBATCH > 0, "TBATCH must be positive");
MFEM_CONTRACT_VAR(bt_);
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
@@ -1137,9 +1154,11 @@ inline void SmemPAMassApply3D(const int NE,
const auto d = d_.Read();
const auto x = x_.Read();
auto y = y_.ReadWrite();
mfem::forall_2D<T_Q1D*T_Q1D>(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE (int e)
mfem::forall_2D_batch<T_Q1D * T_Q1D * TBATCH>(NE, Q1D, Q1D, TBATCH,
[=] MFEM_HOST_DEVICE(int e)
{
internal::SmemPAMassApply3D_Element<T_D1D,T_Q1D>(e, NE, b, d, x, y, d1d, q1d);
internal::SmemPAMassApply3D_Element<T_D1D, T_Q1D, TBATCH>(e, NE, b, d, x,
y, d1d, q1d);
});
}
@@ -1394,7 +1413,16 @@ ApplyKernelType MassIntegrator::ApplyPAKernels::Kernel()
{
if constexpr (DIM == 1) { return internal::PAMassApply1D; }
else if constexpr (DIM == 2) { return internal::SmemPAMassApply2D<T_D1D,T_Q1D>; }
else if constexpr (DIM == 3) { return internal::SmemPAMassApply3D<T_D1D, T_Q1D>; }
else if constexpr (DIM == 3)
{
constexpr int MDQ = T_D1D >= T_Q1D ? T_D1D : T_Q1D;
// max 64 threads in z limit in cuda and hip
if constexpr (MDQ > 0)
{
return internal::SmemPAMassApply3D<T_D1D, T_Q1D,
internal::mass::NBZ3D(MDQ)>;
}
}
MFEM_ABORT("");
}
-44
View File
@@ -207,28 +207,6 @@ inline MFEM_HOST_DEVICE void WriteDofs2d(const int e, const int d1d,
MFEM_SYNC_THREAD;
}
/// Load 3D input DIM vector at element offset into given register tensor
template <int VDIM, int DIM, int MQ1>
inline MFEM_HOST_DEVICE void LoadDofs3d(const int d1d, const int c,
const DeviceTensor<4, const real_t> &X,
vd_regs3d_t<VDIM, DIM, MQ1> &Y)
{
for (int d = 0; d < DIM; d++)
{
for (int dz = 0; dz < d1d; ++dz)
{
MFEM_FOREACH_THREAD_DIRECT(dy, y, d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx, x, d1d)
{
Y[c][d][dz][dy][dx] = X(dx, dy, dz, c);
}
}
}
}
MFEM_SYNC_THREAD;
}
/// Load 3D input VDIM*DIM vector into given register tensor, specific component
template <int VDIM, int DIM, int MQ1>
inline MFEM_HOST_DEVICE void LoadDofs3d(const int e, const int d1d, const int c,
@@ -354,28 +332,6 @@ inline MFEM_HOST_DEVICE void WriteDofs3d(const int e, const int d1d,
MFEM_SYNC_THREAD;
}
/// Write 3D DIM vector into given device tensor for specific component
template <int VDIM, int DIM, int MQ1>
inline MFEM_HOST_DEVICE void WriteDofs3d(const int d1d, const int c,
vd_regs3d_t<VDIM, DIM, MQ1> &X,
DeviceTensor<4, real_t> &Y)
{
for (int dz = 0; dz < d1d; ++dz)
{
MFEM_FOREACH_THREAD_DIRECT(dy, y, d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx, x, d1d)
{
for (int d = 0; d < DIM; ++d)
{
Y(dx, dy, dz, c) += X(c, d, dz, dy, dx);
}
}
}
}
MFEM_SYNC_THREAD;
}
/// 2D scalar contraction, X direction
template <bool Transpose, int MQ1>
inline MFEM_HOST_DEVICE void ContractX2d(const int d1d, const int q1d,
-332
View File
@@ -1,332 +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.
#pragma once
#include "../config/config.hpp"
#include "../general/forall.hpp"
#include "../linalg/dtensor.hpp"
#include "kernels.hpp" // IWYU pragma: keep
namespace mfem::kernels::internal::low
{
#if ((defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)) || \
(defined(MFEM_USE_HIP) && defined(__HIP_DEVICE_COMPILE__)))
template <int DIM, int N>
// struct regs3d_device_wrapper: mfem::future::tensor<real_t, DIM, 0, 0, 0> {};
struct regs3d_device_wrapper: mfem::future::tensor<real_t, 0, 0, 0, DIM> {};
template <int DIM, int N>
using regs3d_t = regs3d_device_wrapper<DIM, N>;
#else
template <int DIM, int N>
using regs3d_t = mfem::future::tensor<real_t, N, N, N, DIM>;
// using regs3d_t = mfem::future::tensor<real_t, DIM, N, N, N>;
#endif
///////////////////////////////////////////////////////////////////////////////
/// Load 2D matrix into shared memory
template <int MQ1>
inline MFEM_HOST_DEVICE void LoadMatrix(const int d1d, const int q1d,
const real_t *M, real_t (*N)[MQ1])
{
if (MFEM_THREAD_ID(z) == 0)
{
MFEM_FOREACH_THREAD_DIRECT(dy, y, d1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, q1d)
{
N[dy][qx] = M[dy * q1d + qx];
}
}
}
}
///////////////////////////////////////////////////////////////////////////////
template <int DIM, int MQ1>
inline MFEM_HOST_DEVICE void LoadDofs3d(const int e, const int d1d,
const DeviceTensor<5, const real_t> &XE,
real_t (&sm0)[MQ1][MQ1][MQ1][DIM])
{
MFEM_FOREACH_THREAD_DIRECT(dy,y,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx,x,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dz,z,d1d)
{
sm0[dz][dy][dx][0] = XE(dx, dy, dz, 0, e);
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient, 1/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradX(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
const real_t (&sm0)[MQ1][MQ1][MQ1][DIM],
real_t (&sm1)[MQ1][MQ1][MQ1][DIM])
{
MFEM_FOREACH_THREAD_DIRECT(dz,z,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dy,y,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
{
real_t u = 0.0, v = 0.0;
MFEM_UNROLL(MQ1)
for (int dx = 0; dx < d1d; ++dx)
{
const auto x = sm0[dz][dy][dx][0];
u = std::fma(B[dx][qx], x, u);
v = std::fma(G[dx][qx], x, v);
}
sm1[dz][dy][qx][0] = u;
sm1[dz][dy][qx][1] = v;
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient, 2/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradY(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
const real_t (&sm1)[MQ1][MQ1][MQ1][DIM],
real_t (&sm0)[MQ1][MQ1][MQ1][DIM])
{
MFEM_FOREACH_THREAD_DIRECT(dz,z,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
{
real_t u = 0.0, v = 0.0, w = 0.0;
MFEM_UNROLL(MQ1)
for (int dy = 0; dy < d1d; ++dy)
{
u = std::fma(sm1[dz][dy][qx][1], B[dy][qy], u);
v = std::fma(sm1[dz][dy][qx][0], G[dy][qy], v);
w = std::fma(sm1[dz][dy][qx][0], B[dy][qy], w);
}
sm0[dz][qy][qx][0] = u;
sm0[dz][qy][qx][1] = v;
sm0[dz][qy][qx][2] = w;
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient, 3/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradZ(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
const real_t (&sm0)[MQ1][MQ1][MQ1][DIM],
regs3d_t<DIM,MQ1> &reg)
{
MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
{
real_t u[3] = {0.0, 0.0, 0.0};
MFEM_UNROLL(MQ1)
for (int dz = 0; dz < d1d; ++dz)
{
u[0] = std::fma(B[dz][qz], sm0[dz][qy][qx][0], u[0]);
u[1] = std::fma(B[dz][qz], sm0[dz][qy][qx][1], u[1]);
u[2] = std::fma(G[dz][qz], sm0[dz][qy][qx][2], u[2]);
}
reg[qz][qy][qx][0] = u[0];
reg[qz][qy][qx][1] = u[1];
reg[qz][qy][qx][2] = u[2];
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D scalar gradient
template <int DIM, int MQ1>
inline MFEM_HOST_DEVICE void Grad3d(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
real_t (&sm0)[MQ1][MQ1][MQ1][DIM],
real_t (&sm1)[MQ1][MQ1][MQ1][DIM],
regs3d_t<DIM,MQ1> &reg)
{
GradX(d1d, q1d, B, G, sm0, sm1); // Grad X
GradY(d1d, q1d, B, G, sm1, sm0); // Grad Y
GradZ(d1d, q1d, B, G, sm0, reg); // Grad Z
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient Transposed, 1/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradTranspose3dX(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
regs3d_t<DIM,MQ1> &reg,
real_t (&sm1)[MQ1][MQ1][MQ1][DIM],
real_t (&sm0)[MQ1][MQ1][MQ1][DIM])
{
MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
{
sm1[qz][qy][qx][0] = reg[qz][qy][qx][0];
sm1[qz][qy][qx][1] = reg[qz][qy][qx][1];
sm1[qz][qy][qx][2] = reg[qz][qy][qx][2];
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx,x,d1d)
{
real_t u = 0.0, v = 0.0, w = 0.0;
MFEM_UNROLL(MQ1)
for (int qx = 0; qx < q1d; ++qx)
{
u = std::fma(sm1[qz][qy][qx][0], G[dx][qx], u);
v = std::fma(sm1[qz][qy][qx][1], B[dx][qx], v);
w = std::fma(sm1[qz][qy][qx][2], B[dx][qx], w);
}
sm0[qz][qy][dx][0] = u;
sm0[qz][qy][dx][1] = v;
sm0[qz][qy][dx][2] = w;
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient Transposed, 2/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradTranspose3dY(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
real_t (&sm0)[MQ1][MQ1][MQ1][DIM],
real_t (&sm1)[MQ1][MQ1][MQ1][DIM])
{
MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(dy,y,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx,x,d1d)
{
real_t u = 0.0, v = 0.0, w = 0.0;
MFEM_UNROLL(MQ1)
for (int qy = 0; qy < q1d; ++qy)
{
u = std::fma(sm0[qz][qy][dx][0], B[dy][qy], u);
v = std::fma(sm0[qz][qy][dx][1], G[dy][qy], v);
w = std::fma(sm0[qz][qy][dx][2], B[dy][qy], w);
}
sm1[qz][dy][dx][0] = u;
sm1[qz][dy][dx][1] = v;
sm1[qz][dy][dx][2] = w;
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient Transposed, 3/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradTranspose3dZ(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
real_t (&sm1)[MQ1][MQ1][MQ1][DIM],
regs3d_t<DIM,MQ1> &reg)
{
MFEM_FOREACH_THREAD_DIRECT(dz,z,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dy,y,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx,x,d1d)
{
real_t u = 0.0, v = 0.0, w = 0.0;
MFEM_UNROLL(MQ1)
for (int qz = 0; qz < q1d; ++qz)
{
u = std::fma(sm1[qz][dy][dx][0], B[dz][qz], u);
v = std::fma(sm1[qz][dy][dx][1], B[dz][qz], v);
w = std::fma(sm1[qz][dy][dx][2], G[dz][qz], w);
}
reg[dz][dy][dx][0] = u;
reg[dz][dy][dx][1] = v;
reg[dz][dy][dx][2] = w;
}
}
}
MFEM_SYNC_THREAD;
}
///////////////////////////////////////////////////////////////////////////////
/// 3D scalar gradient transposed
template <int DIM, int MQ1>
inline MFEM_HOST_DEVICE void GradTranspose3d(const int d1d, const int q1d,
const real_t (*B)[MQ1],
const real_t (*G)[MQ1],
regs3d_t<DIM,MQ1> &reg,
real_t (&sm1)[MQ1][MQ1][MQ1][DIM],
real_t (&sm0)[MQ1][MQ1][MQ1][DIM])
{
GradTranspose3dX(d1d, q1d, B, G, reg, sm1, sm0); // Grad^T X
GradTranspose3dY(d1d, q1d, B, G, sm0, sm1); // Grad^T Y
GradTranspose3dZ(d1d, q1d, B, G, sm1, reg); // Grad^T Z
}
///////////////////////////////////////////////////////////////////////////////
/// 3D Scalar Gradient Transposed, 3/3
template<int DIM, int MQ1>
inline MFEM_HOST_DEVICE void WriteDofs3d(const int d1d,
const int c, const int e,
regs3d_t<DIM,MQ1> &reg,
const DeviceTensor<5, real_t> &YE)
{
MFEM_FOREACH_THREAD_DIRECT(dz,z,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dy,y,d1d)
{
MFEM_FOREACH_THREAD_DIRECT(dx,x,d1d)
{
const real_t u = reg[dz][dy][dx][0];
const real_t v = reg[dz][dy][dx][1];
const real_t w = reg[dz][dy][dx][2];
YE(dx, dy, dz, c, e) += (u + v + w);
}
}
}
}
} // namespace mfem::kernels::internal
-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. */
-44
View File
@@ -1,44 +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 "../quadinterpolator.hpp"
#include "eval_transpose.hpp"
namespace mfem
{
/// @cond Suppress_Doxygen_warnings
QuadratureInterpolator::TensorEvalTransposeKernelType
QuadratureInterpolator::TensorEvalTransposeKernels::Fallback(
int DIM, QVectorLayout Q_LAYOUT, int, int, int)
{
using namespace internal::quadrature_interpolator;
if (Q_LAYOUT == QVectorLayout::byNODES)
{
if (DIM == 1) { return ValuesTranspose1D<QVectorLayout::byNODES>; }
else if (DIM == 2) { return ValuesTranspose2D<QVectorLayout::byNODES>; }
else if (DIM == 3) { return ValuesTranspose3D<QVectorLayout::byNODES>; }
}
else
{
if (DIM == 1) { return ValuesTranspose1D<QVectorLayout::byVDIM>; }
else if (DIM == 2) { return ValuesTranspose2D<QVectorLayout::byVDIM>; }
else if (DIM == 3) { return ValuesTranspose3D<QVectorLayout::byVDIM>; }
}
MFEM_ABORT("Invalid dimension");
return nullptr;
}
/// @endcond
} // namespace mfem
-300
View File
@@ -1,300 +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.
#pragma once
#include "../quadinterpolator.hpp"
#include "../../general/forall.hpp"
#include "../../linalg/dtensor.hpp"
#include "../../linalg/kernels.hpp"
#include "../kernels.hpp"
namespace mfem
{
namespace internal
{
namespace quadrature_interpolator
{
template<QVectorLayout Q_LAYOUT>
static void ValuesTranspose1D(const int NE,
const real_t *b_,
const real_t *q_,
real_t *e_,
const int vdim,
const int d1d,
const int q1d)
{
const auto b = Reshape(b_, q1d, d1d);
const auto qd = Q_LAYOUT == QVectorLayout::byNODES ?
Reshape(q_, q1d, vdim, NE) :
Reshape(q_, vdim, q1d, NE);
auto e = Reshape(e_, d1d, vdim, NE);
mfem::forall(NE, [=] MFEM_HOST_DEVICE (int el)
{
for (int c = 0; c < vdim; c++)
{
for (int d = 0; d < d1d; d++)
{
real_t u = 0.0;
for (int q = 0; q < q1d; q++)
{
const real_t qval = Q_LAYOUT == QVectorLayout::byVDIM ?
qd(c, q, el) : qd(q, c, el);
u += b(q, d) * qval;
}
e(d, c, el) += u;
}
}
});
}
template<QVectorLayout Q_LAYOUT,
int T_VDIM = 0, int T_D1D = 0, int T_Q1D = 0,
int T_NBZ = 1>
static void ValuesTranspose2D(const int NE,
const real_t *b_,
const real_t *q_,
real_t *e_,
const int vdim = 0,
const int d1d = 0,
const int q1d = 0)
{
static constexpr int NBZ = T_NBZ ? T_NBZ : 1;
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
const auto b = Reshape(b_, Q1D, D1D);
const auto q = Q_LAYOUT == QVectorLayout::byNODES ?
Reshape(q_, Q1D, Q1D, VDIM, NE) :
Reshape(q_, VDIM, Q1D, Q1D, NE);
auto e = Reshape(e_, D1D, D1D, VDIM, NE);
mfem::forall_2D_batch(NE, D1D, D1D, NBZ, [=] MFEM_HOST_DEVICE (int el)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
constexpr int MQ1 = T_Q1D ? T_Q1D : DofQuadLimits::MAX_Q1D;
constexpr int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
constexpr int MDQ = (MQ1 > MD1) ? MQ1 : MD1;
const int tidz = MFEM_THREAD_ID(z);
MFEM_SHARED real_t sB[MQ1*MD1];
MFEM_SHARED real_t sm0[NBZ][MDQ*MDQ];
MFEM_SHARED real_t sm1[NBZ][MDQ*MDQ];
kernels::internal::LoadB<MD1,MQ1>(D1D,Q1D,b,sB);
ConstDeviceMatrix B(sB, D1D, Q1D);
DeviceMatrix QQ(sm0[tidz], MQ1, MQ1);
DeviceMatrix DQ(sm1[tidz], MD1, MQ1);
DeviceMatrix DD(sm0[tidz], MD1, MD1);
for (int c = 0; c < VDIM; c++)
{
// Load Q data
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
QQ(qx,qy) = Q_LAYOUT == QVectorLayout::byVDIM ?
q(c,qx,qy,el) : q(qx,qy,c,el);
}
}
MFEM_SYNC_THREAD;
// Transpose in y: QQ -> DQ (apply B^T in y-direction)
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += B(dy,qy) * QQ(qx,qy);
}
DQ(dy,qx) = u;
}
}
MFEM_SYNC_THREAD;
// Transpose in x: DQ -> DD (apply B^T in x-direction)
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += B(dx,qx) * DQ(dy,qx);
}
DD(dx,dy) = u;
}
}
MFEM_SYNC_THREAD;
// Store result
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,c,el) += DD(dx,dy);
}
}
MFEM_SYNC_THREAD;
}
});
}
template<QVectorLayout Q_LAYOUT,
int T_VDIM = 0, int T_D1D = 0, int T_Q1D = 0>
static void ValuesTranspose3D(const int NE,
const real_t *b_,
const real_t *q_,
real_t *e_,
const int vdim = 0,
const int d1d = 0,
const int q1d = 0)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
const auto b = Reshape(b_, Q1D, D1D);
const auto q = Q_LAYOUT == QVectorLayout::byNODES ?
Reshape(q_, Q1D, Q1D, Q1D, VDIM, NE) :
Reshape(q_, VDIM, Q1D, Q1D, Q1D, NE);
auto e = Reshape(e_, D1D, D1D, D1D, VDIM, NE);
mfem::forall_3D(NE, D1D, D1D, D1D, [=] MFEM_HOST_DEVICE (int el)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
constexpr int MQ1 = T_Q1D ? T_Q1D : DofQuadLimits::MAX_INTERP_1D;
constexpr int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_INTERP_1D;
constexpr int MDQ = (MQ1 > MD1) ? MQ1 : MD1;
MFEM_SHARED real_t sB[MQ1*MD1];
MFEM_SHARED real_t sm0[MDQ*MDQ*MDQ];
MFEM_SHARED real_t sm1[MDQ*MDQ*MDQ];
kernels::internal::LoadB<MD1,MQ1>(D1D,Q1D,b,sB);
ConstDeviceMatrix B(sB, D1D, Q1D);
DeviceCube QQQ(sm0, MQ1, MQ1, MQ1);
DeviceCube DQQ(sm1, MD1, MQ1, MQ1);
DeviceCube DDQ(sm0, MD1, MD1, MQ1);
DeviceCube DDD(sm1, MD1, MD1, MD1);
for (int c = 0; c < VDIM; c++)
{
// Load Q data
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
QQQ(qx,qy,qz) = Q_LAYOUT == QVectorLayout::byVDIM ?
q(c,qx,qy,qz,el) : q(qx,qy,qz,c,el);
}
}
}
MFEM_SYNC_THREAD;
// Transpose in z
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qz = 0; qz < Q1D; ++qz)
{
u += B(dz,qz) * QQQ(qx,qy,qz);
}
DQQ(dz,qx,qy) = u;
}
}
}
MFEM_SYNC_THREAD;
// Transpose in y
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += B(dy,qy) * DQQ(dz,qx,qy);
}
DDQ(dz,dy,qx) = u;
}
}
}
MFEM_SYNC_THREAD;
// Transpose in x
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += B(dx,qx) * DDQ(dz,dy,qx);
}
DDD(dx,dy,dz) = u;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,dz,c,el) += DDD(dx,dy,dz);
}
}
}
MFEM_SYNC_THREAD;
}
});
}
} // namespace quadrature_interpolator
} // namespace internal
template<int DIM, QVectorLayout Q_LAYOUT,
int VDIM, int D1D, int Q1D, int NBZ>
QuadratureInterpolator::TensorEvalTransposeKernelType
QuadratureInterpolator::TensorEvalTransposeKernels::Kernel()
{
if (DIM == 1) { return internal::quadrature_interpolator::ValuesTranspose1D<Q_LAYOUT>; }
else if (DIM == 2) { return internal::quadrature_interpolator::ValuesTranspose2D<Q_LAYOUT, VDIM, D1D, Q1D, NBZ>; }
else if (DIM == 3) { return internal::quadrature_interpolator::ValuesTranspose3D<Q_LAYOUT, VDIM, D1D, Q1D>; }
else { MFEM_ABORT(""); }
}
} // namespace mfem
-61
View File
@@ -1,61 +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 "../quadinterpolator.hpp"
#include "eval_transpose.hpp"
namespace mfem
{
namespace internal
{
namespace quadrature_interpolator
{
void InitEvalTransposeByVDimKernels()
{
using k = QuadratureInterpolator::TensorEvalTransposeKernels;
constexpr auto L = QVectorLayout::byVDIM;
// 2D
k::Specialization<2,L,1,2,4>::Opt<8>::Add();
k::Specialization<2,L,1,3,6>::Opt<4>::Add();
k::Specialization<2,L,1,4,8>::Opt<2>::Add();
k::Specialization<2,L,2,2,4>::Opt<8>::Add();
k::Specialization<2,L,2,3,4>::Opt<8>::Add();
k::Specialization<2,L,2,3,6>::Opt<4>::Add();
k::Specialization<2,L,2,4,6>::Opt<2>::Add();
k::Specialization<2,L,2,4,8>::Opt<2>::Add();
// 3D
k::Specialization<3,L,1,2,4>::Opt<1>::Add();
k::Specialization<3,L,1,3,6>::Opt<1>::Add();
k::Specialization<3,L,1,4,8>::Opt<1>::Add();
k::Specialization<3,L,3,2,4>::Opt<1>::Add();
k::Specialization<3,L,3,3,6>::Opt<1>::Add();
k::Specialization<3,L,3,4,8>::Opt<1>::Add();
k::Specialization<3,L,3,2,2>::Opt<1>::Add();
k::Specialization<3,L,3,3,3>::Opt<1>::Add();
k::Specialization<3,L,3,4,4>::Opt<1>::Add();
k::Specialization<3,L,3,5,5>::Opt<1>::Add();
k::Specialization<3,L,3,6,6>::Opt<1>::Add();
k::Specialization<3,L,3,7,7>::Opt<1>::Add();
k::Specialization<3,L,3,8,8>::Opt<1>::Add();
k::Specialization<3,L,3,9,9>::Opt<1>::Add();
k::Specialization<3,L,3,4,6>::Opt<1>::Add();
k::Specialization<3,L,3,3,4>::Opt<1>::Add();
}
} // namespace quadrature_interpolator
} // namespace internal
} // namespace mfem
-62
View File
@@ -1,62 +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 "../quadinterpolator.hpp"
#include "grad_transpose.hpp"
namespace mfem
{
/// @cond Suppress_Doxygen_warnings
QuadratureInterpolator::GradTransposeKernelType
QuadratureInterpolator::GradTransposeKernels::Fallback(
int DIM, QVectorLayout Q_LAYOUT, bool GRAD_PHYS, int, int, int)
{
using namespace internal::quadrature_interpolator;
if (Q_LAYOUT == QVectorLayout::byNODES)
{
if (GRAD_PHYS)
{
if (DIM == 1) { return DerivativesTranspose1D<QVectorLayout::byNODES, true>; }
else if (DIM == 2) { return DerivativesTranspose2D<QVectorLayout::byNODES, true>; }
else if (DIM == 3) { return DerivativesTranspose3D<QVectorLayout::byNODES, true>; }
}
else
{
if (DIM == 1) { return DerivativesTranspose1D<QVectorLayout::byNODES, false>; }
else if (DIM == 2) { return DerivativesTranspose2D<QVectorLayout::byNODES, false>; }
else if (DIM == 3) { return DerivativesTranspose3D<QVectorLayout::byNODES, false>; }
}
}
else
{
if (GRAD_PHYS)
{
if (DIM == 1) { return DerivativesTranspose1D<QVectorLayout::byVDIM, true>; }
else if (DIM == 2) { return DerivativesTranspose2D<QVectorLayout::byVDIM, true>; }
else if (DIM == 3) { return DerivativesTranspose3D<QVectorLayout::byVDIM, true>; }
}
else
{
if (DIM == 1) { return DerivativesTranspose1D<QVectorLayout::byVDIM, false>; }
else if (DIM == 2) { return DerivativesTranspose2D<QVectorLayout::byVDIM, false>; }
else if (DIM == 3) { return DerivativesTranspose3D<QVectorLayout::byVDIM, false>; }
}
}
MFEM_ABORT("Invalid dimension");
return nullptr;
}
/// @endcond
} // namespace mfem
-737
View File
@@ -1,737 +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.
#pragma once
#include "../quadinterpolator.hpp"
#include "../../general/forall.hpp"
#include "../../linalg/dtensor.hpp"
#include "../../linalg/kernels.hpp"
#include "../kernels.hpp"
namespace mfem
{
namespace internal
{
namespace quadrature_interpolator
{
// Transpose gradient operation: integrate against shape function derivatives
// This is the adjoint of the Derivatives operation
template<QVectorLayout Q_LAYOUT, bool GRAD_PHYS>
static void DerivativesTranspose1D(const int NE,
const real_t *b_,
const real_t *g_,
const real_t *j_,
const real_t *q_,
real_t *e_,
const int sdim,
const int vdim,
const int d1d,
const int q1d)
{
MFEM_CONTRACT_VAR(b_);
const int SDIM = GRAD_PHYS ? sdim : 1;
const auto g = Reshape(g_, q1d, d1d);
const auto j = Reshape(j_, q1d, SDIM, NE);
const auto q = Q_LAYOUT == QVectorLayout::byNODES ?
Reshape(q_, q1d, vdim, SDIM, NE):
Reshape(q_, vdim, SDIM, q1d, NE);
auto e = Reshape(e_, d1d, vdim, NE);
mfem::forall(NE, [=] MFEM_HOST_DEVICE (int el)
{
for (int c = 0; c < vdim; c++)
{
for (int d = 0; d < d1d; d++)
{
real_t u = 0.0;
for (int qx = 0; qx < q1d; qx++)
{
// Load gradient from q-vector
real_t dq[3] = {0.0, 0.0, 0.0};
for (int s = 0; s < SDIM; ++s)
{
if (Q_LAYOUT == QVectorLayout::byVDIM) { dq[s] = q(c, s, qx, el); }
if (Q_LAYOUT == QVectorLayout::byNODES) { dq[s] = q(qx, c, s, el); }
}
// Apply inverse Jacobian transpose (adjoint of physical gradient)
real_t du = dq[0];
if (GRAD_PHYS)
{
if (SDIM == 1) { du = dq[0] / j(qx, 0, el); }
else if (SDIM == 2)
{
const real_t Jloc[2] = {j(qx,0,el), j(qx,1,el)};
real_t Jinv[3];
kernels::CalcLeftInverse<2,1>(Jloc, Jinv);
du = Jinv[0]*dq[0] + Jinv[1]*dq[1];
}
else // SDIM == 3
{
const real_t Jloc[3] = {j(qx,0,el), j(qx,1,el), j(qx,2,el)};
real_t Jinv[3];
kernels::CalcLeftInverse<3,1>(Jloc, Jinv);
du = Jinv[0]*dq[0] + Jinv[1]*dq[1] + Jinv[2]*dq[2];
}
}
// Accumulate contribution (transpose of G matrix)
u += g(qx, d) * du;
}
e(d, c, el) += u;
}
}
});
}
template<QVectorLayout Q_LAYOUT, bool GRAD_PHYS,
int T_VDIM = 0, int T_D1D = 0, int T_Q1D = 0,
int T_NBZ = 1>
static void DerivativesTranspose2D(const int NE,
const real_t *b_,
const real_t *g_,
const real_t *j_,
const real_t *q_,
real_t *e_,
const int sdim = 2,
const int vdim = 0,
const int d1d = 0,
const int q1d = 0)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
const int SDIM = GRAD_PHYS ? sdim : 2;
static constexpr int NBZ = T_NBZ ? T_NBZ : 1;
const auto b = Reshape(b_, Q1D, D1D);
const auto g = Reshape(g_, Q1D, D1D);
const auto j = Reshape(j_, Q1D, Q1D, SDIM, 2, NE);
const auto q = Q_LAYOUT == QVectorLayout::byNODES ?
Reshape(q_, Q1D, Q1D, VDIM, SDIM, NE):
Reshape(q_, VDIM, SDIM, Q1D, Q1D, NE);
auto e = Reshape(e_, D1D, D1D, VDIM, NE);
mfem::forall_2D_batch(NE, D1D, D1D, NBZ, [=] MFEM_HOST_DEVICE (int el)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
constexpr int MQ1 = T_Q1D ? T_Q1D : DofQuadLimits::MAX_Q1D;
constexpr int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
constexpr int MDQ = (MQ1 > MD1) ? MQ1 : MD1;
const int tidz = MFEM_THREAD_ID(z);
MFEM_SHARED real_t BG[2][MQ1*MD1];
kernels::internal::LoadBG<MD1,MQ1>(D1D,Q1D,b,g,BG);
DeviceMatrix B(BG[0], D1D, Q1D);
DeviceMatrix G(BG[1], D1D, Q1D);
MFEM_SHARED real_t sm0[NBZ][MDQ*MDQ];
MFEM_SHARED real_t sm1[NBZ][MDQ*MDQ];
DeviceMatrix QQ(sm0[tidz], MQ1, MQ1);
DeviceMatrix DQ0(sm1[tidz], MD1, MQ1);
DeviceMatrix DQ1(sm1[tidz], MD1, MQ1); // Reuse sm1 after DQ0 is done
DeviceMatrix DD(sm0[tidz], MD1, MD1); // Reuse sm0 after QQ is done
for (int c = 0; c < VDIM; c++)
{
// Load Q data and apply inverse Jacobian
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
// Load gradient components
real_t dq[3] = {0.0, 0.0, 0.0};
for (int d = 0; d < SDIM; ++d)
{
if (Q_LAYOUT == QVectorLayout::byVDIM) { dq[d] = q(c, d, qx, qy, el); }
else { dq[d] = q(qx, qy, c, d, el); }
}
// Apply inverse Jacobian transpose (adjoint of physical gradient)
real_t du[2] = {dq[0], dq[1]};
if (GRAD_PHYS)
{
if (SDIM == 2)
{
real_t Jloc[4], Jinv[4];
Jloc[0] = j(qx,qy,0,0,el);
Jloc[1] = j(qx,qy,1,0,el);
Jloc[2] = j(qx,qy,0,1,el);
Jloc[3] = j(qx,qy,1,1,el);
kernels::CalcInverse<2>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[2]*dq[1];
const real_t V = Jinv[1]*dq[0] + Jinv[3]*dq[1];
du[0] = U;
du[1] = V;
}
else // SDIM == 3
{
real_t Jloc[6], Jinv[6];
Jloc[0] = j(qx,qy,0,0,el);
Jloc[1] = j(qx,qy,1,0,el);
Jloc[2] = j(qx,qy,2,0,el);
Jloc[3] = j(qx,qy,0,1,el);
Jloc[4] = j(qx,qy,1,1,el);
Jloc[5] = j(qx,qy,2,1,el);
kernels::CalcLeftInverse<3,2>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[2]*dq[1] + Jinv[4]*dq[2];
const real_t V = Jinv[1]*dq[0] + Jinv[3]*dq[1] + Jinv[5]*dq[2];
du[0] = U;
du[1] = V;
}
}
QQ(qx, qy) = du[0]; // Store du/dx component
}
}
MFEM_SYNC_THREAD;
// Apply B^T in y-direction: QQ -> DQ0
// (Transpose of d/dx which uses DQ1(dy,qx)*B(dy,qy))
// Must produce DQ0(dy,qx) to match forward's DQ1 indexing
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += B(dy,qy) * QQ(qx,qy);
}
DQ0(dy,qx) = u;
}
}
MFEM_SYNC_THREAD;
// Apply G^T in x-direction: DQ0 -> DD
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += G(dx,qx) * DQ0(dy,qx);
}
DD(dx,dy) = u;
}
}
MFEM_SYNC_THREAD;
// Accumulate to output
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,c,el) += DD(dx,dy);
}
}
MFEM_SYNC_THREAD;
// Now process du/dy component
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
// Load gradient components
real_t dq[3] = {0.0, 0.0, 0.0};
for (int d = 0; d < SDIM; ++d)
{
if (Q_LAYOUT == QVectorLayout::byVDIM) { dq[d] = q(c, d, qx, qy, el); }
else { dq[d] = q(qx, qy, c, d, el); }
}
// Apply inverse Jacobian transpose
real_t du[2] = {dq[0], dq[1]};
if (GRAD_PHYS)
{
if (SDIM == 2)
{
real_t Jloc[4], Jinv[4];
Jloc[0] = j(qx,qy,0,0,el);
Jloc[1] = j(qx,qy,1,0,el);
Jloc[2] = j(qx,qy,0,1,el);
Jloc[3] = j(qx,qy,1,1,el);
kernels::CalcInverse<2>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[2]*dq[1];
const real_t V = Jinv[1]*dq[0] + Jinv[3]*dq[1];
du[0] = U;
du[1] = V;
}
else // SDIM == 3
{
real_t Jloc[6], Jinv[6];
Jloc[0] = j(qx,qy,0,0,el);
Jloc[1] = j(qx,qy,1,0,el);
Jloc[2] = j(qx,qy,2,0,el);
Jloc[3] = j(qx,qy,0,1,el);
Jloc[4] = j(qx,qy,1,1,el);
Jloc[5] = j(qx,qy,2,1,el);
kernels::CalcLeftInverse<3,2>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[2]*dq[1] + Jinv[4]*dq[2];
const real_t V = Jinv[1]*dq[0] + Jinv[3]*dq[1] + Jinv[5]*dq[2];
du[0] = U;
du[1] = V;
}
}
QQ(qx, qy) = du[1]; // Store du/dy component
}
}
MFEM_SYNC_THREAD;
// Apply G^T in y-direction: QQ -> DQ1
// (Transpose of d/dy which uses DQ0(dy,qx)*G(dy,qy))
// Must produce DQ1(dy,qx) to match forward's DQ0 indexing
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += G(dy,qy) * QQ(qx,qy);
}
DQ1(dy,qx) = u;
}
}
MFEM_SYNC_THREAD;
// Apply B^T in x-direction: DQ1 -> DD
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += B(dx,qx) * DQ1(dy,qx);
}
DD(dx,dy) = u;
}
}
MFEM_SYNC_THREAD;
// Accumulate to output
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,c,el) += DD(dx,dy);
}
}
MFEM_SYNC_THREAD;
}
});
}
template<QVectorLayout Q_LAYOUT, bool GRAD_PHYS,
int T_VDIM = 0, int T_D1D = 0, int T_Q1D = 0>
static void DerivativesTranspose3D(const int NE,
const real_t *b_,
const real_t *g_,
const real_t *j_,
const real_t *q_,
real_t *e_,
const int sdim = 3,
const int vdim = 0,
const int d1d = 0,
const int q1d = 0)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
const auto b = Reshape(b_, Q1D, D1D);
const auto g = Reshape(g_, Q1D, D1D);
const auto j = Reshape(j_, Q1D, Q1D, Q1D, 3, 3, NE);
const auto q = Q_LAYOUT == QVectorLayout::byNODES ?
Reshape(q_, Q1D, Q1D, Q1D, VDIM, 3, NE):
Reshape(q_, VDIM, 3, Q1D, Q1D, Q1D, NE);
auto e = Reshape(e_, D1D, D1D, D1D, VDIM, NE);
mfem::forall_3D(NE, Q1D, Q1D, Q1D, [=] MFEM_HOST_DEVICE (int el)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
const int VDIM = T_VDIM ? T_VDIM : vdim;
constexpr int MQ1 = T_Q1D ? T_Q1D : DofQuadLimits::MAX_INTERP_1D;
constexpr int MD1 = T_D1D ? T_D1D : DofQuadLimits::MAX_INTERP_1D;
MFEM_SHARED real_t BG[2][MQ1*MD1];
kernels::internal::LoadBG<MD1,MQ1>(D1D,Q1D,b,g,BG);
DeviceMatrix B(BG[0], D1D, Q1D);
DeviceMatrix G(BG[1], D1D, Q1D);
MFEM_SHARED real_t sm0[3][MQ1*MQ1*MQ1];
MFEM_SHARED real_t sm1[3][MQ1*MQ1*MQ1];
DeviceCube QQQ(sm0[0], MQ1, MQ1, MQ1);
DeviceCube DQQ(sm1[0], MD1, MQ1, MQ1);
DeviceCube DDQ(sm0[0], MD1, MD1, MQ1);
DeviceCube DDD(sm1[0], MD1, MD1, MD1);
for (int c = 0; c < VDIM; c++)
{
// Process du/dx component
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t dq[3];
if (Q_LAYOUT == QVectorLayout::byVDIM)
{
dq[0] = q(c,0,qx,qy,qz,el);
dq[1] = q(c,1,qx,qy,qz,el);
dq[2] = q(c,2,qx,qy,qz,el);
}
else
{
dq[0] = q(qx,qy,qz,c,0,el);
dq[1] = q(qx,qy,qz,c,1,el);
dq[2] = q(qx,qy,qz,c,2,el);
}
real_t du[3] = {dq[0], dq[1], dq[2]};
if (GRAD_PHYS)
{
real_t Jloc[9], Jinv[9];
for (int col = 0; col < 3; col++)
{
for (int row = 0; row < 3; row++)
{
Jloc[row+3*col] = j(qx,qy,qz,row,col,el);
}
}
kernels::CalcInverse<3>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[3]*dq[1] + Jinv[6]*dq[2];
const real_t V = Jinv[1]*dq[0] + Jinv[4]*dq[1] + Jinv[7]*dq[2];
const real_t W = Jinv[2]*dq[0] + Jinv[5]*dq[1] + Jinv[8]*dq[2];
du[0] = U; du[1] = V; du[2] = W;
}
QQQ(qx,qy,qz) = du[0];
}
}
}
MFEM_SYNC_THREAD;
// Apply G^T in x: QQQ -> DQQ (transpose of G⊗B⊗B)
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += G(dx,qx) * QQQ(qx,qy,qz);
}
DQQ(dx,qy,qz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Apply B^T in y: DQQ -> DDQ
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += B(dy,qy) * DQQ(dx,qy,qz);
}
DDQ(dx,dy,qz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Apply B^T in z: DDQ -> DDD
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qz = 0; qz < Q1D; ++qz)
{
u += B(dz,qz) * DDQ(dx,dy,qz);
}
DDD(dx,dy,dz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Accumulate result
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,dz,c,el) += DDD(dx,dy,dz);
}
}
}
MFEM_SYNC_THREAD;
// Process du/dy component
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t dq[3];
if (Q_LAYOUT == QVectorLayout::byVDIM)
{
dq[0] = q(c,0,qx,qy,qz,el);
dq[1] = q(c,1,qx,qy,qz,el);
dq[2] = q(c,2,qx,qy,qz,el);
}
else
{
dq[0] = q(qx,qy,qz,c,0,el);
dq[1] = q(qx,qy,qz,c,1,el);
dq[2] = q(qx,qy,qz,c,2,el);
}
real_t du[3] = {dq[0], dq[1], dq[2]};
if (GRAD_PHYS)
{
real_t Jloc[9], Jinv[9];
for (int col = 0; col < 3; col++)
{
for (int row = 0; row < 3; row++)
{
Jloc[row+3*col] = j(qx,qy,qz,row,col,el);
}
}
kernels::CalcInverse<3>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[3]*dq[1] + Jinv[6]*dq[2];
const real_t V = Jinv[1]*dq[0] + Jinv[4]*dq[1] + Jinv[7]*dq[2];
const real_t W = Jinv[2]*dq[0] + Jinv[5]*dq[1] + Jinv[8]*dq[2];
du[0] = U; du[1] = V; du[2] = W;
}
QQQ(qx,qy,qz) = du[1];
}
}
}
MFEM_SYNC_THREAD;
// Apply B^T in x: QQQ -> DQQ (transpose of B⊗G⊗B)
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += B(dx,qx) * QQQ(qx,qy,qz);
}
DQQ(dx,qy,qz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Apply G^T in y: DQQ -> DDQ
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += G(dy,qy) * DQQ(dx,qy,qz);
}
DDQ(dx,dy,qz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Apply B^T in z: DDQ -> DDD
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qz = 0; qz < Q1D; ++qz)
{
u += B(dz,qz) * DDQ(dx,dy,qz);
}
DDD(dx,dy,dz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Accumulate result
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,dz,c,el) += DDD(dx,dy,dz);
}
}
}
MFEM_SYNC_THREAD;
// Process du/dz component
MFEM_FOREACH_THREAD(qz,z,Q1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t dq[3];
if (Q_LAYOUT == QVectorLayout::byVDIM)
{
dq[0] = q(c,0,qx,qy,qz,el);
dq[1] = q(c,1,qx,qy,qz,el);
dq[2] = q(c,2,qx,qy,qz,el);
}
else
{
dq[0] = q(qx,qy,qz,c,0,el);
dq[1] = q(qx,qy,qz,c,1,el);
dq[2] = q(qx,qy,qz,c,2,el);
}
real_t du[3] = {dq[0], dq[1], dq[2]};
if (GRAD_PHYS)
{
real_t Jloc[9], Jinv[9];
for (int col = 0; col < 3; col++)
{
for (int row = 0; row < 3; row++)
{
Jloc[row+3*col] = j(qx,qy,qz,row,col,el);
}
}
kernels::CalcInverse<3>(Jloc, Jinv);
const real_t U = Jinv[0]*dq[0] + Jinv[3]*dq[1] + Jinv[6]*dq[2];
const real_t V = Jinv[1]*dq[0] + Jinv[4]*dq[1] + Jinv[7]*dq[2];
const real_t W = Jinv[2]*dq[0] + Jinv[5]*dq[1] + Jinv[8]*dq[2];
du[0] = U; du[1] = V; du[2] = W;
}
QQQ(qx,qy,qz) = du[2];
}
}
}
MFEM_SYNC_THREAD;
// Apply G^T in z: QQQ -> DQQ (transpose of B⊗B⊗G)
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(qy,y,Q1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qz = 0; qz < Q1D; ++qz)
{
u += G(dz,qz) * QQQ(qx,qy,qz);
}
DQQ(qx,qy,dz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Apply B^T in y: DQQ -> DDQ
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(qx,x,Q1D)
{
real_t u = 0.0;
for (int qy = 0; qy < Q1D; ++qy)
{
u += B(dy,qy) * DQQ(qx,qy,dz);
}
DDQ(qx,dy,dz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Apply B^T in x: DDQ -> DDD
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
real_t u = 0.0;
for (int qx = 0; qx < Q1D; ++qx)
{
u += B(dx,qx) * DDQ(qx,dy,dz);
}
DDD(dx,dy,dz) = u;
}
}
}
MFEM_SYNC_THREAD;
// Accumulate result
MFEM_FOREACH_THREAD(dz,z,D1D)
{
MFEM_FOREACH_THREAD(dy,y,D1D)
{
MFEM_FOREACH_THREAD(dx,x,D1D)
{
e(dx,dy,dz,c,el) += DDD(dx,dy,dz);
}
}
}
MFEM_SYNC_THREAD;
}
});
}
} // namespace quadrature_interpolator
} // namespace internal
template<int DIM, QVectorLayout Q_LAYOUT, bool GRAD_PHYS, int VDIM, int D1D,
int Q1D, int NBZ>
QuadratureInterpolator::GradTransposeKernelType
QuadratureInterpolator::GradTransposeKernels::Kernel()
{
if (DIM == 1) { return internal::quadrature_interpolator::DerivativesTranspose1D<Q_LAYOUT, GRAD_PHYS>; }
else if (DIM == 2) { return internal::quadrature_interpolator::DerivativesTranspose2D<Q_LAYOUT, GRAD_PHYS, VDIM, D1D, Q1D, NBZ>; }
else if (DIM == 3) { return internal::quadrature_interpolator::DerivativesTranspose3D<Q_LAYOUT, GRAD_PHYS, VDIM, D1D, Q1D>; }
else { MFEM_ABORT(""); }
}
} // namespace mfem
-50
View File
@@ -1,50 +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 "../quadinterpolator.hpp"
#include "grad_transpose.hpp"
namespace mfem
{
namespace internal
{
namespace quadrature_interpolator
{
template <bool P>
void InitGradTransposeByNodesKernels()
{
using k = QuadratureInterpolator::GradTransposeKernels;
constexpr auto L = QVectorLayout::byNODES;
// 2D
k::Specialization<2,L,P,1,3,4>::template Opt<8>::Add();
k::Specialization<2,L,P,1,4,6>::template Opt<4>::Add();
k::Specialization<2,L,P,1,5,8>::template Opt<2>::Add();
k::Specialization<2,L,P,2,3,3>::template Opt<8>::Add();
k::Specialization<2,L,P,2,3,4>::template Opt<8>::Add();
k::Specialization<2,L,P,2,4,6>::template Opt<4>::Add();
k::Specialization<2,L,P,2,5,8>::template Opt<2>::Add();
// 3D
k::Specialization<3,L,P,1,3,4>::Add();
k::Specialization<3,L,P,1,4,6>::Add();
k::Specialization<3,L,P,1,5,8>::Add();
k::Specialization<3,L,P,3,3,4>::Add();
k::Specialization<3,L,P,3,4,6>::Add();
k::Specialization<3,L,P,3,5,8>::Add();
}
template void InitGradTransposeByNodesKernels<false>();
template void InitGradTransposeByNodesKernels<true>();
} // namespace quadrature_interpolator
} // namespace internal
} // namespace mfem
-50
View File
@@ -1,50 +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 "../quadinterpolator.hpp"
#include "grad_transpose.hpp"
namespace mfem
{
namespace internal
{
namespace quadrature_interpolator
{
template <bool P>
void InitGradTransposeByVDimKernels()
{
using k = QuadratureInterpolator::GradTransposeKernels;
constexpr auto L = QVectorLayout::byVDIM;
// 2D
k::Specialization<2,L,P,1,3,4>::template Opt<8>::Add();
k::Specialization<2,L,P,1,4,6>::template Opt<4>::Add();
k::Specialization<2,L,P,1,5,8>::template Opt<2>::Add();
k::Specialization<2,L,P,2,3,3>::template Opt<8>::Add();
k::Specialization<2,L,P,2,3,4>::template Opt<8>::Add();
k::Specialization<2,L,P,2,4,6>::template Opt<4>::Add();
k::Specialization<2,L,P,2,5,8>::template Opt<2>::Add();
// 3D
k::Specialization<3,L,P,1,3,4>::Add();
k::Specialization<3,L,P,1,4,6>::Add();
k::Specialization<3,L,P,1,5,8>::Add();
k::Specialization<3,L,P,3,3,4>::Add();
k::Specialization<3,L,P,3,4,6>::Add();
k::Specialization<3,L,P,3,5,8>::Add();
}
template void InitGradTransposeByVDimKernels<false>();
template void InitGradTransposeByVDimKernels<true>();
} // namespace quadrature_interpolator
} // namespace internal
} // namespace mfem
+9 -63
View File
@@ -11,7 +11,6 @@
#include "quadinterpolator.hpp"
#include "qinterp/grad.hpp"
#include "qinterp/grad_transpose.hpp"
#include "qinterp/eval.hpp"
#include "qspace.hpp"
#include "../general/forall.hpp"
@@ -31,10 +30,7 @@ void InitEvalKernels();
void InitDetKernels();
template <bool P> void InitGradByNodesKernels();
template <bool P> void InitGradByVDimKernels();
template <bool P> void InitGradTransposeByNodesKernels();
template <bool P> void InitGradTransposeByVDimKernels();
void InitTensorEvalHDivKernels();
void InitEvalTransposeByVDimKernels();
struct Kernels
{
Kernels()
@@ -49,19 +45,12 @@ struct Kernels
// Phys grad kernels
InitGradByNodesKernels<true>();
InitGradByVDimKernels<true>();
// Non-phys grad transpose kernels
InitGradTransposeByNodesKernels<false>();
InitGradTransposeByVDimKernels<false>();
// Phys grad transpose kernels
InitGradTransposeByNodesKernels<true>();
InitGradTransposeByVDimKernels<true>();
// Determinants
InitDetKernels();
// Non-tensor
InitEvalKernels();
// Tensor (quad,hex) H(div)
InitTensorEvalHDivKernels();
InitEvalTransposeByVDimKernels();
}
};
}
@@ -657,59 +646,16 @@ void QuadratureInterpolator::MultHDiv(const Vector &e_vec,
MFEM_CONTRACT_VAR(q_div);
}
void QuadratureInterpolator::AddMultTranspose(unsigned eval_flags,
const Vector &q_val,
const Vector &q_der,
Vector &e_vec) const
void QuadratureInterpolator::MultTranspose(unsigned eval_flags,
const Vector &q_val,
const Vector &q_der,
Vector &e_vec) const
{
const int ne = fespace->GetNE();
if (ne == 0) { return; }
const FiniteElement *fe = fespace->GetFE(0);
const int vdim = fespace->GetVDim();
const int sdim = fespace->GetMesh()->SpaceDimension();
const bool use_tensor_eval =
use_tensor_products &&
dynamic_cast<const TensorBasisElement*>(fe) != nullptr;
const IntegrationRule *ir =
IntRule ? IntRule : &qspace->GetElementIntRule(0);
const DofToQuad::Mode mode =
use_tensor_eval ? DofToQuad::TENSOR : DofToQuad::FULL;
const DofToQuad &maps = fe->GetDofToQuad(*ir, mode);
const int dim = maps.FE->GetDim();
const int nd = maps.ndof;
const int nq = maps.nqpt;
const GeometricFactors *geom = nullptr;
if (eval_flags & PHYSICAL_DERIVATIVES)
{
const int jacobians = GeometricFactors::JACOBIANS;
geom = fespace->GetMesh()->GetGeometricFactors(*ir, jacobians);
}
if (use_tensor_eval)
{
if (eval_flags & (VALUES | PHYSICAL_VALUES))
{
TensorEvalTransposeKernels::Run(dim, q_layout, vdim, nd, nq, ne,
maps.B.Read(), q_val.Read(),
e_vec.ReadWrite(), vdim, nd, nq);
}
if (eval_flags & (DERIVATIVES | PHYSICAL_DERIVATIVES))
{
const bool phys = (eval_flags & PHYSICAL_DERIVATIVES);
const real_t *J = phys ? geom->J.Read() : nullptr;
const int s_dim = phys ? sdim : dim;
GradTransposeKernels::Run(dim, q_layout, phys, vdim, nd, nq, ne,
maps.B.Read(), maps.G.Read(), J,
q_der.Read(), e_vec.ReadWrite(),
s_dim, vdim, nd, nq);
}
}
else
{
MFEM_ABORT("Non-tensor MultTranspose not yet implemented");
}
MFEM_CONTRACT_VAR(eval_flags);
MFEM_CONTRACT_VAR(q_val);
MFEM_CONTRACT_VAR(q_der);
MFEM_CONTRACT_VAR(e_vec);
MFEM_ABORT("this method is not implemented yet");
}
void QuadratureInterpolator::Values(const Vector &e_vec,
+2 -13
View File
@@ -152,8 +152,8 @@ public:
void Determinants(const Vector &e_vec, Vector &q_det) const;
/// Perform the transpose operation of Mult(). (TODO)
void AddMultTranspose(unsigned eval_flags, const Vector &q_val,
const Vector &q_der, Vector &e_vec) const;
void MultTranspose(unsigned eval_flags, const Vector &q_val,
const Vector &q_der, Vector &e_vec) const;
/// @brief Returns true if the given finite element space is supported by
/// QuadratureInterpolator.
@@ -178,13 +178,6 @@ public:
using TensorEvalHDivKernelType =
void(*)(const int, const real_t *, const real_t *, const real_t *,
const real_t *, real_t *, const int, const int);
using TensorEvalTransposeKernelType = void(*)(const int, const real_t *,
const real_t *,
real_t *, const int, const int, const int);
using GradTransposeKernelType = void(*)(const int, const real_t *,
const real_t *, const real_t *,
const real_t *, real_t *,
const int, const int, const int, const int);
MFEM_REGISTER_KERNELS(TensorEvalKernels, TensorEvalKernelType,
(int, QVectorLayout, int, int, int), (int));
@@ -196,10 +189,6 @@ public:
(int, QVectorLayout, bool, int, int), (int));
MFEM_REGISTER_KERNELS(TensorEvalHDivKernels, TensorEvalHDivKernelType,
(int, QVectorLayout, unsigned, int, int));
MFEM_REGISTER_KERNELS(TensorEvalTransposeKernels, TensorEvalTransposeKernelType,
(int, QVectorLayout, int, int, int), (int));
MFEM_REGISTER_KERNELS(GradTransposeKernels, GradTransposeKernelType,
(int, QVectorLayout, bool, int, int, int), (int));
};
}
-16
View File
@@ -27,14 +27,6 @@
#endif
#include "hip.hpp"
#if defined(MFEM_USE_CUDA)
#define Gpu(...) Cu##__VA_ARGS__
#elif defined(MFEM_USE_HIP)
#define Gpu(...) Hip##__VA_ARGS__
#else
#define Gpu(...) __VA_ARGS__
#endif
#ifdef MFEM_USE_OCCA
#include "occa.hpp"
#endif
@@ -56,7 +48,6 @@ constexpr bool mfem_use_gpu = false;
#define MFEM_DEVICE
#define MFEM_HOST
#define MFEM_LAMBDA
#define MFEM_CONSTANT
// #define MFEM_HOST_DEVICE // defined in config/config.hpp
// MFEM_DEVICE_SYNC is made available for debugging purposes
#define MFEM_DEVICE_SYNC
@@ -74,13 +65,6 @@ constexpr bool mfem_use_gpu = false;
#define MFEM_THREAD_SIZE(k) 1
#define MFEM_FOREACH_THREAD(i,k,N) for(int i=0; i<N; i++)
#define MFEM_FOREACH_THREAD_DIRECT(i,k,N) MFEM_FOREACH_THREAD(i,k,N)
inline const void* MemcpyToSymbol(const void *d_sym, const void *h_src,
size_t bytes)
{
memcpy(const_cast<void *>(d_sym), h_src, bytes);
return d_sym;
}
#endif
// 'double' and 'float' atomicAdd implementation for previous versions of CUDA
-11
View File
@@ -175,17 +175,6 @@ void* CuMemcpyDtoHAsync(void *dst, const void *src, size_t bytes)
return dst;
}
const void* CuMemcpyToSymbol(const void *d_sym, const void *h_src,
size_t bytes)
{
#ifdef MFEM_USE_CUDA
MFEM_GPU_CHECK(cudaMemcpyToSymbol(d_sym, h_src, bytes));
return d_sym;
#endif
MFEM_ABORT("CUDA has no shadow host copy of device symbols");
return memcpy(const_cast<void*>(d_sym), h_src, bytes);
}
void CuCheckLastError()
{
#ifdef MFEM_USE_CUDA
-6
View File
@@ -25,8 +25,6 @@ constexpr bool mfem_use_gpu = true;
#define MFEM_HOST __host__
#define MFEM_LAMBDA __host__
#define MFEM_LAUNCH_BOUNDS __launch_bounds__
#define MFEM_DEVICE __device__
#define MFEM_CONSTANT __constant__
// #define MFEM_HOST_DEVICE __host__ __device__ // defined in config/config.hpp
#define MFEM_DEVICE_SYNC MFEM_GPU_CHECK(cudaDeviceSynchronize())
#define MFEM_STREAM_SYNC MFEM_GPU_CHECK(cudaStreamSynchronize(0))
@@ -96,10 +94,6 @@ void* CuMemcpyDtoH(void *h_dst, const void *d_src, size_t bytes);
/// Copies memory from Device to Host
void* CuMemcpyDtoHAsync(void *h_dst, const void *d_src, size_t bytes);
/// Copies data to the given symbol on the device.
const void* CuMemcpyToSymbol(const void *d_sym, const void *h_src,
size_t bytes);
/// Check the error code returned by cudaGetLastError(), aborting on error.
void CuCheckLastError();
-9
View File
@@ -175,15 +175,6 @@ void* HipMemcpyDtoHAsync(void *dst, const void *src, size_t bytes)
return dst;
}
const void* HipMemcpyToSymbol(const void *d_sym, const void *h_src,
size_t bytes)
{
#ifdef MFEM_USE_HIP
MFEM_GPU_CHECK(hipMemcpyToSymbol(d_sym, h_src, bytes));
#endif
return memcpy(const_cast<void*>(d_sym), h_src, bytes);
}
void HipCheckLastError()
{
#ifdef MFEM_USE_HIP
+1 -6
View File
@@ -21,9 +21,8 @@
#if defined(MFEM_USE_HIP) && defined(__HIP__)
#define MFEM_USE_CUDA_OR_HIP
constexpr bool mfem_use_gpu = true;
#define MFEM_HOST __host__
#define MFEM_DEVICE __device__
#define MFEM_CONSTANT __constant__
#define MFEM_HOST __host__
#define MFEM_LAMBDA __host__ __device__
#define MFEM_LAUNCH_BOUNDS __launch_bounds__
// #define MFEM_HOST_DEVICE __host__ __device__ // defined in config/config.hpp
@@ -97,10 +96,6 @@ void* HipMemcpyDtoH(void *h_dst, const void *d_src, size_t bytes);
/// Copies memory from Device to Host
void* HipMemcpyDtoHAsync(void *h_dst, const void *d_src, size_t bytes);
/// Copies data to the given symbol on the device.
const void* HipMemcpyToSymbol(const void *d_sym, const void *h_src,
size_t bytes);
/// Check the error code returned by hipGetLastError(), aborting on error.
void HipCheckLastError();
-1
View File
@@ -1 +0,0 @@
../../stash/debug/nvtx.hpp
-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
-2
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"
@@ -38,7 +37,6 @@
#include "batched/gpu_blas.hpp"
#include "batched/solver.hpp"
#include "tensor.hpp"
#include "tensor_arrays.hpp"
#include "filteredsolver.hpp"
#include "ordering.hpp"
#include "particlevector.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. */
+9 -106
View File
@@ -33,62 +33,17 @@ struct tensor;
/// The implementation can be drastically generalized by using concepts of the
/// c++17 standard.
template <typename T>
template < typename T >
struct tensor<T>
{
using type = T;
static constexpr int ndim = 1;
static constexpr int first_dim = 0;
MFEM_HOST_DEVICE tensor() = default;
MFEM_HOST_DEVICE tensor(T v) : values(v) {}
MFEM_HOST_DEVICE T& operator[](int) { return values; }
MFEM_HOST_DEVICE const T& operator[](int) const { return values; }
MFEM_HOST_DEVICE T& operator()(int) { return values; }
MFEM_HOST_DEVICE const T& operator()(int) const { return values; }
MFEM_HOST_DEVICE T& operator()() { return values; }
MFEM_HOST_DEVICE const T& operator()() const { return values; }
MFEM_HOST_DEVICE T& operator[](int /*unused*/) { return values; }
MFEM_HOST_DEVICE const T& operator[](int /*unused*/) const { return values; }
MFEM_HOST_DEVICE T& operator()(int /*unused*/) { return values; }
MFEM_HOST_DEVICE const T& operator()(int /*unused*/) const { return values; }
MFEM_HOST_DEVICE operator T() const { return values; }
MFEM_HOST_DEVICE constexpr const T& scalar() const { return values; }
// A * tensor<T> -> A * T
template <typename A,
std::enable_if_t<!std::is_same_v<std::decay_t<A>, tensor>, int> = 0>
MFEM_HOST_DEVICE friend auto operator*(const A& a, const tensor& s)
-> decltype(a * s.scalar())
{
return a * s.scalar();
}
// tensor<T> * A -> T * A
template <typename A,
std::enable_if_t<!std::is_same_v<std::decay_t<A>, tensor>, int> = 0>
MFEM_HOST_DEVICE friend auto operator*(const tensor& s, const A& a)
-> decltype(s.scalar() * a)
{
return s.scalar() * a;
}
// A / tensor<T>, tensor<T> / A
template <typename A,
std::enable_if_t<!std::is_same_v<std::decay_t<A>, tensor>, int> = 0>
MFEM_HOST_DEVICE friend auto operator/(const A& a, const tensor& s)
-> decltype(a / s.scalar())
{
return a / s.scalar();
}
template <typename A,
std::enable_if_t<!std::is_same_v<std::decay_t<A>, tensor>, int> = 0>
MFEM_HOST_DEVICE friend auto operator/(const tensor& s, const A& a)
-> decltype(s.scalar() / a)
{
return s.scalar() / a;
}
T values;
};
@@ -165,23 +120,6 @@ struct tensor<T, n0, n1, n2>
tensor < T, n1, n2 > values[n0];
};
template < typename T, int n1, int n2 >
struct tensor<T, 0, n1, n2>
{
using type = T;
static constexpr int ndim = 3;
static constexpr int first_dim = 0;
MFEM_HOST_DEVICE tensor< T, n1, n2 >& operator[](int /*i*/) { return values; }
MFEM_HOST_DEVICE const tensor< T, n1, n2 >& operator[](int /*i*/) const { return values; }
MFEM_HOST_DEVICE tensor< T, n1, n2 >& operator()(int /*i*/) { return values; }
MFEM_HOST_DEVICE const tensor< T, n1, n2 >& operator()(int /*i*/) const { return values; }
MFEM_HOST_DEVICE tensor< T, n2 >& operator()(int /*i*/, int j) { return values[j]; }
MFEM_HOST_DEVICE const tensor< T, n2 >& operator()(int i, int j) const { return values[i][j]; }
MFEM_HOST_DEVICE T& operator()(int /*i*/, int j, int k) { return values[j][k]; }
MFEM_HOST_DEVICE const T& operator()(int /*i*/, int j, int k) const { return values[j][k]; }
tensor < T, n1, n2 > values;
};
template < typename T, int n0, int n1, int n2, int n3 >
struct tensor<T, n0, n1, n2, n3>
{
@@ -201,26 +139,6 @@ struct tensor<T, n0, n1, n2, n3>
tensor < T, n1, n2, n3 > values[n0];
};
template < typename T, int n1, int n2, int n3 >
struct tensor<T, 0, n1, n2, n3>
{
using type = T;
static constexpr int ndim = 4;
static constexpr int first_dim = 0;
MFEM_HOST_DEVICE tensor< T, n1, n2, n3 >& operator[](int /*i*/) { return values; }
MFEM_HOST_DEVICE const tensor< T, n1, n2, n3 >& operator[](int /*i*/) const { return values; }
MFEM_HOST_DEVICE tensor< T, n1, n2, n3 >& operator()(int /*i*/) { return values; }
MFEM_HOST_DEVICE const tensor< T, n1, n2, n3 >& operator()(int /*i*/) const { return values; }
MFEM_HOST_DEVICE tensor< T, n2, n3 >& operator()(int /*i*/, int j) { return values[j]; }
MFEM_HOST_DEVICE const tensor< T, n2, n3 >& operator()(int /*i*/, int j) const { return values[j]; }
MFEM_HOST_DEVICE tensor< T, n3 >& operator()(int /*i*/, int j, int k) { return values[j][k]; }
MFEM_HOST_DEVICE const tensor< T, n3 >& operator()(int /*i*/, int j,
int k) const { return values[j][k]; }
MFEM_HOST_DEVICE T& operator()(int /*i*/, int j, int k, int l) { return values[j][k][l]; }
MFEM_HOST_DEVICE const T& operator()(int /*i*/, int j, int k, int l) const { return values[j][k][l]; }
tensor < T, n1, n2, n3 > values;
};
template < typename T, int n0, int n1, int n2, int n3, int n4 >
struct tensor<T, n0, n1, n2, n3, n4>
{
@@ -623,21 +541,13 @@ MFEM_HOST_DEVICE auto operator*(S scale, const tensor<T, n...>& A) ->
tensor<decltype(S {} * T{}), n...>
{
tensor<decltype(S{} * T{}), n...> C{};
if constexpr (sizeof...(n) == 0)
for (int i = 0; i < tensor<T, n...>::first_dim; i++)
{
C.values = scale * A.values;
}
else
{
for (int i = 0; i < tensor<T, n...>::first_dim; i++)
{
C[i] = scale * A[i];
}
C[i] = scale * A[i];
}
return C;
}
/**
* @brief multiply a tensor by a scalar value
* @tparam S the scalar value type. Must be arithmetic (e.g. float, real_t, int) or a dual number
@@ -653,16 +563,9 @@ MFEM_HOST_DEVICE auto operator*(const tensor<T, n...>& A, S scale) ->
tensor<decltype(T {} * S{}), n...>
{
tensor<decltype(T{} * S{}), n...> C{};
if constexpr (sizeof...(n) == 0)
for (int i = 0; i < tensor<T, n...>::first_dim; i++)
{
C.values = A.values * scale;
}
else
{
for (int i = 0; i < tensor<T, n...>::first_dim; i++)
{
C[i] = A[i] * scale;
}
C[i] = A[i] * scale;
}
return C;
}
-348
View File
@@ -1,348 +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_TENSOR_ARRAYS_HPP
#define MFEM_TENSOR_ARRAYS_HPP
#include "tensor.hpp"
#include <array> // std::array, std::size_t (indirectly)
#include <type_traits> /* std::remove_cv_t, std::remove_reference_t,
std::is_const_v */
#include <utility> /* std::forward, std::index_sequence,
std::make_index_sequence */
#include <algorithm> // std::min
#include <tuple> // std::apply, std::tuple_size_v
#include <numeric> // std::iota
namespace mfem
{
namespace future
{
template <std::size_t... Is, typename Fn>
constexpr inline void for_unrolled_simple(std::index_sequence<Is...>, Fn &&fn)
{
(fn(Is), ...);
}
template <int... loop_sizes>
__attribute__((annotate("enzyme_inactive")))
constexpr inline auto to_multiindex(std::size_t i)
{
constexpr auto dims = sizeof...(loop_sizes);
constexpr std::array<std::size_t,dims> sizes{loop_sizes...};
std::array<std::size_t,dims> is{}; // value initialization with zeros
for (std::size_t d = 0; d < dims; d++)
{
is[d] = i%sizes[d];
i /= sizes[d];
}
return is;
}
/// lambda_t:
/// - input: const std::array<std::size_t,sizeof...(loop_sizes)> &
/// - output: void
/// Note: 0D loop executes the lambda one time with an array of dim 0.
template <int... loop_sizes, typename lambda_t>
constexpr inline void for_multiindex(lambda_t f)
{
constexpr auto dims = sizeof...(loop_sizes);
if constexpr (dims == 0)
{
f(std::array<std::size_t,0> {});
}
else
{
if constexpr (std::min({loop_sizes...}) <= 0) { return; }
constexpr auto total_loop_size = (loop_sizes * ...);
for_unrolled_simple(std::make_index_sequence<total_loop_size> {},
[&f](std::size_t i)
{
f(to_multiindex<loop_sizes...>(i));
});
}
}
/// Extend std::apply to work with 0-size arrays.
template <typename Fn, typename Tuple>
inline constexpr decltype(auto) apply(Fn&& f, Tuple&& t)
{
if constexpr (std::tuple_size_v<std::remove_reference_t<Tuple>> == 0)
{ return f(); }
return std::apply(std::forward<Fn>(f), std::forward<Tuple>(t));
}
/// Multi-dimensional array of tensors of the same size.
/** The array sizes are dynamic while the tensor sizes are static, i.e. template
parameters.
This class provides flexible global data layout where the dynamic (array)
dimnsions and the tensor dimnsions are stored in memory using a runtime
defined strided layout. */
template <typename scalar_t, int ndims, int... tensor_sizes>
class tensor_ndarray
{
public:
typedef scalar_t scalar_type;
typedef tensor<std::remove_cv_t<scalar_t>,tensor_sizes...> tensor_type;
static constexpr std::integer_sequence<size_t, tensor_sizes...> tensor_sizes_;
static constexpr auto tensor_dims = sizeof...(tensor_sizes);
static constexpr auto total_dims = ndims + tensor_dims;
static constexpr std::array<std::size_t,tensor_dims>
tensor_sizes_array{tensor_sizes...};
private:
scalar_t *data; /// Not owned
std::array<std::size_t,ndims> dyn_sizes;
std::array<std::size_t,total_dims> strides;
public:
/** @brief Constructor with the default, column-major or left, layout where
the dynamic dimensions are first, on the left, and the tensor dimensions
are second. */
tensor_ndarray(scalar_t *ptr, std::array<std::size_t,ndims> dynamic_sizes)
: data(ptr), dyn_sizes(dynamic_sizes)
{
std::array<std::size_t,total_dims> default_perm;
std::iota(default_perm.begin(), default_perm.end(), 0); // 0, 1, 2, ...
set_layout(default_perm);
}
/// Number of dynamic array dimensions.
static constexpr std::size_t rank() { return ndims; }
/// Array size in the @a k-th dynamic dimension.
std::size_t size(int k = 0) const { return dyn_sizes[k]; }
/// Returns the product of all sizes of the dynamic dimensions.
std::size_t total_size() const
{
std::size_t t = 1;
for (int d = 0; d < ndims; d++)
{
t *= dyn_sizes[d];
}
return t;
}
/// Number of tensor (static) dimensions.
static constexpr std::size_t tensor_rank()
{ return sizeof...(tensor_sizes); }
/// Tensor size in the @a k-th tensor (static) dimension.
static constexpr std::size_t tensor_size(int k = 0)
{ return tensor_sizes_array[k]; }
/// Returns the product of all sizes of the static (tensor) dimensions.
static constexpr std::size_t total_tensor_size()
{ return (tensor_sizes * ...); }
/// Set the global data layout based on the given permutation @a perm.
/** The entries of @a perm are numbers identifying either a dynamic or a
tensor (static) dimension. Values in the range [0,rank()) identify the
dynamic dimensions and values in the range [rank(),rank()+tensor_rank())
identify the tensor dymensions. The first entry in @a perm determines
which dynamic or tensor dimension will have stride 1. The k-th entry of
@a perm determines which dimension will use the next stride which is
defined as the product of the sizes of all k-1 previous dimensions from
@a perm.
@note The default layout corresponds to the identity permutation:
{ 0, 1, ..., rank()+tensor_rank()-1 }.
@note This method does not permute the global 1D data array. */
void set_layout(std::array<std::size_t,rank()+tensor_rank()> perm)
{
std::size_t stride = 1;
for (std::size_t d_g = 0; d_g < total_dims; d_g++)
{
const auto d_l = perm[d_g];
strides[d_l] = stride;
stride *= (d_l < ndims) ? dyn_sizes[d_l] :
tensor_sizes_array[d_l-ndims];
}
}
/** @brief Comute the dynamic offset for a given dynamic multi-index @a is.
The total offset in the global data array is the sum of the dynamic and
static (tensor) offsets. */
std::size_t get_dynamic_offset(
const std::array<std::size_t,rank()> &is) const
{
std::size_t dynamic_offset = 0;
for (std::size_t d = 0; d < ndims; d++)
{
dynamic_offset += is[d]*strides[d];
}
return dynamic_offset;
}
/** @brief Comute the static (tensor) offset for a given tensor multi-index
@a js. The total offset in the global data array is the sum of the
dynamic and static (tensor) offsets. */
std::size_t get_static_offset(
const std::array<std::size_t,tensor_rank()> &js) const
{
std::size_t static_offset = 0;
for (std::size_t d = 0; d < tensor_dims; d++)
{
static_offset += js[d]*strides[ndims+d];
}
return static_offset;
}
/** @brief Return a local tensor extracted from the global data array
corresponding to the given dynamic multi-index @a is. */
/** @note Return a const tensor to prevent attempts to assign to the
temporary object which is considered a mistake. */
const tensor_type get_tensor(std::array<std::size_t,rank()> is) const
{
tensor_type result;
const std::size_t dynamic_offset = get_dynamic_offset(is);
for_multiindex<tensor_sizes...>(
[&result, this, dynamic_offset](
const std::array<std::size_t,tensor_rank()> &js)
{
::mfem::future::apply(result, js) =
data[dynamic_offset + get_static_offset(js)];
});
return result;
}
/** @brief Return a local tensor extracted from the global data array
corresponding to the given dynamic indices @a is. */
/** @note Return a const tensor to prevent attempts to assign to the
temporary object which is considered a mistake. */
template <typename... index_types>
const tensor_type get_tensor(index_types... is) const
{
static_assert(sizeof...(is) == rank(), "invalid number of indices!");
return get_tensor(std::array<std::size_t,rank()> {std::size_t(is)...});
}
/** @brief Returns one of the following depending on the type scalar_t:
- get_tensor(std::array<std::size_t,rank()>) iff scalar_t is const,
- get_accessor(std::array<std::size_t,rank()>) iff scalar_t is not
const. */
decltype(auto) operator()(std::array<std::size_t,rank()> is) const
{
if constexpr (std::is_const_v<scalar_t>) { return get_tensor(is); }
else { return get_accessor(is); }
}
/** @brief Returns one of the following depending on the type scalar_t:
- get_tensor(index_types...) iff scalar_t is const,
- get_accessor(index_types...) iff scalar_t is not const. */
template <typename... index_types>
decltype(auto) operator()(index_types... is) const
{
if constexpr (std::is_const_v<scalar_t>) { return get_tensor(is...); }
else { return get_accessor(is...); }
}
/** @brief Helper class facilitating the reading/writing of local tensor
objects to the global data array of the tensor_ndarray. */
class tensor_accessor
{
private:
const tensor_ndarray &base_array;
scalar_t *offset_data; /// Not owned
public:
/** @brief Construct a tensor_accessor to @a base for the given dynamic
multi-index @a is.
During its life time, this object assumes that the @a base object
remains unmodified. */
tensor_accessor(const tensor_ndarray &base,
const std::array<std::size_t,rank()> &is)
: base_array(base)
{
offset_data = base_array.data + base_array.get_dynamic_offset(is);
}
/// Read-write access to a particular entry of the referenced tensor.
/** The returned reference points to the corresponding entry in the global
data array of the base tensor_ndarray. */
scalar_t &operator()(const std::array<std::size_t,tensor_rank()> &js)
{
return offset_data[base_array.get_static_offset(js)];
}
/** @brief Write a tensor to the referenced tensor in the global data
array of the base tensor_ndarray. */
tensor_accessor &operator=(const tensor_type &rhs)
{
for_multiindex<tensor_sizes...>(
[&](const std::array<std::size_t,tensor_rank()> &js)
{
operator()(js) = ::mfem::future::apply(rhs, js);
});
return *this;
}
};
/** @brief Get a tensor_accessor object referencing the tensor stored at the
dynamic multi-index @a is. This object can be used to write tensor
objects into the global data array of the tensor_ndarray. */
tensor_accessor get_accessor(std::array<std::size_t,rank()> is) const
{
return tensor_accessor(*this, is);
}
/** @brief Get a tensor_accessor object referencing the tensor stored at the
dynamic indices @a is. This object can be used to write tensor objects
into the global data array of the tensor_ndarray. */
template <typename... index_types>
tensor_accessor get_accessor(index_types... is) const
{
static_assert(sizeof...(is) == rank(), "invalid number of indices!");
return get_accessor(std::array<std::size_t,rank()> {std::size_t(is)...});
}
};
/** @brief Construct a tensor_ndarray where only the tensor sizes have to be
explicitly given as template parameters, the rest can be deduced from the
function call arguments. */
template <int... tensor_sizes, typename scalar_t, typename... dyn_sizes_t>
decltype(auto) make_tensor_ndarray(scalar_t *ptr, dyn_sizes_t... dynamic_sizes)
{
return tensor_ndarray<scalar_t,sizeof...(dynamic_sizes),tensor_sizes...>(
ptr, {std::size_t(dynamic_sizes)...});
}
/// Alias for make_tensor_ndarray = make_tensor_array.
template <int... tensor_sizes, typename scalar_t, typename... dyn_sizes_t>
decltype(auto) make_tensor_array(scalar_t *ptr, dyn_sizes_t... dynamic_sizes)
{
return tensor_ndarray<scalar_t,sizeof...(dynamic_sizes),tensor_sizes...>(
ptr, {std::size_t(dynamic_sizes)...});
}
/// Short name for one-dimensional tensor_ndarray: tensor_array.
template <typename scalar_t, int... tensor_sizes>
using tensor_array = tensor_ndarray<scalar_t, 1, tensor_sizes...>;
} // namespace mfem::future
} // namespace mfem
#endif // MFEM_TENSOR_ARRAYS_HPP
+34 -12
View File
@@ -123,15 +123,20 @@ EXAMPLE_SUBDIRS = amgx caliper ginkgo hiop petsc pumi sundials superlu moonolith
EXAMPLE_DIRS := examples $(addprefix examples/,$(EXAMPLE_SUBDIRS))
EXAMPLE_TEST_DIRS := examples
MINIAPP_SUBDIRS = common electromagnetics meshing performance tools \
MINIAPP_ALL_SUBDIRS = common electromagnetics meshing performance tools \
toys nurbs gslib adjoint solvers shifted mtop parelag tribol autodiff dfem \
hooke multidomain dpg hdiv-linear-solver spde diag-smoothers contact \
fluids/navier fluids/schrodinger-flow plasma plasma/pic
MINIAPP_RECURSIVE_SUBDIRS = plasma/pic
MINIAPP_SUBDIRS := $(filter-out \
$(MINIAPP_RECURSIVE_SUBDIRS),$(MINIAPP_ALL_SUBDIRS))
MINIAPP_ALL_DIRS := $(addprefix miniapps/,$(MINIAPP_ALL_SUBDIRS))
MINIAPP_DIRS := $(addprefix miniapps/,$(MINIAPP_SUBDIRS))
MINIAPP_TEST_DIRS := $(filter-out %/common,$(MINIAPP_DIRS))
MINIAPP_USE_COMMON := $(addprefix miniapps/,electromagnetics meshing tools \
toys shifted dpg diag-smoothers fluids/navier plasma plasma/pic)
toys gslib shifted dpg diag-smoothers fluids/navier plasma plasma/pic)
EM_ALL_DIRS = $(EXAMPLE_DIRS) $(MINIAPP_ALL_DIRS)
EM_DIRS = $(EXAMPLE_DIRS) $(MINIAPP_DIRS)
TEST_SUBDIRS = unit
@@ -146,7 +151,7 @@ MFEM_BUILD_DIR ?= .
BUILD_DIR := $(MFEM_BUILD_DIR)
BUILD_REAL_DIR := $(abspath $(BUILD_DIR))
ifneq ($(BUILD_REAL_DIR),$(MFEM_REAL_DIR))
BUILD_SUBDIRS = $(DIRS) config $(EM_DIRS) doc $(TEST_DIRS)
BUILD_SUBDIRS = $(DIRS) config $(EM_ALL_DIRS) doc $(TEST_DIRS)
CONFIG_FILE_DEF = -DMFEM_CONFIG_FILE='"$(BUILD_REAL_DIR)/config/_config.hpp"'
BLD := $(if $(BUILD_REAL_DIR:$(CURDIR)=),$(BUILD_DIR)/,)
$(if $(word 2,$(BLD)),$(error Spaces in BLD = "$(BLD)" are not supported))
@@ -483,10 +488,10 @@ $(OBJECT_FILES): $(BLD)%.o: $(SRC)%.cpp $(CONFIG_MK)
all: examples miniapps $(TEST_DIRS)
.PHONY: miniapps $(EM_DIRS) $(TEST_DIRS)
.PHONY: miniapps $(EM_ALL_DIRS) $(TEST_DIRS)
miniapps: $(MINIAPP_DIRS)
$(MINIAPP_USE_COMMON): miniapps/common
$(EM_DIRS) $(TEST_DIRS): lib
$(EM_ALL_DIRS) $(TEST_DIRS): lib
$(MAKE) -C $(BLD)$(@)
.PHONY: doc
@@ -694,7 +699,7 @@ local-config:
.PHONY: build-config
build-config:
for d in $(BUILD_SUBDIRS); do mkdir -p $(BLD)$${d}; done
for dir in "" $(addsuffix /,config $(EM_DIRS) doc $(TEST_DIRS)); do \
for dir in "" $(addsuffix /,config $(EM_ALL_DIRS) doc $(TEST_DIRS)); do\
printf "# Auto-generated file.\n%s\n%s\n" \
"MFEM_DIR = $(MFEM_REAL_DIR)" \
"include \$$(MFEM_DIR)/$${dir}makefile" \
@@ -796,18 +801,20 @@ status info:
ASTYLE = $(ASTYLE_BIN) --options=$(SRC)config/mfem.astylerc
ASTYLE_VER = "Artistic Style Version 3.1"
FORMAT_FILES = $(foreach dir,$(DIRS) $(EM_DIRS) config,$(dir)/*.?pp)
FORMAT_FILES = $(foreach dir,$(DIRS) $(EM_ALL_DIRS) config,$(dir)/*.?pp)
TESTS_SUBDIRS = unit benchmarks convergence mem_manager par-mesh-format
UNIT_TESTS_SUBDIRS = general linalg mesh fem miniapps ceed enzyme
MINIAPPS_SUBDIRS = dpg/util hooke/operators hooke/preconditioners hooke/materials hooke/kernels
UNIT_TESTS_SUBDIRS = general linalg mesh fem miniapps ceed enzyme dfem
MINIAPPS_SUBDIRS = dpg/util hooke/operators hooke/preconditioners \
hooke/materials hooke/kernels
FORMAT_FILES += $(foreach dir,$(TESTS_SUBDIRS),tests/$(dir)/*.?pp)
FORMAT_FILES += $(foreach dir,$(UNIT_TESTS_SUBDIRS),tests/unit/$(dir)/*.?pp)
FORMAT_FILES += $(foreach dir,$(MINIAPPS_SUBDIRS),miniapps/$(dir)/*.?pp)
FORMAT_FILES += config/cmake/config.hpp.in config/config.hpp.in mfem*.hpp
FORMAT_EXCLUDE = general/tinyxml2.cpp tests/unit/catch.hpp
FORMAT_LIST = $(filter-out $(FORMAT_EXCLUDE),$(wildcard $(FORMAT_FILES)))
COUT_CERR_FILES = $(foreach dir,$(DIRS),$(dir)/*.[ch]pp)
COUT_CERR_EXCLUDE = '^general/error\.cpp' '^general/globals\.[ch]pp' '^general/nvtx\.hpp'
COUT_CERR_EXCLUDE = '^general/error\.cpp' '^general/globals\.[ch]pp'
DEPRECATION_WARNING := \
"This feature is planned for removal in the next release."\
@@ -833,14 +840,29 @@ mfem_check_command = \
# Verify the C++ code styling in MFEM and check that std::cout and std::cerr are
# not used in the library (use mfem::out and mfem::err instead).
style:
@echo "Applying C++ code style..."
@astyle_version="$$($(ASTYLE_BIN) --version)";\
if [ "$$astyle_version" != $(ASTYLE_VER) ]; then\
printf "%s\n" "Invalid astyle version: '$$astyle_version'"\
"Please use: '"$(ASTYLE_VER)"'";\
exit 1;\
fi
@err_code=0;\
@err_code=0; \
if command -v git 2>&1 > /dev/null && [ -d $(MFEM_DIR)/.git ]; then \
echo "Checking if all git files are selected for formatting ..."; \
ls -1 $(FORMAT_FILES) | sort > format-files-make.txt; \
git -C $(MFEM_DIR) ls-files '*.[ch]pp*' | sort \
> format-files-git.txt; \
cat format-files-make.txt format-files-git.txt | sort | uniq \
> format-files-make-plus-git.txt; \
rm -f format-files-git.txt; \
$(call mfem_check_command,\
diff format-files-make.txt format-files-make-plus-git.txt | \
grep "^> ",\
"All git files are selected for formatting",\
"The above git files are NOT selected for formatting"); \
rm -f format-files-make.txt format-files-make-plus-git.txt; \
fi; \
echo "Applying C++ code style...";\
$(call mfem_check_command,\
$(ASTYLE) $(FORMAT_LIST) | grep Formatted,\
"No source files were changed",\
+10 -2
View File
@@ -46,8 +46,16 @@ if (MFEM_USE_GSLIB)
if (MFEM_ENABLE_TESTING)
foreach (test "schwarz_ex1" "field-diff" "findpts" "field-interp")
add_test(NAME ${test}
COMMAND $<TARGET_FILE:${test}> -no-vis)
if (MFEM_USE_MPI)
add_test(NAME ${test}
COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} 1
${MPIEXEC_PREFLAGS}
$<TARGET_FILE:${test}> -no-vis
${MPIEXEC_POSTFLAGS})
else()
add_test(NAME ${test}
COMMAND $<TARGET_FILE:${test}> -no-vis)
endif()
endforeach()
endif()
+6 -1
View File
@@ -83,11 +83,16 @@ include $(MFEM_TEST_MK)
# Testing: Parallel vs. serial runs
RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP)
ifeq ($(MFEM_USE_MPI),YES)
RUN_MPI_SER = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) 1
else
RUN_MPI_SER =
endif
TEST_NAME := GSLIB miniapp
%-test-par: %
@$(call mfem-test,$<, $(RUN_MPI), $(TEST_NAME))
%-test-seq: %
@$(call mfem-test,$<,, $(TEST_NAME))
@$(call mfem-test,$<, $(RUN_MPI_SER), $(TEST_NAME))
# Testing: "test" target and mfem-test* variables are defined in config/test.mk
@@ -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
+21 -24
View File
@@ -28,6 +28,12 @@ endif
PLASMA_SUBDIRS = pic
SUBDIRS_ALL = $(addsuffix /all,$(PLASMA_SUBDIRS))
SUBDIRS_TEST = $(addsuffix /test,$(PLASMA_SUBDIRS))
SUBDIRS_TEST_NOCLEAN = $(addsuffix /test-noclean,$(PLASMA_SUBDIRS))
SUBDIRS_CLEAN = $(addsuffix /clean,$(PLASMA_SUBDIRS))
SUBDIRS_TPRINT = $(addsuffix /test-print,$(PLASMA_SUBDIRS))
.SUFFIXES:
.SUFFIXES: .o .cpp .mk
.PHONY: all lib-common clean clean-build clean-exec
@@ -37,31 +43,24 @@ COMMON_LIB = -L$(MFEM_BUILD_DIR)/miniapps/common -lmfem-common
# If MFEM_SHARED is set, add the ../common rpath
COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\
$(if $(MFEM_USE_CUDA:YES=),$(CXX_XLINKER),$(CUDA_XLINKER))-rpath,$(abspath\
$(MFEM_BUILD_DIR)/miniapps/common))
COMMON_O=
$(MFEM_XLINKER)-rpath,$(abspath $(MFEM_BUILD_DIR)/miniapps/common))
# Remove built-in rules
%: %.cpp
%.o: %.cpp
all: $(MINIAPPS) subdirs
all: $(MINIAPPS) $(SUBDIRS_ALL)
.PHONY: subdirs $(PLASMA_SUBDIRS)
subdirs: $(PLASMA_SUBDIRS)
$(PLASMA_SUBDIRS): lib-common
$(MAKE) -C $(BLD)$(@)
.PHONY: $(SUBDIRS_ALL) $(SUBDIRS_TEST) $(SUBDIRS_TEST_NOCLEAN) \
$(SUBDIRS_CLEAN) $(SUBDIRS_TPRINT)
$(SUBDIRS_ALL) $(SUBDIRS_TEST) $(SUBDIRS_TEST_NOCLEAN) $(SUBDIRS_CLEAN):
$(MAKE) -C $(@D) $(@F)
$(SUBDIRS_TPRINT):
@$(MAKE) -C $(@D) $(@F)
# Rules for building the miniapps
%: $(SRC)%.cpp $(COMMON_O) $(MFEM_LIB_FILE) $(CONFIG_MK) | lib-common
$(MFEM_CXX) $(MFEM_LINK_FLAGS) $< -o $@ $(COMMON_O) $(COMMON_LIB) \
$(MFEM_LIBS)
# Rules for compiling miniapp dependencies
$(COMMON_O) $(addsuffix _solver.o,$(MINIAPPS)): \
%.o: $(SRC)%.cpp $(SRC)%.hpp $(CONFIG_MK)
$(MFEM_CXX) $(MFEM_FLAGS) -c $(<) -o $(@)
%: $(SRC)%.cpp $(MFEM_LIB_FILE) $(CONFIG_MK) | lib-common
$(MFEM_CXX) $(MFEM_LINK_FLAGS) $< -o $@ $(COMMON_LIB) $(MFEM_LIBS)
# Rule for building lib-common
lib-common:
@@ -69,6 +68,9 @@ lib-common:
MFEM_TESTS = MINIAPPS
include $(MFEM_TEST_MK)
test: $(SUBDIRS_TEST)
test-noclean: $(SUBDIRS_TEST_NOCLEAN)
test-print: $(SUBDIRS_TPRINT)
# Testing: Specific execution options
RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP)
@@ -79,14 +81,9 @@ RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP)
$(MFEM_LIB_FILE):
$(error The MFEM library is not built)
ALL_CLEAN_SUBDIRS = $(addsuffix /clean,$(PLASMA_SUBDIRS))
.PHONY: $(ALL_CLEAN_SUBDIRS)
$(ALL_CLEAN_SUBDIRS):
$(MAKE) -C $(BLD)$(@D) $(@F)
clean: clean-build clean-exec $(SUBDIRS_CLEAN)
clean: clean-build clean-exec
clean-build: $(addsuffix /clean,$(PLASMA_SUBDIRS))
clean-build:
rm -f *.o *~ $(SEQ_MINIAPPS) $(PAR_MINIAPPS)
rm -rf *.dSYM *.TVD.*breakpoints
+4 -3
View File
@@ -20,6 +20,7 @@ CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
MFEM_LIB_FILE = mfem_is_not_built
-include $(CONFIG_MK)
SEQ_MINIAPPS =
PAR_MINIAPPS =
ifeq ($(MFEM_USE_GSLIB),YES)
@@ -27,9 +28,9 @@ ifeq ($(MFEM_USE_GSLIB),YES)
endif
ifeq ($(MFEM_USE_MPI),NO)
MINIAPPS =
MINIAPPS = $(SEQ_MINIAPPS)
else
MINIAPPS = $(PAR_MINIAPPS)
MINIAPPS = $(PAR_MINIAPPS) $(SEQ_MINIAPPS)
endif
.SUFFIXES:
@@ -50,7 +51,7 @@ COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\
all: $(MINIAPPS)
# Rules for building the miniapps
electrostatic-pic: electrostatic-pic.cpp $(MFEM_LIB_FILE) $(CONFIG_MK) | lib-common
%: $(SRC)%.cpp $(MFEM_LIB_FILE) $(CONFIG_MK) | lib-common
$(MFEM_CXX) $(MFEM_FLAGS) -c $<
$(MFEM_CXX) $(MFEM_LINK_FLAGS) -o $@ $@.o $(COMMON_LIB) $(MFEM_LIBS)
+4
View File
@@ -61,6 +61,10 @@ if (MFEM_USE_MPI)
LIBRARIES mfem-common)
add_dependencies(gridfunction-bounds copy_miniapps_tools_data)
add_mfem_miniapp(random-gridfunction-bounds
MAIN random-gridfunction-bounds.cpp
LIBRARIES mfem)
add_mfem_miniapp(plor-transfer
MAIN plor-transfer.cpp LIBRARIES mfem)
+3 -2
View File
@@ -23,7 +23,8 @@ MFEM_LIB_FILE = mfem_is_not_built
SEQ_MINIAPPS = display-basis load-dc convert-dc get-values lor-transfer \
tmop-check-metric tmop-metric-magnitude compare-dc
PAR_MINIAPPS = nodal-transfer plor-transfer gridfunction-bounds
PAR_MINIAPPS = nodal-transfer plor-transfer gridfunction-bounds \
random-gridfunction-bounds
ifeq ($(MFEM_USE_MPI),NO)
MINIAPPS = $(SEQ_MINIAPPS)
@@ -79,7 +80,7 @@ RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP)
# Do not test: display-basis, load-dc, convert-dc, get-values, lor-transfer, plor-transfer
NO_TEST_APPS = display-basis load-dc convert-dc get-values lor-transfer \
plor-transfer tmop-check-metric tmop-metric-magnitude gridfunction-bounds \
compare-dc
random-gridfunction-bounds compare-dc
$(foreach app,$(NO_TEST_APPS),$(app)-test-seq $(app)-test-par):
@true
@@ -0,0 +1,263 @@
// 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.
//
// ---------------------------------------------------------------------
// Compute bounds of a random grid function on a generated tensor mesh
// ---------------------------------------------------------------------
//
// This miniapp generates a 1D segment mesh or 2D quad mesh, builds a random
// discontinuous grid function, computes element-wise piecewise linear bounds,
// and visualizes the input field together with the lower and upper bounds.
//
// Compile with: make random-gridfunction-bounds
//
// Sample runs:
// mpirun -np 4 random-gridfunction-bounds
// mpirun -np 4 random-gridfunction-bounds -nx 64 -o 6 -ref 3 -d hip
#include "mfem.hpp"
#include <algorithm>
#include <type_traits>
using namespace mfem;
using namespace std;
void VisualizeField(ParMesh &pmesh, ParGridFunction &input,
char *title, int pos_x, int pos_y);
int main(int argc, char *argv[])
{
Mpi::Init(argc, argv);
Hypre::Init();
int dim = 2;
int nx = 16;
int order = 4;
int num_comp = 2;
int ref = 2;
int niter = 1000;
int seed = 12345;
bool kernel_only = true;
bool visualization = false;
const char *device_config = "cpu";
OptionsParser args(argc, argv);
args.AddOption(&dim, "-dim", "--dimension",
"Dimension of the generated tensor-product mesh (1 or 2).");
args.AddOption(&nx, "-nx", "--num-elements",
"Number of elements in each mesh direction.");
args.AddOption(&order, "-o", "--order",
"Polynomial degree of the random discontinuous field.");
args.AddOption(&num_comp, "-nc", "--num-components",
"Number of vector components in the ParFiniteElementSpace.");
args.AddOption(&ref, "-ref", "--piecewise-linear-ref-factor",
"Scaling factor for the resolution of the piecewise linear "
"bounds. If less than 2, the resolution is picked "
"automatically.");
args.AddOption(&niter, "-ni", "--num-iters",
"Number of times to evaluate the bounds.");
args.AddOption(&seed, "-rs", "--random-seed",
"Random seed used to initialize the field.");
args.AddOption(&device_config, "-d", "--device",
"Device configuration string, see Device::Configure().");
args.AddOption(&kernel_only, "-ko", "--kernel-only",
"-no-ko", "--no-kernel-only",
"Run only PLBound::GetElementBoundsKernel on a prebuilt "
"element E-vector.");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.ParseCheck();
MFEM_VERIFY(dim == 1 || dim == 2, "dim must be 1 or 2.");
MFEM_VERIFY(nx > 0, "nx must be positive.");
MFEM_VERIFY(order >= 0, "order must be non-negative.");
MFEM_VERIFY(num_comp > 0, "num_comp must be positive.");
MFEM_VERIFY(niter > 0, "niter must be positive.");
Device device(device_config);
if (Mpi::Root()) { device.Print(); }
Mesh mesh = (dim == 1) ?
Mesh::MakeCartesian1D(nx, 1.0) :
Mesh::MakeCartesian2D(nx, nx, Element::QUADRILATERAL, true,
1.0, 1.0);
ParMesh pmesh(MPI_COMM_WORLD, mesh);
const int mesh_dim = pmesh.Dimension();
L2_FECollection fec(order, mesh_dim, BasisType::GaussLobatto);
ParFiniteElementSpace fes(&pmesh, &fec, num_comp, Ordering::byNODES);
ParGridFunction input(&fes);
input.Randomize(seed + Mpi::WorldRank());
input.UseDevice(true);
L2_FECollection fec_pc(0, mesh_dim);
ParFiniteElementSpace fes_pc(&pmesh, &fec_pc, num_comp, Ordering::byNODES);
ParGridFunction lowerb(&fes_pc), upperb(&fes_pc);
Vector lower_vec, upper_vec;
PLBound plb(&fes, ref*(fes.GetMaxElementOrder() + 1));
if (kernel_only)
{
const FiniteElement &fe = *fes.GetTypicalFE();
const int rdim = fe.GetDim();
const int nd = fe.GetDof();
const int fes_dim = fes.GetVDim();
Vector e_vec(nd*fes_dim*fes.GetNE(), Device::GetDeviceMemoryType());
e_vec.UseDevice(true);
const ElementRestrictionOperator *elem_restr =
fes.GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
MFEM_VERIFY(elem_restr != nullptr,
"Element restriction is required for kernel-only mode.");
elem_restr->Mult(input, e_vec);
for (int i = 0; i < niter; i++)
{
plb.GetElementBoundsKernel(rdim, fes_dim, e_vec, lower_vec, upper_vec);
}
}
else
{
for (int i = 0; i < niter; i++)
{
input.GetElementBounds(plb, lower_vec, upper_vec);
}
}
const real_t *lower_data = lower_vec.HostRead();
const real_t *upper_data = upper_vec.HostRead();
// Build a host reference from the lexicographic E-vector and the scalar
// PLBound::GetNDBounds path to avoid re-entering the device dispatch.
const bool use_dev = input.UseDevice();
PLBound plb_host(&fes, ref*(fes.GetMaxElementOrder() + 1));
Vector lower_ref, upper_ref;
const FiniteElement &fe = *fes.GetTypicalFE();
const int rdim = fe.GetDim();
const int nd = fe.GetDof();
const int nel = fes.GetNE();
const int fes_dim = fes.GetVDim();
Vector e_vec_ref(nd*fes_dim*nel);
lower_ref.SetSize(nel*fes_dim);
upper_ref.SetSize(nel*fes_dim);
const ElementRestrictionOperator *elem_restr =
fes.GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
MFEM_VERIFY(elem_restr != nullptr,
"Element restriction is required for host reference.");
input.UseDevice(false);
input.HostRead();
elem_restr->Mult(input, e_vec_ref);
input.UseDevice(use_dev);
const real_t *e_ref_data = e_vec_ref.HostRead();
for (int d = 0; d < fes_dim; d++)
{
for (int e = 0; e < nel; e++)
{
Vector coeff(nd);
for (int i = 0; i < nd; i++)
{
coeff(i) = e_ref_data[i + nd*(d + fes_dim*e)];
}
Vector lower_c, upper_c;
plb_host.GetNDBounds(rdim, coeff, lower_c, upper_c);
lower_ref(e + d*nel) = lower_c.Min();
upper_ref(e + d*nel) = upper_c.Max();
}
}
const real_t *lower_ref_data = lower_ref.HostRead();
const real_t *upper_ref_data = upper_ref.HostRead();
MFEM_VERIFY(lower_vec.Size() == lower_ref.Size() &&
upper_vec.Size() == upper_ref.Size(),
"Reference element-bound vectors have inconsistent sizes.");
real_t lower_diff = 0.0;
real_t upper_diff = 0.0;
for (int i = 0; i < lower_vec.Size(); i++)
{
lower_diff = std::max(lower_diff,
std::abs(lower_data[i] - lower_ref_data[i]));
}
for (int i = 0; i < upper_vec.Size(); i++)
{
upper_diff = std::max(upper_diff,
std::abs(upper_data[i] - upper_ref_data[i]));
}
MPI_Allreduce(MPI_IN_PLACE, &lower_diff, 1, MPITypeMap<real_t>::mpi_type,
MPI_MAX, pmesh.GetComm());
MPI_Allreduce(MPI_IN_PLACE, &upper_diff, 1, MPITypeMap<real_t>::mpi_type,
MPI_MAX, pmesh.GetComm());
const real_t verify_tol = std::is_same<real_t, float>::value ?
real_t(1.0e-5) : real_t(1.0e-12);
MFEM_VERIFY(lower_diff <= verify_tol && upper_diff <= verify_tol,
"Device element bounds do not match host reference.");
lowerb = lower_vec;
upperb = upper_vec;
real_t lower_min = lowerb.Min();
real_t upper_max = upperb.Max();
MPI_Allreduce(MPI_IN_PLACE, &lower_min, 1, MPITypeMap<real_t>::mpi_type,
MPI_MIN, pmesh.GetComm());
MPI_Allreduce(MPI_IN_PLACE, &upper_max, 1, MPITypeMap<real_t>::mpi_type,
MPI_MAX, pmesh.GetComm());
if (Mpi::Root())
{
cout << "dim: " << mesh_dim << '\n'
<< "nx: " << nx << '\n'
<< "order: " << order << '\n'
<< "num components: " << num_comp << '\n'
<< "PL bound control-point factor: " << ref << '\n'
<< "iterations: " << niter << '\n'
<< "kernel-only mode: " << (kernel_only ? "yes" : "no") << '\n'
<< "host/device lower max diff: " << lower_diff << '\n'
<< "host/device upper max diff: " << upper_diff << '\n'
<< "global lower bound minimum: " << lower_min << '\n'
<< "global upper bound maximum: " << upper_max << endl;
}
if (visualization)
{
char title1[] = "Random input gridfunction";
char title2[] = "Element-wise lower bound";
char title3[] = "Element-wise upper bound";
VisualizeField(pmesh, input, title1, 0, 0);
VisualizeField(pmesh, lowerb, title2, 450, 0);
VisualizeField(pmesh, upperb, title3, 900, 0);
}
return 0;
}
void VisualizeField(ParMesh &pmesh, ParGridFunction &input,
char *title, int pos_x, int pos_y)
{
socketstream sock;
if (pmesh.GetMyRank() == 0)
{
sock.open("localhost", 19916);
sock << "solution\n";
}
pmesh.PrintAsOne(sock);
input.SaveAsOne(sock);
if (pmesh.GetMyRank() == 0)
{
sock << "window_title '" << title << "'\n"
<< "window_geometry "
<< pos_x << " " << pos_y << " " << 400 << " " << 400 << "\n"
<< "keys jRmclApppppppppppp//]]]]]]]]" << endl;
}
}
+1 -6
View File
@@ -32,11 +32,7 @@ function(add_benchmark name)
endif(MFEM_USE_CUDA)
add_executable(bench_${name} ${${NAME}_BENCH_SRCS})
if (fmt_FOUND)
target_link_libraries(bench_${name} mfem pthread fmt::fmt)
else()
target_link_libraries(bench_${name} mfem pthread)
endif()
target_link_libraries(bench_${name} mfem pthread)
add_dependencies(${MFEM_ALL_BENCHMARKS_TARGET_NAME} bench_${name})
add_test(NAME bench_${name}_cpu
@@ -55,7 +51,6 @@ endfunction(add_benchmark)
#-------------------------------------------------------------------------------
add_benchmark(assembly_levels)
add_benchmark(ceed)
add_benchmark(dfem)
add_benchmark(dg_amr)
add_benchmark(elasticity)
add_benchmark(tmop)
-845
View File
@@ -1,845 +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 "bench.hpp" // IWYU pragma: keep
#ifdef MFEM_USE_BENCHMARK
#include <cstdlib>
#include <memory>
#include <fem/qinterp/det.cpp>
#include <fem/qinterp/grad.hpp> // IWYU pragma: keep
#include "fem/integ/lininteg_domain_kernels.hpp" // IWYU pragma: keep
#include "fem/dfem/doperator.hpp"
#include <linalg/tensor.hpp>
#include <fem/kernels3d.hpp>
namespace ker = mfem::kernels::internal;
namespace low = mfem::kernels::internal::low;
#include "bench_dfem_mma.hpp"
#undef NVTX_COLOR
#define NVTX_COLOR ::nvtx::kNvidia
using namespace mfem;
using mfem::future::tuple;
using mfem::future::tensor;
using future::DifferentiableOperator;
using future::UniformParameterSpace;
using future::ParameterFunction;
using future::FieldDescriptor;
using future::make_tensor;
using future::Gradient;
using future::Weight;
using future::Identity;
/// info //////////////////////////////////////////////////////////////////////
static void DumpVersionInfo()
{
mfem::out << "\x1b[33m";
mfem::out << "version 0: PA std" << std::endl;
mfem::out << "version 1: PA reg" << std::endl; // can do high order
mfem::out << "version 2: PA low" << std::endl;
mfem::out << "version 3: PA mma" << std::endl;
// mfem::out << "version 4: PA ∂fem new, not specialized" << std::endl;
mfem::out << "version 5: PA ∂fem new, specialized" << std::endl;
// mfem::out << "version 6: PA ∂fem std" << std::endl; // ⚠️ max p=3
// mfem::out << "version 7: MF ∂fem std" << std::endl;
// mfem::out << "version 8: MF ∂fem new" << std::endl; // ⚠️ not supported
mfem::out << "\x1b[m" << std::endl;
}
// Custom benchmark arguments generator ///////////////////////////////////////
static void CustomArguments(bm::Benchmark *b) noexcept
{
constexpr int MAX_NDOFS = 8 * 1024 * (mfem_use_gpu ? 1024 : 8);
const auto versions = { 0, 1, 2, 3, /*4,*/ 5, /*6, 7, 8*/ };
const auto orders = { 6, 5, 4, 3, 2, 1 };
constexpr auto ndofs = [](int n) constexpr noexcept -> int
{
return (n + 1) * (n + 1) * (n + 1);
};
constexpr auto inc = [](int n) constexpr noexcept -> int
{
return n < 160 ? 4 : n < 240 ? 8 : n < 320 ? 16 : 32;
};
for (auto k : versions)
{
for (auto p : orders)
{
for (int n = 16; ndofs(n) <= MAX_NDOFS; n += inc(n))
{
b->Args({k, p, n});
}
}
}
}
/// Basic Kernels Specializations /////////////////////////////////////////////
static void AddBasicKernelSpecializations()
{
using Det = QuadratureInterpolator::DetKernels;
Det::Specialization<3, 3, 2, 2>::Add();
Det::Specialization<3, 3, 2, 3>::Add();
Det::Specialization<3, 3, 2, 5>::Add();
Det::Specialization<3, 3, 2, 6>::Add();
// Others might exceed memory limits
using Grad = QuadratureInterpolator::GradKernels;
Grad::Specialization<3, QVectorLayout::byVDIM, false, 3, 2, 3>::Add();
Grad::Specialization<3, QVectorLayout::byVDIM, false, 3, 2, 4>::Add();
Grad::Specialization<3, QVectorLayout::byVDIM, false, 3, 2, 5>::Add();
Grad::Specialization<3, QVectorLayout::byVDIM, false, 3, 2, 6>::Add();
Grad::Specialization<3, QVectorLayout::byVDIM, false, 3, 2, 7>::Add();
Grad::Specialization<3, QVectorLayout::byVDIM, false, 3, 2, 8>::Add();
Grad::Specialization<3, QVectorLayout::byNODES, false, 3, 2, 7>::Add();
Grad::Specialization<3, QVectorLayout::byNODES, false, 3, 2, 8>::Add();
using LIN = DomainLFIntegrator::AssembleKernels;
LIN::Specialization<3, 7, 7>::Add();
LIN::Specialization<3, 6, 6>::Add();
LIN::Specialization<3, 8, 8>::Add();
}
/// Globals ///////////////////////////////////////////////////////////////////
Device *device_ptr = nullptr;
static int gD1D = 0, gQ1D = 0;
/// StiffnessIntegrator ///////////////////////////////////////////////////////
struct StiffnessIntegrator : public BilinearFormIntegrator
{
const FiniteElementSpace *fes;
const real_t *B, *G, *DX;
int ne, d1d, q1d;
Vector J0, dx;
Vector &qdata;
public:
StiffnessIntegrator(Vector &qdata): qdata(qdata)
{
StiffnessKernels::Specialization<2,3>::Add(); // 1
StiffnessKernels::Specialization<3,4>::Add(); // 2
StiffnessKernels::Specialization<4,5>::Add(); // 3
StiffnessKernels::Specialization<5,6>::Add(); // 4
StiffnessKernels::Specialization<6,7>::Add(); // 5
StiffnessKernels::Specialization<7,8>::Add(); // 6
StiffnessKernels::Specialization<9,10>::Add(); // 8
}
void AssemblePA(const FiniteElementSpace &fespace) override
{
NVTX();
fes = &fespace;
auto *mesh = fes->GetMesh();
const int DIM = mesh->Dimension();
ne = mesh->GetNE();
const auto p = fes->GetFE(0)->GetOrder();
const auto q = 2 * p + mesh->GetElementTransformation(0)->OrderW();
const auto type = mesh->GetElementBaseGeometry(0);
const IntegrationRule &ir = IntRules.Get(type, q);
const int NQPT = ir.GetNPoints();
d1d = p + 1;
q1d = IntRules.Get(Geometry::SEGMENT, ir.GetOrder()).GetNPoints();
MFEM_VERIFY(d1d == gD1D, "D1D mismatch: " << d1d << " != " << gD1D);
MFEM_VERIFY(q1d == gQ1D, "Q1D mismatch: " << q1d << " != " << gQ1D);
MFEM_VERIFY(NQPT == q1d * q1d * q1d, "");
const DofToQuad *maps =
&fes->GetFE(0)->GetDofToQuad(ir, DofToQuad::TENSOR);
const GridFunction *nodes = (mesh->EnsureNodes(), mesh->GetNodes());
const FiniteElementSpace *nfes = nodes->FESpace();
const int nVDIM = nfes->GetVDim();
dx.SetSize(nVDIM * DIM * NQPT * ne, Device::GetDeviceMemoryType());
J0.SetSize(nVDIM * DIM * NQPT * ne, Device::GetDeviceMemoryType());
dx.UseDevice(true), J0.UseDevice(true);
B = maps->B.Read(), G = maps->G.Read(), DX = dx.Read();
const Operator *NR =
nfes->GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
const QuadratureInterpolator *nqi = nfes->GetQuadratureInterpolator(ir);
nqi->SetOutputLayout(QVectorLayout::byVDIM);
const int nd = nfes->GetFE(0)->GetDof();
Vector xe(nVDIM * nd * ne, Device::GetDeviceMemoryType());
NR->Mult(*nodes, (xe.UseDevice(true), xe));
nqi->Derivatives(xe, J0);
const int Q1D = q1d;
const auto w_r = ir.GetWeights().Read();
const auto W = Reshape(w_r, q1d, q1d, q1d);
const auto J = Reshape(J0.Read(), 3, 3, q1d, q1d, q1d, ne);
auto DX_w = Reshape(dx.Write(), 3, 3, q1d, q1d, q1d, ne);
mfem::forall_3D(ne, Q1D, Q1D, Q1D,[=] MFEM_HOST_DEVICE(int e)
{
MFEM_FOREACH_THREAD_DIRECT(qz, z, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, Q1D)
{
const real_t w = W(qx, qy, qz);
const real_t *Jtr = &J(0, 0, qx, qy, qz, e);
const real_t detJ = kernels::Det<3>(Jtr);
const real_t wd = w * detJ;
const real_t D[9] = { wd, 0.0, 0.0,
0.0, wd, 0.0,
0.0, 0.0, wd
};
real_t Jrt[9], A[9];
kernels::CalcInverse<3>(Jtr, Jrt);
kernels::MultABt(3, 3, 3, D, Jrt, A);
kernels::Mult(3, 3, 3, A, Jrt, &DX_w(0, 0, qx, qy, qz, e));
}
}
}
MFEM_SYNC_THREAD;
});
qdata = dx;
}
//////////////////////////////////////////////////////////////////
template <int T_D1D = 0, int T_Q1D = 0>
static void StiffnessMult(const int NE, const real_t *b, const real_t *g,
const real_t *dx, const real_t *xe, real_t *ye,
const int d1d, const int q1d)
{
const int D1D = T_D1D ? T_D1D : d1d;
const int Q1D = T_Q1D ? T_Q1D : q1d;
constexpr int DIM = 3, VDIM = 1;
const auto XE = Reshape(xe, D1D, D1D, D1D, VDIM, NE);
const auto DX = Reshape(dx, 3, 3, Q1D, Q1D, Q1D, NE);
auto YE = Reshape(ye, D1D, D1D, D1D, VDIM, NE);
mfem::forall_2D<T_Q1D*T_Q1D>(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE(int e)
{
constexpr int MD1 = T_D1D > 0 ? kernels::internal::SetMaxOf(T_D1D) : 8;
constexpr int MQ1 = T_Q1D > 0 ? kernels::internal::SetMaxOf(T_Q1D) : 8;
MFEM_SHARED real_t smem[MQ1][MQ1];
MFEM_SHARED real_t sB[MD1][MQ1], sG[MD1][MQ1];
ker::vd_regs3d_t<VDIM, DIM, MQ1> r0, r1;
ker::LoadMatrix(D1D, Q1D, b, sB);
ker::LoadMatrix(D1D, Q1D, g, sG);
ker::LoadDofs3d(e, D1D, XE, r0);
ker::Grad3d(D1D, Q1D, smem, sB, sG, r0, r1);
for (int qz = 0; qz < Q1D; qz++)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, Q1D)
{
real_t v[3], u[3] = { r1[0][0][qz][qy][qx],
r1[0][1][qz][qy][qx],
r1[0][2][qz][qy][qx]
};
const real_t *dx = &DX(0, 0, qx, qy, qz, e);
kernels::Mult(3, 3, dx, u, v);
r0[0][0][qz][qy][qx] = v[0];
r0[0][1][qz][qy][qx] = v[1];
r0[0][2][qz][qy][qx] = v[2];
}
}
}
ker::GradTranspose3d(D1D, Q1D, smem, sB, sG, r0, r1);
ker::WriteDofs3d(e, D1D, r1, YE);
});
}
using StiffnessKernelType = decltype(&StiffnessMult<>);
MFEM_REGISTER_KERNELS(StiffnessKernels, StiffnessKernelType, (int, int));
void AddMultPA(const Vector &x, Vector &y) const override
{
db1("\x1b[32md1d:{} q1d:{}", d1d, q1d);
StiffnessKernels::Run(d1d, q1d,
ne, B, G, DX, x.Read(), y.ReadWrite(),
d1d, q1d);
}
};
template <int D1D, int Q1D>
StiffnessIntegrator::StiffnessKernelType
StiffnessIntegrator::StiffnessKernels::Kernel()
{
db1("D1D:{} Q1D:{}", D1D, Q1D);
return StiffnessMult<D1D, Q1D>;
}
StiffnessIntegrator::StiffnessKernelType
StiffnessIntegrator::StiffnessKernels::Fallback([[maybe_unused]] int d1d,
[[maybe_unused]] int q1d)
{
dbg("\x1b[33mFallback d1d:{} q1d:{}", d1d, q1d);
// MFEM_ABORT("No kernel for d1d=" << d1d << " q1d=" << q1d);
// return nullptr;
return StiffnessMult<>;
}
/// PADiffLowIntegrator ///////////////////////////////////////////////////////
struct PADiffLowIntegrator : public BilinearFormIntegrator
{
const FiniteElementSpace *fes;
const real_t *B, *G, *DX;
int ne, d1d, q1d;
Vector J0, dx;
public: // for nvcc
//////////////////////////////////////////////////////////////////
template <int T_Q1D = 0>
static void PADiffLowMult(const int ne, const int d1d,
const real_t *b, const real_t *g,
const real_t *dx, const real_t *xe,
real_t *ye,
const int q1d)
{
constexpr int DIM = 3, VDIM = 1;
const auto XE = Reshape(xe, d1d, d1d, d1d, VDIM, ne);
auto YE = Reshape(ye, d1d, d1d, d1d, VDIM, ne);
mfem::forall_3D<T_Q1D*T_Q1D*T_Q1D>(ne, q1d, q1d, q1d,
[=] MFEM_HOST_DEVICE(int e)
{
constexpr int MQ1 = T_Q1D;
MFEM_SHARED real_t sm0[MQ1][MQ1][MQ1][3];
MFEM_SHARED real_t sm1[MQ1][MQ1][MQ1][3];
MFEM_SHARED real_t sB[MQ1][MQ1];
MFEM_SHARED real_t sG[MQ1][MQ1];
low::regs3d_t<DIM, MQ1> reg;
low::LoadMatrix(d1d, q1d, b, sB);
low::LoadMatrix(d1d, q1d, g, sG);
low::LoadDofs3d(e, d1d, XE, sm0); // Load & sync
// Grad: sm0 -X-> sm1 -Y-> sm0 -Z-> reg
low::Grad3d(d1d, q1d, sB, sG, sm0, sm1, reg); // Grad 3D
// Q-function
MFEM_FOREACH_THREAD_DIRECT(qz,z,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qy,y,q1d)
{
MFEM_FOREACH_THREAD_DIRECT(qx,x,q1d)
{
// pull
real_t v[3], u[3] = { reg[qz][qy][qx][0],
reg[qz][qy][qx][1],
reg[qz][qy][qx][2]
};
// Q-function
kernels::Mult(3, 3, dx + 9*(qx*q1d*q1d + qy*q1d + qz), u, v);
// push
reg[qz][qy][qx][0] = v[0];
reg[qz][qy][qx][1] = v[1];
reg[qz][qy][qx][2] = v[2];
}
}
}
MFEM_SYNC_THREAD;
// Grad^T: reg -=-> sm1 -X^T-> sm0 -Y^T-> sm1 -Z^T-> reg -> YE
low::GradTranspose3d(d1d, q1d, sB, sG, reg, sm1, sm0); // Grad^T 3D
low::WriteDofs3d(d1d, 0, e, reg, YE); // Write YE
});
}
using PADiffLowKernelType = decltype(&PADiffLowMult<>);
MFEM_REGISTER_KERNELS(PADiffLowKernels, PADiffLowKernelType, (int));
public:
PADiffLowIntegrator()
{
PADiffLowKernels::Specialization<3>::Add(); // 1
PADiffLowKernels::Specialization<4>::Add(); // 2
PADiffLowKernels::Specialization<5>::Add(); // 3
PADiffLowKernels::Specialization<6>::Add(); // 4
PADiffLowKernels::Specialization<7>::Add(); // 5
PADiffLowKernels::Specialization<8>::Add(); // 6
}
void AssemblePA(const FiniteElementSpace &fespace) override
{
NVTX();
fes = &fespace;
auto *mesh = fes->GetMesh();
const int DIM = mesh->Dimension();
ne = mesh->GetNE();
const auto p = fes->GetFE(0)->GetOrder();
const auto q = 2 * p + mesh->GetElementTransformation(0)->OrderW();
const auto type = mesh->GetElementBaseGeometry(0);
const IntegrationRule &ir = IntRules.Get(type, q);
const int NQPT = ir.GetNPoints();
d1d = p + 1;
q1d = IntRules.Get(Geometry::SEGMENT, ir.GetOrder()).GetNPoints();
MFEM_VERIFY(d1d == gD1D, "D1D mismatch: " << d1d << " != " << gD1D);
MFEM_VERIFY(q1d == gQ1D, "Q1D mismatch: " << q1d << " != " << gQ1D);
MFEM_VERIFY(NQPT == q1d * q1d * q1d, "");
const DofToQuad *maps =
&fes->GetFE(0)->GetDofToQuad(ir, DofToQuad::TENSOR);
const GridFunction *nodes = (mesh->EnsureNodes(), mesh->GetNodes());
const FiniteElementSpace *nfes = nodes->FESpace();
const int nVDIM = nfes->GetVDim();
dx.SetSize(nVDIM * DIM * NQPT * ne, Device::GetDeviceMemoryType());
J0.SetSize(nVDIM * DIM * NQPT * ne, Device::GetDeviceMemoryType());
dx.UseDevice(true), J0.UseDevice(true);
B = maps->B.Read(), G = maps->G.Read(), DX = dx.Read();
const Operator *NR =
nfes->GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
const QuadratureInterpolator *nqi = nfes->GetQuadratureInterpolator(ir);
nqi->SetOutputLayout(QVectorLayout::byVDIM);
const int nd = nfes->GetFE(0)->GetDof();
Vector xe(nVDIM * nd * ne, Device::GetDeviceMemoryType());
NR->Mult(*nodes, (xe.UseDevice(true), xe));
nqi->Derivatives(xe, J0);
const int Q1D = q1d;
const auto w_r = ir.GetWeights().Read();
const auto W = Reshape(w_r, q1d, q1d, q1d);
const auto J = Reshape(J0.Read(), 3, 3, q1d, q1d, q1d, ne);
auto DX_w = Reshape(dx.Write(), 3, 3, q1d, q1d, q1d, ne);
mfem::forall_3D(ne, Q1D, Q1D, Q1D,[=] MFEM_HOST_DEVICE(int e)
{
MFEM_FOREACH_THREAD_DIRECT(qz, z, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, Q1D)
{
const real_t w = W(qx, qy, qz);
const real_t *Jtr = &J(0, 0, qx, qy, qz, e);
const real_t detJ = kernels::Det<3>(Jtr);
const real_t wd = w * detJ;
const real_t D[9] = { wd, 0.0, 0.0,
0.0, wd, 0.0,
0.0, 0.0, wd
};
real_t Jrt[9], A[9];
kernels::CalcInverse<3>(Jtr, Jrt);
kernels::MultABt(3, 3, 3, D, Jrt, A);
kernels::Mult(3, 3, 3, A, Jrt, &DX_w(0, 0, qz, qy, qx, e));
}
}
}
MFEM_SYNC_THREAD;
});
}
void AddMultPA(const Vector &x, Vector &y) const override
{
db1("\x1b[32md1d:{} q1d:{}", d1d, q1d);
PADiffLowKernels::Run(q1d,
ne, d1d, B, G, DX, x.Read(), y.ReadWrite(),
q1d);
}
};
template <int Q1D>
PADiffLowIntegrator::PADiffLowKernelType
PADiffLowIntegrator::PADiffLowKernels::Kernel()
{
db1("Q1D:{}", Q1D);
return PADiffLowMult<Q1D>;
}
PADiffLowIntegrator::PADiffLowKernelType
PADiffLowIntegrator::PADiffLowKernels::Fallback(int q1d)
{
dbg("\x1b[33mFallback d1d:{} q1d:{}", q1d);
MFEM_ABORT("No kernel for q1d=" << q1d);
return nullptr;
// return StiffnessMult<>;
}
/// BakeOff ///////////////////////////////////////////////////////////////////
template <int VDIM, bool GLL>
struct BakeOff
{
static constexpr int DIM = 3;
const int p, c, q, n, nx, ny, nz;
const bool check_x, check_y, check_z, checked;
Mesh smesh;
ParMesh pmesh;
H1_FECollection fec;
ParFiniteElementSpace pfes;
const Geometry::Type geom_type;
IntegrationRules irs;
const IntegrationRule *ir;
ConstantCoefficient one;
Vector uvec;
VectorConstantCoefficient unit_vec;
const int dofs;
ParGridFunction *nodes;
ParFiniteElementSpace& mfes;
ParGridFunction x, y;
ParBilinearForm a;
std::unique_ptr<DifferentiableOperator> dop;
const int elem_size, total_size, d1d, q1d;
UniformParameterSpace qd_ps;
ParameterFunction qdata;
double mdofs{};
BakeOff(int p, int side):
p(p), c(side), q(2 * p + (GLL ? -1 : 3)), n((assert(c >= p), c / p)),
nx(n + (p * (n + 1) * p * n * p * n < c * c * c ? 1 : 0)),
ny(n + (p * (n + 1) * p * (n + 1) * p * n < c * c * c ? 1 : 0)), nz(n),
check_x(p * nx * p * ny * p * nz <= c * c * c),
check_y(p * (nx + 1) * p * (ny + 1) * p * nz > c * c * c),
check_z(p * (nx + 1) * p * (ny + 1) * p * (nz + 1) > c * c * c),
checked((assert(check_x &&check_y &&check_z), true)),
smesh(Mesh::MakeCartesian3D(nx, ny, nz, Element::HEXAHEDRON)),
pmesh(MPI_COMM_WORLD, (smesh.EnsureNodes(), smesh)),
fec(p, DIM, BasisType::GaussLobatto),
pfes(&pmesh, &fec, VDIM),//, Ordering::byNODES),
geom_type(pmesh.GetTypicalElementGeometry()),
irs(0, GLL ? Quadrature1D::GaussLobatto : Quadrature1D::GaussLegendre),
ir(&irs.Get(geom_type, q)), one(1.0), uvec(DIM),
unit_vec((uvec = 1.0, uvec /= uvec.Norml2(), uvec)),
dofs(pfes.GetTrueVSize()),
nodes(static_cast<ParGridFunction*>(pmesh.GetNodes())),
mfes(*nodes->ParFESpace()),
x(&pfes),
y(&pfes),
a(&pfes),
elem_size(DIM * DIM * ir->GetNPoints()),
total_size(elem_size * pmesh.GetNE()),
d1d(p + 1),
q1d(IntRules.Get(Geometry::SEGMENT, ir->GetOrder()).GetNPoints()),
qd_ps(pmesh, *ir, DIM*DIM),
qdata(qd_ps)
{
NVTX_MARK_FUNCTION;
dbg("p:{} q:{}", p, q);
smesh.Clear();
x = 0.0;
gD1D = d1d, gQ1D = q1d;
dbg("D1D: {}, Q1D: {}", gD1D, gQ1D);
qdata.UseDevice(true);
qdata = 0.0;
MFEM_VERIFY(q1d*q1d*q1d == ir->GetNPoints(), "");
}
virtual void Benchmark() { MFEM_ABORT("Not implemented."); }
[[nodiscard]] double SumMdofs() const noexcept { return mdofs; }
[[nodiscard]] double MDofs() const noexcept { return 1e-6 * dofs; }
};
/// Q-Functions ///////////////////////////////////////////////////////////////
template<int DIM>
struct MFApply
{
MFEM_HOST_DEVICE inline
auto operator()(const tensor<real_t, DIM>& Gu,
const tensor<real_t, DIM, DIM>& J,
const real_t& w) const
{
auto invJ = inv(J);
return tuple{((Gu * invJ)) * transpose(invJ) * det(J) * w};
}
};
template<int DIM>
struct PASetup
{
MFEM_HOST_DEVICE inline
auto operator()([[maybe_unused]] const real_t &u,
const tensor<real_t, DIM, DIM> &J,
const real_t &w)const
{
return tuple{inv(J) * transpose(inv(J)) * det(J) * w};
}
};
template<int DIM>
struct PAApply
{
MFEM_HOST_DEVICE inline
auto operator()(const tensor<real_t, DIM> &Gu,
const tensor<real_t, DIM, DIM> &q) const
{
return tuple{q * Gu};
};
};
/// Diffusion /////////////////////////////////////////////////////////////////
template <int VDIM = 1, bool GLL = false>
struct Diffusion : public BakeOff<VDIM, GLL>
{
static constexpr int DIM = 3;
static constexpr int U = 0, Ξ = 1, Q = 2;
const real_t rtol = 0.0;
const int max_it = 32, print_lvl = -1;
Array<int> ess_tdof_list, ess_bdr, all_domain_attr;
ParLinearForm b;
FieldDescriptor u_fd, Ξ_fd, q_fd;
std::vector<FieldDescriptor> u_sol, q_param, Ξ_q_params;
OperatorPtr A;
Operator *A_ptr;
Vector B, X;
CGSolver cg;
using BakeOff<VDIM, GLL>::a;
using BakeOff<VDIM, GLL>::ir;
using BakeOff<VDIM, GLL>::one;
using BakeOff<VDIM, GLL>::pmesh;
using BakeOff<VDIM, GLL>::pfes;
using BakeOff<VDIM, GLL>::mfes;
using BakeOff<VDIM, GLL>::x;
using BakeOff<VDIM, GLL>::y;
using BakeOff<VDIM, GLL>::mdofs;
using BakeOff<VDIM, GLL>::dop;
using BakeOff<VDIM, GLL>::nodes;
using BakeOff<VDIM, GLL>::qdata;
using BakeOff<VDIM, GLL>::qd_ps;
using BakeOff<VDIM, GLL>::dofs;
Diffusion(int version, int order, int side):
BakeOff<VDIM, GLL>(order, side),
ess_bdr(pmesh.bdr_attributes.Max()),
all_domain_attr(pmesh.bdr_attributes.Max()),
b(&pfes),
u_fd{U, &pfes}, Ξ_fd{Ξ, &mfes}, q_fd{Q, &qd_ps},
u_sol{u_fd},
q_param {q_fd},
Ξ_q_params {Ξ_fd, q_fd},
cg(MPI_COMM_WORLD)
{
// dbg("pmesh.bdr_attributes.Max():{}",pmesh.bdr_attributes.Max());
static_assert(VDIM == 1 && GLL == false);
ess_bdr = 1;
all_domain_attr = 1;
pfes.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
b.AddDomainIntegrator(new DomainLFIntegrator(this->one));
b.UseFastAssembly(true);
b.Assemble();
// MF setup ///////////////////////////////////////////////////
const auto dMFOperatorSetup = [&] (bool use_new_kernels,
bool use_kernels_specialization)
{
dbg("MF ∂fem {} kernels", use_new_kernels ? "NEW" : "STD");
std::vector<FieldDescriptor> in_fds = {{U, &pfes}, {Ξ, &mfes}};
std::vector<FieldDescriptor> out_fds = {{U, &pfes}};
dop = std::make_unique<DifferentiableOperator>(in_fds, out_fds, pmesh);
// dop->SetParameters({nodes});
if (use_kernels_specialization) { dop->UseKernelsSpecialization(); }
if (use_new_kernels) { dop->UseNewKernels(); }
// MFApply<DIM> mf_apply;
// dop->AddDomainIntegrator(mf_apply,
// tuple{Gradient<U>{}, Gradient<Ξ>{}, Weight{}}, // local API 🔥
// tuple{Gradient<U>{}},
// *ir, ess_bdr);
dop->FormLinearSystem(ess_tdof_list, x, b, A_ptr, X, B);
A.Reset(A_ptr);
};
// PA setup ///////////////////////////////////////////////////
const auto dPAOperatorSetup = [&] (bool use_new_kernels,
bool use_kernels_specialization)
{
#if 0
dbg("[PA ∂fem] Setup");
auto Iu = Identity<U> {};
auto = Gradient<Ξ> {};
auto W = Weight{};
tuple Iu_GΞ_W = {Iu, , W};
PASetup<DIM> pa_setup_qf;
DifferentiableOperator dSetup(u_sol, Ξ_q_params, pmesh);
if (use_kernels_specialization) { dSetup.UseKernelsSpecialization(); }
if (use_new_kernels) { dSetup.UseNewKernels(); }
dSetup.AddDomainIntegrator(pa_setup_qf, Iu_GΞ_W, tuple{Iq}, *ir, ess_bdr);
dSetup.SetParameters({nodes, &qdata});
X.SetSize(pfes.GetTrueVSize());
pfes.GetRestrictionMatrix()->Mult(x, X);
dSetup.Mult(X, qdata);
#else
dbg("[PA ∂fem] Setup (borrowing PA setup)");
{
ParBilinearForm bf(&pfes);
bf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
bf.AddDomainIntegrator(new StiffnessIntegrator(qdata));
bf.Assemble();
}
#endif
dbg("[PA ∂fem] Apply");
// auto Iq = Identity<Q> {};
// auto Gu = Gradient<U> {};
// tuple Gu_Iq = {Gu, Iq};
// PAApply<DIM> pa_apply_qf;
dop = std::make_unique<DifferentiableOperator>(u_sol, q_param, pmesh);
dop->SetMultLevel(DifferentiableOperator::MultLevel::LVECTOR);
if (use_kernels_specialization) { dop->UseKernelsSpecialization(); }
if (use_new_kernels) { dop->UseNewKernels(); }
else { dbg("[PA ∂fem] NOT using kernels specialization"); }
// dop->AddDomainIntegrator(pa_apply_qf, Gu_Iq, tuple{Gu}, *ir, ess_bdr); // local API 🔥
assert(qdata*qdata > 0.0);
// dop->SetParameters({ &qdata });
dop->FormLinearSystem(ess_tdof_list, x, b, A_ptr, X, B);
A.Reset(A_ptr);
dbg("[PA ∂fem] done");
};
if (version <= 3) // std, reg, low & mma
{
a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
if (version == 0) { a.AddDomainIntegrator(new DiffusionIntegrator(ir)); }
if (version == 1) { a.AddDomainIntegrator(new StiffnessIntegrator(qdata)); }
if (version == 2) { a.AddDomainIntegrator(new PADiffLowIntegrator()); }
if (version == 3) { a.AddDomainIntegrator(new PADiffMmaIntegrator()); }
a.Assemble();
a.FormLinearSystem(ess_tdof_list, x, b, A, X, B);
if (version == 0)
{
BilinearFormIntegrator *bfi = a.GetDBFI()->operator[](0);
auto *di = dynamic_cast<DiffusionIntegrator*>(bfi);
assert(di);
const int d1d = di->dofs1D, q1d = di->quad1D;
// dbg("\x1b[33md1d:{} q1d:{}", d1d, q1d);
MFEM_VERIFY(d1d == gD1D, "D1D mismatch: " << d1d << " != " << gD1D);
MFEM_VERIFY(q1d == gQ1D, "Q1D mismatch: " << q1d << " != " << gQ1D);
}
}
else if (version == 4) // PA ∂fem new kernels, not specialized
{
dPAOperatorSetup(true, false);
}
else if (version == 5) // PA ∂fem new kernels, specialized
{
dPAOperatorSetup(true, true);
}
else if (version == 6) // PA ∂fem std
{
dPAOperatorSetup(false, false);
}
else if (version == 7) // MF ∂fem std
{
dMFOperatorSetup(false, false);
}
else if (version == 8) // MF ∂fem new kernels
{
MFEM_ABORT("MF ∂fem new kernels not implemented");
// dMFOperatorSetup(true, true);
}
else { MFEM_ABORT("Invalid version"); }
cg.SetOperator(*A);
cg.iterative_mode = false;
cg.SetAbsTol(0.0);
if (dofs < 128 * 1024) // check
{
cg.SetPrintLevel(3/*-1*/);
cg.SetMaxIter(2000);
cg.SetRelTol(1e-8);
cg.Mult(B, X);
MFEM_VERIFY(cg.GetConverged(), "❌ CG solver did not converge.");
// mfem::out << (cg.GetConverged() ? "✅" : "❌") << std::endl;
// mfem::out << "✅" << std::endl;
}
cg.SetPrintLevel(print_lvl);
cg.SetMaxIter(max_it);
cg.SetRelTol(rtol);
Benchmark();
mdofs = 0.0;
}
void Benchmark() override
{
NVTX_MARK_FUNCTION;
cg.Mult(B, X);
MFEM_DEVICE_SYNC;
mdofs += this->MDofs() * cg.GetNumIterations();
}
};
///////////////////////////////////////////////////////////////////////////////
#define BakeOff_Problem(i, Problem) \
static void BP##i(bm::State &state) \
{ \
const auto version = static_cast<int>(state.range(0)); \
const auto order = static_cast<int>(state.range(1)); \
const auto side = static_cast<int>(state.range(2)); \
Problem ker(version, order, side); \
while (state.KeepRunning()) { ker.Benchmark(); } \
bm::Counter::Flags flags = bm::Counter::kIsRate; \
state.counters["MDof/s"] = bm::Counter(ker.SumMdofs(), flags); \
state.counters["Dofs"] = bm::Counter(ker.dofs); \
state.counters["p"] = bm::Counter(order); \
state.counters["version"] = bm::Counter(version); \
} \
BENCHMARK(BP##i) \
->Apply(CustomArguments) \
->Unit(bm::kMillisecond)
BakeOff_Problem(3, Diffusion);
/// main //////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[])
{
dbg();
DumpVersionInfo();
AddBasicKernelSpecializations();
static mfem::MPI_Session mpi(argc, argv);
bm::ConsoleReporter CR;
bm::Initialize(&argc, argv);
// Device setup, cpu by default
std::string device_context = "cpu",
kernels_context = "std",
kernels_specialization = "yes";
const auto global_context = bmi::GetGlobalContext();
if (global_context != nullptr)
{
const auto device = global_context->find("device");
if (device != global_context->end())
{
mfem::out << device->first << " : "
<< device->second << std::endl;
device_context = device->second;
}
}
dbg("device_config: {}", device_context);
Device device(device_context.c_str());
device_ptr = &device;
device.Print();
if (bm::ReportUnrecognizedArguments(argc, argv)) { return EXIT_FAILURE; }
bm::RunSpecifiedBenchmarks(&CR);
return EXIT_SUCCESS;
}
#endif // MFEM_USE_BENCHMARK
-841
View File
@@ -1,841 +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.
#pragma once
#include "fem/bilininteg.hpp"
#include <fem/quadinterpolator.hpp>
#include "general/forall.hpp"
#include "linalg/dtensor.hpp"
#include "linalg/kernels.hpp"
using namespace mfem;
/// MMA ///////////////////////////////////////////////////////////////////////
namespace mma
{
MFEM_HOST_DEVICE inline int getThreadIdx()
{
#ifdef __CUDA_ARCH__
return threadIdx.x + blockDim.x * (threadIdx.y + blockDim.y * threadIdx.z);
#else
return 0;
#endif
}
MFEM_HOST_DEVICE inline int getWarpId(int thread)
{
return thread / 32;
}
MFEM_HOST_DEVICE inline int getLaneId(int thread)
{
return thread % 32;
}
MFEM_HOST_DEVICE inline int getGroupId(int laneId)
{
return laneId / 4;
}
MFEM_HOST_DEVICE inline int getThreadIdInGroup(int laneId)
{
return laneId % 4;
}
/// Load B1d & G1d matrices into shared memory
template<int MD1, int MQ1>
MFEM_HOST_DEVICE inline void LoadBG(const int D1D, const int Q1D,
const ConstDeviceMatrix &b,
const ConstDeviceMatrix &g,
real_t (&sBG)[2][MQ1*MD1])
{
DeviceMatrix B(sBG[0], D1D, Q1D);
DeviceMatrix G(sBG[1], D1D, Q1D);
int tid = getThreadIdx();
if (tid < D1D * Q1D)
{
int q = tid / D1D;
int d = tid % D1D;
B(d,q) = b(q,d);
G(d,q) = g(q,d);
}
}
/// Load Bt1d & Gt1d matrices into shared memory
template<int MD1, int MQ1>
MFEM_HOST_DEVICE inline void LoadBtGt(const int D1D, const int Q1D,
// const ConstDeviceMatrix &bt,
// const ConstDeviceMatrix &gt,
const ConstDeviceMatrix &b,
const ConstDeviceMatrix &g,
real_t (&sBG)[2][MQ1*MD1])
{
DeviceMatrix Bt(sBG[0], Q1D, D1D);
DeviceMatrix Gt(sBG[1], Q1D, D1D);
int thread = getThreadIdx();
if (thread < D1D * Q1D)
{
int q = thread % Q1D;
int d = thread / Q1D;
// Bt(q,d) = bt(d,q);
// Gt(q,d) = gt(d,q);
Bt(q,d) = b(q,d);
Gt(q,d) = g(q,d);
}
}
/// Load 3D input vector into shared memory
template<int MQ1>
MFEM_HOST_DEVICE inline void LoadX(const int e, const int D1D,
const DeviceTensor<4, const real_t> &x,
real_t (&sm)[3][MQ1*MQ1*MQ1])
{
const int DDD = D1D * D1D * D1D;
DeviceCube X(sm[0], D1D,D1D,D1D);
int tid = getThreadIdx();
if (tid < DDD)
{
int dx = tid % D1D;
int div = tid / D1D;
int dy = div % D1D;
int dz = div / D1D;
X(dx,dy,dz) = x(dx,dy,dz,e);
}
}
// using the m8n8k4 DMMA instriction
constexpr int mmaM = 8;
[[maybe_unused]] constexpr int mmaN = 8;
constexpr int mmaK = 4;
MFEM_HOST_DEVICE inline void dmmaSync([[maybe_unused]] double aReg[1],
[[maybe_unused]] double bReg[1],
[[maybe_unused]] double cReg[2])
{
#ifdef __CUDA_ARCH__
asm volatile("mma.sync.aligned.m8n8k4.row.col.f64.f64.f64.f64 {%0,%1}, {%2}, {%3}, {%0,%1};"
: "+d"(cReg[0]), "+d"(cReg[1]) : "d"(aReg[0]), "d"(bReg[0]));
#endif
}
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void dmma_GradX(const int m, const int n, const int k,
const real_t (&BG)[2][MQ1*MD1],
const real_t (*A)[MDQ*MDQ*MDQ],
real_t (*C)[MDQ*MDQ*MDQ])
{
ConstDeviceMatrix B(BG[0], k, n);
ConstDeviceMatrix G(BG[1], k, n);
int thread = getThreadIdx();
int warpId = getWarpId(thread);
int laneId = getLaneId(thread);
int groupId = getGroupId(laneId);
int threadIdInGroup = getThreadIdInGroup(laneId);
// using the m8n8k4 DMMA instriction
int mPass = (m + mmaM - 1) / mmaM;
if (warpId < mPass) // Spread the warps.
{
int aRowInWarp = groupId;
int aColumnInWarp = threadIdInGroup;
int bRowInWarp = threadIdInGroup;
int bColumnInWarp = groupId;
constexpr int magicNumber =
0b100011111010110001101000; // jump table [0,5,1,6,2,7,3,4]
int mM = warpId;
double cReg[4] = {};
for (int mK = 0; mK < (k + mmaK - 1) / mmaK; mK++)
{
double bReg[1];
double gReg[1];
int bRow = bRowInWarp + mK * mmaK;
int bColumn = (magicNumber >> (3 * bColumnInWarp)) & 0b111;
if (bColumn < n && bRow < k)
{
bReg[0] = B(bRow, bColumn);
gReg[0] = G(bRow, bColumn);
}
else
{
bReg[0] = 0;
gReg[0] = 0;
}
double aReg[1];
int aRow = aRowInWarp * mPass + mM;
int aColumn = aColumnInWarp + mK * mmaK;
if (aRow < m && aColumn < k)
{
ConstDeviceMatrix aA(A[0], k, m);
aReg[0] = aA(aColumn, aRow);
}
else
{
aReg[0] = 0;
}
dmmaSync(aReg, gReg, &cReg[0]);
dmmaSync(aReg, bReg, &cReg[2]);
}
for (int d = 0; d < 2; d++)
{
#pragma unroll
for (int i = 0; i < 2; i++)
{
int cRow = groupId * mPass + mM;
int cColumn = (magicNumber >> (3 * (threadIdInGroup * 2 + i))) & 0b111;
if (cRow < m && cColumn < n)
{
DeviceMatrix cC(C[d], m, n);
cC(cRow, cColumn) = cReg[d * 2 + i];
}
}
}
}
}
/// 3D Gradient, 1/3
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void GradX(const int D1D, const int Q1D,
const real_t (&sBG)[2][MQ1*MD1],
const real_t (*sDDD)[MDQ*MDQ*MDQ],
real_t (*sDDQ)[MDQ*MDQ*MDQ])
{
dmma_GradX<MD1, MQ1>(D1D * D1D, Q1D, D1D, sBG, sDDD, sDDQ);
}
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void dmma_GradY(const int m, const int n,
const int k,
const real_t (&BG)[2][MQ1*MD1],
const real_t (*A)[MDQ*MDQ*MDQ],
real_t (*C)[MDQ*MDQ*MDQ])
{
ConstDeviceMatrix B(BG[0], k, n);
ConstDeviceMatrix G(BG[1], k, n);
int thread = getThreadIdx();
int warpId = getWarpId(thread);
int laneId = getLaneId(thread);
int groupId = getGroupId(laneId);
int threadIdInGroup = getThreadIdInGroup(laneId);
// using the m8n8k4 DMMA instriction
int mPass = (m + mmaM - 1) / mmaM;
if (warpId < mPass) // Spread the warps.
{
int aRowInWarp = groupId;
int aColumnInWarp = threadIdInGroup;
int bRowInWarp = threadIdInGroup;
int bColumnInWarp = groupId;
constexpr int magicNumber =
0b100011111010110001101000; // jump table [0,5,1,6,2,7,3,4]
int mM = warpId;
double cReg[6] = {};
for (int mK = 0; mK < (k + mmaK - 1) / mmaK; mK++)
{
double bReg[1];
double gReg[1];
int bRow = bRowInWarp + mK * mmaK;
int bColumn = (magicNumber >> (3 * bColumnInWarp)) & 0b111;
if (bColumn < n && bRow < k)
{
bReg[0] = B(bRow, bColumn);
gReg[0] = G(bRow, bColumn);
}
else
{
bReg[0] = 0;
gReg[0] = 0;
}
double agReg[1];
double abReg[1];
int aRow = aRowInWarp * mPass + mM;
int aColumn = aColumnInWarp + mK * mmaK;
if (aRow < m && aColumn < k)
{
ConstDeviceMatrix gA(A[0], k, m);
ConstDeviceMatrix bA(A[1], k, m);
agReg[0] = gA(aColumn, aRow);
abReg[0] = bA(aColumn, aRow);
}
else
{
agReg[0] = 0;
abReg[0] = 0;
}
dmmaSync(agReg, bReg, &cReg[0]);
dmmaSync(abReg, gReg, &cReg[2]);
dmmaSync(abReg, bReg, &cReg[4]);
}
for (int d = 0; d < 3; d++)
{
#pragma unroll
for (int i = 0; i < 2; i++)
{
int cRow = groupId * mPass + mM;
int cColumn = (magicNumber >> (3 * (threadIdInGroup * 2 + i))) & 0b111;
if (cRow < m && cColumn < n)
{
DeviceMatrix cC(C[d], m, n);
cC(cRow, cColumn) = cReg[d * 2 + i];
}
}
}
}
}
/// 3D Gradient, 2/3
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void GradY(const int D1D, const int Q1D,
const real_t (&sBG)[2][MQ1*MD1],
const real_t (*sDDQ)[MDQ*MDQ*MDQ],
real_t (*sDQQ)[MDQ*MDQ*MDQ])
{
dmma_GradY<MD1, MQ1>(D1D * Q1D, Q1D, D1D, sBG, sDDQ, sDQQ);
}
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void dmma_GradZ(const int m, const int n,
const int k,
const real_t (&BG)[2][MQ1*MD1],
const real_t (*A)[MDQ*MDQ*MDQ],
real_t (*C)[MDQ*MDQ*MDQ],
int gIdx)
{
ConstDeviceMatrix B(BG[0], k, n);
ConstDeviceMatrix G(BG[1], k, n);
int thread = getThreadIdx();
int warpId = getWarpId(thread);
int laneId = getLaneId(thread);
int groupId = getGroupId(laneId);
int threadIdInGroup = getThreadIdInGroup(laneId);
// using the m8n8k4 DMMA instriction
int mPass = (m + mmaM - 1) / mmaM;
if (warpId < mPass) // Spread the warps.
{
int aRowInWarp = groupId;
int aColumnInWarp = threadIdInGroup;
int bRowInWarp = threadIdInGroup;
int bColumnInWarp = groupId;
constexpr int magicNumber =
0b100011111010110001101000; // jump table [0,5,1,6,2,7,3,4]
int mM = warpId;
double cReg[6] = {};
for (int mK = 0; mK < (k + mmaK - 1) / mmaK; mK++)
{
double bReg[1];
double gReg[1];
int bRow = bRowInWarp + mK * mmaK;
int bColumn = (magicNumber >> (3 * bColumnInWarp)) & 0b111;
if (bColumn < n && bRow < k)
{
bReg[0] = B(bRow, bColumn);
gReg[0] = G(bRow, bColumn);
}
else
{
bReg[0] = 0;
gReg[0] = 0;
}
for (int d = 0; d < 3; d++)
{
double aReg[1];
int aRow = aRowInWarp * mPass + mM;
int aColumn = aColumnInWarp + mK * mmaK;
if (aRow < m && aColumn < k)
{
ConstDeviceMatrix aA(A[d], k, m);
aReg[0] = aA(aColumn, aRow);
}
else
{
aReg[0] = 0;
}
dmmaSync(aReg, d == gIdx ? gReg : bReg, &cReg[d * 2]);
}
}
for (int d = 0; d < 3; d++)
{
#pragma unroll
for (int i = 0; i < 2; i++)
{
int cRow = groupId * mPass + mM;
int cColumn = (magicNumber >> (3 * (threadIdInGroup * 2 + i))) & 0b111;
if (cRow < m && cColumn < n)
{
DeviceMatrix cC(C[d], m, n);
cC(cRow, cColumn) = cReg[d * 2 + i];
}
}
}
}
}
/// 3D Gradient, 3/3
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void GradZ(const int D1D, const int Q1D,
const real_t (&sBG)[2][MQ1*MD1],
const real_t (*sDQQ)[MDQ*MDQ*MDQ],
real_t (*sQQQ)[MDQ*MDQ*MDQ])
{
dmma_GradZ<MD1, MQ1>(Q1D * Q1D, Q1D, D1D, sBG, sDQQ, sQQQ, 2);
}
/// 3D Transposed Gradient, 1/3
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void GradZt(const int D1D, const int Q1D,
const real_t (&sBG)[2][MQ1*MD1],
const real_t (*sQQQ)[MDQ*MDQ*MDQ],
real_t (*sDQQ)[MDQ*MDQ*MDQ])
{
ConstDeviceMatrix Bt(sBG[0], Q1D, D1D);
ConstDeviceMatrix Gt(sBG[1], Q1D, D1D);
int thread = getThreadIdx();
int warpId = getWarpId(thread);
int laneId = getLaneId(thread);
int groupId = getGroupId(laneId);
int threadIdInGroup = getThreadIdInGroup(laneId);
// using the m8n8k4 DMMA instriction
// qy (Q1D), qz (Q1D) === M, dx (D1D) === N, qx (Q1D) === K
int mPass = (Q1D * Q1D + mmaM - 1) / mmaM;
if (warpId < mPass) // Spread the warps to calculate the 3 directions.
{
int aRowInWarp = groupId;
int aColumnInWarp = threadIdInGroup;
int bRowInWarp = threadIdInGroup;
int bColumnInWarp = groupId;
constexpr int magicNumber =
0b100011111010110001101000; // jump table [0,5,1,6,2,7,3,4]
int mM = warpId;
double cReg[6] = {};
for (int mK = 0; mK < (Q1D + mmaK - 1) / mmaK; mK++)
{
double BtReg[1];
double GtReg[1];
int bRow = bRowInWarp + mK * mmaK;
int bColumn = (magicNumber >> (3 * bColumnInWarp)) & 0b111;
if (bColumn < D1D && bRow < Q1D)
{
BtReg[0] = Bt(bRow, bColumn);
GtReg[0] = Gt(bRow, bColumn);
}
else
{
BtReg[0] = 0;
GtReg[0] = 0;
}
for (int d = 0; d < 3; d++)
{
double aReg[1];
int aRow = aRowInWarp * mPass + mM;
int aColumn = aColumnInWarp + mK * mmaK;
if (aRow < Q1D * Q1D && aColumn < Q1D)
{
ConstDeviceMatrix XxBBG(sQQQ[d], Q1D, Q1D * Q1D);
aReg[0] = XxBBG(aColumn, aRow);
}
else
{
aReg[0] = 0;
}
dmmaSync(aReg, d == 0 ? GtReg : BtReg, &cReg[d * 2]);
}
}
for (int d = 0; d < 3; d++)
{
#pragma unroll
for (int i = 0; i < 2; i++)
{
int cRow = groupId * mPass + mM;
int cColumn = (magicNumber >> (3 * (threadIdInGroup * 2 + i))) & 0b111;
if (cRow < Q1D * Q1D && cColumn < D1D)
{
DeviceMatrix Xx(sDQQ[d], Q1D * Q1D, D1D); // qy, qz, dx
Xx(cRow, cColumn) = cReg[d * 2 + i];
}
}
}
}
}
/// 3D Transposed Gradient, 2/3
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void GradYt(const int D1D, const int Q1D,
const real_t (&sBG)[2][MQ1*MD1],
const real_t (*sDQQ)[MDQ*MDQ*MDQ],
real_t (*sDDQ)[MDQ*MDQ*MDQ])
{
ConstDeviceMatrix Bt(sBG[0], Q1D, D1D);
ConstDeviceMatrix Gt(sBG[1], Q1D, D1D);
int thread = getThreadIdx();
int warpId = getWarpId(thread);
int laneId = getLaneId(thread);
int groupId = getGroupId(laneId);
int threadIdInGroup = getThreadIdInGroup(laneId);
// using the m8n8k4 DMMA instriction
// dx (D1D), qz (Q1D) === M, dy (D1D) === N, qy (Q1D) === K
int mPass = (D1D * Q1D + mmaM - 1) / mmaM;
if (warpId < mPass) // Spread the warps.
{
int aRowInWarp = groupId;
int aColumnInWarp = threadIdInGroup;
int bRowInWarp = threadIdInGroup;
int bColumnInWarp = groupId;
constexpr int magicNumber =
0b100011111010110001101000; // jump table [0,5,1,6,2,7,3,4]
int mM = warpId;
double cReg[6] = {}; // initialized to zero
for (int mK = 0; mK < (Q1D + mmaK - 1) / mmaK; mK++)
{
double BtReg[1];
double GtReg[1];
int bRow = bRowInWarp + mK * mmaK;
int bColumn = (magicNumber >> (3 * bColumnInWarp)) & 0b111;
if (bColumn < D1D && bRow < Q1D)
{
BtReg[0] = Bt(bRow, bColumn);
GtReg[0] = Gt(bRow, bColumn);
}
else
{
BtReg[0] = 0;
GtReg[0] = 0;
}
for (int d = 0; d < 3; d++)
{
double aReg[1];
int aRow = aRowInWarp * mPass + mM;
int aColumn = aColumnInWarp + mK * mmaK;
if (aRow < D1D * Q1D && aColumn < Q1D)
{
ConstDeviceMatrix XxBB(sDQQ[d], Q1D, D1D * Q1D); // qy, qz, dx
aReg[0] = XxBB(aColumn, aRow);
}
else
{
aReg[0] = 0;
}
dmmaSync(aReg, d == 1 ? GtReg : BtReg, &cReg[d * 2]);
}
}
for (int d = 0; d < 3; d++)
{
#pragma unroll
for (int i = 0; i < 2; i++)
{
int cRow = groupId * mPass + mM;
int cColumn = (magicNumber >> (3 * (threadIdInGroup * 2 + i))) & 0b111;
if (cRow < D1D * Q1D && cColumn < D1D)
{
DeviceMatrix Xx(sDDQ[d], D1D * Q1D, D1D); // qz, dx, dy
Xx(cRow, cColumn) = cReg[d * 2 + i];
}
}
}
}
}
/// 3D Transposed Gradient, 3/3
template<int MD1, int MQ1, int MDQ = (MQ1 > MD1 ? MQ1 : MD1)>
MFEM_HOST_DEVICE inline void GradXt(const int D1D, const int Q1D,
const real_t (&sBG)[2][MQ1*MD1],
const real_t (&sDDQ)[3][MDQ*MDQ*MDQ],
const DeviceTensor<4> &Y, // output
const int e)
{
ConstDeviceMatrix Bt(sBG[0], Q1D, D1D);
ConstDeviceMatrix Gt(sBG[1], Q1D, D1D);
int thread = getThreadIdx();
int warpId = getWarpId(thread);
int laneId = getLaneId(thread);
int groupId = getGroupId(laneId);
int threadIdInGroup = getThreadIdInGroup(laneId);
// using the m8n8k4 DMMA instriction
// dx (D1D), dy (D1D) === M, dz (D1D) === N, qz (Q1D) === K
int mPass = (D1D * D1D + mmaM - 1) / mmaM;
if (warpId < mPass) // Spread the warps to calculate the 3 directions.
{
int aRowInWarp = groupId;
int aColumnInWarp = threadIdInGroup;
int bRowInWarp = threadIdInGroup;
int bColumnInWarp = groupId;
constexpr int magicNumber =
0b100011111010110001101000; // jump table [0,5,1,6,2,7,3,4]
int mM = warpId;
{
double BtReg[1];
double GtReg[1];
double cReg[2] = {}; // initialized to zero
for (int mK = 0; mK < (Q1D + mmaK - 1) / mmaK; mK++)
{
int bRow = bRowInWarp + mK * mmaK;
int bColumn = (magicNumber >> (3 * bColumnInWarp)) & 0b111;
if (bColumn < D1D && bRow < Q1D)
{
BtReg[0] = Bt(bRow, bColumn);
GtReg[0] = Gt(bRow, bColumn);
}
else
{
BtReg[0] = 0;
GtReg[0] = 0;
}
for (int d = 0; d < 3; d++)
{
double aReg[1];
int aRow = aRowInWarp * mPass + mM;
int aColumn = aColumnInWarp + mK * mmaK;
if (aRow < D1D * D1D && aColumn < Q1D)
{
ConstDeviceMatrix Xx(sDDQ[d], Q1D, D1D * D1D); // qz, dx, dy
aReg[0] = Xx(aColumn, aRow);
}
else
{
aReg[0] = 0;
}
dmmaSync(aReg, d == 2 ? GtReg : BtReg, cReg);
}
}
#pragma unroll
for (int i = 0; i < 2; i++)
{
int cRow = groupId * mPass + mM;
int cColumn = (magicNumber >> (3 * (threadIdInGroup * 2 + i))) & 0b111;
if (cRow < D1D * D1D && cColumn < D1D)
{
int dx = cRow % D1D;
int dy = cRow / D1D;
int dz = cColumn;
Y(dx,dy,dz,e) += cReg[i];
}
}
}
}
}
} // namespace mma
/// PADiffMmaIntegrator ///////////////////////////////////////////////////////
struct PADiffMmaIntegrator : public BilinearFormIntegrator
{
const FiniteElementSpace *fes;
const real_t *B, *G, *DX;
int ne, d1d, q1d;
Vector J0, dx;
public: // for nvcc
//////////////////////////////////////////////////////////////////
template <int T_D1D = 0, int T_Q1D = 0>
static void PADiffMmaMult(const int ne,
const real_t *b, const real_t *g,
const real_t *dx, const real_t *xe,
real_t *ye,
const int, const int)
{
constexpr int Q1D = T_Q1D, D1D = T_D1D;
const auto B = Reshape(b, Q1D, D1D);
const auto G = Reshape(g, Q1D, D1D);
const auto XE = Reshape(xe, D1D, D1D, D1D, ne);
const auto DX = Reshape(dx, 3, 3, Q1D, Q1D, Q1D, ne);
auto YE = Reshape(ye, D1D, D1D, D1D, ne);
mfem::forall_3D(ne, ((Q1D * Q1D * Q1D + 31) / 32) * 32, 1, 1,
[=] MFEM_HOST_DEVICE(int e)
{
constexpr int MQ1 = T_Q1D, MD1 = T_D1D;
MFEM_SHARED real_t sm0[3][MQ1*MQ1*MQ1];
MFEM_SHARED real_t sm1[3][MQ1*MQ1*MQ1];
MFEM_SHARED real_t BG[2][MD1*MQ1];
mma::LoadBG<MD1, MQ1>(D1D, Q1D, B, G, BG);
mma::LoadX<MQ1>(e, D1D, XE, sm0);
MFEM_SYNC_THREAD;
mma::GradX<MD1, MQ1>(D1D, Q1D, BG, sm0, sm1);
MFEM_SYNC_THREAD;
mma::GradY<MD1, MQ1>(D1D, Q1D, BG, sm1, sm0);
MFEM_SYNC_THREAD;
mma::GradZ<MD1, MQ1>(D1D, Q1D, BG, sm0, sm1);
MFEM_SYNC_THREAD;
int thread = mma::getThreadIdx();
if (thread < Q1D * Q1D * Q1D)
{
int qx = thread % Q1D;
int div = thread / Q1D;
int qy = div % Q1D;
int qz = div / Q1D;
{
// pull
real_t v[3], u[3] = { sm1[0][qz + qy*Q1D + qx*Q1D*Q1D],
sm1[1][qz + qy*Q1D + qx*Q1D*Q1D],
sm1[2][qz + qy*Q1D + qx*Q1D*Q1D]
};
// Q-function
const real_t *dx = &DX(0, 0, qx, qy, qz, e);
kernels::Mult(3, 3, dx, u, v);
// push
sm0[0][qz + qy*Q1D + qx*Q1D*Q1D] = v[0];
sm0[1][qz + qy*Q1D + qx*Q1D*Q1D] = v[1];
sm0[2][qz + qy*Q1D + qx*Q1D*Q1D] = v[2];
}
}
mma::LoadBtGt<MD1,MQ1>(D1D, Q1D, B, G, BG);
MFEM_SYNC_THREAD;
mma::GradZt<MD1, MQ1>(D1D, Q1D, BG, sm0, sm1);
MFEM_SYNC_THREAD;
mma::GradYt<MD1, MQ1>(D1D, Q1D, BG, sm1, sm0);
MFEM_SYNC_THREAD;
mma::GradXt<MD1,MQ1>(D1D, Q1D, BG, sm0, YE, e);
});
}
using PADiffMmaKernelType = decltype(&PADiffMmaMult<>);
MFEM_REGISTER_KERNELS(PADiffMmaKernels, PADiffMmaKernelType, (int, int));
public:
PADiffMmaIntegrator()
{
// PADiffMmaKernels::Specialization<2,3>::Add(); // 1 ❌
PADiffMmaKernels::Specialization<3,4>::Add(); // 2
PADiffMmaKernels::Specialization<4,5>::Add(); // 3
PADiffMmaKernels::Specialization<5,6>::Add(); // 4
PADiffMmaKernels::Specialization<6,7>::Add(); // 5
PADiffMmaKernels::Specialization<7,8>::Add(); // 6
}
void AssemblePA(const FiniteElementSpace &fespace) override
{
NVTX();
fes = &fespace;
auto *mesh = fes->GetMesh();
const int DIM = mesh->Dimension();
ne = mesh->GetNE();
const auto p = fes->GetFE(0)->GetOrder();
const auto q = 2 * p + mesh->GetElementTransformation(0)->OrderW();
const auto type = mesh->GetElementBaseGeometry(0);
const IntegrationRule &ir = IntRules.Get(type, q);
const int NQPT = ir.GetNPoints();
d1d = p + 1;
q1d = IntRules.Get(Geometry::SEGMENT, ir.GetOrder()).GetNPoints();
MFEM_VERIFY(NQPT == q1d * q1d * q1d, "");
const DofToQuad *maps =
&fes->GetFE(0)->GetDofToQuad(ir, DofToQuad::TENSOR);
const GridFunction *nodes = (mesh->EnsureNodes(), mesh->GetNodes());
const FiniteElementSpace *nfes = nodes->FESpace();
const int nVDIM = nfes->GetVDim();
dx.SetSize(nVDIM * DIM * NQPT * ne, Device::GetDeviceMemoryType());
J0.SetSize(nVDIM * DIM * NQPT * ne, Device::GetDeviceMemoryType());
dx.UseDevice(true), J0.UseDevice(true);
B = maps->B.Read(), G = maps->G.Read(), DX = dx.Read();
const Operator *NR =
nfes->GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
const QuadratureInterpolator *nqi = nfes->GetQuadratureInterpolator(ir);
nqi->SetOutputLayout(QVectorLayout::byVDIM);
const int nd = nfes->GetFE(0)->GetDof();
Vector xe(nVDIM * nd * ne, Device::GetDeviceMemoryType());
NR->Mult(*nodes, (xe.UseDevice(true), xe));
nqi->Derivatives(xe, J0);
const int Q1D = q1d;
const auto w_r = ir.GetWeights().Read();
const auto W = Reshape(w_r, q1d, q1d, q1d);
const auto J = Reshape(J0.Read(), 3, 3, q1d, q1d, q1d, ne);
auto DX_w = Reshape(dx.Write(), 3, 3, q1d, q1d, q1d, ne);
mfem::forall_3D(ne, Q1D, Q1D, Q1D,[=] MFEM_HOST_DEVICE(int e)
{
MFEM_FOREACH_THREAD_DIRECT(qz, z, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qy, y, Q1D)
{
MFEM_FOREACH_THREAD_DIRECT(qx, x, Q1D)
{
const real_t w = W(qx, qy, qz);
const real_t *Jtr = &J(0, 0, qx, qy, qz, e);
const real_t detJ = kernels::Det<3>(Jtr);
const real_t wd = w * detJ;
const real_t D[9] = { wd, 0.0, 0.0,
0.0, wd, 0.0,
0.0, 0.0, wd
};
real_t Jrt[9], A[9];
kernels::CalcInverse<3>(Jtr, Jrt);
kernels::MultABt(3, 3, 3, D, Jrt, A);
kernels::Mult(3, 3, 3, A, Jrt, &DX_w(0, 0, qz, qy, qx, e));
}
}
}
MFEM_SYNC_THREAD;
});
}
void AddMultPA(const Vector &x, Vector &y) const override
{
db1("\x1b[32md1d:{} q1d:{}", d1d, q1d);
PADiffMmaKernels::Run(d1d, q1d,
ne, B, G, DX, x.Read(), y.ReadWrite(),
d1d, q1d);
}
};
template <int D1D, int Q1D>
PADiffMmaIntegrator::PADiffMmaKernelType
PADiffMmaIntegrator::PADiffMmaKernels::Kernel()
{
db1("D1D:{} Q1D:{}", D1D, Q1D);
return PADiffMmaMult<D1D, Q1D>;
}
PADiffMmaIntegrator::PADiffMmaKernelType
PADiffMmaIntegrator::PADiffMmaKernels::Fallback(int d1d, int q1d)
{
dbg("\x1b[33mFallback d1d:{} q1d:{}", d1d, q1d);
MFEM_ABORT("No kernel for q1d=" << q1d);
return nullptr;
// return PADiffMmaMult;
}
+2 -2
View File
@@ -20,8 +20,8 @@ CONFIG_MK = $(or $(wildcard $(MFEM_BUILD_DIR)/config/config.mk),\
MFEM_LIB_FILE = mfem_is_not_built
-include $(CONFIG_MK)
SEQ_TESTS = bench_assembly_levels bench_ceed bench_dfem bench_dg_amr \
bench_elasticity bench_tmop bench_vector bench_virtuals
SEQ_TESTS = bench_assembly_levels bench_ceed bench_dg_amr bench_elasticity \
bench_tmop bench_vector bench_virtuals
PAR_TESTS =
ifeq ($(MFEM_USE_MPI),NO)
TESTS = $(SEQ_TESTS)
+7 -9
View File
@@ -25,8 +25,8 @@ add_custom_target(copy_test_data
# Add 'copy_test_data' as a prerequisite for test executables, if the source and the
# build directories are not the same.
if (NOT ("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}"))
add_dependencies(${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME} copy_test_data)
add_dependencies(${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME} copy_data)
add_dependencies(${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME} copy_test_data)
add_dependencies(${MFEM_TEST_EXEC_PREREQUISITES_TARGET_NAME} copy_data)
endif()
# Include the source directory for the unit tests - catch.hpp is there.
@@ -35,12 +35,10 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR})
# The following list can be updated using (in bash):
# for d in dfem general linalg mesh fem enzyme; do ls -1 $d/*.cpp; done
set(UNIT_TESTS_SRCS
# dfem/test_diffusion.cpp
# dfem/test_diffusion_q1d.cpp
# dfem/test_divergence.cpp
# dfem/test_lvector_interface.cpp
# dfem/test_mass.cpp
dfem/test_multiple_outputs.cpp
dfem/test_diffusion.cpp
dfem/test_divergence.cpp
dfem/test_lvector_interface.cpp
dfem/test_mass.cpp
general/test_array.cpp
general/test_scan.cpp
general/test_arrays_by_name.cpp
@@ -258,7 +256,7 @@ function(add_serial_miniapp_test name test_uvm)
set_property(SOURCE ${${NAME}_TESTS_SRCS} PROPERTY LANGUAGE CUDA)
endif(MFEM_USE_CUDA)
if (MFEM_USE_HIP)
set_property(SOURCE ${${NAME}_TESTS_SRCS} PROPERTY HIP_SOURCE_PROPERTY_FORMAT TRUE)
set_property(SOURCE ${${NAME}_TESTS_SRCS} PROPERTY HIP_SOURCE_PROPERTY_FORMAT TRUE)
endif(MFEM_USE_HIP)
mfem_add_executable(${name}_tests_cpu ${${NAME}_TESTS_SRCS})
+1 -1
View File
@@ -302,7 +302,7 @@ void diffusion(const char *filename, int p)
TEST_CASE("dFEM Diffusion", "[Parallel][dFEM][GPU]")
{
// const bool all_tests = launch_all_non_regression_tests;
const bool all_tests = launch_all_non_regression_tests;
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
-338
View File
@@ -1,338 +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.
#define NVTX_COLOR nvtx::kGold
#include "unit_tests.hpp"
#include "mfem.hpp"
// #include <type_traits>
#include "fem/dfem/doperator.hpp"
#include "fem/dfem/util.hpp"
#include <fem/integ/bilininteg_diffusion_kernels.hpp>
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
using mfem::future::dual;
using DOperator = future::DifferentiableOperator;
enum class MQ1Settings : int { kRuntime,
kCompileTime,
kDefault
};
namespace dfem_pa_kernels
{
///////////////////////////////////////////////////////////////////////////////
template <typename T, int DIM, int T_MQ1 = 0> struct Diffusion
{
using dvecd_t = tensor<T, DIM>;
using matd_t = tensor<real_t, DIM, DIM>;
struct MFApply
{
static constexpr int MQ1 = T_MQ1;
MFEM_HOST_DEVICE inline auto operator()(const dvecd_t &dudxi,
const real_t &rho,
const matd_t &J,
const real_t &w) const
{
const auto invJ = inv(J), TinJ = transpose(invJ);
return mfem::future::tuple{ (dudxi * invJ) * TinJ * det(J) * w * rho };
}
};
struct PASetup
{
MFEM_HOST_DEVICE inline auto operator()(const real_t u,
const real_t &rho,
const matd_t &J,
const real_t &w) const
{
return mfem::future::tuple{ inv(J) * transpose(inv(J)) * det(J) * w * rho };
}
};
struct PAApply
{
MFEM_HOST_DEVICE inline auto operator()(const dvecd_t &dudxi,
const matd_t &q) const
{
return mfem::future::tuple{ q * dudxi };
};
};
};
///////////////////////////////////////////////////////////////////////////////
template <typename T, int DIM, std::size_t... MQ1s>
struct MFDiffusionFactory
{
static auto All()
{
// could also use a map instead of a tuple
return mfem::future::make_tuple(typename Diffusion<T, DIM, MQ1s>::MFApply{}...);
}
};
template <typename T, int DIM>
using MFDiffusionFactory_1_4 = MFDiffusionFactory<T, DIM, 1, 2, 3, 4>;
template <typename T, int DIM>
class MFDiffusionQFs
{
using MFApplyTuple = decltype(MFDiffusionFactory_1_4<T, DIM>::All());
MFApplyTuple mf_qfs;
public:
MFDiffusionQFs(): mf_qfs(MFDiffusionFactory_1_4<T, DIM>::All()) {}
template <typename F>
void run(int i, F&& f)
{
MFEM_VERIFY(i >= 1, "Index must be >= 1");
const auto I = static_cast<size_t>(i - 1);
runtime_get_impl(I, std::forward<F>(f),
std::make_index_sequence<mfem::future::tuple_size<MFApplyTuple>::value>());
}
private:
template <typename F, size_t... I>
void runtime_get_impl(size_t index, F&& f, std::index_sequence<I...>)
{
using fun_ptr = std::function<void(F&&)>;
fun_ptr table[] = { [&](F&& f) { f(mfem::future::get<I>(mf_qfs)); } ... };
if (index < mfem::future::tuple_size<MFApplyTuple>::value)
{
table[index](std::forward<F>(f));
}
else
{
throw std::out_of_range("Index out of bounds");
}
}
};
///////////////////////////////////////////////////////////////////////////////
template <int DIM>
void DFemDiffusion(const char *filename, int p, const int r,
const MQ1Settings mq1_setting)
{
dbg("DIM:{}", DIM);
CAPTURE(filename, DIM, p, r);
Mesh smesh(filename);
ParMesh pmesh(MPI_COMM_WORLD, smesh);
MFEM_VERIFY(pmesh.Dimension() == DIM, "Mesh dimension mismatch");
pmesh.EnsureNodes();
auto *nodes = static_cast<ParGridFunction *>(pmesh.GetNodes());
p = std::max(p, pmesh.GetNodalFESpace()->GetMaxElementOrder());
smesh.Clear();
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
H1_FECollection fec(p, DIM);
ParFiniteElementSpace pfes(&pmesh, &fec);
ParFiniteElementSpace *mfes = nodes->ParFESpace();
const int NE = pfes.GetNE(), d1d(p + 1), q = 2 * p + r;
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), q);
const int q1d(IntRules.Get(Geometry::SEGMENT, ir->GetOrder()).GetNPoints());
MFEM_VERIFY(d1d <= q1d, "q1d should be >= d1d");
ParGridFunction x(&pfes), y(&pfes), z(&pfes);
Vector X(pfes.GetTrueVSize()), Y(pfes.GetTrueVSize()), Z(pfes.GetTrueVSize());
X.Randomize(1);
x.SetFromTrueDofs(X);
auto rho = [](const Vector &xyz)
{
const real_t x = xyz(0), y = xyz(1), z = DIM == 3 ? xyz(2) : 0.0;
real_t r = M_PI * pow(x, 2);
if (DIM >= 2) { r += pow(y, 3); }
if (DIM >= 3) { r += pow(z, 4); }
return r;
};
FunctionCoefficient rho_coeff(rho);
ParBilinearForm blf_fa(&pfes);
blf_fa.AddDomainIntegrator(new DiffusionIntegrator(rho_coeff, ir));
blf_fa.Assemble();
blf_fa.Finalize();
QuadratureSpace qs(pmesh, *ir);
CoefficientVector rho_coeff_cv(rho_coeff, qs);
MFEM_VERIFY(rho_coeff_cv.GetVDim() == 1, "Coefficient should be scalar");
MFEM_VERIFY(rho_coeff_cv.Size() == q1d * q1d * (DIM == 3 ? q1d : 1) * NE, "");
UniformParameterSpace rho_ps(pmesh, *ir, 1);
static constexpr int U = 0, Coords = 1, Rho = 3;
const auto sol = std::vector{ FieldDescriptor{ U, &pfes } };
SECTION("DFEM Matrix free")
{
// fields = {solutions, parameters}
dbg("fields = {{solutions, parameters}} = {{{{U}}, {{Rho, Coords}}}}");
DOperator dop_mf(sol, {{Rho, &rho_ps}, {Coords, mfes}}, pmesh);
dbg("AddDomainIntegrator: {{∇U, Rho, ∇Coords, Weight}} -> {{∇U}}");
if (mq1_setting == MQ1Settings::kRuntime)
{
dbg("MQ1Settings::kRuntime");
MFEM_VERIFY(q1d == (int)floor(std::pow(ir->GetNPoints(), 1.0/DIM) + 0.5),
"q1d and ir->GetNPoints() have to match");
auto add_domain_integrator = [&](auto &qf)
{
dbg("q1d:{} MQ1:{}", q1d, qf.MQ1);
MFEM_VERIFY(q1d == qf.MQ1, "q1d and qf.MQ1 have to match");
dop_mf.AddDomainIntegrator(qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
};
// select the right qf from the factory
MFDiffusionQFs<real_t, DIM> {}.run(q1d, add_domain_integrator);
}
else if (mq1_setting == MQ1Settings::kCompileTime) // hardcoded, MQ1 = 2,3,4,5
{
dbg("MQ1Settings::kCompileTime");
dbg("q1d:{}", q1d);
if (q1d == 2)
{
typename Diffusion<real_t, DIM, 2>::MFApply mf_apply_qf;
MFEM_VERIFY(q1d == 2, "q1d and 2 have to match");
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
}
else if (q1d == 3)
{
typename Diffusion<real_t, DIM, 3>::MFApply mf_apply_qf;
MFEM_VERIFY(q1d == 3, "q1d and 3 have to match");
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
}
else if (q1d == 4)
{
typename Diffusion<real_t, DIM, 4>::MFApply mf_apply_qf;
MFEM_VERIFY(q1d == 4, "q1d and 4 have to match");
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
}
else if (q1d == 5)
{
typename Diffusion<real_t, DIM, 5>::MFApply mf_apply_qf;
MFEM_VERIFY(q1d == 5, "q1d and 5 have to match");
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
}
else { MFEM_ABORT("Not supported q1d:" << q1d); }
}
else // MQ1Settings::kDefault, MQ1 = 0
{
dbg("MQ1Settings::kDefault");
typename Diffusion<real_t, DIM>::MFApply mf_apply_qf;
dop_mf.AddDomainIntegrator(mf_apply_qf,
tuple{ Gradient<U>{}, Identity<Rho>{},
Gradient<Coords>{}, Weight{} },
tuple{ Gradient<U>{} }, *ir,
all_domain_attr);
}
dop_mf.SetParameters({ &rho_coeff_cv, nodes });
pfes.GetRestrictionMatrix()->Mult(x, X);
dop_mf.Mult(X, Z);
blf_fa.Mult(x, y);
pfes.GetProlongationMatrix()->MultTranspose(y, Y);
Y -= Z;
real_t norm_global = 0.0;
real_t norm_local = Y.Normlinf();
MPI_Allreduce(&norm_local, &norm_global, 1, MPI_DOUBLE, MPI_MAX,
pmesh.GetComm());
REQUIRE(norm_global == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
}
TEST_CASE("DFEM Diffusion Q1D", "[Parallel][DFEM][MQ1]")
{
// const bool all_tests = launch_all_non_regression_tests;
// const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
// const auto r = !all_tests ? 1 : GENERATE(0, 1, 2, 3);
const int p = 2, r = 1;
dbg("p:{} r:{}", p, r);
const auto mq1_setting = MQ1Settings::kCompileTime;
/*const auto mq1_setting = GENERATE(MQ1Settings::kRuntime,
MQ1Settings::kCompileTime,
MQ1Settings::kDefault);*/
DiffusionIntegrator::AddSpecialization<3,3,3>();
/*SECTION("2D p=" + std::to_string(p) + " r=" + std::to_string(r))
{
const auto filename =
GENERATE("../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/rt-2d-q3.mesh",
"../../data/inline-quad.mesh",
"../../data/periodic-square.mesh");
DFemDiffusion<2>(filename, p, r);
}*/
// SECTION("3D p=" + std::to_string(p) + " r=" + std::to_string(r))
{
#if 0
const auto filename =
GENERATE("../../data/fichera.mesh",
"../../data/fichera-q3.mesh",
"../../data/inline-hex.mesh",
"../../data/toroid-hex.mesh",
"../../data/periodic-cube.mesh");
#else
const auto filename = "../../data/fichera.mesh";
#endif
dbg("DFemDiffusion");
DFemDiffusion<3>(filename, p, r, mq1_setting);
}
}
} // namespace dfem_pa_kernels
#endif
-1
View File
@@ -11,7 +11,6 @@
#include "../unit_tests.hpp"
#include "mfem.hpp"
#include <fem/dfem/doperator.hpp>
#ifdef MFEM_USE_MPI
-1
View File
@@ -12,7 +12,6 @@
#include "../unit_tests.hpp"
#include "../linalg/test_same_matrices.hpp"
#include "mfem.hpp"
#include <fem/dfem/doperator.hpp>
#ifdef MFEM_USE_MPI
-383
View File
@@ -1,383 +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 "../unit_tests.hpp"
#include "mfem.hpp"
#include "../fem/dfem/doperator.hpp"
#include "../fem/dfem/backends/local_qf/prelude.hpp"
#include "linalg/tensor_arrays.hpp"
#include <proteus/JitInterface.h>
#ifdef MFEM_USE_MPI
using namespace mfem;
using namespace mfem::future;
using mfem::future::tensor;
#ifdef MFEM_USE_ENZYME
using dscalar_t = real_t;
#else
using mfem::future::dual;
using dscalar_t = dual<real_t, real_t>;
#endif
constexpr int DIM = 2;
class DummyParameterSpace : public ParameterSpace
{
public:
class Bimpl : public Operator
{
virtual void Mult(const Vector &x, Vector &y) const
{
for (int i = 0; i < y.Size(); i++)
{
y(i) = x(0);
}
}
};
class Btimpl : public Operator
{
virtual void Mult(const Vector &x, Vector &y) const
{
y(0) = x(0);
}
};
DummyParameterSpace() : ParameterSpace(1) {}
virtual int GetTrueVSize() const override
{
return 1;
}
virtual int GetVSize() const override
{
return 1;
}
virtual const Operator* GetB() const override
{
if (!B)
{
B.reset(new Bimpl());
}
return B.get();
}
virtual const Operator* GetBt() const override
{
if (!Bt)
{
Bt.reset(new Btimpl());
}
return Bt.get();
}
};
struct massqf
{
inline MFEM_HOST_DEVICE
void operator()(
tensor_array<const real_t> &u,
tensor_array<const real_t, DIM, DIM> &J,
tensor_array<const real_t> &w,
tensor_array<real_t> &out1,
tensor_array<real_t> &out2) const
{
for (size_t q = 0; q < u.size(); q++)
{
const auto v = u(q) * det(J(q)) * w(q);
out1(q) = v;
out2(q) = v;
}
}
};
struct mass_diffusion_qdata_qf
{
inline MFEM_HOST_DEVICE
void operator()(
tensor_array<const real_t> &u,
tensor_array<const real_t, DIM> &dudxi,
tensor_array<const real_t, DIM, DIM> &J,
tensor_array<const real_t, DIM, DIM> &qdata,
tensor_array<const real_t> &w,
tensor_array<const real_t> &dummy_parameter,
tensor_array<real_t> &out1,
tensor_array<real_t, DIM> &out2,
tensor_array<real_t, DIM, DIM> &out3) const
{
for (size_t q = 0; q < u.size(); q++)
{
const auto invJq = inv(J(q));
const auto detJq = det(J(q));
out1(q) = u(q) * detJq * w(q);
// out2(q) = (dudxi(q) * invJq) * transpose(invJq) * (detJq * w(q));
out3(q) = J(q);
}
jit_bounds(dudxi, J, w, out2, u.size());
}
// XXX: Attribute instrumentation does not work due to ABI differences that
// change the argument number.
//__attribute__((annotate("jit", 5)))
void jit_bounds(
tensor_array<const real_t, DIM> &dudxi,
tensor_array<const real_t, DIM, DIM> &J,
tensor_array<const real_t> &w,
tensor_array<real_t, DIM> &out,
size_t NQ) const
{
for (size_t q = 0; q < NQ; q++)
{
const auto invJq = inv(J(q));
const auto detJq = det(J(q));
out(q) = (dudxi(q) * invJq) * transpose(invJq) * (detJq * w(q));
}
}
};
TEST_CASE("dFEM Multiple Outputs", "[Parallel][dFEM]")
{
const bool all_tests = launch_all_non_regression_tests;
const auto p = !all_tests ? 2 : GENERATE(1, 2, 3);
const char *filename = "../../data/inline-quad.mesh";
CAPTURE(filename, DIM, p);
Mesh smesh(filename);
MFEM_ASSERT(smesh.Dimension() == DIM, "DIM and mesh dimension have to match");
ParMesh pmesh(MPI_COMM_WORLD, smesh);
pmesh.EnsureNodes();
auto* nodes = static_cast<ParGridFunction*>(pmesh.GetNodes());
smesh.Clear();
H1_FECollection fec(p, DIM);
ParFiniteElementSpace fes(&pmesh, &fec);
const auto *ir = &IntRules.Get(pmesh.GetTypicalElementGeometry(), 2 * p);
ParGridFunction x(&fes), y(&fes), z(&fes);
ConstantCoefficient one(1.0);
Array<int> all_domain_attr;
if (pmesh.attributes.Size() > 0)
{
all_domain_attr.SetSize(pmesh.attributes.Max());
all_domain_attr = 1;
}
// {
// Array<int> inoffsets(3);
// inoffsets[0] = 0;
// inoffsets[1] = fes.GetTrueVSize();
// inoffsets[2] = nodes->ParFESpace()->GetTrueVSize();
// inoffsets.PartialSum();
// BlockVector X(inoffsets);
// X.GetBlock(0).Randomize(1);
// X.GetBlock(1) = *nodes;
// x.SetFromTrueDofs(X.GetBlock(0));
// Array<int> outoffsets(2);
// outoffsets[0] = 0;
// outoffsets[1] = fes.GetTrueVSize();
// outoffsets.PartialSum();
// BlockVector Z(outoffsets);
// ParBilinearForm blf(&fes);
// blf.AddDomainIntegrator(new MassIntegrator(one, ir));
// blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
// blf.Assemble();
// blf.Mult(x, y);
// Vector Y(fes.GetTrueVSize());
// fes.GetProlongationMatrix()->MultTranspose(y, Y);
// static constexpr int U = 0, COORDINATES = 1, V = 2;
// const std::vector<FieldDescriptor> in
// {
// {U, &fes},
// {COORDINATES, nodes->ParFESpace()}
// };
// const std::vector<FieldDescriptor> out // test spaces?
// {
// {V, &fes},
// };
// DifferentiableOperator dop(in, out, pmesh);
// auto derivatives = std::integer_sequence<size_t, U> {};
// auto mass_qfunc = massqf{};
// dop.AddDomainIntegrator(mass_qfunc,
// tuple{ Value<U>{}, Gradient<COORDINATES>{}, Weight{} },
// tuple{ Value<V>{}, Value<V>{} },
// *ir, all_domain_attr, derivatives);
// fes.GetRestrictionMatrix()->Mult(x, X.GetBlock(0));
// dop.Mult(X, Z);
// Vector Y0(Y);
// Y0 *= 2.0;
// Y0 -= Z.GetBlock(0);
// real_t norm_g, norm_l = Y0.Normlinf();
// MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
// REQUIRE(norm_g == MFEM_Approx(0.0));
// MPI_Barrier(MPI_COMM_WORLD);
// auto ddop = dop.GetDerivative(U, X);
// ddop->Mult(X.GetBlock(0), Z);
// Y0 = Y;
// Y0 *= 2.0;
// Y0 -= Z.GetBlock(0);
// norm_l = Y0.Normlinf();
// MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
// REQUIRE(norm_g == MFEM_Approx(0.0));
// MPI_Barrier(MPI_COMM_WORLD);
// }
{
QuadratureSpace qs(pmesh, *ir);
QuadratureFunction qdata(qs, DIM*DIM);
DummyParameterSpace dps;
ParameterFunction dpf(dps);
dpf = 9.12345;
auto coef_func = [](const Vector &coords)
{
return coords[0] * coords[1] * (DIM == 3 ? coords[2] : 1.0);
};
FunctionCoefficient coef(coef_func);
x.ProjectCoefficient(coef);
Vector xtvec, ytvec, ytvecmfem;
x.GetTrueDofs(xtvec);
ytvec.SetSize(xtvec.Size());
ytvecmfem.SetSize(xtvec.Size());
Vector nodestvec;
nodes->GetTrueDofs(nodestvec);
qdata = 123.0;
Vector yqdata(qdata.Size());
MultiVector X{xtvec, nodestvec, qdata, dpf};
MultiVector Z{ytvec, yqdata};
ParBilinearForm blf(&fes);
blf.AddDomainIntegrator(new MassIntegrator(ir));
blf.AddDomainIntegrator(new DiffusionIntegrator(ir));
blf.SetAssemblyLevel(AssemblyLevel::PARTIAL);
blf.Assemble();
blf.Mult(x, y);
fes.GetProlongationMatrix()->MultTranspose(y, ytvecmfem);
std::cout << "mfem: ";
pretty_print(ytvecmfem);
static constexpr int U = 0, COORDINATES = 1, V = 2, S = 3, L = 4;
const std::vector<FieldDescriptor> in
{
{U, &fes},
{COORDINATES, nodes->ParFESpace()},
{S, &qdata},
{L, &dps}
};
const std::vector<FieldDescriptor> out
{
{V, &fes},
{S, &qdata}
};
{
DifferentiableOperator dop(in, out, pmesh);
dop.SetQLayouts({{Value<U>{}, {1, 0}}}, {});
auto derivatives = std::integer_sequence<size_t, U> {};
auto mass_diffusion_qfunc = mass_diffusion_qdata_qf{};
dop.AddDomainIntegrator(mass_diffusion_qfunc,
tuple{Value<U>{}, Gradient<U>{}, Gradient<COORDINATES>{}, Identity<S>{}, Weight{}, Value<L>{}},
tuple{Value<V>{}, Gradient<V>{}, Identity<S>{}},
*ir, all_domain_attr, derivatives);
fes.GetRestrictionMatrix()->Mult(x, xtvec);
dop.Mult(X, Z);
std::cout << "dfem: ";
pretty_print(Z[0]);
Vector Y0(ytvecmfem);
Y0 -= Z[0];
real_t norm_l = Y0.Normlinf();
real_t norm_g = norm_l;
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
auto ddop = dop.GetDerivative(U, X);
ddop->Mult(X[0], Z);
Y0 = ytvecmfem;
Y0 -= Z[0];
std::cout << "∂dfem: ";
pretty_print(Z[0]);
norm_l = Y0.Normlinf();
norm_g = norm_l;
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
{
DifferentiableOperator dop(in, out, pmesh);
dop.SetQLayouts({{Value<U>{}, {1, 0}}}, {});
auto mass_diffusion_qfunc = mass_diffusion_qdata_qf{};
dop.AddDomainIntegrator<LocalQFBackend>(
mass_diffusion_qfunc,
tuple{Value<U>{}, Gradient<U>{}, Gradient<COORDINATES>{}, Identity<S>{}, Weight{}, Value<L>{}},
tuple{Value<V>{}, Gradient<V>{}, Identity<S>{}},
*ir, all_domain_attr);
fes.GetRestrictionMatrix()->Mult(x, xtvec);
dop.Mult(X, Z);
std::cout << "dfem: ";
pretty_print(Z[0]);
Vector Y0(ytvecmfem);
Y0 -= Z[0];
real_t norm_l = Y0.Normlinf();
real_t norm_g = norm_l;
MPI_Allreduce(&norm_l, &norm_g, 1, MPI_DOUBLE, MPI_MAX, pmesh.GetComm());
REQUIRE(norm_g == MFEM_Approx(0.0));
MPI_Barrier(MPI_COMM_WORLD);
}
}
}
#endif // MFEM_USE_MPI
+2 -2
View File
@@ -962,9 +962,9 @@ TEST_CASE("Dispatch Map Specializations")
DiffusionIntegrator{};
REQUIRE_FALSE(
DiffusionIntegrator::DiffusionApplyPAKernel::GetDispatchTable().empty());
DiffusionIntegrator::ApplyPAKernels::GetDispatchTable().empty());
REQUIRE_FALSE(
DiffusionIntegrator::DiffusionDiagonalPAKernel::GetDispatchTable().empty());
DiffusionIntegrator::DiagonalPAKernels::GetDispatchTable().empty());
Mesh mesh = Mesh::MakeCartesian2D(2, 2, Element::QUADRILATERAL);
H1_FECollection fec(1, mesh.Dimension());