Compare commits
92
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7167757d39 | ||
|
|
60ef17a321 | ||
|
|
97272d60e8 | ||
|
|
bb9eb4872b | ||
|
|
41798f7028 | ||
|
|
633ee29601 | ||
|
|
bb194bbcd5 | ||
|
|
9d5977165f | ||
|
|
68fb8e80a9 | ||
|
|
274416caf5 | ||
|
|
5853221745 | ||
|
|
c79cccdc08 | ||
|
|
91f30e88c9 | ||
|
|
1432ac14a6 | ||
|
|
02cfe4ff0a | ||
|
|
447214ef57 | ||
|
|
fba6b4d832 | ||
|
|
12ec6db87c | ||
|
|
f117c0a4e7 | ||
|
|
7cd6686e42 | ||
|
|
4101b62099 | ||
|
|
eb0df95218 | ||
|
|
a828dbb5fe | ||
|
|
538ac3a6ab | ||
|
|
3c0d346d0d | ||
|
|
4914e93d0f | ||
|
|
6e63b81c94 | ||
|
|
edbaa5b3fd | ||
|
|
eb1cf52ebd | ||
|
|
03aa166858 | ||
|
|
37efde9e55 | ||
|
|
a17a1f1a2d | ||
|
|
0640727b77 | ||
|
|
01c0f4af68 | ||
|
|
8de1d37871 | ||
|
|
0359c410a9 | ||
|
|
d3028ea057 | ||
|
|
9f8e2f8bc7 | ||
|
|
2436fa67ca | ||
|
|
b4ba111505 | ||
|
|
7bf942176e | ||
|
|
4149fbc0d6 | ||
|
|
949d43f80e | ||
|
|
6be78c57e1 | ||
|
|
ca37dc2e67 | ||
|
|
eb7833533f | ||
|
|
9d0a7ac938 | ||
|
|
f34b48335a | ||
|
|
4e570ad11c | ||
|
|
4b09bc532f | ||
|
|
3ab4dd4194 | ||
|
|
ca3f53b37b | ||
|
|
799069babc | ||
|
|
a14b018692 | ||
|
|
c10ede8fba | ||
|
|
81dcca3e12 | ||
|
|
bdd237c7ea | ||
|
|
cacc93d8dc | ||
|
|
3b9c4b28ad | ||
|
|
3df5658695 | ||
|
|
8cc5feb057 | ||
|
|
84d4013a50 | ||
|
|
c507d3dc3d | ||
|
|
62d78501a3 | ||
|
|
5fbe7bd995 | ||
|
|
e4a8344e94 | ||
|
|
cb5f1830de | ||
|
|
d91e31458c | ||
|
|
7893c819a9 | ||
|
|
561daa66b6 | ||
|
|
cd1cacd1a6 | ||
|
|
2b94b8bd43 | ||
|
|
b6e4244806 | ||
|
|
59eaee6f13 | ||
|
|
9fd56d22a2 | ||
|
|
fc3babf85d | ||
|
|
1d0b509827 | ||
|
|
50b525907f | ||
|
|
5137ef5604 | ||
|
|
8f1d08dc93 | ||
|
|
e63f9887f4 | ||
|
|
2819c0e361 | ||
|
|
30b0ae9581 | ||
|
|
59a32942f2 | ||
|
|
90af7527d0 | ||
|
|
ac6ff275ba | ||
|
|
b78402803f | ||
|
|
6e5bb7e5e1 | ||
|
|
99a3803b4f | ||
|
|
c0495229fc | ||
|
|
adce9a5755 | ||
|
|
bb28ac7435 |
@@ -8,6 +8,24 @@
|
||||
https://mfem.org
|
||||
|
||||
|
||||
Version 4.2.1 (development)
|
||||
===========================
|
||||
|
||||
- Added interface to MUMPS direct solver. Its usage is demonstrated in ex25p.
|
||||
See http://mumps.enseeiht.fr/ for more details. Supported versions >= 5.1.1.
|
||||
|
||||
- Added three ESDIRK time integrators: implicit trapezoid rule, L-stable
|
||||
ESDIRK-32, and A-stable ESDIRK-33.
|
||||
|
||||
- Added a "scaled Jacobian" visualization option in the Mesh Explorer miniapp to
|
||||
help identify elements with poor mesh quality.
|
||||
|
||||
- Upgraded the Catch unit test framework from version 2.13.0 to version 2.13.2.
|
||||
|
||||
- Implemented a filter method for the Navier miniapp to stabilize highly
|
||||
turbulent flows in direct numerical simulation.
|
||||
|
||||
|
||||
Version 4.2, released on October 30, 2020
|
||||
=========================================
|
||||
|
||||
|
||||
+13
-4
@@ -14,7 +14,7 @@ set(USER_CONFIG "${CMAKE_CURRENT_SOURCE_DIR}/config/user.cmake" CACHE PATH
|
||||
"Path to optional user configuration file.")
|
||||
|
||||
# Require C++11 and disable compiler-specific extensions
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
@@ -50,7 +50,7 @@ project(mfem NONE)
|
||||
# Current version of MFEM, see also `makefile`.
|
||||
# mfem_VERSION = (string)
|
||||
# MFEM_VERSION = (int) [automatically derived from mfem_VERSION]
|
||||
set(${PROJECT_NAME}_VERSION 4.2.0)
|
||||
set(${PROJECT_NAME}_VERSION 4.2.1)
|
||||
|
||||
# Prohibit in-source build
|
||||
if (${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR})
|
||||
@@ -185,7 +185,7 @@ if (MFEM_USE_MPI)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
set(PKGS_NEED_MPI SUPERLU PETSC SLEPC STRUMPACK PUMI)
|
||||
set(PKGS_NEED_MPI SUPERLU MUMPS PETSC SLEPC STRUMPACK PUMI)
|
||||
foreach(PKG IN LISTS PKGS_NEED_MPI)
|
||||
if (MFEM_USE_${PKG})
|
||||
message(STATUS "Disabling package ${PKG} - requires MPI")
|
||||
@@ -264,6 +264,15 @@ if (MFEM_USE_SUPERLU)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# MUMPS can only be enabled in parallel
|
||||
if (MFEM_USE_MUMPS)
|
||||
if (MFEM_USE_MPI)
|
||||
find_package(MUMPS REQUIRED mumps_common pord)
|
||||
else()
|
||||
message(FATAL_ERROR " *** MUMPS requires that MPI be enabled.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# STRUMPACK can only be enabled in parallel
|
||||
if (MFEM_USE_STRUMPACK)
|
||||
if (MFEM_USE_MPI)
|
||||
@@ -382,7 +391,7 @@ endif()
|
||||
# integers, the METIS header (with 32-bit indices, as used by mfem) needs to
|
||||
# be before SuiteSparse.
|
||||
set(MFEM_TPLS MPI_CXX OPENMP BLAS LAPACK METIS HYPRE SuiteSparse SUNDIALS PETSC
|
||||
SLEPC MESQUITE SuperLUDist STRUMPACK AXOM CONDUIT Ginkgo GNUTLS GSLIB NETCDF
|
||||
SLEPC MESQUITE SuperLUDist MUMPS STRUMPACK AXOM CONDUIT Ginkgo GNUTLS GSLIB NETCDF
|
||||
MPFR PUMI HIOP POSIXCLOCKS MFEMBacktrace ZLIB OCCA CEED RAJA UMPIRE ADIOS2
|
||||
CUSPARSE MKL_CPARDISO AMGX)
|
||||
# Add all *_FOUND libraries in the variable TPL_LIBRARIES.
|
||||
|
||||
@@ -348,6 +348,12 @@ MFEM_USE_SUPERLU5 = YES/NO
|
||||
If SuperLU functionality is enabled, use the older 5.1.0 version rather than
|
||||
the more recent 6+ versions.
|
||||
|
||||
MFEM_USE_MUMPS = YES/NO
|
||||
Enable MFEM functionality based on the MUMPS library. Currently, this
|
||||
option adds the class MUMPSSolver (a parallel sparse direct solver).
|
||||
When enabled, this option uses the MUMPS_* library options, see
|
||||
below.
|
||||
|
||||
MFEM_USE_STRUMPACK = YES/NO
|
||||
Enable MFEM functionality based on the STRUMPACK sparse direct solver and
|
||||
preconditioner through the STRUMPACKSolver and STRUMPACKRowLocMatrix
|
||||
@@ -578,6 +584,12 @@ The specific libraries and their options are:
|
||||
Options: SUPERLU_OPT, SUPERLU_LIB.
|
||||
Versions: SuperLU_DIST >= 5.1.0.
|
||||
|
||||
- MUMPS (optional), used when MFEM_USE_MUMPS = YES. Note that MUMPS
|
||||
requires LAPACK, SCALAPACK and a reordering package such as PORD or METIS.
|
||||
URL: http://mumps.enseeiht.fr
|
||||
Options: MUMPS_OPT, MUMPS_LIB.
|
||||
Versions: MUMPS >= 5.1.1
|
||||
|
||||
- STRUMPACK (optional), used when MFEM_USE_STRUMPACK = YES. Note that STRUMPACK
|
||||
requires the PT-Scotch and Scalapack libraries as well as ParMETIS, which
|
||||
includes METIS 5 in its distribution. Starting with STRUMPACK v2.2.0, ParMETIS
|
||||
@@ -837,6 +849,7 @@ MFEM_TIMER_TYPE - Set automatically, can be overwritten.
|
||||
MFEM_USE_MESQUITE
|
||||
MFEM_USE_SUITESPARSE
|
||||
MFEM_USE_SUPERLU
|
||||
MFEM_USE_MUMPS
|
||||
MFEM_USE_STRUMPACK
|
||||
MFEM_USE_GINKGO
|
||||
MFEM_USE_AMGX
|
||||
|
||||
@@ -32,6 +32,7 @@ set(MFEM_USE_SUNDIALS @MFEM_USE_SUNDIALS@)
|
||||
set(MFEM_USE_MESQUITE @MFEM_USE_MESQUITE@)
|
||||
set(MFEM_USE_SUITESPARSE @MFEM_USE_SUITESPARSE@)
|
||||
set(MFEM_USE_SUPERLU @MFEM_USE_SUPERLU@)
|
||||
set(MFEM_USE_MUMPS @MFEM_USE_MUMPS@)
|
||||
set(MFEM_USE_STRUMPACK @MFEM_USE_STRUMPACK@)
|
||||
set(MFEM_USE_GINKGO @MFEM_USE_GINKGO@)
|
||||
set(MFEM_USE_AMGX @MFEM_USE_AMGX@)
|
||||
|
||||
@@ -86,6 +86,9 @@
|
||||
// Enable MFEM functionality based on the SuperLU_DIST library.
|
||||
#cmakedefine MFEM_USE_SUPERLU
|
||||
|
||||
// Enable MFEM functionality based on the MUMPS library.
|
||||
#cmakedefine MFEM_USE_MUMPS
|
||||
|
||||
// Enable MFEM functionality based on the STRUMPACK library.
|
||||
#cmakedefine MFEM_USE_STRUMPACK
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2010-2020, Lawrence Livermore National Security, LLC. Produced
|
||||
# at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
# LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
#
|
||||
# This file is part of the MFEM library. For more information and source code
|
||||
# availability visit https://mfem.org.
|
||||
#
|
||||
# MFEM is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
# Sets the following variables:
|
||||
# - MUMPS_FOUND
|
||||
# - MUMPS_INCLUDE_DIRS
|
||||
# - MUMPS_LIBRARIES
|
||||
|
||||
include(MfemCmakeUtilities)
|
||||
mfem_find_package(MUMPS MUMPS MUMPS_DIR
|
||||
"include" dmumps_c.h "lib" dmumps
|
||||
"Paths to headers required by MUMPS."
|
||||
"Libraries required by MUMPS."
|
||||
ADD_COMPONENT mumps_common "include" dmumps_c.h "lib" mumps_common
|
||||
ADD_COMPONENT pord "include" dmumps_c.h "lib" pord)
|
||||
@@ -42,6 +42,9 @@
|
||||
#ifdef MFEM_USE_SUPERLU
|
||||
#error Building with SuperLU_DIST (MFEM_USE_SUPERLU=YES) requires MPI (MFEM_USE_MPI=YES)
|
||||
#endif
|
||||
#ifdef MFEM_USE_MUMPS
|
||||
#error Building with MUMPS (MFEM_USE_MUMPS=YES) requires MPI (MFEM_USE_MPI=YES)
|
||||
#endif
|
||||
#ifdef MFEM_USE_STRUMPACK
|
||||
#error Building with STRUMPACK (MFEM_USE_STRUMPACK=YES) requires MPI (MFEM_USE_MPI=YES)
|
||||
#endif
|
||||
|
||||
@@ -95,6 +95,10 @@
|
||||
// #define MFEM_USE_SUPERLU
|
||||
// #define MFEM_USE_SUPERLU5
|
||||
|
||||
// Enable MFEM functionality based on the MUMPS library.
|
||||
// #define MFEM_USE_MUMPS
|
||||
// #define MFEM_MUMPS_VERSION @MFEM_MUMPS_VERSION@
|
||||
|
||||
// Enable MFEM functionality based on the STRUMPACK library.
|
||||
// #define MFEM_USE_STRUMPACK
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ MFEM_USE_MESQUITE = @MFEM_USE_MESQUITE@
|
||||
MFEM_USE_SUITESPARSE = @MFEM_USE_SUITESPARSE@
|
||||
MFEM_USE_SUPERLU = @MFEM_USE_SUPERLU@
|
||||
MFEM_USE_SUPERLU5 = @MFEM_USE_SUPERLU5@
|
||||
MFEM_USE_MUMPS = @MFEM_USE_MUMPS@
|
||||
MFEM_USE_STRUMPACK = @MFEM_USE_STRUMPACK@
|
||||
MFEM_USE_GINKGO = @MFEM_USE_GINKGO@
|
||||
MFEM_USE_AMGX = @MFEM_USE_AMGX@
|
||||
|
||||
@@ -34,6 +34,7 @@ option(MFEM_USE_MESQUITE "Enable MESQUITE usage" OFF)
|
||||
option(MFEM_USE_SUITESPARSE "Enable SuiteSparse usage" OFF)
|
||||
option(MFEM_USE_SUPERLU "Enable SuperLU_DIST usage" OFF)
|
||||
option(MFEM_USE_SUPERLU5 "Use the old SuperLU_DIST 5.1 version" OFF)
|
||||
option(MFEM_USE_MUMPS "Enable MUMPS usage" OFF)
|
||||
option(MFEM_USE_STRUMPACK "Enable STRUMPACK usage" OFF)
|
||||
option(MFEM_USE_GINKGO "Enable Ginkgo usage" OFF)
|
||||
option(MFEM_USE_AMGX "Enable AmgX usage" OFF)
|
||||
@@ -118,6 +119,15 @@ set(SuperLUDist_DIR "${MFEM_DIR}/../SuperLU_DIST_6.3.1" CACHE PATH
|
||||
set(SuperLUDist_REQUIRED_PACKAGES "MPI" "BLAS" "ParMETIS" CACHE STRING
|
||||
"Additional packages required by SuperLU_DIST.")
|
||||
|
||||
set(MUMPS_DIR "${MFEM_DIR}/../MUMPS_5.2.0" CACHE PATH
|
||||
"Path to the MUMPS library.")
|
||||
# Packages required by MUMPS, depending on how it was compiled.
|
||||
set(MUMPS_REQUIRED_PACKAGES "MPI" "BLAS" "METIS" "ScaLAPACK" CACHE STRING
|
||||
"Additional packages required by MUMPS.")
|
||||
# If the MPI package does not find all required Fortran libraries:
|
||||
# set(MUMPS_REQUIRED_LIBRARIES "gfortran" "mpi_mpifh" CACHE STRING
|
||||
# "Additional libraries required by MUMPS.")
|
||||
|
||||
set(STRUMPACK_DIR "${MFEM_DIR}/../STRUMPACK-build" CACHE PATH
|
||||
"Path to the STRUMPACK library.")
|
||||
# STRUMPACK may also depend on "OpenMP", depending on how it was compiled.
|
||||
|
||||
+10
-3
@@ -123,6 +123,7 @@ MFEM_USE_MESQUITE = NO
|
||||
MFEM_USE_SUITESPARSE = NO
|
||||
MFEM_USE_SUPERLU = NO
|
||||
MFEM_USE_SUPERLU5 = NO
|
||||
MFEM_USE_MUMPS = NO
|
||||
MFEM_USE_STRUMPACK = NO
|
||||
MFEM_USE_GINKGO = NO
|
||||
MFEM_USE_AMGX = NO
|
||||
@@ -171,7 +172,7 @@ HYPRE_OPT = -I$(HYPRE_DIR)/include
|
||||
HYPRE_LIB = -L$(HYPRE_DIR)/lib -lHYPRE
|
||||
|
||||
# METIS library configuration
|
||||
ifeq ($(MFEM_USE_SUPERLU)$(MFEM_USE_STRUMPACK),NONO)
|
||||
ifeq ($(MFEM_USE_SUPERLU)$(MFEM_USE_STRUMPACK)$(MFEM_USE_MUMPS),NONONO)
|
||||
ifeq ($(MFEM_USE_METIS_5),NO)
|
||||
METIS_DIR = @MFEM_DIR@/../metis-4.0
|
||||
METIS_OPT =
|
||||
@@ -252,12 +253,12 @@ SCOTCH_OPT = -I$(SCOTCH_DIR)/include
|
||||
SCOTCH_LIB = -L$(SCOTCH_DIR)/lib -lptscotch -lptscotcherr -lscotch -lscotcherr\
|
||||
-lpthread
|
||||
|
||||
# SCALAPACK library configuration (required by STRUMPACK)
|
||||
# SCALAPACK library configuration (required by STRUMPACK and MUMPS)
|
||||
SCALAPACK_DIR = @MFEM_DIR@/../scalapack-2.0.2
|
||||
SCALAPACK_OPT = -I$(SCALAPACK_DIR)/SRC
|
||||
SCALAPACK_LIB = -L$(SCALAPACK_DIR)/lib -lscalapack $(LAPACK_LIB)
|
||||
|
||||
# MPI Fortran library, needed e.g. by STRUMPACK
|
||||
# MPI Fortran library, needed e.g. by STRUMPACK or MUMPS
|
||||
# MPICH:
|
||||
MPI_FORTRAN_LIB = -lmpifort
|
||||
# OpenMPI:
|
||||
@@ -265,6 +266,12 @@ MPI_FORTRAN_LIB = -lmpifort
|
||||
# Additional Fortan library:
|
||||
# MPI_FORTRAN_LIB += -lgfortran
|
||||
|
||||
# MUMPS library configuration
|
||||
MUMPS_DIR = @MFEM_DIR@/../MUMPS_5.2.0
|
||||
MUMPS_OPT = -I$(MUMPS_DIR)/include
|
||||
MUMPS_LIB = -Wl,-rpath,$(MUMPS_DIR)/lib -L$(MUMPS_DIR)/lib -ldmumps\
|
||||
-lmumps_common -lpord $(SCALAPACK_LIB) $(LAPACK_LIB) $(MPI_FORTRAN_LIB)
|
||||
|
||||
# STRUMPACK library configuration
|
||||
STRUMPACK_DIR = @MFEM_DIR@/../STRUMPACK-build
|
||||
STRUMPACK_OPT = -I$(STRUMPACK_DIR)/include $(SCOTCH_OPT)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2010-2020, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#include "dmumps_c.h"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
// Macros to expand a macro as a string
|
||||
#define STR_EXPAND(s) #s
|
||||
#define STR(s) STR_EXPAND(s)
|
||||
|
||||
int main()
|
||||
{
|
||||
#ifdef MUMPS_VERSION
|
||||
const char *ptr = STR(MUMPS_VERSION);
|
||||
std::string s(ptr);
|
||||
s.erase(std::remove(s.begin(), s.end(), '"'), s.end());
|
||||
s.erase(std::remove(s.begin(), s.end(), '.'), s.end());
|
||||
std::cout << s << "\n";
|
||||
return 0;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
+21
-2
@@ -42,6 +42,10 @@ GHV_FLAGS = $(subst @MFEM_DIR@,$(if $(MFEM_DIR),$(MFEM_DIR),..),$(HYPRE_OPT))
|
||||
SMX = $(if $(MFEM_USE_PUMI:NO=),MFEM_USE_SIMMETRIX)
|
||||
SMX_PATH = $(PUMI_DIR)/include/gmi_sim.h
|
||||
SMX_FILE = $(subst @MFEM_DIR@,$(if $(MFEM_DIR),$(MFEM_DIR),..),$(SMX_PATH))
|
||||
MUMPS = $(MFEM_USE_MUMPS:NO=)
|
||||
GMV_CXX ?= $(MFEM_CXX)
|
||||
GMV = get_mumps_version
|
||||
GMV_FLAGS = $(subst @MFEM_DIR@,$(if $(MFEM_DIR),$(MFEM_DIR),..),$(MUMPS_OPT))
|
||||
|
||||
$(GHV): $(SRC)$(GHV).cpp
|
||||
$(call mfem-info, Determining HYPRE version ...)
|
||||
@@ -50,6 +54,13 @@ $(GHV).out: $(GHV)
|
||||
./$(GHV) > $(GHV).out
|
||||
.INTERMEDIATE: $(GHV) $(GHV).out
|
||||
|
||||
$(GMV): $(SRC)$(GMV).cpp
|
||||
$(call mfem-info, Determining MUMPS version ...)
|
||||
$(GMV_CXX) ${GMV_FLAGS} $(SRC)$(GMV).cpp -o $(GMV)
|
||||
$(GMV).out: $(GMV)
|
||||
./$(GMV) > $(GMV).out
|
||||
.INTERMEDIATE: $(GMV) $(GMV).out
|
||||
|
||||
get-hypre-version: $(GHV).out
|
||||
$(eval MFEM_HYPRE_VERSION:=$(shell cat $(GHV).out))
|
||||
$(if $(MFEM_HYPRE_VERSION),$(eval export MFEM_HYPRE_VERSION)\
|
||||
@@ -62,10 +73,18 @@ check-smx:
|
||||
$(call mfem-info, MFEM_USE_SIMMETRIX = $(MFEM_USE_SIMMETRIX))
|
||||
$(eval export MFEM_USE_SIMMETRIX)
|
||||
|
||||
header: $(if $(MPI),get-hypre-version,) $(if $(SMX),check-smx)
|
||||
get-mumps-version: $(GMV).out
|
||||
$(eval MFEM_MUMPS_VERSION:=$(shell cat $(GMV).out))
|
||||
$(if $(MFEM_MUMPS_VERSION),$(eval export MFEM_MUMPS_VERSION)\
|
||||
$(info MUMPS version: $(MFEM_MUMPS_VERSION)),\
|
||||
$(error Unable to determine MUMPS version))
|
||||
|
||||
header: $(if $(MPI),get-hypre-version,) $(if $(SMX),check-smx,) \
|
||||
$(if $(MUMPS),get-mumps-version,)
|
||||
$(call mfem-info, Writing $(CONFIG_HPP) ...)
|
||||
@set -- && \
|
||||
for def in $${MFEM_DEFINES} $(if $(MPI),MFEM_HYPRE_VERSION) $(SMX); do \
|
||||
for def in $${MFEM_DEFINES} $(if $(MPI),MFEM_HYPRE_VERSION) $(SMX) \
|
||||
$(if $(MUMPS),MFEM_MUMPS_VERSION); do \
|
||||
eval var=\$$$$def && \
|
||||
if [ "NO" != "$${var}" ]; then \
|
||||
set -- "$$@" -e "s|// \(#define $${def} \)|\1|" && \
|
||||
|
||||
@@ -38,7 +38,7 @@ PROJECT_NAME = "MFEM"
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = v4.2.0
|
||||
PROJECT_NUMBER = v4.2.1
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
+33
-2
@@ -164,6 +164,8 @@ int main(int argc, char *argv[])
|
||||
int iprob = 4;
|
||||
double freq = 5.0;
|
||||
bool herm_conv = true;
|
||||
bool slu_solver = false;
|
||||
bool mumps_solver = false;
|
||||
bool visualization = 1;
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
@@ -185,10 +187,26 @@ int main(int argc, char *argv[])
|
||||
"Frequency (in Hz).");
|
||||
args.AddOption(&herm_conv, "-herm", "--hermitian", "-no-herm",
|
||||
"--no-hermitian", "Use convention for Hermitian operators.");
|
||||
#ifdef MFEM_USE_SUPERLU
|
||||
args.AddOption(&slu_solver, "-slu", "--superlu", "-no-slu",
|
||||
"--no-superlu", "Use the SuperLU Solver.");
|
||||
#endif
|
||||
#ifdef MFEM_USE_MUMPS
|
||||
args.AddOption(&mumps_solver, "-mumps", "--mumps-solver", "-no-mumps",
|
||||
"--no-mumps-solver", "Use the MUMPS Solver.");
|
||||
#endif
|
||||
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.Parse();
|
||||
if (slu_solver && mumps_solver)
|
||||
{
|
||||
if (myid == 0)
|
||||
cout << "WARNING: Both SuperLU and MUMPS have been selected,"
|
||||
<< " please choose either one." << endl
|
||||
<< " Defaulting to SuperLU." << endl;
|
||||
mumps_solver = false;
|
||||
}
|
||||
|
||||
if (iprob > 4) { iprob = 4; }
|
||||
prob = (prob_type)iprob;
|
||||
@@ -430,6 +448,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
// 15. Solve using a direct or an iterative solver
|
||||
#ifdef MFEM_USE_SUPERLU
|
||||
if (slu_solver)
|
||||
{
|
||||
// Transform to monolithic HypreParMatrix
|
||||
HypreParMatrix *A = Ah.As<ComplexHypreParMatrix>()->GetSystemMatrix();
|
||||
@@ -442,7 +461,19 @@ int main(int argc, char *argv[])
|
||||
superlu.Mult(B, X);
|
||||
delete A;
|
||||
}
|
||||
#else
|
||||
#endif
|
||||
#ifdef MFEM_USE_MUMPS
|
||||
if (mumps_solver)
|
||||
{
|
||||
HypreParMatrix *A = Ah.As<ComplexHypreParMatrix>()->GetSystemMatrix();
|
||||
MUMPSSolver mumps;
|
||||
mumps.SetPrintLevel(0);
|
||||
mumps.SetMatrixSymType(MUMPSSolver::MatType::UNSYMMETRIC);
|
||||
mumps.SetOperator(*A);
|
||||
mumps.Mult(B,X);
|
||||
delete A;
|
||||
}
|
||||
#endif
|
||||
// 16a. Set up the parallel Bilinear form a(.,.) for the preconditioner
|
||||
//
|
||||
// In Comp
|
||||
@@ -450,6 +481,7 @@ int main(int argc, char *argv[])
|
||||
//
|
||||
// In PML: 1/mu (abs(1/det(J) J^T J) Curl E, Curl F)
|
||||
// + omega^2 * epsilon (abs(det(J) * (J^T J)^-1) * E, F)
|
||||
if (!slu_solver && !mumps_solver)
|
||||
{
|
||||
ConstantCoefficient absomeg(pow(omega, 2) * epsilon);
|
||||
RestrictedCoefficient restr_absomeg(absomeg,attr);
|
||||
@@ -499,7 +531,6 @@ int main(int argc, char *argv[])
|
||||
gmres.SetPreconditioner(BlockAMS);
|
||||
gmres.Mult(B, X);
|
||||
}
|
||||
#endif
|
||||
|
||||
// 17. Recover the parallel grid function corresponding to X. This is the
|
||||
// local finite element solution on each processor.
|
||||
|
||||
@@ -83,6 +83,11 @@ if (MFEM_USE_SUPERLU)
|
||||
list(APPEND HDRS superlu.hpp)
|
||||
endif()
|
||||
|
||||
if (MFEM_USE_MUMPS)
|
||||
list(APPEND SRCS mumps.cpp)
|
||||
list(APPEND HDRS mumps.hpp)
|
||||
endif()
|
||||
|
||||
if (MFEM_USE_STRUMPACK)
|
||||
list(APPEND SRCS strumpack.cpp)
|
||||
list(APPEND HDRS strumpack.hpp)
|
||||
|
||||
@@ -49,6 +49,10 @@
|
||||
#include "hypre_parcsr.hpp"
|
||||
#include "hypre.hpp"
|
||||
|
||||
#ifdef MFEM_USE_MUMPS
|
||||
#include "mumps.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef MFEM_USE_PETSC
|
||||
#include "petsc.hpp"
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,422 @@
|
||||
// Copyright (c) 2010-2020, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#include "../config/config.hpp"
|
||||
|
||||
#ifdef MFEM_USE_MUMPS
|
||||
#ifdef MFEM_USE_MPI
|
||||
|
||||
#include "mumps.hpp"
|
||||
|
||||
#ifdef HYPRE_BIGINT
|
||||
#error "MUMPSSolver requires HYPRE_Int == int, for now."
|
||||
#endif
|
||||
|
||||
// macro s.t. indices match MUMPS documentation
|
||||
#define MUMPS_ICNTL(I) icntl[(I) -1]
|
||||
#define MUMPS_INFO(I) info[(I) -1]
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
void MUMPSSolver::SetOperator(const Operator &op)
|
||||
{
|
||||
auto APtr = dynamic_cast<const HypreParMatrix *>(&op);
|
||||
|
||||
MFEM_VERIFY(APtr, "Not compatible matrix type");
|
||||
|
||||
height = op.Height();
|
||||
width = op.Width();
|
||||
|
||||
comm = APtr->GetComm();
|
||||
MPI_Comm_size(comm, &numProcs);
|
||||
MPI_Comm_rank(comm, &myid);
|
||||
|
||||
auto parcsr_op = (hypre_ParCSRMatrix *) const_cast<HypreParMatrix &>(*APtr);
|
||||
|
||||
hypre_CSRMatrix *csr_op = hypre_MergeDiagAndOffd(parcsr_op);
|
||||
#if MFEM_HYPRE_VERSION >= 21600
|
||||
hypre_CSRMatrixBigJtoJ(csr_op);
|
||||
#endif
|
||||
|
||||
int *Iptr = csr_op->i;
|
||||
int *Jptr = csr_op->j;
|
||||
int n_loc = csr_op->num_rows;
|
||||
|
||||
row_start = parcsr_op->first_row_index;
|
||||
|
||||
MUMPS_INT8 nnz = 0;
|
||||
if (mat_type)
|
||||
{
|
||||
// count nnz in case of symmetric mode
|
||||
int k = 0;
|
||||
for (int i = 0; i < n_loc; i++)
|
||||
{
|
||||
for (int j = Iptr[i]; j < Iptr[i + 1]; j++)
|
||||
{
|
||||
int ii = row_start + i + 1;
|
||||
int jj = Jptr[k] + 1;
|
||||
k++;
|
||||
if (ii>=jj) { nnz++; }
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nnz = csr_op->num_nonzeros;
|
||||
}
|
||||
|
||||
int * I = new int[nnz];
|
||||
int * J = new int[nnz];
|
||||
|
||||
// Fill in I and J arrays for
|
||||
// COO format in 1-based indexing
|
||||
int k = 0;
|
||||
double * data;
|
||||
if (mat_type)
|
||||
{
|
||||
int l = 0;
|
||||
data = new double[nnz];
|
||||
for (int i = 0; i < n_loc; i++)
|
||||
{
|
||||
for (int j = Iptr[i]; j < Iptr[i + 1]; j++)
|
||||
{
|
||||
int ii = row_start + i + 1;
|
||||
int jj = Jptr[k] + 1;
|
||||
if (ii >= jj)
|
||||
{
|
||||
I[l] = ii;
|
||||
J[l] = jj;
|
||||
data[l++] = csr_op->data[k];
|
||||
}
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < n_loc; i++)
|
||||
{
|
||||
for (int j = Iptr[i]; j < Iptr[i + 1]; j++)
|
||||
{
|
||||
I[k] = row_start + i + 1;
|
||||
J[k] = Jptr[k] + 1;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
data = csr_op->data;
|
||||
}
|
||||
|
||||
// new MUMPS object
|
||||
if (id)
|
||||
{
|
||||
id->job = -2;
|
||||
dmumps_c(id);
|
||||
delete id;
|
||||
}
|
||||
id = new DMUMPS_STRUC_C;
|
||||
// C to Fortran communicator
|
||||
id->comm_fortran = (MUMPS_INT) MPI_Comm_c2f(comm);
|
||||
|
||||
// Host is involved in computation
|
||||
id->par = 1;
|
||||
|
||||
id->sym = mat_type;
|
||||
|
||||
// MUMPS init
|
||||
id->job = -1;
|
||||
dmumps_c(id);
|
||||
|
||||
// Set MUMPS default parameters
|
||||
SetParameters();
|
||||
|
||||
id->n = parcsr_op->global_num_rows;
|
||||
|
||||
id->nnz_loc = nnz;
|
||||
|
||||
id->irn_loc = I;
|
||||
|
||||
id->jcn_loc = J;
|
||||
|
||||
id->a_loc = data;
|
||||
|
||||
// MUMPS Analysis
|
||||
id->job = 1;
|
||||
dmumps_c(id);
|
||||
|
||||
// MUMPS Factorization
|
||||
id->job = 2;
|
||||
dmumps_c(id);
|
||||
|
||||
hypre_CSRMatrixDestroy(csr_op);
|
||||
delete [] I;
|
||||
delete [] J;
|
||||
if (mat_type) { delete [] data; }
|
||||
|
||||
#if MFEM_MUMPS_VERSION >= 530
|
||||
delete [] irhs_loc;
|
||||
irhs_loc = new int[n_loc];
|
||||
for (int i = 0; i < n_loc; i++)
|
||||
{
|
||||
irhs_loc[i] = row_start + i + 1;
|
||||
}
|
||||
row_starts.SetSize(numProcs);
|
||||
MPI_Allgather(&row_start, 1, MPI_INT, row_starts, 1, MPI_INT, comm);
|
||||
#else
|
||||
if (myid == 0)
|
||||
{
|
||||
delete [] rhs_glob;
|
||||
delete [] recv_counts;
|
||||
rhs_glob = new double[parcsr_op->global_num_rows];
|
||||
recv_counts = new int[numProcs];
|
||||
}
|
||||
MPI_Gather(&n_loc, 1, MPI_INT, recv_counts, 1, MPI_INT, 0, comm);
|
||||
if (myid == 0)
|
||||
{
|
||||
delete [] displs;
|
||||
displs = new int[numProcs];
|
||||
displs[0] = 0;
|
||||
int s = 0;
|
||||
for (int k = 0; k < numProcs-1; k++)
|
||||
{
|
||||
s += recv_counts[k];
|
||||
displs[k+1] = s;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MUMPSSolver::Mult(const Vector &x, Vector &y) const
|
||||
{
|
||||
#if MFEM_MUMPS_VERSION >= 530
|
||||
|
||||
id->nloc_rhs = x.Size();
|
||||
id->lrhs_loc = x.Size();
|
||||
id->rhs_loc = x.GetData();
|
||||
id->irhs_loc = irhs_loc;
|
||||
|
||||
id->lsol_loc = id->MUMPS_INFO(23);
|
||||
id->isol_loc = new int[id->MUMPS_INFO(23)];
|
||||
id->sol_loc = new double[id->MUMPS_INFO(23)];
|
||||
|
||||
// MUMPS solve
|
||||
id->job = 3;
|
||||
dmumps_c(id);
|
||||
|
||||
RedistributeSol(id->isol_loc, id->sol_loc, y.GetData());
|
||||
|
||||
delete [] id->sol_loc;
|
||||
delete [] id->isol_loc;
|
||||
#else
|
||||
MPI_Gatherv(x.GetData(), x.Size(), MPI_DOUBLE,
|
||||
rhs_glob, recv_counts,
|
||||
displs, MPI_DOUBLE, 0, comm);
|
||||
|
||||
if (myid == 0) { id->rhs = rhs_glob; }
|
||||
|
||||
// MUMPS solve
|
||||
id->job = 3;
|
||||
dmumps_c(id);
|
||||
|
||||
MPI_Scatterv(rhs_glob, recv_counts, displs,
|
||||
MPI_DOUBLE, y.GetData(), y.Size(),
|
||||
MPI_DOUBLE, 0, comm);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MUMPSSolver::MultTranspose(const Vector &x, Vector &y) const
|
||||
{
|
||||
// Set flag for Transpose Solve
|
||||
id->MUMPS_ICNTL(9) = 0;
|
||||
Mult(x,y);
|
||||
// Reset the flag
|
||||
id->MUMPS_ICNTL(9) = 1;
|
||||
|
||||
}
|
||||
|
||||
void MUMPSSolver::SetPrintLevel(int print_lvl)
|
||||
{
|
||||
print_level = print_lvl;
|
||||
}
|
||||
|
||||
void MUMPSSolver::SetMatrixSymType(MatType mtype)
|
||||
{
|
||||
mat_type = mtype;
|
||||
}
|
||||
|
||||
MUMPSSolver::~MUMPSSolver()
|
||||
{
|
||||
if (id)
|
||||
{
|
||||
#if MFEM_MUMPS_VERSION >= 530
|
||||
delete [] irhs_loc;
|
||||
#else
|
||||
delete [] recv_counts;
|
||||
delete [] displs;
|
||||
delete [] rhs_glob;
|
||||
#endif
|
||||
id->job = -2;
|
||||
dmumps_c(id);
|
||||
delete id;
|
||||
}
|
||||
}
|
||||
|
||||
void MUMPSSolver::SetParameters()
|
||||
{
|
||||
// output stream for error messages
|
||||
id->MUMPS_ICNTL(1) = 6;
|
||||
// output stream for diagnosting printing local to each proc
|
||||
id->MUMPS_ICNTL(2) = 6;
|
||||
// output stream for global info
|
||||
id->MUMPS_ICNTL(3) = 6;
|
||||
// Level of error printing
|
||||
id->MUMPS_ICNTL(4) = print_level;
|
||||
//input matrix format (assembled)
|
||||
id->MUMPS_ICNTL(5) = 0;
|
||||
// Use A or A^T
|
||||
id->MUMPS_ICNTL(9) = 1;
|
||||
// Iterative refinement (disabled)
|
||||
id->MUMPS_ICNTL(10) = 0;
|
||||
// Error analysis-statistics (disabled)
|
||||
id->MUMPS_ICNTL(11) = 0;
|
||||
// Use of ScaLAPACK (Parallel factorization on root)
|
||||
id->MUMPS_ICNTL(13) = 0;
|
||||
// Percentage increase of estimated workspace (default = 20%)
|
||||
id->MUMPS_ICNTL(14) = 20;
|
||||
// Number of OpenMP threads (default)
|
||||
id->MUMPS_ICNTL(16) = 0;
|
||||
// Matrix input format (distributed)
|
||||
id->MUMPS_ICNTL(18) = 3;
|
||||
// Schur complement (no Schur complement matrix returned)
|
||||
id->MUMPS_ICNTL(19) = 0;
|
||||
|
||||
#if MFEM_MUMPS_VERSION >= 530
|
||||
// Distributed RHS
|
||||
id->MUMPS_ICNTL(20) = 10;
|
||||
// Distributed Sol
|
||||
id->MUMPS_ICNTL(21) = 1;
|
||||
#else
|
||||
// Centralized RHS
|
||||
id->MUMPS_ICNTL(20) = 0;
|
||||
// Centralized Sol
|
||||
id->MUMPS_ICNTL(21) = 0;
|
||||
#endif
|
||||
// Out of core factorization and solve (disabled)
|
||||
id->MUMPS_ICNTL(22) = 0;
|
||||
// Max size of working memory (default = based on estimates)
|
||||
id->MUMPS_ICNTL(23) = 0;
|
||||
}
|
||||
|
||||
#if MFEM_MUMPS_VERSION >= 530
|
||||
int MUMPSSolver::GetRowRank(int i, const Array<int> &row_starts_) const
|
||||
{
|
||||
if (row_starts_.Size() == 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
auto up = std::upper_bound(row_starts_.begin(), row_starts_.end(), i);
|
||||
return std::distance(row_starts_.begin(), up) - 1;
|
||||
}
|
||||
|
||||
void MUMPSSolver::RedistributeSol(const int * row_map,
|
||||
const double * x, double * y) const
|
||||
{
|
||||
int size = id->MUMPS_INFO(23);
|
||||
int * send_count = new int[numProcs]();
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
int j = row_map[i] - 1;
|
||||
int row_rank = GetRowRank(j, row_starts);
|
||||
if (myid == row_rank) { continue; }
|
||||
send_count[row_rank]++;
|
||||
}
|
||||
|
||||
int * recv_count = new int[numProcs];
|
||||
MPI_Alltoall(send_count, 1, MPI_INT, recv_count, 1, MPI_INT, comm);
|
||||
|
||||
int * send_displ = new int [numProcs]; send_displ[0] = 0;
|
||||
int * recv_displ = new int [numProcs]; recv_displ[0] = 0;
|
||||
int sbuff_size = send_count[numProcs-1];
|
||||
int rbuff_size = recv_count[numProcs-1];
|
||||
for (int k = 0; k < numProcs - 1; k++)
|
||||
{
|
||||
send_displ[k + 1] = send_displ[k] + send_count[k];
|
||||
recv_displ[k + 1] = recv_displ[k] + recv_count[k];
|
||||
sbuff_size += send_count[k];
|
||||
rbuff_size += recv_count[k];
|
||||
}
|
||||
|
||||
int * sendbuf_index = new int[sbuff_size];
|
||||
double * sendbuf_values = new double[sbuff_size];
|
||||
int * soffs = new int[numProcs]();
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
int j = row_map[i] - 1;
|
||||
int row_rank = GetRowRank(j, row_starts);
|
||||
if (myid == row_rank)
|
||||
{
|
||||
int local_index = j - row_start;
|
||||
y[local_index] = x[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
int k = send_displ[row_rank] + soffs[row_rank];
|
||||
sendbuf_index[k] = j;
|
||||
sendbuf_values[k] = x[i];
|
||||
soffs[row_rank]++;
|
||||
}
|
||||
}
|
||||
|
||||
int * recvbuf_index = new int[rbuff_size];
|
||||
double * recvbuf_values = new double[rbuff_size];
|
||||
MPI_Alltoallv(sendbuf_index,
|
||||
send_count,
|
||||
send_displ,
|
||||
MPI_INT,
|
||||
recvbuf_index,
|
||||
recv_count,
|
||||
recv_displ,
|
||||
MPI_INT,
|
||||
comm);
|
||||
MPI_Alltoallv(sendbuf_values,
|
||||
send_count,
|
||||
send_displ,
|
||||
MPI_DOUBLE,
|
||||
recvbuf_values,
|
||||
recv_count,
|
||||
recv_displ,
|
||||
MPI_DOUBLE,
|
||||
comm);
|
||||
|
||||
// Unpack recv buffer
|
||||
for (int i = 0; i < rbuff_size; i++)
|
||||
{
|
||||
int local_index = recvbuf_index[i] - row_start;
|
||||
y[local_index] = recvbuf_values[i];
|
||||
}
|
||||
|
||||
delete [] recvbuf_values;
|
||||
delete [] recvbuf_index;
|
||||
delete [] soffs;
|
||||
delete [] sendbuf_values;
|
||||
delete [] sendbuf_index;
|
||||
delete [] recv_displ;
|
||||
delete [] send_displ;
|
||||
delete [] recv_count;
|
||||
delete [] send_count;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
#endif // MFEM_USE_MPI
|
||||
#endif // MFEM_USE_MUMPS
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2010-2020, Lawrence Livermore National Security, LLC. Produced
|
||||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||||
//
|
||||
// This file is part of the MFEM library. For more information and source code
|
||||
// availability visit https://mfem.org.
|
||||
//
|
||||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||||
// CONTRIBUTING.md for details.
|
||||
|
||||
#ifndef MFEM_MUMPS
|
||||
#define MFEM_MUMPS
|
||||
|
||||
#include "../config/config.hpp"
|
||||
|
||||
#ifdef MFEM_USE_MUMPS
|
||||
#ifdef MFEM_USE_MPI
|
||||
#include "operator.hpp"
|
||||
#include "hypre.hpp"
|
||||
|
||||
#include <mpi.h>
|
||||
#include "dmumps_c.h"
|
||||
#include <vector>
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief MUMPS: A Parallel Sparse Direct Solver
|
||||
*
|
||||
* Interface for the distributed MUMPS solver
|
||||
*/
|
||||
class MUMPSSolver : public mfem::Solver
|
||||
{
|
||||
public:
|
||||
enum MatType
|
||||
{
|
||||
UNSYMMETRIC = 0,
|
||||
SYMMETRIC_INDEFINITE = 1,
|
||||
SYMMETRIC_POSITIVE_DEFINITE = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Default Constructor
|
||||
*/
|
||||
MUMPSSolver() {}
|
||||
|
||||
/**
|
||||
* @brief Set the Operator and perform factorization
|
||||
*
|
||||
* @a op needs to be of type HypreParMatrix.
|
||||
*
|
||||
* @param op Operator used in factorization and solve
|
||||
*/
|
||||
void SetOperator(const Operator &op);
|
||||
|
||||
/**
|
||||
* @brief Solve y = Op^{-1} x.
|
||||
*
|
||||
* @param x RHS vector
|
||||
* @param y Solution vector
|
||||
*/
|
||||
void Mult(const Vector &x, Vector &y) const;
|
||||
|
||||
/**
|
||||
* @brief Transpose Solve y = Op^{-T} x.
|
||||
*
|
||||
* @param x RHS vector
|
||||
* @param y Solution vector
|
||||
*/
|
||||
void MultTranspose(const Vector &x, Vector &y) const;
|
||||
|
||||
/**
|
||||
* @brief Set the error print level for MUMPS
|
||||
*
|
||||
* @param print_lvl Print level
|
||||
*
|
||||
* @note This method has to be called before SetOperator.
|
||||
*/
|
||||
void SetPrintLevel(int print_lvl);
|
||||
|
||||
/**
|
||||
* @brief Set the matrix type
|
||||
*
|
||||
* Supported matrix types: General, symmetric indefinite and
|
||||
* symmetric positive definite
|
||||
*
|
||||
* @param mtype Matrix type
|
||||
*
|
||||
* @note This method has to be called before SetOperator.
|
||||
*/
|
||||
void SetMatrixSymType(MatType mtype);
|
||||
|
||||
// Destructor
|
||||
~MUMPSSolver();
|
||||
|
||||
private:
|
||||
|
||||
// MPI communicator
|
||||
MPI_Comm comm;
|
||||
|
||||
// Number of procs
|
||||
int numProcs;
|
||||
|
||||
// local mpi id
|
||||
int myid;
|
||||
|
||||
// parameter controling the matrix type
|
||||
MatType mat_type = MatType::UNSYMMETRIC;
|
||||
|
||||
// parameter controling the printing level
|
||||
int print_level = 0;
|
||||
|
||||
// local row offsets
|
||||
int row_start;
|
||||
|
||||
// MUMPS object
|
||||
DMUMPS_STRUC_C *id=nullptr;
|
||||
|
||||
// Method for setting MUMPS interal parameters
|
||||
void SetParameters();
|
||||
|
||||
#if MFEM_MUMPS_VERSION >= 530
|
||||
|
||||
// row offests array on all procs
|
||||
Array<int> row_starts;
|
||||
|
||||
// row map
|
||||
int * irhs_loc = nullptr;
|
||||
|
||||
// These two methods are needed to distribute the local solution
|
||||
// vectors returned by MUMPS to the original MFEM parallel partition
|
||||
int GetRowRank(int i, const Array<int> &row_starts_) const;
|
||||
|
||||
void RedistributeSol(const int * row_map,
|
||||
const double * x,
|
||||
double * y) const;
|
||||
#else
|
||||
|
||||
// Arrays needed for MPI_Gather and MPI_Scatter
|
||||
int * recv_counts = nullptr;
|
||||
|
||||
int * displs = nullptr;
|
||||
|
||||
double * rhs_glob = nullptr;
|
||||
|
||||
#endif
|
||||
|
||||
}; // mfem::MUMPSSolver class
|
||||
|
||||
} // namespace mfem
|
||||
|
||||
#endif // MFEM_USE_MPI
|
||||
#endif // MFEM_USE_MUMPS
|
||||
#endif // MFEM_MUMPS
|
||||
+96
-1
@@ -677,7 +677,7 @@ void SDIRK33Solver::Step(Vector &x, double &t, double &dt)
|
||||
|
||||
f->SetTime(t + c*dt);
|
||||
f->ImplicitSolve(a*dt, y, k);
|
||||
x.Add((1.-a-b)*dt, k);
|
||||
x.Add((1.0-a-b)*dt, k);
|
||||
|
||||
f->SetTime(t + dt);
|
||||
f->ImplicitSolve(a*dt, x, k);
|
||||
@@ -685,6 +685,101 @@ void SDIRK33Solver::Step(Vector &x, double &t, double &dt)
|
||||
t += dt;
|
||||
}
|
||||
|
||||
void TrapezoidalRuleSolver::Init(TimeDependentOperator &_f)
|
||||
{
|
||||
ODESolver::Init(_f);
|
||||
k.SetSize(f->Width(), mem_type);
|
||||
y.SetSize(f->Width(), mem_type);
|
||||
}
|
||||
|
||||
void TrapezoidalRuleSolver::Step(Vector &x, double &t, double &dt)
|
||||
{
|
||||
// 0 | 0 0
|
||||
// 1 | 1/2 1/2
|
||||
// ------+-----------
|
||||
// | 1/2 1/2
|
||||
f->SetTime(t);
|
||||
f->Mult(x,k);
|
||||
add(x, dt/2.0, k, y);
|
||||
x.Add(dt/2.0, k);
|
||||
|
||||
f->SetTime(t + dt);
|
||||
f->ImplicitSolve(dt/2.0, y, k);
|
||||
x.Add(dt/2.0, k);
|
||||
t += dt;
|
||||
}
|
||||
|
||||
void ESDIRK32Solver::Init(TimeDependentOperator &_f)
|
||||
{
|
||||
ODESolver::Init(_f);
|
||||
k.SetSize(f->Width(), mem_type);
|
||||
y.SetSize(f->Width(), mem_type);
|
||||
z.SetSize(f->Width(), mem_type);
|
||||
}
|
||||
|
||||
void ESDIRK32Solver::Step(Vector &x, double &t, double &dt)
|
||||
{
|
||||
// 0 | 0 0 0
|
||||
// 2a | a a 0
|
||||
// 1 | 1-b-a b a
|
||||
// ------+--------------------
|
||||
// | 1-b-a b a
|
||||
const double a = (2.0 - sqrt(2.0)) / 2.0;
|
||||
const double b = (1.0 - 2.0*a) / (4.0*a);
|
||||
|
||||
f->SetTime(t);
|
||||
f->Mult(x,k);
|
||||
add(x, a*dt, k, y);
|
||||
add(x, (1.0-b-a)*dt, k, z);
|
||||
x.Add((1.0-b-a)*dt, k);
|
||||
|
||||
f->SetTime(t + (2.0*a)*dt);
|
||||
f->ImplicitSolve(a*dt, y, k);
|
||||
z.Add(b*dt, k);
|
||||
x.Add(b*dt, k);
|
||||
|
||||
f->SetTime(t + dt);
|
||||
f->ImplicitSolve(a*dt, z, k);
|
||||
x.Add(a*dt, k);
|
||||
t += dt;
|
||||
}
|
||||
|
||||
void ESDIRK33Solver::Init(TimeDependentOperator &_f)
|
||||
{
|
||||
ODESolver::Init(_f);
|
||||
k.SetSize(f->Width(), mem_type);
|
||||
y.SetSize(f->Width(), mem_type);
|
||||
z.SetSize(f->Width(), mem_type);
|
||||
}
|
||||
|
||||
void ESDIRK33Solver::Step(Vector &x, double &t, double &dt)
|
||||
{
|
||||
// 0 | 0 0 0
|
||||
// 2a | a a 0
|
||||
// 1 | 1-b-a b a
|
||||
// ------+----------------------------
|
||||
// | 1-b_2-b_3 b_2 b_3
|
||||
const double a = (3.0 + sqrt(3.0)) / 6.0;
|
||||
const double b = (1.0 - 2.0*a) / (4.0*a);
|
||||
const double b_2 = 1.0 / ( 12.0*a*(1.0 - 2.0*a) );
|
||||
const double b_3 = (1.0 - 3.0*a) / ( 3.0*(1.0 - 2.0*a) );
|
||||
|
||||
f->SetTime(t);
|
||||
f->Mult(x,k);
|
||||
add(x, a*dt, k, y);
|
||||
add(x, (1.0-b-a)*dt, k, z);
|
||||
x.Add((1.0-b_2-b_3)*dt, k);
|
||||
|
||||
f->SetTime(t + (2.0*a)*dt);
|
||||
f->ImplicitSolve(a*dt, y, k);
|
||||
z.Add(b*dt, k);
|
||||
x.Add(b_2*dt, k);
|
||||
|
||||
f->SetTime(t + dt);
|
||||
f->ImplicitSolve(a*dt, z, k);
|
||||
x.Add(b_3*dt, k);
|
||||
t += dt;
|
||||
}
|
||||
|
||||
void GeneralizedAlphaSolver::Init(TimeDependentOperator &_f)
|
||||
{
|
||||
|
||||
@@ -460,6 +460,48 @@ public:
|
||||
};
|
||||
|
||||
|
||||
/** Two stage, explicit singly diagonal implicit Runge-Kutta (ESDIRK) method
|
||||
of order 2. A-stable. */
|
||||
class TrapezoidalRuleSolver : public ODESolver
|
||||
{
|
||||
protected:
|
||||
Vector k, y;
|
||||
|
||||
public:
|
||||
virtual void Init(TimeDependentOperator &_f);
|
||||
|
||||
virtual void Step(Vector &x, double &t, double &dt);
|
||||
};
|
||||
|
||||
|
||||
/** Three stage, explicit singly diagonal implicit Runge-Kutta (ESDIRK) method
|
||||
of order 2. L-stable. */
|
||||
class ESDIRK32Solver : public ODESolver
|
||||
{
|
||||
protected:
|
||||
Vector k, y, z;
|
||||
|
||||
public:
|
||||
virtual void Init(TimeDependentOperator &_f);
|
||||
|
||||
virtual void Step(Vector &x, double &t, double &dt);
|
||||
};
|
||||
|
||||
|
||||
/** Three stage, explicit singly diagonal implicit Runge-Kutta (ESDIRK) method
|
||||
of order 3. A-stable. */
|
||||
class ESDIRK33Solver : public ODESolver
|
||||
{
|
||||
protected:
|
||||
Vector k, y, z;
|
||||
|
||||
public:
|
||||
virtual void Init(TimeDependentOperator &_f);
|
||||
|
||||
virtual void Step(Vector &x, double &t, double &dt);
|
||||
};
|
||||
|
||||
|
||||
/// Generalized-alpha ODE solver from "A generalized-α method for integrating
|
||||
/// the filtered Navier-Stokes equations with a stabilized finite element
|
||||
/// method" by K.E. Jansen, C.H. Whiting and G.M. Hulbert.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
# CONTRIBUTING.md for details.
|
||||
|
||||
# The current MFEM version as an integer, see also `CMakeLists.txt`.
|
||||
MFEM_VERSION = 40200
|
||||
MFEM_VERSION = 40201
|
||||
MFEM_VERSION_STRING = $(shell printf "%06d" $(MFEM_VERSION) | \
|
||||
sed -e 's/^0*\(.*.\)\(..\)\(..\)$$/\1.\2.\3/' -e 's/\.0/./g' -e 's/\.0$$//')
|
||||
|
||||
@@ -204,7 +204,7 @@ CXXFLAGS ?= $(OPTIM_FLAGS)
|
||||
# MPI configuration
|
||||
ifneq ($(MFEM_USE_MPI),YES)
|
||||
MFEM_HOST_CXX = $(CXX)
|
||||
PKGS_NEED_MPI = SUPERLU STRUMPACK PETSC PUMI SLEPC MKL_CPARDISO
|
||||
PKGS_NEED_MPI = SUPERLU MUMPS STRUMPACK PETSC PUMI SLEPC MKL_CPARDISO
|
||||
$(foreach mpidep,$(PKGS_NEED_MPI),$(if $(MFEM_USE_$(mpidep):NO=),\
|
||||
$(warning *** [MPI is OFF] setting MFEM_USE_$(mpidep) = NO)\
|
||||
$(eval override MFEM_USE_$(mpidep)=NO),))
|
||||
@@ -265,7 +265,7 @@ ifeq ($(MFEM_USE_LEGACY_OPENMP),YES)
|
||||
endif
|
||||
|
||||
# List of MFEM dependencies, that require the *_LIB variable to be non-empty
|
||||
MFEM_REQ_LIB_DEPS = SUPERLU METIS CONDUIT SIDRE LAPACK SUNDIALS MESQUITE\
|
||||
MFEM_REQ_LIB_DEPS = SUPERLU MUMPS METIS CONDUIT SIDRE LAPACK SUNDIALS MESQUITE\
|
||||
SUITESPARSE STRUMPACK GINKGO GNUTLS NETCDF PETSC SLEPC MPFR PUMI HIOP GSLIB\
|
||||
OCCA CEED RAJA UMPIRE MKL_CPARDISO AMGX
|
||||
|
||||
@@ -332,7 +332,8 @@ MFEM_DEFINES = MFEM_VERSION MFEM_VERSION_STRING MFEM_GIT_STRING MFEM_USE_MPI\
|
||||
MFEM_USE_SLEPC MFEM_USE_MPFR MFEM_USE_SIDRE MFEM_USE_CONDUIT MFEM_USE_PUMI\
|
||||
MFEM_USE_HIOP MFEM_USE_GSLIB MFEM_USE_CUDA MFEM_USE_HIP MFEM_USE_OCCA\
|
||||
MFEM_USE_CEED MFEM_USE_RAJA MFEM_USE_UMPIRE MFEM_USE_SIMD MFEM_USE_ADIOS2\
|
||||
MFEM_USE_MKL_CPARDISO MFEM_USE_AMGX MFEM_SOURCE_DIR MFEM_INSTALL_DIR
|
||||
MFEM_USE_MKL_CPARDISO MFEM_USE_AMGX MFEM_USE_MUMPS MFEM_SOURCE_DIR\
|
||||
MFEM_INSTALL_DIR
|
||||
|
||||
# List of makefile variables that will be written to config.mk:
|
||||
MFEM_CONFIG_VARS = MFEM_CXX MFEM_HOST_CXX MFEM_CPPFLAGS MFEM_CXXFLAGS\
|
||||
@@ -366,7 +367,7 @@ MFEM_INSTALL_DIR = $(abspath $(MFEM_PREFIX))
|
||||
# If we have 'config' target, export variables used by config/makefile
|
||||
ifneq (,$(filter config,$(MAKECMDGOALS)))
|
||||
export $(MFEM_DEFINES) MFEM_DEFINES $(MFEM_CONFIG_VARS) MFEM_CONFIG_VARS
|
||||
export VERBOSE HYPRE_OPT PUMI_DIR
|
||||
export VERBOSE HYPRE_OPT PUMI_DIR MUMPS_OPT
|
||||
endif
|
||||
|
||||
# If we have 'install' target, export variables used by config/makefile
|
||||
@@ -634,6 +635,7 @@ status info:
|
||||
$(info MFEM_USE_MESQUITE = $(MFEM_USE_MESQUITE))
|
||||
$(info MFEM_USE_SUITESPARSE = $(MFEM_USE_SUITESPARSE))
|
||||
$(info MFEM_USE_SUPERLU = $(MFEM_USE_SUPERLU))
|
||||
$(info MFEM_USE_MUMPS = $(MFEM_USE_MUMPS))
|
||||
$(info MFEM_USE_STRUMPACK = $(MFEM_USE_STRUMPACK))
|
||||
$(info MFEM_USE_GINKGO = $(MFEM_USE_GINKGO))
|
||||
$(info MFEM_USE_AMGX = $(MFEM_USE_AMGX))
|
||||
|
||||
@@ -26,3 +26,4 @@ add_subdirectory(tools)
|
||||
add_subdirectory(toys)
|
||||
add_subdirectory(nurbs)
|
||||
add_subdirectory(gslib)
|
||||
add_subdirectory(variationalform)
|
||||
|
||||
@@ -351,6 +351,7 @@ int main (int argc, char *argv[])
|
||||
"e) View elements\n"
|
||||
"h) View element sizes, h\n"
|
||||
"k) View element ratios, kappa\n"
|
||||
"J) View scaled Jacobian\n"
|
||||
"l) Plot a function\n"
|
||||
"x) Print sub-element stats\n"
|
||||
"f) Find physical point in reference space\n"
|
||||
@@ -558,6 +559,8 @@ int main (int argc, char *argv[])
|
||||
cin >> sd;
|
||||
Array<int> bad_elems_by_geom(Geometry::NumGeom);
|
||||
bad_elems_by_geom = 0;
|
||||
// Only print so many to keep output compact
|
||||
const int max_to_print = 10;
|
||||
for (int i = 0; i < mesh->GetNE(); i++)
|
||||
{
|
||||
Geometry::Type geom = mesh->GetElementBaseGeometry(i);
|
||||
@@ -589,10 +592,23 @@ int main (int argc, char *argv[])
|
||||
max_det_J = fmax(max_det_J, max_det_J_z);
|
||||
if (min_det_J_z <= 0.0)
|
||||
{
|
||||
if (nz < max_to_print)
|
||||
{
|
||||
Vector center;
|
||||
mesh->GetElementCenter(i, center);
|
||||
cout << "det(J) < 0 = " << min_det_J_z << " in element "
|
||||
<< i << ", centered at: ";
|
||||
center.Print();
|
||||
}
|
||||
nz++;
|
||||
bad_elems_by_geom[geom]++;
|
||||
}
|
||||
}
|
||||
if (nz >= max_to_print)
|
||||
{
|
||||
cout << "det(J) < 0 for " << nz - max_to_print << " more elements "
|
||||
<< "not printed.\n";
|
||||
}
|
||||
cout << "\nbad elements = " << nz;
|
||||
if (nz)
|
||||
{
|
||||
@@ -692,7 +708,7 @@ int main (int argc, char *argv[])
|
||||
|
||||
// These are most of the cases that open a new GLVis window
|
||||
if (mk == 'm' || mk == 'b' || mk == 'e' || mk == 'v' || mk == 'h' ||
|
||||
mk == 'k' || mk == 'p')
|
||||
mk == 'k' || mk == 'J' || mk == 'p')
|
||||
{
|
||||
Array<int> bdr_part;
|
||||
Array<int> part(mesh->GetNE());
|
||||
@@ -765,7 +781,7 @@ int main (int argc, char *argv[])
|
||||
h_max = -h_min;
|
||||
for (int i = 0; i < mesh->GetNE(); i++)
|
||||
{
|
||||
int geom = mesh->GetElementBaseGeometry(i);
|
||||
Geometry::Type geom = mesh->GetElementBaseGeometry(i);
|
||||
ElementTransformation *T = mesh->GetElementTransformation(i);
|
||||
T->SetIntPoint(&Geometries.GetCenter(geom));
|
||||
Geometries.JacToPerfJac(geom, T->Jacobian(), J);
|
||||
@@ -790,7 +806,7 @@ int main (int argc, char *argv[])
|
||||
DenseMatrix J(dim);
|
||||
for (int i = 0; i < mesh->GetNE(); i++)
|
||||
{
|
||||
int geom = mesh->GetElementBaseGeometry(i);
|
||||
Geometry::Type geom = mesh->GetElementBaseGeometry(i);
|
||||
ElementTransformation *T = mesh->GetElementTransformation(i);
|
||||
T->SetIntPoint(&Geometries.GetCenter(geom));
|
||||
Geometries.JacToPerfJac(geom, T->Jacobian(), J);
|
||||
@@ -798,6 +814,48 @@ int main (int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
if (mk == 'J')
|
||||
{
|
||||
// The "scaled Jacobian" is the determinant of the Jacobian scaled
|
||||
// by the l2 norms of its columns. It can be used to identify badly
|
||||
// skewed elements, since it takes values between 0 and 1, with 0
|
||||
// corresponding to a flat element, and 1 to orthogonal columns.
|
||||
DenseMatrix J(dim);
|
||||
int sd;
|
||||
cout << "subdivision factor ---> " << flush;
|
||||
cin >> sd;
|
||||
for (int i = 0; i < mesh->GetNE(); i++)
|
||||
{
|
||||
Geometry::Type geom = mesh->GetElementBaseGeometry(i);
|
||||
ElementTransformation *T = mesh->GetElementTransformation(i);
|
||||
|
||||
RefinedGeometry *RefG = GlobGeometryRefiner.Refine(geom, sd, 1);
|
||||
IntegrationRule &ir = RefG->RefPts;
|
||||
|
||||
// For each element, find the minimal scaled Jacobian in a
|
||||
// lattice of points with the given subdivision factor.
|
||||
attr(i) = infinity();
|
||||
for (int j = 0; j < ir.GetNPoints(); j++)
|
||||
{
|
||||
T->SetIntPoint(&ir.IntPoint(j));
|
||||
Geometries.JacToPerfJac(geom, T->Jacobian(), J);
|
||||
|
||||
// Jacobian determinant
|
||||
double sJ = J.Det();
|
||||
|
||||
for (int k = 0; k < J.Width(); k++)
|
||||
{
|
||||
Vector col;
|
||||
J.GetColumnReference(k,col);
|
||||
// Scale by column norms
|
||||
sJ /= col.Norml2();
|
||||
}
|
||||
|
||||
attr(i) = fmin(sJ, attr(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mk == 'p')
|
||||
{
|
||||
int *partitioning = NULL, np;
|
||||
@@ -950,7 +1008,7 @@ int main (int argc, char *argv[])
|
||||
else
|
||||
{
|
||||
sol_sock << "fem3d_gf_data_keys\n";
|
||||
if (mk == 'v' || mk == 'h' || mk == 'k')
|
||||
if (mk == 'v' || mk == 'h' || mk == 'k' || mk == 'J')
|
||||
{
|
||||
mesh->Print(sol_sock);
|
||||
}
|
||||
|
||||
@@ -290,6 +290,21 @@ void NavierSolver::Setup(double dt)
|
||||
|
||||
un_gf.GetTrueDofs(un);
|
||||
|
||||
if (filter_alpha != 0.0)
|
||||
{
|
||||
vfec_filter = new H1_FECollection(order - filter_cutoff_modes,
|
||||
pmesh->Dimension());
|
||||
vfes_filter = new ParFiniteElementSpace(pmesh,
|
||||
vfec_filter,
|
||||
pmesh->Dimension());
|
||||
|
||||
un_NM1_gf.SetSpace(vfes_filter);
|
||||
un_NM1_gf = 0.0;
|
||||
|
||||
un_filtered_gf.SetSpace(vfes);
|
||||
un_filtered_gf = 0.0;
|
||||
}
|
||||
|
||||
sw_setup.Stop();
|
||||
}
|
||||
|
||||
@@ -518,6 +533,18 @@ void NavierSolver::Step(double &time, double dt, int cur_step)
|
||||
|
||||
un_gf.GetTrueDofs(un);
|
||||
|
||||
if (filter_alpha != 0.0)
|
||||
{
|
||||
un_NM1_gf.ProjectGridFunction(un_gf);
|
||||
un_filtered_gf.ProjectGridFunction(un_NM1_gf);
|
||||
const auto d_un_filtered_gf = un_filtered_gf.Read();
|
||||
auto d_un_gf = un_gf.ReadWrite();
|
||||
MFEM_FORALL(i,
|
||||
un_gf.Size(),
|
||||
d_un_gf[i] = (1.0 - filter_alpha) * d_un_gf[i]
|
||||
+ filter_alpha * d_un_filtered_gf[i];);
|
||||
}
|
||||
|
||||
sw_step.Stop();
|
||||
|
||||
if (verbose && pmesh->GetMyRank() == 0)
|
||||
@@ -1075,4 +1102,6 @@ NavierSolver::~NavierSolver()
|
||||
delete pfec;
|
||||
delete vfes;
|
||||
delete pfes;
|
||||
delete vfec_filter;
|
||||
delete vfes_filter;
|
||||
}
|
||||
|
||||
@@ -241,6 +241,19 @@ public:
|
||||
/// Compute CFL
|
||||
double ComputeCFL(ParGridFunction &u, double dt);
|
||||
|
||||
/// Set the number of modes to cut off in the interpolation filter
|
||||
void SetCutoffModes(int c) { filter_cutoff_modes = c; }
|
||||
|
||||
/// Set the interpolation filter parameter @a a
|
||||
/**
|
||||
* If @a a is > 0, the filtering algorithm for the velocity field after every
|
||||
* time step from [1] is used. The parameter should be 0 > @a >= 1.
|
||||
*
|
||||
* [1] Paul Fischer, Julia Mullen (2001) Filter-based stabilization of
|
||||
* spectral element methods
|
||||
*/
|
||||
void SetFilterAlpha(double a) { filter_alpha = a; }
|
||||
|
||||
protected:
|
||||
/// Print informations about the Navier version.
|
||||
void PrintInfo();
|
||||
@@ -412,6 +425,14 @@ protected:
|
||||
OperatorHandle Mv_lor;
|
||||
OperatorHandle Sp_lor;
|
||||
OperatorHandle H_lor;
|
||||
|
||||
// Filter-based stabilization
|
||||
int filter_cutoff_modes = 1;
|
||||
double filter_alpha = 0.0;
|
||||
FiniteElementCollection *vfec_filter = nullptr;
|
||||
ParFiniteElementSpace *vfes_filter = nullptr;
|
||||
ParGridFunction un_NM1_gf;
|
||||
ParGridFunction un_filtered_gf;
|
||||
};
|
||||
|
||||
} // namespace navier
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
add_library(parvariationalform parvariationalform.cpp)
|
||||
target_include_directories(parvariationalform PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(parvariationalform mfem)
|
||||
|
||||
add_executable(parvariationalform_example parvariationalform_example.cpp)
|
||||
target_include_directories(parvariationalform_example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(parvariationalform_example parvariationalform mfem)
|
||||
|
||||
add_executable(parvariationalform_ex1 parvariationalform_ex1.cpp)
|
||||
target_include_directories(parvariationalform_ex1 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(parvariationalform_ex1 parvariationalform mfem)
|
||||
|
||||
add_executable(parvariationalform_unit_tests test_tensor_ad.cpp)
|
||||
target_include_directories(parvariationalform_unit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/tests/unit)
|
||||
target_link_libraries(parvariationalform_unit_tests parvariationalform mfem)
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "mfem.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
class GenericIntegrator
|
||||
{
|
||||
public:
|
||||
GenericIntegrator(const IntegrationRule *ir = nullptr) : IntRule(ir) {}
|
||||
|
||||
virtual void Setup(const FiniteElementSpace &) = 0;
|
||||
|
||||
virtual void Apply(const Vector &x, Vector &y) const = 0;
|
||||
|
||||
virtual void ApplyGradient(const Vector &x,
|
||||
const Vector &v,
|
||||
Vector &y) const = 0;
|
||||
|
||||
protected:
|
||||
const IntegrationRule *IntRule;
|
||||
};
|
||||
} // namespace mfem
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "parvariationalform.hpp"
|
||||
#include "qfuncintegrator.hpp"
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
ParVariationalForm::ParVariationalForm(ParFiniteElementSpace *f)
|
||||
: Operator(f->GetTrueVSize()), fes(f), P(f->GetProlongationMatrix()),
|
||||
grad(*this)
|
||||
{
|
||||
G = fes->GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
|
||||
MFEM_ASSERT(G, "Some GetElementRestriction error");
|
||||
x_local.SetSize(G->Height(), Device::GetMemoryType());
|
||||
v_local.SetSize(G->Height(), Device::GetMemoryType());
|
||||
y_local.SetSize(G->Height(), Device::GetMemoryType());
|
||||
}
|
||||
|
||||
void ParVariationalForm::Mult(const Vector &x, Vector &y) const
|
||||
{
|
||||
px.SetSize(P->Height());
|
||||
py.SetSize(P->Height());
|
||||
|
||||
P->Mult(x, px);
|
||||
G->Mult(px, x_local);
|
||||
|
||||
y_local = 0.0;
|
||||
for (int i = 0; i < domain_integrators.Size(); ++i)
|
||||
{
|
||||
// y += F(x)
|
||||
domain_integrators[i]->Apply(x_local, y_local);
|
||||
}
|
||||
|
||||
G->MultTranspose(y_local, py);
|
||||
P->MultTranspose(py, y);
|
||||
|
||||
y.HostReadWrite();
|
||||
for (int i = 0; i < ess_tdof_list.Size(); i++)
|
||||
{
|
||||
y(ess_tdof_list[i]) = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void ParVariationalForm::GradientMult(const Vector &v, Vector &y) const
|
||||
{
|
||||
px.SetSize(P->Height());
|
||||
py.SetSize(P->Height());
|
||||
pv.SetSize(P->Height());
|
||||
|
||||
P->Mult(v, pv);
|
||||
G->Mult(pv, v_local);
|
||||
|
||||
P->Mult(x_lin, px);
|
||||
G->Mult(px, x_local);
|
||||
|
||||
y_local = 0.0;
|
||||
for (int i = 0; i < domain_integrators.Size(); ++i)
|
||||
{
|
||||
// y += dF(x)/dx * v
|
||||
if (is_linear)
|
||||
{
|
||||
// take care of RHS
|
||||
// domain_integrators[i]->Apply(v_local, y_local);
|
||||
}
|
||||
domain_integrators[i]->ApplyGradient(x_local, v_local, y_local);
|
||||
}
|
||||
|
||||
G->MultTranspose(y_local, py);
|
||||
P->MultTranspose(py, y);
|
||||
|
||||
y.HostReadWrite();
|
||||
for (int i = 0; i < ess_tdof_list.Size(); i++)
|
||||
{
|
||||
y(ess_tdof_list[i]) = v(ess_tdof_list[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Operator &ParVariationalForm::GetGradient(const Vector &x) const
|
||||
{
|
||||
x_lin = x;
|
||||
return grad;
|
||||
}
|
||||
|
||||
HypreParMatrix *ParVariationalForm::GetGradientMatrix(const Vector &x)
|
||||
{
|
||||
delete gradient_matrix;
|
||||
gradient_matrix = new HypreParMatrix;
|
||||
return gradient_matrix;
|
||||
}
|
||||
|
||||
void ParVariationalForm::SetEssentialBC(const Array<int> &ess_attr)
|
||||
{
|
||||
fes->GetEssentialTrueDofs(ess_attr, ess_tdof_list);
|
||||
}
|
||||
|
||||
} // namespace mfem
|
||||
@@ -0,0 +1,109 @@
|
||||
#include "mfem.hpp"
|
||||
#include "genericintegrator.hpp"
|
||||
#include "qfuncintegrator.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
class ParVariationalForm : public Operator
|
||||
{
|
||||
class Gradient : public Operator
|
||||
{
|
||||
public:
|
||||
Gradient(ParVariationalForm &f) : Operator(f.Height()), form(f){};
|
||||
|
||||
void Mult(const Vector &x, Vector &y) const override
|
||||
{
|
||||
form.GradientMult(x, y);
|
||||
}
|
||||
|
||||
private:
|
||||
ParVariationalForm &form;
|
||||
};
|
||||
|
||||
public:
|
||||
ParVariationalForm(ParFiniteElementSpace *f);
|
||||
|
||||
void Mult(const Vector &x, Vector &y) const override;
|
||||
|
||||
void AddDomainIntegrator(GenericIntegrator *i)
|
||||
{
|
||||
domain_integrators.Append(i);
|
||||
i->Setup(*fes);
|
||||
}
|
||||
|
||||
template<typename integrator_type,
|
||||
typename qfunc_type,
|
||||
typename... qfunc_args_type>
|
||||
void AddDomainIntegrator(qfunc_type f, qfunc_args_type const &... fargs)
|
||||
{
|
||||
if constexpr (std::is_same_v<integrator_type, DomainLFIntegrator>)
|
||||
{
|
||||
auto i = new QFunctionIntegrator(
|
||||
[&](auto... args) {
|
||||
auto du = std::get<1>(std::tuple{args...});
|
||||
return std::tuple{f(args...), decltype(du){}};
|
||||
},
|
||||
0,
|
||||
*fes->GetParMesh(),
|
||||
fargs...);
|
||||
|
||||
domain_integrators.Append(i);
|
||||
i->Setup(*fes);
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<integrator_type, DiffusionIntegrator>)
|
||||
{
|
||||
auto i = new QFunctionIntegrator(
|
||||
[&](auto... args) {
|
||||
auto du = std::get<1>(std::tuple{args...});
|
||||
return std::tuple{0.0, f(args...) * du};
|
||||
},
|
||||
0,
|
||||
*fes->GetParMesh(),
|
||||
fargs...);
|
||||
|
||||
domain_integrators.Append(i);
|
||||
i->Setup(*fes);
|
||||
}
|
||||
}
|
||||
|
||||
void AssumeLinear() { is_linear = true; }
|
||||
|
||||
// Return an Operator that provides a Mult(x, y) which is the MatVec of the
|
||||
// gradient of the ParVariationalForm wrt x. Acts as a "passthrough" to
|
||||
// ::GradientMult in order to satisfy mfem interfaces.
|
||||
Operator &GetGradient(const Vector &x) const override;
|
||||
|
||||
// Return an assmbled parallel matrix which represents the gradient of the
|
||||
// ParVariationalForm wrt to x.
|
||||
HypreParMatrix *GetGradientMatrix(const Vector &x);
|
||||
|
||||
void SetEssentialBC(const Array<int> &ess_attr);
|
||||
|
||||
protected:
|
||||
// y = F'(x_lin) * v
|
||||
void GradientMult(const Vector &v, Vector &y) const;
|
||||
|
||||
bool is_linear = false;
|
||||
ParFiniteElementSpace *fes;
|
||||
Array<GenericIntegrator *> domain_integrators;
|
||||
Array<int> ess_tdof_list;
|
||||
|
||||
// T -> L
|
||||
const Operator *P;
|
||||
|
||||
// L -> E
|
||||
const Operator *G;
|
||||
|
||||
mutable Vector x_local, y_local, v_local, px, py, pv;
|
||||
|
||||
// State to build the Gradient on, single source of the true state.
|
||||
mutable Vector x_lin;
|
||||
|
||||
mutable Gradient grad;
|
||||
|
||||
HypreParMatrix *gradient_matrix = nullptr;
|
||||
};
|
||||
} // namespace mfem
|
||||
@@ -0,0 +1,164 @@
|
||||
#include "mfem.hpp"
|
||||
#include "parvariationalform.hpp"
|
||||
#include "tensor.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int num_procs, myid;
|
||||
MPI_Init(&argc, &argv);
|
||||
MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
|
||||
|
||||
const char *mesh_file = "../data/inline-quad.mesh";
|
||||
int order = 1;
|
||||
bool static_cond = false;
|
||||
bool pa = false;
|
||||
const char *device_config = "cpu";
|
||||
bool visualization = true;
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
|
||||
args.AddOption(&order,
|
||||
"-o",
|
||||
"--order",
|
||||
"Finite element order (polynomial degree) or -1 for"
|
||||
" isoparametric space.");
|
||||
args.AddOption(&visualization,
|
||||
"-vis",
|
||||
"--visualization",
|
||||
"-no-vis",
|
||||
"--no-visualization",
|
||||
"Enable or disable GLVis visualization.");
|
||||
args.Parse();
|
||||
if (!args.Good())
|
||||
{
|
||||
if (myid == 0)
|
||||
{
|
||||
args.PrintUsage(cout);
|
||||
}
|
||||
MPI_Finalize();
|
||||
return 1;
|
||||
}
|
||||
if (myid == 0)
|
||||
{
|
||||
args.PrintOptions(cout);
|
||||
}
|
||||
|
||||
Mesh mesh(mesh_file, 1, 1);
|
||||
int dim = mesh.Dimension();
|
||||
|
||||
{
|
||||
int ref_levels = (int) floor(log(10. / mesh.GetNE()) / log(2.) / dim);
|
||||
for (int l = 0; l < ref_levels; l++)
|
||||
{
|
||||
mesh.UniformRefinement();
|
||||
}
|
||||
}
|
||||
|
||||
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
||||
mesh.Clear();
|
||||
{
|
||||
int par_ref_levels = 2;
|
||||
for (int l = 0; l < par_ref_levels; l++)
|
||||
{
|
||||
pmesh.UniformRefinement();
|
||||
}
|
||||
}
|
||||
|
||||
FiniteElementCollection *fec;
|
||||
bool delete_fec;
|
||||
if (order > 0)
|
||||
{
|
||||
fec = new H1_FECollection(order, dim);
|
||||
delete_fec = true;
|
||||
}
|
||||
else if (pmesh.GetNodes())
|
||||
{
|
||||
fec = pmesh.GetNodes()->OwnFEC();
|
||||
delete_fec = false;
|
||||
if (myid == 0)
|
||||
{
|
||||
cout << "Using isoparametric FEs: " << fec->Name() << endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fec = new H1_FECollection(order = 1, dim);
|
||||
delete_fec = true;
|
||||
}
|
||||
ParFiniteElementSpace fespace(&pmesh, fec);
|
||||
HYPRE_Int size = fespace.GlobalTrueVSize();
|
||||
if (myid == 0)
|
||||
{
|
||||
cout << "Number of finite element unknowns: " << size << endl;
|
||||
}
|
||||
|
||||
Array<int> ess_bdr(pmesh.bdr_attributes.Max());
|
||||
ess_bdr = 1;
|
||||
|
||||
FunctionCoefficient u_excoeff([&](const Vector &coords) {
|
||||
double x = coords(0);
|
||||
double y = coords(1);
|
||||
return x * x + y * y;
|
||||
});
|
||||
|
||||
ParGridFunction x(&fespace);
|
||||
|
||||
x.ProjectBdrCoefficient(u_excoeff, ess_bdr);
|
||||
|
||||
ParVariationalForm form(&fespace);
|
||||
|
||||
auto b_coeff = [&](auto u, auto du, auto x) {
|
||||
return 4.0 * (1.0 + 2.0 * x[0] * x[0] + 2.0 * x[1] * x[1]);
|
||||
};
|
||||
|
||||
form.AddDomainIntegrator<DomainLFIntegrator>(b_coeff);
|
||||
|
||||
auto a_coeff = [&](auto u, auto du, auto x) { return 1.0 + u; };
|
||||
|
||||
form.AddDomainIntegrator<DiffusionIntegrator>(a_coeff);
|
||||
|
||||
form.SetEssentialBC(ess_bdr);
|
||||
|
||||
CGSolver cg(MPI_COMM_WORLD);
|
||||
cg.SetRelTol(1e-6);
|
||||
cg.SetMaxIter(2000);
|
||||
cg.SetPrintLevel(1);
|
||||
|
||||
NewtonSolver newton(MPI_COMM_WORLD);
|
||||
newton.SetOperator(form);
|
||||
newton.SetSolver(cg);
|
||||
newton.SetPrintLevel(1);
|
||||
newton.SetRelTol(1e-8);
|
||||
newton.SetMaxIter(100);
|
||||
|
||||
Vector zero;
|
||||
Vector X;
|
||||
x.GetTrueDofs(X);
|
||||
newton.Mult(zero, X);
|
||||
|
||||
x.Distribute(X);
|
||||
|
||||
if (visualization)
|
||||
{
|
||||
char vishost[] = "localhost";
|
||||
int visport = 19916;
|
||||
socketstream sol_sock(vishost, visport);
|
||||
sol_sock << "parallel " << num_procs << " " << myid << "\n";
|
||||
sol_sock.precision(8);
|
||||
sol_sock << "solution\n" << pmesh << x << flush;
|
||||
}
|
||||
|
||||
if (delete_fec)
|
||||
{
|
||||
delete fec;
|
||||
}
|
||||
MPI_Finalize();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
#include "mfem.hpp"
|
||||
#include "parvariationalform.hpp"
|
||||
#include "qfuncintegrator.hpp"
|
||||
#include "tensor.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace mfem;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int num_procs, myid;
|
||||
MPI_Init(&argc, &argv);
|
||||
MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
|
||||
|
||||
const char *mesh_file = "../data/inline-quad.mesh";
|
||||
int order = 1;
|
||||
int refinements = 0;
|
||||
double p = 5.0;
|
||||
|
||||
OptionsParser args(argc, argv);
|
||||
args.AddOption(&refinements, "-r", "--ref", "");
|
||||
args.AddOption(&order, "-o", "--order", "");
|
||||
|
||||
args.Parse();
|
||||
if (!args.Good())
|
||||
{
|
||||
if (myid == 0)
|
||||
{
|
||||
args.PrintUsage(cout);
|
||||
}
|
||||
MPI_Finalize();
|
||||
return 1;
|
||||
}
|
||||
if (myid == 0)
|
||||
{
|
||||
args.PrintOptions(cout);
|
||||
}
|
||||
|
||||
Mesh mesh(mesh_file, 1, 1);
|
||||
|
||||
int dim = mesh.Dimension();
|
||||
{
|
||||
for (int l = 0; l < refinements; l++)
|
||||
{
|
||||
mesh.UniformRefinement();
|
||||
}
|
||||
}
|
||||
|
||||
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
||||
|
||||
auto fec = H1_FECollection(order, dim);
|
||||
ParFiniteElementSpace fespace(&pmesh, &fec);
|
||||
|
||||
Array<int> ess_bdr(pmesh.bdr_attributes.Max());
|
||||
ess_bdr = 1;
|
||||
|
||||
FunctionCoefficient u_excoeff([&](const Vector &coords) {
|
||||
double x = coords(0);
|
||||
double y = coords(1);
|
||||
|
||||
// return 1.0 - pow(sqrt(x * x + y * y), p / (p - 1.0));
|
||||
return x * x + y * y;
|
||||
});
|
||||
|
||||
ParGridFunction x(&fespace);
|
||||
x.Randomize();
|
||||
|
||||
x.ProjectBdrCoefficient(u_excoeff, ess_bdr);
|
||||
|
||||
ParVariationalForm form(&fespace);
|
||||
|
||||
auto plaplacian = new QFunctionIntegrator([&](auto u, auto du, auto x) {
|
||||
// auto f0 = -1.0;
|
||||
// auto f1 = pow(norm(du), p - 2.0) * du;
|
||||
auto f0 = 4.0 * (1.0 + 2.0 * x[0] * x[0] + 2.0 * x[1] * x[1]);
|
||||
auto f1 = (1.0 + u) * du;
|
||||
return std::tuple{f0, f1};
|
||||
}, 0, pmesh);
|
||||
|
||||
form.AddDomainIntegrator(plaplacian);
|
||||
|
||||
form.SetEssentialBC(ess_bdr);
|
||||
|
||||
CGSolver cg(MPI_COMM_WORLD);
|
||||
cg.SetRelTol(1e-6);
|
||||
cg.SetMaxIter(2000);
|
||||
cg.SetPrintLevel(1);
|
||||
|
||||
NewtonSolver newton(MPI_COMM_WORLD);
|
||||
newton.SetOperator(form);
|
||||
newton.SetSolver(cg);
|
||||
newton.SetPrintLevel(1);
|
||||
newton.SetRelTol(1e-8);
|
||||
newton.SetMaxIter(100);
|
||||
|
||||
Vector zero;
|
||||
Vector X;
|
||||
x.GetTrueDofs(X);
|
||||
newton.Mult(zero, X);
|
||||
|
||||
x.Distribute(X);
|
||||
|
||||
// x.ProjectCoefficient(u_excoeff);
|
||||
|
||||
char vishost[] = "localhost";
|
||||
int visport = 19916;
|
||||
socketstream sol_sock(vishost, visport);
|
||||
sol_sock << "parallel " << num_procs << " " << myid << "\n";
|
||||
sol_sock.precision(8);
|
||||
sol_sock << "solution\n" << pmesh << x << flush;
|
||||
|
||||
MPI_Finalize();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
#include "mfem.hpp"
|
||||
#include "../../general/forall.hpp"
|
||||
#include "genericintegrator.hpp"
|
||||
#include "tensor.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace mfem
|
||||
{
|
||||
template<typename T>
|
||||
struct supported_type
|
||||
{
|
||||
static constexpr bool value = false;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct supported_type<ParMesh>
|
||||
{
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
|
||||
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
|
||||
class QFunctionIntegrator : public GenericIntegrator
|
||||
{
|
||||
public:
|
||||
QFunctionIntegrator(qfunc_type f,
|
||||
qfunc_grad_type f_grad,
|
||||
qfunc_args_type const &... fargs);
|
||||
|
||||
QFunctionIntegrator(qfunc_type f, qfunc_args_type const &... fargs);
|
||||
|
||||
void Setup(const FiniteElementSpace &fes) override;
|
||||
|
||||
void Apply(const Vector &, Vector &) const override;
|
||||
|
||||
// y += F'(x) * v
|
||||
void ApplyGradient(const Vector &x,
|
||||
const Vector &v,
|
||||
Vector &y) const override;
|
||||
|
||||
protected:
|
||||
template<int D1D, int Q1D>
|
||||
void Apply2D(const Vector &u_in_, Vector &y_) const;
|
||||
|
||||
template<int D1D, int Q1D>
|
||||
void ApplyGradient2D(const Vector &u_in_,
|
||||
const Vector &v_in_,
|
||||
Vector &y_) const;
|
||||
|
||||
auto EvaluateFargValue(const Mesh &m, const double qx, const double qy) const;
|
||||
|
||||
const FiniteElementSpace *fespace;
|
||||
const DofToQuad *maps; ///< Not owned
|
||||
const GeometricFactors *geom; ///< Not owned
|
||||
int dim, ne, nq, dofs1D, quad1D;
|
||||
|
||||
// Geometric factors
|
||||
Vector J_;
|
||||
Vector W_;
|
||||
|
||||
qfunc_type qf;
|
||||
qfunc_grad_type qf_grad;
|
||||
std::tuple<qfunc_args_type...> qf_farg_values;
|
||||
};
|
||||
|
||||
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
|
||||
QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::
|
||||
QFunctionIntegrator(qfunc_type f,
|
||||
qfunc_grad_type df,
|
||||
qfunc_args_type const &... fargs)
|
||||
: GenericIntegrator(nullptr), maps(nullptr), geom(nullptr), qf(f),
|
||||
qf_grad(df), qf_farg_values(std::tuple{fargs...})
|
||||
{
|
||||
static_assert((supported_type<qfunc_args_type>::value && ...),
|
||||
"Type not supported for parameter expansion. See "
|
||||
"documentation for supported types.");
|
||||
}
|
||||
|
||||
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
|
||||
QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::
|
||||
QFunctionIntegrator(qfunc_type f, qfunc_args_type const &... fargs)
|
||||
: GenericIntegrator(nullptr), maps(nullptr), geom(nullptr), qf(f),
|
||||
qf_grad(qfunc_grad_type{}), qf_farg_values(std::tuple{fargs...})
|
||||
{
|
||||
static_assert((supported_type<qfunc_args_type>::value && ...),
|
||||
"Type not supported for parameter expansion. See "
|
||||
"documentation for supported types.");
|
||||
}
|
||||
|
||||
template<typename qfunc_type, typename... qfunc_args_type>
|
||||
QFunctionIntegrator(qfunc_type, qfunc_args_type const &...)
|
||||
-> QFunctionIntegrator<qfunc_type, int, qfunc_args_type const &...>;
|
||||
|
||||
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
|
||||
void QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::Setup(
|
||||
const FiniteElementSpace &fes)
|
||||
{
|
||||
// Assuming the same element type
|
||||
fespace = &fes;
|
||||
Mesh *mesh = fes.GetMesh();
|
||||
if (mesh->GetNE() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const FiniteElement &el = *fes.GetFE(0);
|
||||
ElementTransformation *T = mesh->GetElementTransformation(0);
|
||||
const IntegrationRule *ir = nullptr;
|
||||
if (!IntRule)
|
||||
{
|
||||
IntRule = &IntRules.Get(el.GetGeomType(), el.GetOrder() * 2);
|
||||
}
|
||||
ir = IntRule;
|
||||
|
||||
dim = mesh->Dimension();
|
||||
ne = fes.GetMesh()->GetNE();
|
||||
nq = ir->GetNPoints();
|
||||
geom = mesh->GetGeometricFactors(*ir,
|
||||
GeometricFactors::COORDINATES
|
||||
| GeometricFactors::JACOBIANS);
|
||||
maps = &el.GetDofToQuad(*ir, DofToQuad::TENSOR);
|
||||
dofs1D = maps->ndof;
|
||||
quad1D = maps->nqpt;
|
||||
// pa_data.SetSize(ne * nq, Device::GetDeviceMemoryType());
|
||||
|
||||
W_.SetSize(nq, Device::GetDeviceMemoryType());
|
||||
W_.GetMemory().CopyFrom(ir->GetWeights().GetMemory(), nq);
|
||||
|
||||
// J.SetSize(ne * nq, Device::GetDeviceMemoryType());
|
||||
J_ = geom->J;
|
||||
}
|
||||
|
||||
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
|
||||
auto QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::
|
||||
EvaluateFargValue(const Mesh &m, const double qx, const double qy) const
|
||||
{
|
||||
Vector trip(3);
|
||||
trip = 0.0;
|
||||
ElementTransformation *tr = const_cast<Mesh &>(m).GetElementTransformation(0);
|
||||
tr->Transform(IntRule->IntPoint(qx + quad1D * qy), trip);
|
||||
return tensor<double, 3>{
|
||||
{trip(0), trip(1), m.SpaceDimension() == 2 ? 0.0 : trip(2)}};
|
||||
}
|
||||
|
||||
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
|
||||
void QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::Apply(
|
||||
const Vector &x, Vector &y) const
|
||||
{
|
||||
if (dim == 2)
|
||||
{
|
||||
switch ((dofs1D << 4) | quad1D)
|
||||
{
|
||||
case 0x22:
|
||||
return Apply2D<2, 2>(x, y);
|
||||
default:
|
||||
MFEM_ASSERT(false, "NOPE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
|
||||
template<int D1D, int Q1D>
|
||||
void QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::Apply2D(
|
||||
const Vector &u_in_, Vector &y_) const
|
||||
{
|
||||
int NE = ne;
|
||||
|
||||
auto v1d = Reshape(maps->B.Read(), Q1D, D1D);
|
||||
auto dv1d_dX = Reshape(maps->G.Read(), Q1D, D1D);
|
||||
// (NQ x SDIM x DIM x NE)
|
||||
auto J = Reshape(J_.Read(), Q1D, Q1D, 2, 2, NE);
|
||||
auto W = Reshape(W_.Read(), Q1D, Q1D);
|
||||
auto u = Reshape(u_in_.Read(), D1D, D1D, NE);
|
||||
auto y = Reshape(y_.ReadWrite(), D1D, D1D, NE);
|
||||
|
||||
// MFEM_FORALL(e, NE, {
|
||||
for (int e = 0; e < NE; e++)
|
||||
{
|
||||
// loop over quadrature points
|
||||
for (int qy = 0; qy < Q1D; ++qy)
|
||||
{
|
||||
for (int qx = 0; qx < Q1D; ++qx)
|
||||
{
|
||||
double u_q = 0.0;
|
||||
double du_dX_q[2] = {0.0};
|
||||
for (int ix = 0; ix < D1D; ix++)
|
||||
{
|
||||
for (int iy = 0; iy < D1D; iy++)
|
||||
{
|
||||
u_q += u(ix, iy, e) * v1d(qx, ix) * v1d(qy, iy);
|
||||
du_dX_q[0] += u(ix, iy, e) * dv1d_dX(qx, ix) * v1d(qy, iy);
|
||||
du_dX_q[1] += u(ix, iy, e) * v1d(qx, ix) * dv1d_dX(qy, iy);
|
||||
}
|
||||
}
|
||||
|
||||
// du_dx_q = invJ^T * du_dX_q
|
||||
// = (adjJ^T * du_dX_q) / detJ
|
||||
double J_q[2][2] = {{J(qx, qy, 0, 0, e),
|
||||
J(qx, qy, 0, 1, e)}, // J_q[0][0], J_q[0][1]
|
||||
{J(qx, qy, 1, 0, e),
|
||||
J(qx, qy, 1, 1, e)}}; // J_q[1][0], J_q[1][1]
|
||||
|
||||
double detJ_q = (J_q[0][0] * J_q[1][1]) - (J_q[0][1] * J_q[1][0]);
|
||||
|
||||
double adjJ[2][2] = {{J_q[1][1], -J_q[0][1]},
|
||||
{-J_q[1][0], J_q[0][0]}};
|
||||
|
||||
tensor<double, 2> du_dx_q
|
||||
= {(adjJ[0][0] * du_dX_q[0] + adjJ[1][0] * du_dX_q[1]) / detJ_q,
|
||||
(adjJ[0][1] * du_dX_q[0] + adjJ[1][1] * du_dX_q[1]) / detJ_q};
|
||||
|
||||
auto processed_qf_farg_values = std::apply(
|
||||
[=](auto &... a) {
|
||||
return std::make_tuple(u_q,
|
||||
du_dx_q,
|
||||
EvaluateFargValue(a, qx, qy)...);
|
||||
},
|
||||
qf_farg_values);
|
||||
|
||||
auto [f0, f1] = std::apply(qf, processed_qf_farg_values);
|
||||
|
||||
double f0_X = f0 * detJ_q;
|
||||
|
||||
// f1_X = invJ * f1 * detJ
|
||||
// = adjJ * f1
|
||||
double f1_X[2] = {
|
||||
adjJ[0][0] * f1[0] + adjJ[0][1] * f1[1],
|
||||
adjJ[1][0] * f1[0] + adjJ[1][1] * f1[1],
|
||||
};
|
||||
|
||||
for (int ix = 0; ix < D1D; ix++)
|
||||
{
|
||||
for (int iy = 0; iy < D1D; iy++)
|
||||
{
|
||||
// accumulate v * f0 + dot(dv_dx, f1)
|
||||
y(ix, iy, e) += (f0_X * v1d(qx, ix) * v1d(qy, iy)
|
||||
+ f1_X[0] * dv1d_dX(qx, ix) * v1d(qy, iy)
|
||||
+ f1_X[1] * dv1d_dX(qy, iy) * v1d(qx, ix))
|
||||
* W(qx, qy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// });
|
||||
}
|
||||
|
||||
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
|
||||
void QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::
|
||||
ApplyGradient(const Vector &x, const Vector &v, Vector &y) const
|
||||
{
|
||||
ApplyGradient2D<2, 2>(x, v, y);
|
||||
}
|
||||
|
||||
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
|
||||
template<int D1D, int Q1D>
|
||||
void QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::
|
||||
ApplyGradient2D(const Vector &u_in_, const Vector &v_in_, Vector &y_) const
|
||||
{
|
||||
int NE = ne;
|
||||
|
||||
auto v1d = Reshape(maps->B.Read(), Q1D, D1D);
|
||||
auto dv1d_dX = Reshape(maps->G.Read(), Q1D, D1D);
|
||||
// (NQ x SDIM x DIM x NE)
|
||||
auto J = Reshape(J_.Read(), Q1D, Q1D, 2, 2, NE);
|
||||
auto W = Reshape(W_.Read(), Q1D, Q1D);
|
||||
auto u = Reshape(u_in_.Read(), D1D, D1D, NE);
|
||||
auto v = Reshape(v_in_.Read(), D1D, D1D, NE);
|
||||
auto y = Reshape(y_.ReadWrite(), D1D, D1D, NE);
|
||||
|
||||
for (int e = 0; e < NE; e++)
|
||||
{
|
||||
// loop over quadrature points
|
||||
for (int qy = 0; qy < Q1D; ++qy)
|
||||
{
|
||||
for (int qx = 0; qx < Q1D; ++qx)
|
||||
{
|
||||
double u_q = 0.0;
|
||||
double du_dX_q[2] = {0.0};
|
||||
double v_q = 0.0;
|
||||
double dv_dX_q[2] = {0.0};
|
||||
for (int ix = 0; ix < D1D; ix++)
|
||||
{
|
||||
for (int iy = 0; iy < D1D; iy++)
|
||||
{
|
||||
u_q += u(ix, iy, e) * v1d(qx, ix) * v1d(qy, iy);
|
||||
|
||||
du_dX_q[0] += u(ix, iy, e) * dv1d_dX(qx, ix) * v1d(qy, iy);
|
||||
du_dX_q[1] += u(ix, iy, e) * v1d(qx, ix) * dv1d_dX(qy, iy);
|
||||
|
||||
v_q += v(ix, iy, e) * v1d(qx, ix) * v1d(qy, iy);
|
||||
|
||||
dv_dX_q[0] += v(ix, iy, e) * dv1d_dX(qx, ix) * v1d(qy, iy);
|
||||
dv_dX_q[1] += v(ix, iy, e) * v1d(qx, ix) * dv1d_dX(qy, iy);
|
||||
}
|
||||
}
|
||||
|
||||
// du_dx_q = invJ^T * du_dX_q
|
||||
// = (adjJ^T * du_dX_q) / detJ
|
||||
double J_q[2][2] = {{J(qx, qy, 0, 0, e),
|
||||
J(qx, qy, 0, 1, e)}, // J_q[0][0], J_q[0][1]
|
||||
{J(qx, qy, 1, 0, e),
|
||||
J(qx, qy, 1, 1, e)}}; // J_q[1][0], J_q[1][1]
|
||||
|
||||
double detJ_q = (J_q[0][0] * J_q[1][1]) - (J_q[0][1] * J_q[1][0]);
|
||||
|
||||
double adjJ[2][2] = {{J_q[1][1], -J_q[0][1]},
|
||||
{-J_q[1][0], J_q[0][0]}};
|
||||
|
||||
tensor<double, 2> du_dx_q
|
||||
= {(adjJ[0][0] * du_dX_q[0] + adjJ[1][0] * du_dX_q[1]) / detJ_q,
|
||||
(adjJ[0][1] * du_dX_q[0] + adjJ[1][1] * du_dX_q[1]) / detJ_q};
|
||||
|
||||
double dv_dx_q[2]
|
||||
= {(adjJ[0][0] * dv_dX_q[0] + adjJ[1][0] * dv_dX_q[1]) / detJ_q,
|
||||
(adjJ[0][1] * dv_dX_q[0] + adjJ[1][1] * dv_dX_q[1]) / detJ_q};
|
||||
|
||||
// compute dF(u, du)/du
|
||||
auto processed_qf_farg_values_u = std::apply(
|
||||
[=](auto &... a) {
|
||||
return std::make_tuple(derivative_wrt(u_q),
|
||||
du_dx_q,
|
||||
EvaluateFargValue(a, qx, qy)...);
|
||||
},
|
||||
qf_farg_values);
|
||||
|
||||
auto [f0u, f1u] = std::apply(qf, processed_qf_farg_values_u);
|
||||
|
||||
double f00 = 0.0;
|
||||
if constexpr (std::is_same_v<decltype(f0u), double>)
|
||||
{
|
||||
f00 = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
f00 = f0u.gradient;
|
||||
}
|
||||
|
||||
tensor<double, 2> f10;
|
||||
if constexpr (std::is_same_v<decltype(f1u), tensor<double, 2>>)
|
||||
{
|
||||
f10 = {0.0, 0.0};
|
||||
}
|
||||
else
|
||||
{
|
||||
f10 = {f1u[0].gradient, f1u[1].gradient};
|
||||
}
|
||||
|
||||
// compute dF(u, du)/ddu
|
||||
auto processed_qf_farg_values_du = std::apply(
|
||||
[=](auto &... a) {
|
||||
return std::make_tuple(u_q,
|
||||
derivative_wrt(du_dx_q),
|
||||
EvaluateFargValue(a, qx, qy)...);
|
||||
},
|
||||
qf_farg_values);
|
||||
|
||||
auto [f0du, f1du] = std::apply(qf, processed_qf_farg_values_du);
|
||||
|
||||
tensor<double, 2> f01;
|
||||
if constexpr (std::is_same_v<decltype(f0du), double>)
|
||||
{
|
||||
f01 = {0.0, 0.0};
|
||||
}
|
||||
else
|
||||
{
|
||||
f01 = f0du.gradient;
|
||||
}
|
||||
tensor<double, 2, 2> f11 = {
|
||||
{{f1du[0].gradient[0], f1du[0].gradient[1]},
|
||||
{f1du[1].gradient[0], f1du[1].gradient[1]}}};
|
||||
|
||||
double W0 = f00 * v_q + f01[0] * dv_dx_q[0] + f01[1] * dv_dx_q[1];
|
||||
|
||||
double W1[2] = {f10[0] * v_q + f11[0][0] * dv_dx_q[0]
|
||||
+ f11[0][1] * dv_dx_q[1],
|
||||
f10[1] * v_q + f11[1][0] * dv_dx_q[0]
|
||||
+ +f11[1][1] * dv_dx_q[1]};
|
||||
|
||||
double W0_X = W0 * detJ_q;
|
||||
|
||||
// W1_X = invJ * W1 * detJ
|
||||
// = adjJ * W1
|
||||
double W1_X[2] = {
|
||||
adjJ[0][0] * W1[0] + adjJ[0][1] * W1[1],
|
||||
adjJ[1][0] * W1[0] + adjJ[1][1] * W1[1],
|
||||
};
|
||||
|
||||
for (int ix = 0; ix < D1D; ix++)
|
||||
{
|
||||
for (int iy = 0; iy < D1D; iy++)
|
||||
{
|
||||
// @TODO: proper comment
|
||||
y(ix, iy, e) += (W0_X * v1d(qx, ix) * v1d(qy, iy)
|
||||
+ W1_X[0] * dv1d_dX(qx, ix) * v1d(qy, iy)
|
||||
+ W1_X[1] * dv1d_dX(qy, iy) * v1d(qx, ix))
|
||||
* W(qx, qy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mfem
|
||||
@@ -0,0 +1,347 @@
|
||||
// tensor
|
||||
#include <iostream>
|
||||
|
||||
#pragma once
|
||||
|
||||
template<typename T, int... n>
|
||||
struct tensor;
|
||||
|
||||
template<typename T>
|
||||
struct tensor<T, 1>
|
||||
{
|
||||
static constexpr int shape[1] = {1};
|
||||
operator T() { return value; }
|
||||
T value;
|
||||
};
|
||||
|
||||
template<typename T, int n>
|
||||
struct tensor<T, n>
|
||||
{
|
||||
static constexpr int shape[1] = {n};
|
||||
constexpr auto &operator[](int i) { return value[i]; };
|
||||
constexpr auto operator[](int i) const { return value[i]; };
|
||||
T value[n];
|
||||
};
|
||||
|
||||
template<typename T, int first, int... rest>
|
||||
struct tensor<T, first, rest...>
|
||||
{
|
||||
static constexpr int shape[1 + sizeof...(rest)] = {first, rest...};
|
||||
constexpr auto &operator[](int i) { return value[i]; };
|
||||
constexpr auto operator[](int i) const { return value[i]; };
|
||||
tensor<T, rest...> value[first];
|
||||
};
|
||||
|
||||
template<int n>
|
||||
constexpr int product(int (&values)[n])
|
||||
{
|
||||
int p = 1;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
p *= values[i];
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
template<typename S, typename T, int... n>
|
||||
auto operator+(tensor<S, n...> A, tensor<T, n...> B)
|
||||
{
|
||||
tensor<decltype(S{} + T{}), n...> C{};
|
||||
for (int i = 0; i < tensor<T, n...>::shape[0]; i++)
|
||||
{
|
||||
C[i] = A[i] + B[i];
|
||||
}
|
||||
return C;
|
||||
}
|
||||
|
||||
template<typename S, typename T, int... n>
|
||||
auto operator-(tensor<S, n...> A, tensor<T, n...> B)
|
||||
{
|
||||
tensor<decltype(S{} - T{}), n...> C{};
|
||||
for (int i = 0; i < tensor<T, n...>::shape[0]; i++)
|
||||
{
|
||||
C[i] = A[i] - B[i];
|
||||
}
|
||||
return C;
|
||||
}
|
||||
|
||||
template<typename S, typename T, int... n>
|
||||
auto operator*(S scale, tensor<T, n...> A)
|
||||
{
|
||||
tensor<decltype(S{} * T{}), n...> C{};
|
||||
for (int i = 0; i < tensor<T, n...>::shape[0]; i++)
|
||||
{
|
||||
C[i] = scale * A[i];
|
||||
}
|
||||
return C;
|
||||
}
|
||||
|
||||
template<typename S, typename T, int... n>
|
||||
auto operator*(tensor<T, n...> A, S scale)
|
||||
{
|
||||
tensor<decltype(T{} * S{}), n...> C{};
|
||||
for (int i = 0; i < tensor<T, n...>::shape[0]; i++)
|
||||
{
|
||||
C[i] = A[i] * scale;
|
||||
}
|
||||
return C;
|
||||
}
|
||||
|
||||
template<typename S, typename T, int... n>
|
||||
auto operator/(S scale, tensor<T, n...> A)
|
||||
{
|
||||
tensor<decltype(S{} / T{}), n...> C{};
|
||||
for (int i = 0; i < tensor<T, n...>::shape[0]; i++)
|
||||
{
|
||||
C[i] = scale / A[i];
|
||||
}
|
||||
return C;
|
||||
}
|
||||
|
||||
template<typename S, typename T, int... n>
|
||||
auto operator/(tensor<T, n...> A, S scale)
|
||||
{
|
||||
tensor<decltype(T{} / S{}), n...> C{};
|
||||
for (int i = 0; i < tensor<T, n...>::shape[0]; i++)
|
||||
{
|
||||
C[i] = A[i] / scale;
|
||||
}
|
||||
return C;
|
||||
}
|
||||
|
||||
template<typename S, typename T, int m, int n, int p>
|
||||
auto dot(tensor<S, m, n> A, tensor<T, n, p> B)
|
||||
{
|
||||
tensor<decltype(S{} * T{}), m, p> AB{};
|
||||
for (int i = 0; i < m; i++)
|
||||
{
|
||||
for (int j = 0; j < p; j++)
|
||||
{
|
||||
for (int k = 0; k < n; k++)
|
||||
{
|
||||
AB[i][j] = AB[i][j] + A[i][k] * B[k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
return AB;
|
||||
}
|
||||
|
||||
template<typename T, int m, int n>
|
||||
auto inner(tensor<T, m, n> A, tensor<T, m, n> B)
|
||||
{
|
||||
double value = 0.0;
|
||||
for (int i = 0; i < m; i++)
|
||||
{
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
value += A[i][j] * B[i][j];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
auto inner(double a, double b)
|
||||
{
|
||||
return a * b;
|
||||
}
|
||||
|
||||
template<typename T, int n>
|
||||
auto tr(tensor<T, n, n> A)
|
||||
{
|
||||
T trA{};
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
trA = trA + A[i][i];
|
||||
}
|
||||
return trA;
|
||||
}
|
||||
|
||||
template<int dim>
|
||||
constexpr tensor<double, dim, dim> Identity()
|
||||
{
|
||||
tensor<double, dim, dim> I{};
|
||||
for (int i = 0; i < dim; i++)
|
||||
{
|
||||
for (int j = 0; j < dim; j++)
|
||||
{
|
||||
I[i][j] = (i == j);
|
||||
}
|
||||
}
|
||||
return I;
|
||||
}
|
||||
|
||||
template<typename T, int m, int n>
|
||||
auto transpose(const tensor<T, m, n> &A)
|
||||
{
|
||||
tensor<T, n, m> AT{};
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
for (int j = 0; j < m; j++)
|
||||
{
|
||||
AT[i][j] = A[j][i];
|
||||
}
|
||||
}
|
||||
return AT;
|
||||
}
|
||||
|
||||
template<typename T, int n>
|
||||
auto norm(const tensor<T, n> &A)
|
||||
{
|
||||
T r = {};
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
r = r + A[i] * A[i];
|
||||
}
|
||||
return pow(r, 0.5);
|
||||
}
|
||||
|
||||
template<typename gradient_type>
|
||||
struct dual
|
||||
{
|
||||
double value;
|
||||
gradient_type gradient;
|
||||
};
|
||||
|
||||
template<typename gradient_type>
|
||||
auto operator+(dual<gradient_type> a, double b)
|
||||
{
|
||||
return dual<gradient_type>{a.value + b, a.gradient};
|
||||
}
|
||||
|
||||
template<typename gradient_type>
|
||||
auto operator+(double a, dual<gradient_type> b)
|
||||
{
|
||||
return dual<gradient_type>{a + b.value, b.gradient};
|
||||
}
|
||||
|
||||
template<typename gradient_type>
|
||||
auto operator+(dual<gradient_type> a, dual<gradient_type> b)
|
||||
{
|
||||
return dual<gradient_type>{a.value + b.value, a.gradient + b.gradient};
|
||||
}
|
||||
|
||||
template<typename gradient_type>
|
||||
auto operator*(dual<gradient_type> a, double b)
|
||||
{
|
||||
return dual<gradient_type>{a.value * b, a.gradient * b};
|
||||
}
|
||||
|
||||
template<typename gradient_type>
|
||||
auto operator*(double a, dual<gradient_type> b)
|
||||
{
|
||||
return dual<gradient_type>{a * b.value, a * b.gradient};
|
||||
}
|
||||
|
||||
template<typename gradient_type>
|
||||
auto operator*(dual<gradient_type> a, dual<gradient_type> b)
|
||||
{
|
||||
return dual<gradient_type>{a.value * b.value,
|
||||
b.value * a.gradient + a.value * b.gradient};
|
||||
}
|
||||
|
||||
template<typename gradient_type>
|
||||
auto cos(dual<gradient_type> a)
|
||||
{
|
||||
return dual<gradient_type>{cos(a.value), -a.gradient * sin(a.value)};
|
||||
}
|
||||
|
||||
template<typename gradient_type>
|
||||
auto exp(dual<gradient_type> a)
|
||||
{
|
||||
return dual<gradient_type>{exp(a.value), exp(a.value)};
|
||||
}
|
||||
|
||||
template<typename gradient_type>
|
||||
auto log(dual<gradient_type> a)
|
||||
{
|
||||
return dual<gradient_type>{log(a.value), a.gradient / a.value};
|
||||
}
|
||||
|
||||
template<typename gradient_type>
|
||||
auto pow(dual<gradient_type> a, dual<gradient_type> b)
|
||||
{
|
||||
double value = pow(a.value, b.value);
|
||||
return dual<gradient_type>{value,
|
||||
value
|
||||
* (a.gradient * (b.value / a.value)
|
||||
+ b.gradient * log(a.value))};
|
||||
}
|
||||
|
||||
template<typename gradient_type>
|
||||
auto pow(double a, dual<gradient_type> b)
|
||||
{
|
||||
double value = pow(a, b.value);
|
||||
return dual<gradient_type>{value, value * b.gradient * log(a)};
|
||||
}
|
||||
|
||||
template<typename gradient_type>
|
||||
auto pow(dual<gradient_type> a, double b)
|
||||
{
|
||||
double value = pow(a.value, b);
|
||||
return dual<gradient_type>{value, value * a.gradient * b / a.value};
|
||||
}
|
||||
|
||||
template<typename T, int... n>
|
||||
auto &operator<<(std::ostream &out, dual<T> A)
|
||||
{
|
||||
out << '(' << A.value << ' ' << A.gradient << ')';
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename T, int... n>
|
||||
auto &operator<<(std::ostream &out, tensor<T, n...> A)
|
||||
{
|
||||
out << '{' << A[0];
|
||||
for (int i = 1; i < tensor<T, n...>::shape[0]; i++)
|
||||
{
|
||||
out << ", " << A[i];
|
||||
}
|
||||
out << '}';
|
||||
return out;
|
||||
}
|
||||
|
||||
auto derivative_wrt(double a)
|
||||
{
|
||||
return dual<double>{a, 1};
|
||||
}
|
||||
|
||||
template<typename T, int m>
|
||||
auto derivative_wrt(tensor<T, m> A)
|
||||
{
|
||||
tensor<dual<tensor<double, m>>, m> A_dual{};
|
||||
for (int i = 0; i < m; i++)
|
||||
{
|
||||
A_dual[i].value = A[i];
|
||||
A_dual[i].gradient[i] = 1.0;
|
||||
}
|
||||
return A_dual;
|
||||
}
|
||||
|
||||
template<typename T, int m, int n>
|
||||
auto derivative_wrt(tensor<T, m, n> A)
|
||||
{
|
||||
tensor<dual<tensor<double, m, n>>, m, n> A_dual{};
|
||||
for (int i = 0; i < m; i++)
|
||||
{
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
A_dual[i][j].value = A[i][j];
|
||||
A_dual[i][j].gradient[i][j] = 1.0;
|
||||
}
|
||||
}
|
||||
return A_dual;
|
||||
}
|
||||
|
||||
template<typename grad_type, int nrows, int ncols>
|
||||
auto directional_derivative(tensor<dual<grad_type>, nrows, ncols> A, grad_type n)
|
||||
{
|
||||
tensor<double, nrows, ncols> dA_dn{};
|
||||
for (int i = 0; i < nrows; i++)
|
||||
{
|
||||
for (int j = 0; j < ncols; j++)
|
||||
{
|
||||
dA_dn[i][j] = inner(A[i][j].gradient, n);
|
||||
}
|
||||
}
|
||||
return dA_dn;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "mfem.hpp"
|
||||
#include "tensor.hpp"
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch.hpp"
|
||||
|
||||
using namespace mfem;
|
||||
|
||||
TEST_CASE("Tensor tests", "[tensor]")
|
||||
{
|
||||
SECTION("norm")
|
||||
{
|
||||
tensor<double, 5> a = {{1.0, 2.0, 3.0, 4.0, 5.0}};
|
||||
REQUIRE(norm(a) - sqrt(55) == Approx(0.0));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Dual number tensor tests", "[DualNumber]")
|
||||
{
|
||||
auto eps = std::numeric_limits<double>::epsilon();
|
||||
double x = 0.5;
|
||||
|
||||
SECTION("cos")
|
||||
{
|
||||
auto xd = cos(derivative_wrt(x));
|
||||
REQUIRE(abs(-sin(x) - xd.gradient) == Approx(0.0));
|
||||
}
|
||||
|
||||
SECTION("exp")
|
||||
{
|
||||
auto xd = exp(derivative_wrt(x));
|
||||
REQUIRE(abs(exp(x) - xd.gradient) == Approx(0.0));
|
||||
}
|
||||
|
||||
SECTION("log")
|
||||
{
|
||||
auto xd = log(derivative_wrt(x));
|
||||
REQUIRE(abs(1.0 / x - xd.gradient) == Approx(0.0));
|
||||
}
|
||||
|
||||
SECTION("pow")
|
||||
{
|
||||
// f(x) = x^3/2
|
||||
auto xd = pow(derivative_wrt(x), 1.5);
|
||||
REQUIRE(abs(1.5 * pow(x, 0.5) - xd.gradient) == Approx(0.0));
|
||||
}
|
||||
|
||||
SECTION("mixed operations")
|
||||
{
|
||||
auto xd = derivative_wrt(x);
|
||||
auto r = cos(xd) * cos(xd);
|
||||
REQUIRE(abs(-2.0 * sin(x) * cos(x) - r.gradient) == Approx(0.0));
|
||||
|
||||
r = exp(xd) * cos(xd);
|
||||
REQUIRE(abs(exp(x) * (cos(x) - sin(x)) - r.gradient) < eps);
|
||||
|
||||
r = log(xd) * cos(xd);
|
||||
REQUIRE(abs((cos(x) / x - log(x) * sin(x)) - r.gradient) < eps);
|
||||
|
||||
r = exp(xd) * pow(xd, 1.5);
|
||||
REQUIRE(abs((exp(x) * (pow(x, 1.5) + 1.5 * pow(x, 0.5))) - r.gradient)
|
||||
< eps);
|
||||
|
||||
tensor<double, 2> vx = {{0.5, 0.25}};
|
||||
tensor<double, 2> vre = {{0.894427190999916, 0.4472135954999579}};
|
||||
auto vr = norm(derivative_wrt(vx));
|
||||
REQUIRE(norm(vr.gradient - vre) < eps);
|
||||
}
|
||||
}
|
||||
+58
-31
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Catch v2.13.0
|
||||
* Generated: 2020-07-12 20:07:49.015950
|
||||
* Catch v2.13.2
|
||||
* Generated: 2020-10-07 11:32:53.302017
|
||||
* ----------------------------------------------------------
|
||||
* This file has been merged from multiple headers. Please don't edit it directly
|
||||
* Copyright (c) 2020 Two Blue Cubes Ltd. All rights reserved.
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
#define CATCH_VERSION_MAJOR 2
|
||||
#define CATCH_VERSION_MINOR 13
|
||||
#define CATCH_VERSION_PATCH 0
|
||||
#define CATCH_VERSION_PATCH 2
|
||||
|
||||
#ifdef __clang__
|
||||
# pragma clang system_header
|
||||
@@ -132,13 +132,9 @@ namespace Catch {
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_lib_uncaught_exceptions)
|
||||
# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
|
||||
#endif
|
||||
|
||||
// We have to avoid both ICC and Clang, because they try to mask themselves
|
||||
// as gcc, and we want only GCC in this block
|
||||
#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC)
|
||||
#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__)
|
||||
# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" )
|
||||
# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" )
|
||||
|
||||
@@ -162,7 +158,7 @@ namespace Catch {
|
||||
// ```
|
||||
//
|
||||
// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented.
|
||||
# if !defined(__ibmxl__)
|
||||
# if !defined(__ibmxl__) && !defined(__CUDACC__)
|
||||
# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */
|
||||
# endif
|
||||
|
||||
@@ -244,10 +240,6 @@ namespace Catch {
|
||||
# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) )
|
||||
# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) )
|
||||
|
||||
# if _MSC_VER >= 1900 // Visual Studio 2015 or newer
|
||||
# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
|
||||
# endif
|
||||
|
||||
// Universal Windows platform does not support SEH
|
||||
// Or console colours (or console at all...)
|
||||
# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
|
||||
@@ -330,7 +322,10 @@ namespace Catch {
|
||||
|
||||
// Check if byte is available and usable
|
||||
# if __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER)
|
||||
# define CATCH_INTERNAL_CONFIG_CPP17_BYTE
|
||||
# include <cstddef>
|
||||
# if __cpp_lib_byte > 0
|
||||
# define CATCH_INTERNAL_CONFIG_CPP17_BYTE
|
||||
# endif
|
||||
# endif // __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER)
|
||||
|
||||
// Check if variant is available and usable
|
||||
@@ -373,10 +368,6 @@ namespace Catch {
|
||||
# define CATCH_CONFIG_CPP17_OPTIONAL
|
||||
#endif
|
||||
|
||||
#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)
|
||||
# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
|
||||
#endif
|
||||
|
||||
#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW)
|
||||
# define CATCH_CONFIG_CPP17_STRING_VIEW
|
||||
#endif
|
||||
@@ -1105,7 +1096,7 @@ struct AutoReg : NonCopyable {
|
||||
int index = 0; \
|
||||
constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\
|
||||
using expander = int[];\
|
||||
(void)expander{(reg_test(Types{}, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++, 0)... };/* NOLINT */ \
|
||||
(void)expander{(reg_test(Types{}, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \
|
||||
}\
|
||||
};\
|
||||
static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
|
||||
@@ -1151,7 +1142,7 @@ struct AutoReg : NonCopyable {
|
||||
constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\
|
||||
constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\
|
||||
constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\
|
||||
(void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++, 0)... };/* NOLINT */\
|
||||
(void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++)... };/* NOLINT */\
|
||||
} \
|
||||
}; \
|
||||
static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \
|
||||
@@ -1195,7 +1186,7 @@ struct AutoReg : NonCopyable {
|
||||
void reg_tests() { \
|
||||
int index = 0; \
|
||||
using expander = int[]; \
|
||||
(void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++, 0)... };/* NOLINT */\
|
||||
(void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\
|
||||
} \
|
||||
};\
|
||||
static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \
|
||||
@@ -1229,7 +1220,7 @@ struct AutoReg : NonCopyable {
|
||||
int index = 0; \
|
||||
constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\
|
||||
using expander = int[];\
|
||||
(void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++, 0)... };/* NOLINT */ \
|
||||
(void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \
|
||||
}\
|
||||
};\
|
||||
static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
|
||||
@@ -1278,7 +1269,7 @@ struct AutoReg : NonCopyable {
|
||||
constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\
|
||||
constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\
|
||||
constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\
|
||||
(void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++, 0)... };/* NOLINT */ \
|
||||
(void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++)... };/* NOLINT */ \
|
||||
}\
|
||||
};\
|
||||
static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
|
||||
@@ -1325,7 +1316,7 @@ struct AutoReg : NonCopyable {
|
||||
void reg_tests(){\
|
||||
int index = 0;\
|
||||
using expander = int[];\
|
||||
(void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++, 0)... };/* NOLINT */ \
|
||||
(void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \
|
||||
}\
|
||||
};\
|
||||
static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
|
||||
@@ -1829,8 +1820,8 @@ namespace Catch {
|
||||
#endif
|
||||
|
||||
namespace Detail {
|
||||
template<typename InputIterator>
|
||||
std::string rangeToString(InputIterator first, InputIterator last) {
|
||||
template<typename InputIterator, typename Sentinel = InputIterator>
|
||||
std::string rangeToString(InputIterator first, Sentinel last) {
|
||||
ReusableStringStream rss;
|
||||
rss << "{ ";
|
||||
if (first != last) {
|
||||
@@ -12042,7 +12033,7 @@ namespace Catch {
|
||||
if (tmpnam_s(m_buffer)) {
|
||||
CATCH_RUNTIME_ERROR("Could not get a temp filename");
|
||||
}
|
||||
if (fopen_s(&m_file, m_buffer, "w")) {
|
||||
if (fopen_s(&m_file, m_buffer, "w+")) {
|
||||
char buffer[100];
|
||||
if (strerror_s(buffer, errno)) {
|
||||
CATCH_RUNTIME_ERROR("Could not translate errno to a string");
|
||||
@@ -14091,10 +14082,10 @@ namespace Catch {
|
||||
|
||||
namespace {
|
||||
struct TestHasher {
|
||||
explicit TestHasher(Catch::SimplePcg32& rng) {
|
||||
basis = rng();
|
||||
explicit TestHasher(Catch::SimplePcg32& rng_instance) {
|
||||
basis = rng_instance();
|
||||
basis <<= 32;
|
||||
basis |= rng();
|
||||
basis |= rng_instance();
|
||||
}
|
||||
|
||||
uint64_t basis;
|
||||
@@ -15192,6 +15183,41 @@ namespace Catch {
|
||||
// end catch_totals.cpp
|
||||
// start catch_uncaught_exceptions.cpp
|
||||
|
||||
// start catch_config_uncaught_exceptions.hpp
|
||||
|
||||
// Copyright Catch2 Authors
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE_1_0.txt or copy at
|
||||
// https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#ifndef CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP
|
||||
#define CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# if _MSC_VER >= 1900 // Visual Studio 2015 or newer
|
||||
# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <exception>
|
||||
|
||||
#if defined(__cpp_lib_uncaught_exceptions) \
|
||||
&& !defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)
|
||||
|
||||
# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
|
||||
#endif // __cpp_lib_uncaught_exceptions
|
||||
|
||||
#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) \
|
||||
&& !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) \
|
||||
&& !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)
|
||||
|
||||
# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
|
||||
#endif
|
||||
|
||||
#endif // CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP
|
||||
// end catch_config_uncaught_exceptions.hpp
|
||||
#include <exception>
|
||||
|
||||
namespace Catch {
|
||||
@@ -15238,7 +15264,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
Version const& libraryVersion() {
|
||||
static Version version( 2, 13, 0, "", 0 );
|
||||
static Version version( 2, 13, 2, "", 0 );
|
||||
return version;
|
||||
}
|
||||
|
||||
@@ -17796,3 +17822,4 @@ using Catch::Detail::Approx;
|
||||
// end catch_reenable_warnings.h
|
||||
// end catch.hpp
|
||||
#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
|
||||
|
||||
|
||||
@@ -251,6 +251,24 @@ TEST_CASE("First order ODE methods",
|
||||
REQUIRE(conv_rate + tol > 4.0);
|
||||
}
|
||||
|
||||
SECTION("TrapezoidalRuleSolver")
|
||||
{
|
||||
std::cout <<"\nTesting TrapezoidalRuleSolver" << std::endl;
|
||||
REQUIRE(check.order(new TrapezoidalRuleSolver) + tol > 2.0 );
|
||||
}
|
||||
|
||||
SECTION("ESDIRK32Solver")
|
||||
{
|
||||
std::cout <<"\nTesting ESDIRK32Solver" << std::endl;
|
||||
REQUIRE(check.order(new ESDIRK32Solver) + tol > 2.0 );
|
||||
}
|
||||
|
||||
SECTION("ESDIRK33Solver")
|
||||
{
|
||||
std::cout <<"\nTesting ESDIRK33Solver" << std::endl;
|
||||
REQUIRE(check.order(new ESDIRK33Solver) + tol > 3.0 );
|
||||
}
|
||||
|
||||
// Generalized-alpha
|
||||
SECTION("GeneralizedAlphaSolver(1.0)")
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user