feat(libmeanfield): centrifugal + pressure
This commit is contained in:
@@ -45,6 +45,7 @@ target_sources(mean_field
|
||||
libmeanfield/impl/fem.cpp
|
||||
libmeanfield/impl/mapping/coefficients.cpp
|
||||
libmeanfield/impl/mapping/domain_mapper.cpp
|
||||
libmeanfield/impl/mapping/compactification/kelvin.cpp
|
||||
libmeanfield/impl/physics/gravity.cpp
|
||||
libmeanfield/impl/physics/solid.cpp
|
||||
libmeanfield/impl/utils/domain.cpp
|
||||
@@ -55,6 +56,21 @@ target_sources(mean_field
|
||||
libmeanfield/impl/integrators/gravity.cpp
|
||||
libmeanfield/impl/integrators/mass_continuity.cpp
|
||||
libmeanfield/impl/integrators/viscosity.cpp
|
||||
libmeanfield/impl/mapping/domain_mapper_new.cpp
|
||||
libmeanfield/impl/mapping/transformations.cpp
|
||||
libmeanfield/impl/operators/gravity_field.cpp
|
||||
libmeanfield/impl/operators/gravity_field_jacobian.cpp
|
||||
libmeanfield/impl/operators/kernels/gravity_kernels.cpp
|
||||
libmeanfield/impl/operators/prepared_hdiv_mass.cpp
|
||||
libmeanfield/impl/operators/prepared_gravity_source.cpp
|
||||
libmeanfield/impl/operators/contexts/gravity_field_context.cpp
|
||||
libmeanfield/impl/operators/kernels/barotropic_closure_kernels.cpp
|
||||
libmeanfield/impl/operators/prepared_barotropic_closure.cpp
|
||||
libmeanfield/impl/operators/contexts/barotropic_closure_linearization_context.cpp
|
||||
libmeanfield/impl/operators/kernels/hydrostatic_equilibrium_kernels.cpp
|
||||
libmeanfield/impl/operators/contexts/hydrostatic_equilibrium_context.cpp
|
||||
libmeanfield/impl/operators/prepared_hydrostatic_equilibrium.cpp
|
||||
libmeanfield/impl/operators/kernels/pressure_force_kernels.cpp
|
||||
)
|
||||
|
||||
target_sources(mean_field
|
||||
@@ -67,6 +83,10 @@ target_sources(mean_field
|
||||
libmeanfield/interface/mapping/coefficients.cppm
|
||||
libmeanfield/interface/mapping/domain_mapper.cppm
|
||||
libmeanfield/interface/mapping/types.cppm
|
||||
libmeanfield/interface/mapping/transformations.cppm
|
||||
libmeanfield/interface/mapping/compactification/compactification.cppm
|
||||
libmeanfield/interface/mapping/compactification/kelvin.cppm
|
||||
libmeanfield/interface/mapping/compactification/options.cppm
|
||||
libmeanfield/interface/physics/context.cppm
|
||||
libmeanfield/interface/physics/gravity.cppm
|
||||
libmeanfield/interface/physics/solid.cppm
|
||||
@@ -82,6 +102,26 @@ target_sources(mean_field
|
||||
libmeanfield/interface/integrators/viscosity.cppm
|
||||
libmeanfield/interface/quadrature/policy.cppm
|
||||
libmeanfield/interface/quadrature/mfem.cppm
|
||||
libmeanfield/interface/solver/fields.cppm
|
||||
libmeanfield/interface/operators/gravity_field.cppm
|
||||
libmeanfield/interface/operators/gravity_field_jacobian.cppm
|
||||
libmeanfield/interface/operators/kernels/gravity_kernels.cppm
|
||||
libmeanfield/interface/operators/kernels/barotropic_closure_kernels.cppm
|
||||
libmeanfield/interface/utils/blocks.cppm
|
||||
libmeanfield/interface/operators/contexts/gravity_field_context.cppm
|
||||
libmeanfield/interface/operators/prepared_gravity_source.cppm
|
||||
libmeanfield/interface/operators/prepared_hdiv_mass.cppm
|
||||
libmeanfield/interface/operators/prepared_hydrostatic_equilibrium_operator.cppm
|
||||
libmeanfield/interface/field/field_base.cppm
|
||||
libmeanfield/interface/field/field_registry.cppm
|
||||
libmeanfield/interface/field/field_mfem.cppm
|
||||
libmeanfield/interface/physics/barotrope.cppm
|
||||
libmeanfield/interface/operators/prepared_barotropic_closure_operator.cppm
|
||||
libmeanfield/interface/operators/contexts/barotropic_closure_linearization_context.cppm
|
||||
libmeanfield/interface/physics/rigid_rotation.cppm
|
||||
libmeanfield/interface/operators/kernels/hydrostatic_equilibrium_kernels.cppm
|
||||
libmeanfield/interface/operators/contexts/hydrostatic_equilibrium_context.cppm
|
||||
libmeanfield/interface/operators/kernels/pressure_force_kernels.cppm
|
||||
)
|
||||
|
||||
|
||||
@@ -116,10 +156,58 @@ add_executable(tests
|
||||
tests/physics/gravity.cpp
|
||||
tests/geometry/volume.cpp
|
||||
tests/quadrature/policy.cpp
|
||||
tests/integrators/centrifugal.cpp
|
||||
tests/integrators/gravity.cpp
|
||||
tests/mapping/domain_mapper.cpp
|
||||
tests/mapping/compactification/kelvin.cpp
|
||||
tests/utils/blocks.cpp
|
||||
tests/operators/gravity_field.cpp
|
||||
tests/mapping/hdiv_mass_tensor.cpp
|
||||
tests/operators/prepared_hdiv_mass.cpp
|
||||
tests/operators/prepared_gravity_source.cpp
|
||||
tests/operators/contexts/gravity_field_context.cpp
|
||||
tests/physics/gravity_monopole_accuracy.cpp
|
||||
tests/physics/barotrope.cpp
|
||||
tests/operators/kernels/barotropic_closure_kernels.cpp
|
||||
tests/operators/prepared_barotropic_closure.cpp
|
||||
tests/operators/contexts/barotropic_closure_linearization_context.cpp
|
||||
tests/operators/kernels/hydrostatic_equilibrium_kernels.cpp
|
||||
tests/operators/contexts/hydrostatic_equilibrium_context.cpp
|
||||
tests/operators/prepared_hydrostatic_equilibrium.cpp
|
||||
tests/operators/prepared_hydrostatic_equilibrium_jacobian.cpp
|
||||
tests/operators/prepared_hydrostatic_equilibrium_displacement_jacobian.cpp
|
||||
tests/operators/prepared_hydrostatic_equilibrium_complete_jacobian.cpp
|
||||
tests/operators/prepared_hydrostatic_equilibrium_analytic_accuracy.cpp
|
||||
tests/physics/barotrope_pressure.cpp
|
||||
tests/operators/kernels/pressure_force_kernels.cpp
|
||||
|
||||
)
|
||||
|
||||
target_link_libraries(tests PRIVATE mean_field test_mod Catch2::Catch2 Boost::boost)
|
||||
|
||||
add_library(experiment_mod)
|
||||
target_sources(experiment_mod
|
||||
PUBLIC
|
||||
FILE_SET CXX_MODULES FILES
|
||||
experiments/experiment_results.cppm
|
||||
)
|
||||
target_link_libraries(experiment_mod
|
||||
PUBLIC
|
||||
mean_field
|
||||
test_mod
|
||||
)
|
||||
|
||||
add_executable(experiments
|
||||
experiments/experiment_main.cpp
|
||||
experiments/gravity_accuracy_budget.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(experiments PRIVATE mean_field test_mod experiment_mod Catch2::Catch2 Boost::boost)
|
||||
|
||||
include (CTest)
|
||||
include (Catch)
|
||||
catch_discover_tests(tests)
|
||||
catch_discover_tests(
|
||||
tests
|
||||
experiments
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
)
|
||||
296
clang-format-styles/style
Normal file
296
clang-format-styles/style
Normal file
@@ -0,0 +1,296 @@
|
||||
---
|
||||
BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: true
|
||||
AcrossComments: true
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: true
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCaseArrows: false
|
||||
AlignCaseColons: false
|
||||
AlignConsecutiveTableGenBreakingDAGArgColons:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveTableGenCondOperatorColons:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveTableGenDefinitionColons:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Always
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowBreakBeforeNoexceptSpecifier: Never
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseExpressionOnASingleLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortCompoundRequirementOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortNamespacesOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: true
|
||||
BinPackLongBracedList: true
|
||||
BinPackParameters: AlwaysOnePerLine
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: true
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BracedInitializerIndentWidth: -1
|
||||
BreakAdjacentStringLiterals: true
|
||||
BreakAfterAttributes: Leave
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakAfterReturnType: None
|
||||
BreakArrays: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTemplateCloser: false
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakBinaryOperations: Never
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakFunctionDefinitionParameters: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: true
|
||||
BreakTemplateDeclarations: MultiLine
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: "^ IWYU pragma:"
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: Always
|
||||
EnumTrailingComma: Leave
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: ^"(llvm|llvm-c|clang|clang-c)/
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: ^(<|"(gtest|gmock|isl|json)/)
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: .*
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: (Test)?$
|
||||
IncludeIsMainSourceRegex: ""
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExportBlock: true
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
InsertNewlineAtEOF: false
|
||||
InsertTrailingCommas: None
|
||||
IntegerLiteralSeparator:
|
||||
Binary: 0
|
||||
BinaryMinDigits: 0
|
||||
Decimal: 0
|
||||
DecimalMinDigits: 0
|
||||
Hex: 0
|
||||
HexMinDigits: 0
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLines:
|
||||
AtEndOfFile: false
|
||||
AtStartOfBlock: true
|
||||
AtStartOfFile: true
|
||||
KeepFormFeed: false
|
||||
LambdaBodyIndentation: Signature
|
||||
LineEnding: DeriveLF
|
||||
MacroBlockBegin: ""
|
||||
MacroBlockEnd: ""
|
||||
MainIncludeChar: Quote
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: All
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
OneLineFormatOffRegex: ""
|
||||
PPIndentWidth: -1
|
||||
PackConstructorInitializers: BinPack
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakBeforeMemberAccess: 150
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakScopeResolution: 500
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Right
|
||||
QualifierAlignment: Leave
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: Always
|
||||
RemoveBracesLLVM: false
|
||||
RemoveEmptyLinesInUnwrappedLines: false
|
||||
RemoveParentheses: Leave
|
||||
RemoveSemicolon: false
|
||||
RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SkipMacroDefinitionBody: false
|
||||
SortIncludes:
|
||||
Enabled: true
|
||||
IgnoreCase: false
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: LexicographicNumeric
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterOperatorKeyword: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeJsonColon: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterForeachMacros: true
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterIfMacros: true
|
||||
AfterNot: false
|
||||
AfterOverloadedOperator: false
|
||||
AfterPlacementOperator: true
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: true
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParens: Never
|
||||
SpacesInParensOptions:
|
||||
ExceptDoubleParentheses: false
|
||||
InConditionalStatements: false
|
||||
InCStyleCasts: false
|
||||
InEmptyParentheses: false
|
||||
Other: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Latest
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
TableGenBreakInsideDAGArg: DontBreak
|
||||
UseTab: Never
|
||||
VerilogBreakBetweenInstancePorts: true
|
||||
WhitespaceSensitiveMacros:
|
||||
- BOOST_PP_STRINGIZE
|
||||
- CF_SWIFT_NAME
|
||||
- NS_SWIFT_NAME
|
||||
- PP_STRINGIZE
|
||||
- STRINGIZE
|
||||
WrapNamespaceBodyWithEmptyLines: Leave
|
||||
Language: Cpp
|
||||
30
experiments/README.md
Normal file
30
experiments/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Accuracy experiments
|
||||
|
||||
This directory is deliberately separate from `tests/`. It uses Catch2 only as
|
||||
an MPI-aware runner, selector, configuration host, and failure reporter. A run
|
||||
fails only when the calculation is invalid (for example, non-finite output or a
|
||||
failed linear solve); measured accuracy is written as data, not as a regression
|
||||
tolerance.
|
||||
|
||||
`experiment_main.cpp` registers the `experiment` Catch2 reporter. It collects
|
||||
rows recorded with `record_experiment_result` and writes one wide CSV table on
|
||||
MPI rank zero. Each row carries sweep parameters and numerical metrics, so
|
||||
results can be plotted or compared across commits.
|
||||
|
||||
The initial budget has three sweeps at fixed mesh and polynomial orders:
|
||||
|
||||
1. linear-solver tolerance: `1e-8`, `1e-10`, `1e-12`, `1e-14`;
|
||||
2. production quadrature boost: `0`, `4`, `8`;
|
||||
3. reference-space decomposition: numerical field/potential error, analytic
|
||||
projection error, and numerical-to-projection gap.
|
||||
|
||||
Run only the budget and choose its output path with:
|
||||
|
||||
```text
|
||||
./mean_field_experiments --experiment-output gravity_budget.csv --catch2 "[accuracy]"
|
||||
```
|
||||
|
||||
The executable needs the same dependencies, generated module mapping, and
|
||||
configuration registration as the existing Catch2 test executable. Add
|
||||
`experiment_main.cpp` and `gravity_accuracy_budget.cpp` as a second executable
|
||||
next to that target; do not add them to the ordinary test executable.
|
||||
211
experiments/experiment_main.cpp
Normal file
211
experiments/experiment_main.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
#include <catch2/catch_session.hpp>
|
||||
#include <catch2/reporters/catch_reporter_registrars.hpp>
|
||||
#include <catch2/reporters/catch_reporter_streaming_base.hpp>
|
||||
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <fourdst/config/config.h>
|
||||
#include <mfem.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <catch2/catch_test_case_info.hpp>
|
||||
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
import experiment;
|
||||
|
||||
using namespace experiment;
|
||||
|
||||
static std::string escape_csv(const std::string& value) {
|
||||
if (value.find_first_of(",\"\n") == std::string::npos) {
|
||||
return value;
|
||||
}
|
||||
|
||||
std::string escaped{"\""};
|
||||
for (const char character : value) {
|
||||
if (character == '\"') {
|
||||
escaped += "\"\"";
|
||||
} else {
|
||||
escaped += character;
|
||||
}
|
||||
}
|
||||
escaped += '\"';
|
||||
return escaped;
|
||||
}
|
||||
|
||||
static void write_experiment_csv() {
|
||||
int rank = 0;
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||
if (rank != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::vector<ExperimentResult> results = ExperimentRegistry::instance().results();
|
||||
std::set<std::string> parameter_names;
|
||||
std::set<std::string> metric_names;
|
||||
|
||||
for (const ExperimentResult& result : results) {
|
||||
for (const auto& [name, value] : result.parameters) {
|
||||
parameter_names.insert(name);
|
||||
}
|
||||
for (const auto& [name, value] : result.metrics) {
|
||||
metric_names.insert(name);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string output_path = ExperimentRegistry::instance().output_path();
|
||||
std::ofstream output(output_path);
|
||||
if (!output) {
|
||||
std::cerr << "Unable to write experiment results to " << output_path << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
output << "experiment,case";
|
||||
for (const std::string& name : parameter_names) {
|
||||
output << ',' << escape_csv(name);
|
||||
}
|
||||
for (const std::string& name : metric_names) {
|
||||
output << ',' << escape_csv(name);
|
||||
}
|
||||
output << '\n';
|
||||
|
||||
output << std::setprecision(17);
|
||||
for (const ExperimentResult& result : results) {
|
||||
output << escape_csv(result.experiment_name) << ',' << escape_csv(result.case_name);
|
||||
for (const std::string& name : parameter_names) {
|
||||
const auto iterator = result.parameters.find(name);
|
||||
output << ',' << (iterator == result.parameters.end() ? "" : escape_csv(iterator->second));
|
||||
}
|
||||
for (const std::string& name : metric_names) {
|
||||
const auto iterator = result.metrics.find(name);
|
||||
output << ',';
|
||||
if (iterator != result.metrics.end()) {
|
||||
output << iterator->second;
|
||||
}
|
||||
}
|
||||
output << '\n';
|
||||
}
|
||||
|
||||
std::cout << "Wrote " << results.size() << " experiment rows to " << output_path << '\n';
|
||||
}
|
||||
|
||||
class ExperimentReporter final : public Catch::StreamingReporterBase {
|
||||
public:
|
||||
using StreamingReporterBase::StreamingReporterBase;
|
||||
|
||||
static std::string getDescription() {
|
||||
return "Compact console reporter that writes structured experiment measurements to CSV.";
|
||||
}
|
||||
|
||||
void testCaseEnded(const Catch::TestCaseStats& statistics) override {
|
||||
StreamingReporterBase::testCaseEnded(statistics);
|
||||
const bool passed = statistics.totals.assertions.allPassed();
|
||||
std::cout << (passed ? "PASS " : "FAIL ")
|
||||
<< statistics.testInfo->name
|
||||
<< " (" << statistics.totals.assertions.passed
|
||||
<< " assertions)\n";
|
||||
}
|
||||
|
||||
void testRunEnded(const Catch::TestRunStats& statistics) override {
|
||||
StreamingReporterBase::testRunEnded(statistics);
|
||||
write_experiment_csv();
|
||||
}
|
||||
};
|
||||
|
||||
CATCH_REGISTER_REPORTER("experiment", ExperimentReporter)
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
fourdst::config::Config<mean_field::utils::Args> config;
|
||||
CLI::App app{"Mean Field accuracy experiments"};
|
||||
|
||||
app.allow_extras();
|
||||
app.set_help_flag("--config-help", "Show mean-field configuration options");
|
||||
fourdst::config::register_as_cli(config, app);
|
||||
|
||||
std::string output_path{"accuracy_budget.csv"};
|
||||
app.add_option("--experiment-output", output_path, "CSV path for structured measurements");
|
||||
|
||||
std::vector<std::string> configuration_arguments{argv[0]};
|
||||
std::vector<std::string> catch_arguments_from_command_line;
|
||||
bool parsing_catch_arguments = false;
|
||||
|
||||
for (int index = 1; index < argc; ++index) {
|
||||
if (std::string_view(argv[index]) == "--catch2") {
|
||||
parsing_catch_arguments = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parsing_catch_arguments) {
|
||||
catch_arguments_from_command_line.emplace_back(argv[index]);
|
||||
} else {
|
||||
configuration_arguments.emplace_back(argv[index]);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<const char*> configuration_argv;
|
||||
configuration_argv.reserve(configuration_arguments.size());
|
||||
for (const std::string& argument : configuration_arguments) {
|
||||
configuration_argv.push_back(argument.c_str());
|
||||
}
|
||||
|
||||
try {
|
||||
app.parse(static_cast<int>(configuration_argv.size()), configuration_argv.data());
|
||||
} catch (const CLI::ParseError& error) {
|
||||
return app.exit(error);
|
||||
}
|
||||
|
||||
std::vector<std::string> catch_arguments{argv[0]};
|
||||
for (const std::string& argument : app.remaining()) {
|
||||
catch_arguments.push_back(argument);
|
||||
}
|
||||
for (const std::string& argument : catch_arguments_from_command_line) {
|
||||
catch_arguments.push_back(argument);
|
||||
}
|
||||
|
||||
bool has_reporter = false;
|
||||
for (const std::string& argument : catch_arguments) {
|
||||
has_reporter = has_reporter || argument == "-r" || argument == "--reporter" ||
|
||||
argument.starts_with("-r=") || argument.starts_with("--reporter=");
|
||||
}
|
||||
if (!has_reporter) {
|
||||
catch_arguments.emplace_back("--reporter");
|
||||
catch_arguments.emplace_back("experiment");
|
||||
}
|
||||
|
||||
std::vector<const char*> catch_argv;
|
||||
catch_argv.reserve(catch_arguments.size());
|
||||
for (const std::string& argument : catch_arguments) {
|
||||
catch_argv.push_back(argument.c_str());
|
||||
}
|
||||
|
||||
Catch::Session session;
|
||||
if (const int parse_result = session.applyCommandLine(static_cast<int>(catch_argv.size()), catch_argv.data());
|
||||
parse_result != 0) {
|
||||
return parse_result;
|
||||
}
|
||||
|
||||
mfem::Mpi::Init(argc, argv);
|
||||
mfem::Device device("cpu");
|
||||
|
||||
mean_field::utils::Args args = config.main();
|
||||
if (app.count("--mesh_file") == 0) {
|
||||
args.mesh_file = "sandbox.smesh";
|
||||
}
|
||||
if (app.count("--p.rtol") == 0) {
|
||||
args.p.rtol = 1.0e-12;
|
||||
}
|
||||
if (app.count("--p.atol") == 0) {
|
||||
args.p.atol = 1.0e-12;
|
||||
}
|
||||
|
||||
ExperimentRegistry::instance().set_output_path(output_path);
|
||||
test_utils::set_args(std::move(args));
|
||||
return session.run();
|
||||
}
|
||||
65
experiments/experiment_results.cppm
Normal file
65
experiments/experiment_results.cppm
Normal file
@@ -0,0 +1,65 @@
|
||||
module;
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
export module experiment;
|
||||
|
||||
export namespace experiment {
|
||||
struct ExperimentResult {
|
||||
std::string experiment_name;
|
||||
std::string case_name;
|
||||
std::map<std::string, std::string> parameters;
|
||||
std::map<std::string, double> metrics;
|
||||
};
|
||||
|
||||
class ExperimentRegistry {
|
||||
public:
|
||||
static ExperimentRegistry& instance() {
|
||||
static ExperimentRegistry registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
void add_result(ExperimentResult result) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_results.push_back(std::move(result));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<ExperimentResult> results() const {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
return m_results;
|
||||
}
|
||||
|
||||
void set_output_path(std::string output_path) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_output_path = std::move(output_path);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string output_path() const {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
return m_output_path;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex m_mutex;
|
||||
std::vector<ExperimentResult> m_results;
|
||||
std::string m_output_path{"accuracy_budget.csv"};
|
||||
};
|
||||
|
||||
inline void record_experiment_result(
|
||||
const std::string& experiment_name,
|
||||
const std::string& case_name,
|
||||
std::map<std::string, std::string> parameters,
|
||||
std::map<std::string, double> metrics
|
||||
) {
|
||||
ExperimentRegistry::instance().add_result({
|
||||
.experiment_name = experiment_name,
|
||||
.case_name = case_name,
|
||||
.parameters = std::move(parameters),
|
||||
.metrics = std::move(metrics)
|
||||
});
|
||||
}
|
||||
}
|
||||
616
experiments/gravity_accuracy_budget.cpp
Normal file
616
experiments/gravity_accuracy_budget.cpp
Normal file
@@ -0,0 +1,616 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
import experiment;
|
||||
|
||||
using namespace experiment;
|
||||
|
||||
struct AccuracyBudgetEnergies {
|
||||
double binding{0.0};
|
||||
double virial{0.0};
|
||||
};
|
||||
|
||||
struct AccuracyBudgetMetrics {
|
||||
double direct_relative_residual{0.0};
|
||||
double gradient_relative_error{0.0};
|
||||
double gradient_projection_relative_error{0.0};
|
||||
double gradient_solution_projection_gap{0.0};
|
||||
double potential_relative_error{0.0};
|
||||
double potential_projection_relative_error{0.0};
|
||||
double potential_solution_projection_gap{0.0};
|
||||
double binding_relative_error{0.0};
|
||||
double virial_relative_error{0.0};
|
||||
double virial_consistency_error{0.0};
|
||||
};
|
||||
|
||||
static double global_norm(const mfem::Vector& vector, MPI_Comm communicator) {
|
||||
const double local_norm_squared = vector * vector;
|
||||
double global_norm_squared = 0.0;
|
||||
MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
||||
return std::sqrt(global_norm_squared);
|
||||
}
|
||||
|
||||
static double global_dot(const mfem::Vector& left, const mfem::Vector& right, MPI_Comm communicator) {
|
||||
const double local_dot = left * right;
|
||||
double global_dot_product = 0.0;
|
||||
MPI_Allreduce(&local_dot, &global_dot_product, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
||||
return global_dot_product;
|
||||
}
|
||||
|
||||
static void zero_vacuum_density(const mean_field::fem::FEM& fem, mfem::GridFunction& density) {
|
||||
for (int index = 0; index < fem.vacuum_tdof_rho.Size(); ++index) {
|
||||
density(fem.vacuum_tdof_rho[index]) = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
static int diagnostic_quadrature_order(const mean_field::fem::FEM& fem) {
|
||||
return 2 * std::max(fem.L2_fes->GetMaxElementOrder(), fem.RT_fes->GetMaxElementOrder()) + 8;
|
||||
}
|
||||
|
||||
static mfem::Vector assemble_monopole_projection_rhs(
|
||||
mean_field::fem::FEM& fem,
|
||||
const mfem::GridFunction& displacement,
|
||||
const double mass,
|
||||
const double stellar_radius
|
||||
) {
|
||||
static_cast<void>(displacement);
|
||||
|
||||
mfem::Vector local_rhs(fem.RT_fes->GetVSize());
|
||||
local_rhs = 0.0;
|
||||
|
||||
const int vacuum_attribute = fem.domain_mapper_stateless->GetVacuumElementAttribute();
|
||||
const int quadrature_order = diagnostic_quadrature_order(fem);
|
||||
|
||||
for (int element_id = 0; element_id < fem.mesh->GetNE(); ++element_id) {
|
||||
const mfem::FiniteElement& gravity_element = *fem.RT_fes->GetFE(element_id);
|
||||
mfem::ElementTransformation* transformation = fem.mesh->GetElementTransformation(element_id);
|
||||
|
||||
mfem::Array<int> gravity_dofs;
|
||||
mfem::DofTransformation* gravity_transform = fem.RT_fes->GetElementVDofs(element_id, gravity_dofs);
|
||||
|
||||
const int dof_count = gravity_element.GetDof();
|
||||
const int dimension = transformation->GetSpaceDim();
|
||||
mfem::Vector element_rhs(dof_count);
|
||||
mfem::Vector physical_position(dimension);
|
||||
mfem::Vector analytic_field(dimension);
|
||||
mfem::Vector pulled_field(dimension);
|
||||
mfem::DenseMatrix mapping_jacobian(dimension);
|
||||
mfem::DenseMatrix vector_shape(dof_count, dimension);
|
||||
element_rhs = 0.0;
|
||||
|
||||
const mfem::IntegrationRule& rule = mfem::IntRules.Get(
|
||||
transformation->GetGeometryType(),
|
||||
quadrature_order
|
||||
);
|
||||
|
||||
for (int quadrature_point_id = 0; quadrature_point_id < rule.GetNPoints(); ++quadrature_point_id) {
|
||||
const mfem::IntegrationPoint& point = rule.IntPoint(quadrature_point_id);
|
||||
fem.mapping->GetPhysicalPoint(*transformation, point, physical_position);
|
||||
|
||||
const double radius = physical_position.Norml2();
|
||||
MFEM_VERIFY(std::isfinite(radius) && radius > 0.0, "Invalid radius in monopole projection RHS.");
|
||||
|
||||
analytic_field = physical_position;
|
||||
if (transformation->Attribute == vacuum_attribute) {
|
||||
analytic_field *= mean_field::utils::G * mass / (radius * radius * radius);
|
||||
} else {
|
||||
analytic_field *= mean_field::utils::G * mass /
|
||||
(stellar_radius * stellar_radius * stellar_radius);
|
||||
}
|
||||
|
||||
fem.mapping->ComputeJacobian(*transformation, mapping_jacobian);
|
||||
mapping_jacobian.MultTranspose(analytic_field, pulled_field);
|
||||
|
||||
transformation->SetIntPoint(&point);
|
||||
gravity_element.CalcVShape(*transformation, vector_shape);
|
||||
const double reference_weight = point.weight * transformation->Weight();
|
||||
|
||||
for (int dof = 0; dof < dof_count; ++dof) {
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
element_rhs(dof) += reference_weight * vector_shape(dof, component) * pulled_field(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gravity_transform != nullptr) {
|
||||
gravity_transform->TransformDual(element_rhs);
|
||||
}
|
||||
local_rhs.AddElementVector(gravity_dofs, element_rhs);
|
||||
}
|
||||
|
||||
mfem::Vector true_rhs(fem.RT_fes->GetTrueVSize());
|
||||
true_rhs = 0.0;
|
||||
const mfem::Operator* prolongation = fem.RT_fes->GetProlongationMatrix();
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->MultTranspose(local_rhs, true_rhs);
|
||||
} else {
|
||||
true_rhs = local_rhs;
|
||||
}
|
||||
|
||||
return true_rhs;
|
||||
}
|
||||
|
||||
static mfem::Vector project_monopole_gradient(
|
||||
mean_field::fem::FEM& fem,
|
||||
const mfem::GridFunction& displacement,
|
||||
const double mass,
|
||||
const double stellar_radius
|
||||
) {
|
||||
mfem::Vector displacement_true;
|
||||
displacement.GetTrueDofs(displacement_true);
|
||||
|
||||
const mfem::Vector projection_rhs = assemble_monopole_projection_rhs(
|
||||
fem,
|
||||
displacement,
|
||||
mass,
|
||||
stellar_radius
|
||||
);
|
||||
|
||||
mean_field::operators::PreparedMappedHDivMassOperator mass_operator(
|
||||
fem,
|
||||
*fem.domain_mapper_stateless
|
||||
);
|
||||
mass_operator.Prepare(displacement_true);
|
||||
|
||||
mfem::CGSolver solver(fem.RT_fes->GetComm());
|
||||
solver.SetOperator(mass_operator);
|
||||
solver.SetRelTol(1.0e-11);
|
||||
solver.SetAbsTol(1.0e-13);
|
||||
solver.SetMaxIter(4000);
|
||||
solver.SetPrintLevel(0);
|
||||
|
||||
mfem::Vector projected_gradient(fem.RT_fes->GetTrueVSize());
|
||||
projected_gradient = 0.0;
|
||||
solver.Mult(projection_rhs, projected_gradient);
|
||||
|
||||
mfem::Vector residual;
|
||||
mass_operator.Mult(projected_gradient, residual);
|
||||
residual -= projection_rhs;
|
||||
|
||||
const double relative_residual = global_norm(residual, fem.RT_fes->GetComm()) /
|
||||
std::max(global_norm(projection_rhs, fem.RT_fes->GetComm()), std::numeric_limits<double>::epsilon());
|
||||
|
||||
REQUIRE(std::isfinite(relative_residual));
|
||||
REQUIRE(relative_residual < 1.0e-8);
|
||||
return projected_gradient;
|
||||
}
|
||||
|
||||
static double mapped_hdiv_relative_gap(
|
||||
mean_field::fem::FEM& fem,
|
||||
const mfem::GridFunction& displacement,
|
||||
const mfem::Vector& calculated,
|
||||
const mfem::Vector& reference
|
||||
) {
|
||||
mfem::Vector displacement_true;
|
||||
displacement.GetTrueDofs(displacement_true);
|
||||
|
||||
mean_field::operators::PreparedMappedHDivMassOperator mass_operator(
|
||||
fem,
|
||||
*fem.domain_mapper_stateless
|
||||
);
|
||||
mass_operator.Prepare(displacement_true);
|
||||
|
||||
mfem::Vector difference(calculated);
|
||||
difference -= reference;
|
||||
mfem::Vector difference_action;
|
||||
mfem::Vector reference_action;
|
||||
mass_operator.Mult(difference, difference_action);
|
||||
mass_operator.Mult(reference, reference_action);
|
||||
|
||||
const double difference_energy = global_dot(difference, difference_action, fem.RT_fes->GetComm());
|
||||
const double reference_energy = global_dot(reference, reference_action, fem.RT_fes->GetComm());
|
||||
MFEM_VERIFY(reference_energy > 0.0, "Projected monopole field has zero mapped H(div) norm.");
|
||||
|
||||
return std::sqrt(std::max(0.0, difference_energy) / reference_energy);
|
||||
}
|
||||
|
||||
static AccuracyBudgetEnergies measure_stellar_energies(
|
||||
mean_field::fem::FEM& fem,
|
||||
const mfem::GridFunction& density,
|
||||
const mean_field::physics::GravitySolution& solution
|
||||
) {
|
||||
const int vacuum_attribute = fem.domain_mapper_stateless->GetVacuumElementAttribute();
|
||||
const int quadrature_order = diagnostic_quadrature_order(fem);
|
||||
double local_binding = 0.0;
|
||||
double local_virial = 0.0;
|
||||
|
||||
mfem::Vector physical_position(3);
|
||||
mfem::Vector reference_field(3);
|
||||
mfem::Vector physical_field(3);
|
||||
mfem::DenseMatrix mapping_jacobian(3);
|
||||
|
||||
for (int element_id = 0; element_id < fem.mesh->GetNE(); ++element_id) {
|
||||
mfem::ElementTransformation* transformation = fem.mesh->GetElementTransformation(element_id);
|
||||
if (transformation->Attribute == vacuum_attribute) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mfem::IntegrationRule& rule = mfem::IntRules.Get(
|
||||
transformation->GetGeometryType(),
|
||||
quadrature_order
|
||||
);
|
||||
|
||||
for (int quadrature_point_id = 0; quadrature_point_id < rule.GetNPoints(); ++quadrature_point_id) {
|
||||
const mfem::IntegrationPoint& point = rule.IntPoint(quadrature_point_id);
|
||||
transformation->SetIntPoint(&point);
|
||||
|
||||
fem.mapping->GetPhysicalPoint(*transformation, point, physical_position);
|
||||
fem.mapping->ComputeJacobian(*transformation, mapping_jacobian);
|
||||
const double mapping_determinant = mapping_jacobian.Det();
|
||||
MFEM_VERIFY(mapping_determinant > 0.0, "Non-positive mapping determinant in energy diagnostic.");
|
||||
|
||||
solution.gradPhi.GetVectorValue(element_id, point, reference_field);
|
||||
mapping_jacobian.Mult(reference_field, physical_field);
|
||||
physical_field /= mapping_determinant;
|
||||
|
||||
const double weight = point.weight * transformation->Weight() * mapping_determinant;
|
||||
const double rho = density.GetValue(element_id, point);
|
||||
const double phi = solution.phi.GetValue(element_id, point);
|
||||
local_binding += 0.5 * rho * phi * weight;
|
||||
local_virial -= rho * (physical_position * physical_field) * weight;
|
||||
}
|
||||
}
|
||||
|
||||
AccuracyBudgetEnergies energies;
|
||||
MPI_Allreduce(&local_binding, &energies.binding, 1, MPI_DOUBLE, MPI_SUM, fem.L2_fes->GetComm());
|
||||
MPI_Allreduce(&local_virial, &energies.virial, 1, MPI_DOUBLE, MPI_SUM, fem.L2_fes->GetComm());
|
||||
return energies;
|
||||
}
|
||||
|
||||
static double reduced_gravity_relative_residual(
|
||||
mean_field::fem::FEM& fem,
|
||||
const mfem::GridFunction& density,
|
||||
const mfem::GridFunction& displacement,
|
||||
const mean_field::physics::GravitySolution& solution
|
||||
) {
|
||||
using GravityFieldForm = mean_field::utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto gradient_block = mean_field::utils::blocks::get_residual_block<GravityFieldForm>(
|
||||
mean_field::utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto poisson_block = mean_field::utils::blocks::get_residual_block<GravityFieldForm>(
|
||||
mean_field::utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
|
||||
const std::array<int, GravityFieldForm::value_block_count> value_sizes{
|
||||
fem.L2_fes->GetTrueVSize(), fem.Vec_H1_fes->GetTrueVSize(),
|
||||
fem.RT_fes->GetTrueVSize(), fem.L2_fes->GetTrueVSize()
|
||||
};
|
||||
const std::array<int, GravityFieldForm::residual_block_count> residual_sizes{
|
||||
fem.RT_fes->GetTrueVSize(), fem.L2_fes->GetTrueVSize()
|
||||
};
|
||||
const mean_field::utils::blocks::form_layout<GravityFieldForm> layout(value_sizes, residual_sizes);
|
||||
|
||||
mfem::Vector density_true;
|
||||
mfem::Vector displacement_true;
|
||||
mfem::Vector gradient_true;
|
||||
mfem::Vector potential_true;
|
||||
density.GetTrueDofs(density_true);
|
||||
displacement.GetTrueDofs(displacement_true);
|
||||
solution.gradPhi.GetTrueDofs(gradient_true);
|
||||
solution.phi.GetTrueDofs(potential_true);
|
||||
|
||||
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
||||
fem,
|
||||
*fem.domain_mapper_stateless
|
||||
);
|
||||
mean_field::operators::GravityFieldJacobianOperator jacobian(
|
||||
fem,
|
||||
*fem.domain_mapper_stateless,
|
||||
linearization_context,
|
||||
layout.value_offsets(),
|
||||
layout.residual_offsets()
|
||||
);
|
||||
mean_field::operators::GravityFieldOperator field_operator(
|
||||
fem,
|
||||
*fem.domain_mapper_stateless,
|
||||
linearization_context,
|
||||
layout.value_offsets(),
|
||||
jacobian
|
||||
);
|
||||
mean_field::operators::context::gravity_field::GravityFieldGeometryContext geometry_context(
|
||||
fem,
|
||||
*fem.domain_mapper_stateless
|
||||
);
|
||||
mean_field::operators::ReducedGravityFieldOperator reduced_operator(
|
||||
field_operator,
|
||||
geometry_context,
|
||||
displacement_true
|
||||
);
|
||||
|
||||
mfem::Vector right_hand_side;
|
||||
reduced_operator.BuildRightHandSide(density_true, right_hand_side);
|
||||
|
||||
mfem::BlockVector state(layout.residual_offsets());
|
||||
state = 0.0;
|
||||
state.GetBlock(gradient_block) = gradient_true;
|
||||
state.GetBlock(poisson_block) = potential_true;
|
||||
|
||||
mfem::Vector residual;
|
||||
reduced_operator.Mult(state, residual);
|
||||
residual -= right_hand_side;
|
||||
|
||||
return global_norm(residual, fem.L2_fes->GetComm()) /
|
||||
std::max(global_norm(right_hand_side, fem.L2_fes->GetComm()), std::numeric_limits<double>::epsilon());
|
||||
}
|
||||
|
||||
static AccuracyBudgetMetrics measure_monopole_accuracy(
|
||||
mean_field::fem::FEM& fem,
|
||||
const mfem::GridFunction& density,
|
||||
const mfem::GridFunction& displacement,
|
||||
const mean_field::physics::GravitySolution& solution,
|
||||
const mfem::ParGridFunction& projected_potential,
|
||||
const mfem::Vector& projected_gradient,
|
||||
const double mass,
|
||||
const double stellar_radius
|
||||
) {
|
||||
mfem::Vector solution_gradient;
|
||||
solution.gradPhi.GetTrueDofs(solution_gradient);
|
||||
|
||||
mfem::Vector solution_potential;
|
||||
mfem::Vector projection_potential;
|
||||
solution.phi.GetTrueDofs(solution_potential);
|
||||
projected_potential.GetTrueDofs(projection_potential);
|
||||
|
||||
mfem::ParGridFunction projected_gradient_grid_function(fem.RT_fes.get());
|
||||
projected_gradient_grid_function.SetFromTrueDofs(projected_gradient);
|
||||
|
||||
double local_solution_gradient_error = 0.0;
|
||||
double local_projection_gradient_error = 0.0;
|
||||
double local_gradient_norm = 0.0;
|
||||
double local_solution_potential_error = 0.0;
|
||||
double local_projection_potential_error = 0.0;
|
||||
double local_potential_norm = 0.0;
|
||||
|
||||
const int vacuum_attribute = fem.domain_mapper_stateless->GetVacuumElementAttribute();
|
||||
const int quadrature_order = diagnostic_quadrature_order(fem);
|
||||
mfem::Vector physical_position(3);
|
||||
mfem::Vector analytic_gradient(3);
|
||||
mfem::Vector solution_reference_gradient(3);
|
||||
mfem::Vector projection_reference_gradient(3);
|
||||
mfem::Vector solution_physical_gradient(3);
|
||||
mfem::Vector projection_physical_gradient(3);
|
||||
mfem::DenseMatrix mapping_jacobian(3);
|
||||
|
||||
for (int element_id = 0; element_id < fem.mesh->GetNE(); ++element_id) {
|
||||
mfem::ElementTransformation* transformation = fem.mesh->GetElementTransformation(element_id);
|
||||
const mfem::IntegrationRule& rule = mfem::IntRules.Get(
|
||||
transformation->GetGeometryType(),
|
||||
quadrature_order
|
||||
);
|
||||
|
||||
for (int quadrature_point_id = 0; quadrature_point_id < rule.GetNPoints(); ++quadrature_point_id) {
|
||||
const mfem::IntegrationPoint& point = rule.IntPoint(quadrature_point_id);
|
||||
transformation->SetIntPoint(&point);
|
||||
fem.mapping->GetPhysicalPoint(*transformation, point, physical_position);
|
||||
fem.mapping->ComputeJacobian(*transformation, mapping_jacobian);
|
||||
const double mapping_determinant = mapping_jacobian.Det();
|
||||
MFEM_VERIFY(mapping_determinant > 0.0, "Non-positive mapping determinant in accuracy diagnostic.");
|
||||
|
||||
const double radius = physical_position.Norml2();
|
||||
MFEM_VERIFY(std::isfinite(radius) && radius > 0.0, "Invalid radius in monopole diagnostic.");
|
||||
|
||||
analytic_gradient = physical_position;
|
||||
double analytic_potential = 0.0;
|
||||
if (transformation->Attribute == vacuum_attribute) {
|
||||
analytic_gradient *= mean_field::utils::G * mass / (radius * radius * radius);
|
||||
analytic_potential = -mean_field::utils::G * mass / radius;
|
||||
} else {
|
||||
analytic_gradient *= mean_field::utils::G * mass /
|
||||
(stellar_radius * stellar_radius * stellar_radius);
|
||||
analytic_potential = -mean_field::utils::G * mass *
|
||||
(3.0 * stellar_radius * stellar_radius - radius * radius) /
|
||||
(2.0 * stellar_radius * stellar_radius * stellar_radius);
|
||||
}
|
||||
|
||||
solution.gradPhi.GetVectorValue(element_id, point, solution_reference_gradient);
|
||||
mapping_jacobian.Mult(solution_reference_gradient, solution_physical_gradient);
|
||||
solution_physical_gradient /= mapping_determinant;
|
||||
|
||||
projected_gradient_grid_function.GetVectorValue(element_id, point, projection_reference_gradient);
|
||||
mapping_jacobian.Mult(projection_reference_gradient, projection_physical_gradient);
|
||||
projection_physical_gradient /= mapping_determinant;
|
||||
|
||||
const double solution_potential_value = solution.phi.GetValue(element_id, point);
|
||||
const double projection_potential_value = projected_potential.GetValue(element_id, point);
|
||||
const double weight = point.weight * transformation->Weight() * mapping_determinant;
|
||||
|
||||
solution_physical_gradient -= analytic_gradient;
|
||||
projection_physical_gradient -= analytic_gradient;
|
||||
local_solution_gradient_error += weight * (solution_physical_gradient * solution_physical_gradient);
|
||||
local_projection_gradient_error += weight * (projection_physical_gradient * projection_physical_gradient);
|
||||
local_gradient_norm += weight * (analytic_gradient * analytic_gradient);
|
||||
local_solution_potential_error += weight *
|
||||
(solution_potential_value - analytic_potential) * (solution_potential_value - analytic_potential);
|
||||
local_projection_potential_error += weight *
|
||||
(projection_potential_value - analytic_potential) * (projection_potential_value - analytic_potential);
|
||||
local_potential_norm += weight * analytic_potential * analytic_potential;
|
||||
}
|
||||
}
|
||||
|
||||
const std::array<double, 6> local_values{
|
||||
local_solution_gradient_error, local_projection_gradient_error, local_gradient_norm,
|
||||
local_solution_potential_error, local_projection_potential_error, local_potential_norm
|
||||
};
|
||||
std::array<double, 6> global_values{};
|
||||
MPI_Allreduce(
|
||||
local_values.data(), global_values.data(), static_cast<int>(local_values.size()),
|
||||
MPI_DOUBLE, MPI_SUM, fem.L2_fes->GetComm()
|
||||
);
|
||||
|
||||
const AccuracyBudgetEnergies energies = measure_stellar_energies(fem, density, solution);
|
||||
const double analytic_energy = -3.0 * mean_field::utils::G * mass * mass / (5.0 * stellar_radius);
|
||||
|
||||
REQUIRE(global_values[2] > 0.0);
|
||||
REQUIRE(global_values[5] > 0.0);
|
||||
|
||||
AccuracyBudgetMetrics metrics;
|
||||
metrics.direct_relative_residual = reduced_gravity_relative_residual(fem, density, displacement, solution);
|
||||
metrics.gradient_relative_error = std::sqrt(global_values[0] / global_values[2]);
|
||||
metrics.gradient_projection_relative_error = std::sqrt(global_values[1] / global_values[2]);
|
||||
metrics.gradient_solution_projection_gap = mapped_hdiv_relative_gap(
|
||||
fem, displacement, solution_gradient, projected_gradient
|
||||
);
|
||||
metrics.potential_relative_error = std::sqrt(global_values[3] / global_values[5]);
|
||||
metrics.potential_projection_relative_error = std::sqrt(global_values[4] / global_values[5]);
|
||||
mfem::Vector potential_difference(solution_potential);
|
||||
potential_difference -= projection_potential;
|
||||
const double projection_potential_norm = global_norm(projection_potential, fem.L2_fes->GetComm());
|
||||
REQUIRE(projection_potential_norm > 0.0);
|
||||
metrics.potential_solution_projection_gap = global_norm(potential_difference, fem.L2_fes->GetComm()) /
|
||||
projection_potential_norm;
|
||||
metrics.binding_relative_error = std::abs(energies.binding - analytic_energy) / std::abs(analytic_energy);
|
||||
metrics.virial_relative_error = std::abs(energies.virial - analytic_energy) / std::abs(analytic_energy);
|
||||
metrics.virial_consistency_error = std::abs(energies.binding - energies.virial) /
|
||||
std::max(std::abs(energies.binding), std::numeric_limits<double>::epsilon());
|
||||
return metrics;
|
||||
}
|
||||
|
||||
static void run_monopole_case(
|
||||
const std::string& sweep_name,
|
||||
const std::string& case_name,
|
||||
mean_field::utils::Args args,
|
||||
const double solver_tolerance,
|
||||
const int quadrature_boost
|
||||
) {
|
||||
args.p.rtol = solver_tolerance;
|
||||
args.p.atol = std::min(args.p.atol, solver_tolerance * 1.0e-2);
|
||||
args.p.max_iters = std::max(args.p.max_iters, 2000);
|
||||
args.quadrature.global_boost = quadrature_boost;
|
||||
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(fem.mapping != nullptr);
|
||||
REQUIRE(fem.domain_mapper_stateless != nullptr);
|
||||
|
||||
const double stellar_radius = mean_field::utils::RADIUS;
|
||||
const double mass = mean_field::utils::MASS;
|
||||
const double density_value = mass / ((4.0 / 3.0) * M_PI * stellar_radius * stellar_radius * stellar_radius);
|
||||
|
||||
mfem::ParGridFunction displacement(fem.Vec_H1_fes.get());
|
||||
displacement = 0.0;
|
||||
fem.mapping->ResetDisplacement();
|
||||
mean_field::physics::update_stiffness_matrix(fem);
|
||||
|
||||
mfem::GridFunction density(fem.L2_fes.get());
|
||||
density = density_value;
|
||||
zero_vacuum_density(fem, density);
|
||||
mean_field::analysis::conserve_mass(fem, density, mass);
|
||||
fem.com = mean_field::analysis::get_com(fem, density);
|
||||
fem.Q = mean_field::physics::compute_quadrupole_moment_tensor(fem, density, fem.com);
|
||||
|
||||
const mean_field::physics::GravitySolution solution =
|
||||
mean_field::physics::grav_potential_new(fem, args, density, displacement);
|
||||
|
||||
auto analytic_potential = [mass, stellar_radius](const mfem::Vector& position) {
|
||||
const double radius = position.Norml2();
|
||||
if (radius >= stellar_radius) {
|
||||
return -mean_field::utils::G * mass / radius;
|
||||
}
|
||||
return -mean_field::utils::G * mass *
|
||||
(3.0 * stellar_radius * stellar_radius - radius * radius) /
|
||||
(2.0 * stellar_radius * stellar_radius * stellar_radius);
|
||||
};
|
||||
mean_field::mapping::PhysicalPositionFunctionCoefficient potential_coefficient(
|
||||
*fem.mapping,
|
||||
analytic_potential
|
||||
);
|
||||
mfem::ParGridFunction projected_potential(fem.L2_fes.get());
|
||||
projected_potential.ProjectCoefficient(potential_coefficient);
|
||||
|
||||
const mfem::Vector projected_gradient = project_monopole_gradient(
|
||||
fem,
|
||||
displacement,
|
||||
mass,
|
||||
stellar_radius
|
||||
);
|
||||
|
||||
const AccuracyBudgetMetrics metrics = measure_monopole_accuracy(
|
||||
fem,
|
||||
density,
|
||||
displacement,
|
||||
solution,
|
||||
projected_potential,
|
||||
projected_gradient,
|
||||
mass,
|
||||
stellar_radius
|
||||
);
|
||||
|
||||
REQUIRE(std::isfinite(metrics.direct_relative_residual));
|
||||
REQUIRE(std::isfinite(metrics.gradient_relative_error));
|
||||
REQUIRE(std::isfinite(metrics.potential_relative_error));
|
||||
REQUIRE(std::isfinite(metrics.virial_consistency_error));
|
||||
|
||||
record_experiment_result(
|
||||
sweep_name,
|
||||
case_name,
|
||||
{
|
||||
{"solver_rtol", std::to_string(solver_tolerance)},
|
||||
{"quadrature_global_boost", std::to_string(quadrature_boost)},
|
||||
{"mesh_file", args.mesh_file}
|
||||
},
|
||||
{
|
||||
{"direct_relative_residual", metrics.direct_relative_residual},
|
||||
{"gradient_relative_error", metrics.gradient_relative_error},
|
||||
{"gradient_projection_relative_error", metrics.gradient_projection_relative_error},
|
||||
{"gradient_solution_projection_gap", metrics.gradient_solution_projection_gap},
|
||||
{"potential_relative_error", metrics.potential_relative_error},
|
||||
{"potential_projection_relative_error", metrics.potential_projection_relative_error},
|
||||
{"potential_solution_projection_gap", metrics.potential_solution_projection_gap},
|
||||
{"binding_relative_error", metrics.binding_relative_error},
|
||||
{"virial_relative_error", metrics.virial_relative_error},
|
||||
{"virial_consistency_error", metrics.virial_consistency_error}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE("Uniform Monopole Accuracy Budget: Solver Tolerance", tags::gravity & tags::accuracy & tags::integration) {
|
||||
const mean_field::utils::Args args = test_utils::setup_args();
|
||||
constexpr std::array<double, 4> solver_tolerances{1.0e-8, 1.0e-10, 1.0e-12, 1.0e-14};
|
||||
|
||||
for (const double solver_tolerance : solver_tolerances) {
|
||||
run_monopole_case(
|
||||
"solver_tolerance",
|
||||
"uniform_monopole",
|
||||
args,
|
||||
solver_tolerance,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Uniform Monopole Accuracy Budget: Quadrature", tags::gravity & tags::accuracy & tags::integration) {
|
||||
const mean_field::utils::Args args = test_utils::setup_args();
|
||||
constexpr std::array<int, 3> quadrature_boosts{0, 4, 8};
|
||||
|
||||
for (const int quadrature_boost : quadrature_boosts) {
|
||||
run_monopole_case(
|
||||
"quadrature",
|
||||
"uniform_monopole",
|
||||
args,
|
||||
1.0e-13,
|
||||
quadrature_boost
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Uniform Monopole Accuracy Budget: Projection Decomposition", tags::gravity & tags::accuracy & tags::integration) {
|
||||
run_monopole_case(
|
||||
"projection_decomposition",
|
||||
"uniform_monopole",
|
||||
test_utils::setup_args(),
|
||||
1.0e-13,
|
||||
0
|
||||
);
|
||||
}
|
||||
3
format
Executable file
3
format
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
find libmeanfield tests -type f \( -name '*.cpp' \) | xargs -I{} clang-format -style=file:clang-format-styles/style -i {}
|
||||
find libmeanfield tests -type f \( -name '*.cppm' \) | xargs -I{} clang-format -style=file:clang-format-styles/style -i {}
|
||||
@@ -1,53 +1,110 @@
|
||||
module;
|
||||
#include <array>
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
import :mapping.coefficients;
|
||||
|
||||
namespace {
|
||||
template <typename FormT>
|
||||
const mfem::IntegrationRule &get_density_rule(
|
||||
const mean_field::fem::FEM &fem,
|
||||
const mfem::ElementTransformation &transformation,
|
||||
const std::array<
|
||||
int,
|
||||
FormT::dynamicOrderCount> &dynamic_orders = {},
|
||||
const mean_field::utils::DOMAINS domain =
|
||||
mean_field::utils::DOMAINS::ALL
|
||||
) {
|
||||
using DensityField =
|
||||
mean_field::field::Field<mean_field::field::Density>;
|
||||
|
||||
const mean_field::quadrature::Query query =
|
||||
DensityField::make_query<FormT>(
|
||||
mean_field::quadrature::QuadratureRole::diagnostic,
|
||||
transformation.OrderW(), dynamic_orders, domain,
|
||||
fem.has_mapping() ? mean_field::quadrature::MappingKind::general
|
||||
: mean_field::quadrature::MappingKind::none
|
||||
);
|
||||
|
||||
return *fem.quadratureFactory
|
||||
->get(query, transformation.GetGeometryType())
|
||||
.integration_rule;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::analysis {
|
||||
double domain_integrate_grid_function(const fem::FEM &fem, const mfem::GridFunction &gf, utils::DOMAINS domain, mapping::COORDINATE_SPACE coord_space) {
|
||||
mfem::LinearForm lf(fem.H1_fes.get());
|
||||
double domain_integrate_grid_function(
|
||||
const fem::FEM &fem,
|
||||
const mfem::GridFunction &gf,
|
||||
utils::DOMAINS domain,
|
||||
mapping::COORDINATE_SPACE coord_space
|
||||
) {
|
||||
mfem::LinearForm lf(fem.densityFes.get());
|
||||
mfem::GridFunctionCoefficient gf_c(&gf);
|
||||
double local_integral;
|
||||
mfem::Array<int> elem_markers;
|
||||
populate_element_mask(fem.mesh.get(), domain, elem_markers);
|
||||
const mfem::ElementTransformation &representative_transformation =
|
||||
*fem.mesh->GetElementTransformation(0);
|
||||
const mfem::IntegrationRule &integration_rule =
|
||||
get_density_rule<field::Density::Form::MassConservation>(
|
||||
fem, representative_transformation, {}, domain
|
||||
);
|
||||
|
||||
if (fem.has_mapping() && coord_space == mapping::COORDINATE_SPACE::PHYSICAL) {
|
||||
if (fem.has_mapping() &&
|
||||
coord_space == mapping::COORDINATE_SPACE::PHYSICAL) {
|
||||
mapping::MappedScalarCoefficient mapped_gf_c(*fem.mapping, gf_c);
|
||||
|
||||
// ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM takes ownership so memory is not leaked
|
||||
// ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM
|
||||
// takes ownership so memory is not leaked
|
||||
auto *lf_integrator = new mfem::DomainLFIntegrator(mapped_gf_c);
|
||||
lf_integrator->SetIntRule(fem.int_rule.get());
|
||||
lf_integrator->SetIntRule(&integration_rule);
|
||||
lf.AddDomainIntegrator(lf_integrator, elem_markers);
|
||||
lf.Assemble();
|
||||
|
||||
|
||||
local_integral = lf.Sum();
|
||||
} else {
|
||||
if (coord_space == mapping::COORDINATE_SPACE::PHYSICAL) {
|
||||
MFEM_ABORT(
|
||||
"Physical evaluation mode requested but no mapping provided. Check domain bounds and mapping setup.");
|
||||
"Physical evaluation mode requested but no mapping "
|
||||
"provided. Check "
|
||||
"domain bounds and mapping setup."
|
||||
);
|
||||
}
|
||||
lf.AddDomainIntegrator(new mfem::DomainLFIntegrator(gf_c), elem_markers);
|
||||
auto *lf_integrator = new mfem::DomainLFIntegrator(gf_c);
|
||||
lf_integrator->SetIntRule(&integration_rule);
|
||||
lf.AddDomainIntegrator(lf_integrator, elem_markers);
|
||||
lf.Assemble();
|
||||
local_integral = lf.Sum();
|
||||
}
|
||||
|
||||
double global_integral = 0.0;
|
||||
MPI_Allreduce(&local_integral, &global_integral, 1, MPI_DOUBLE, MPI_SUM, fem.H1_fes->GetComm());
|
||||
MPI_Allreduce(
|
||||
&local_integral, &global_integral, 1, MPI_DOUBLE, MPI_SUM,
|
||||
fem.mesh->GetComm()
|
||||
);
|
||||
return global_integral;
|
||||
}
|
||||
|
||||
mfem::Vector get_com(const fem::FEM &fem, const mfem::GridFunction &rho) {
|
||||
mfem::Vector get_com(
|
||||
const fem::FEM &fem,
|
||||
const mfem::GridFunction &rho
|
||||
) {
|
||||
const int dim = fem.mesh->Dimension();
|
||||
mfem::Vector local_com(dim);
|
||||
local_com = 0.0;
|
||||
double local_mass = 0.0;
|
||||
|
||||
for (int i = 0; i < fem.H1_fes->GetNE(); ++i) {
|
||||
if (fem.mesh->GetAttribute(i) == 3) continue;
|
||||
mfem::ElementTransformation *trans = fem.H1_fes->GetElementTransformation(i);
|
||||
const mfem::IntegrationRule &ir = *fem.int_rule;
|
||||
for (int i = 0; i < fem.mesh->GetNE(); ++i) {
|
||||
if (fem.mesh->GetAttribute(i) == 3)
|
||||
continue;
|
||||
mfem::ElementTransformation *trans =
|
||||
fem.mesh->GetElementTransformation(i);
|
||||
const mfem::IntegrationRule &ir =
|
||||
get_density_rule<field::Density::Form::CenterOfMass>(
|
||||
fem, *trans, std::array<int, 1>{1}, utils::DOMAINS::STELLAR
|
||||
);
|
||||
|
||||
for (int j = 0; j < ir.GetNPoints(); ++j) {
|
||||
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
|
||||
@@ -77,11 +134,14 @@ namespace mean_field::analysis {
|
||||
|
||||
double global_mass = 0.0;
|
||||
mfem::Vector global_com(dim);
|
||||
MPI_Comm comm = fem.H1_fes->GetComm();
|
||||
MPI_Comm comm = fem.mesh->GetComm();
|
||||
|
||||
MPI_Allreduce(&local_mass, &global_mass, 1, MPI_DOUBLE, MPI_SUM, comm);
|
||||
|
||||
MPI_Allreduce(local_com.GetData(), global_com.GetData(), dim, MPI_DOUBLE, MPI_SUM, comm);
|
||||
MPI_Allreduce(
|
||||
local_com.GetData(), global_com.GetData(), dim, MPI_DOUBLE, MPI_SUM,
|
||||
comm
|
||||
);
|
||||
|
||||
if (global_mass > 1e-18) {
|
||||
global_com /= global_mass;
|
||||
@@ -92,19 +152,32 @@ namespace mean_field::analysis {
|
||||
return global_com;
|
||||
}
|
||||
|
||||
void conserve_mass(const fem::FEM &fem, mfem::GridFunction &rho, const double target_mass) {
|
||||
if (const double current_mass = domain_integrate_grid_function(fem, rho, utils::DOMAINS::STELLAR); current_mass > 1e-15)
|
||||
void conserve_mass(
|
||||
const fem::FEM &fem,
|
||||
mfem::GridFunction &rho,
|
||||
const double target_mass
|
||||
) {
|
||||
if (const double current_mass = domain_integrate_grid_function(
|
||||
fem, rho, utils::DOMAINS::STELLAR
|
||||
);
|
||||
current_mass > 1e-15)
|
||||
rho *= (target_mass / current_mass);
|
||||
}
|
||||
|
||||
double get_moment_of_inertia(const fem::FEM &fem, const mfem::GridFunction &rho) {
|
||||
double get_moment_of_inertia(
|
||||
const fem::FEM &fem,
|
||||
const mfem::GridFunction &rho
|
||||
) {
|
||||
auto s2_func = [](const mfem::Vector &x) {
|
||||
return std::pow(x(0), 2) + std::pow(x(1), 2);
|
||||
};
|
||||
|
||||
std::unique_ptr<mfem::Coefficient> s2_coeff;
|
||||
if (fem.has_mapping()) {
|
||||
s2_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(*fem.mapping, s2_func);
|
||||
s2_coeff =
|
||||
std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(
|
||||
*fem.mapping, s2_func
|
||||
);
|
||||
} else {
|
||||
s2_coeff = std::make_unique<mfem::FunctionCoefficient>(s2_func);
|
||||
}
|
||||
@@ -112,36 +185,59 @@ namespace mean_field::analysis {
|
||||
mfem::GridFunctionCoefficient rho_coeff(&rho);
|
||||
mfem::ProductCoefficient I_integrand(rho_coeff, *s2_coeff);
|
||||
|
||||
mfem::LinearForm I_lf(fem.H1_fes.get());
|
||||
mfem::LinearForm I_lf(fem.densityFes.get());
|
||||
const mfem::ElementTransformation &representative_transformation =
|
||||
*fem.mesh->GetElementTransformation(0);
|
||||
const mfem::IntegrationRule &integration_rule =
|
||||
get_density_rule<field::Density::Form::Quadrupole>(
|
||||
fem, representative_transformation, std::array<int, 1>{2},
|
||||
utils::DOMAINS::STELLAR
|
||||
);
|
||||
mfem::Array<int> stellar_markers;
|
||||
populate_element_mask(
|
||||
fem.mesh.get(), utils::DOMAINS::STELLAR, stellar_markers
|
||||
);
|
||||
|
||||
double I = 0.0;
|
||||
// TODO: Need to filter here to just the stellar domain and also update the IntRule
|
||||
double local_I = 0.0;
|
||||
if (fem.has_mapping()) {
|
||||
mapping::MappedScalarCoefficient mapped_integrand(*fem.mapping, I_integrand);
|
||||
I_lf.AddDomainIntegrator(new mfem::DomainLFIntegrator(mapped_integrand));
|
||||
mapping::MappedScalarCoefficient mapped_integrand(
|
||||
*fem.mapping, I_integrand
|
||||
);
|
||||
auto *integrator = new mfem::DomainLFIntegrator(mapped_integrand);
|
||||
integrator->SetIntRule(&integration_rule);
|
||||
I_lf.AddDomainIntegrator(integrator, stellar_markers);
|
||||
I_lf.Assemble();
|
||||
I = I_lf.Sum();
|
||||
local_I = I_lf.Sum();
|
||||
} else {
|
||||
I_lf.AddDomainIntegrator(new mfem::DomainLFIntegrator(I_integrand));
|
||||
auto *integrator = new mfem::DomainLFIntegrator(I_integrand);
|
||||
integrator->SetIntRule(&integration_rule);
|
||||
I_lf.AddDomainIntegrator(integrator, stellar_markers);
|
||||
I_lf.Assemble();
|
||||
I = I_lf.Sum();
|
||||
local_I = I_lf.Sum();
|
||||
}
|
||||
|
||||
return I;
|
||||
double global_I = 0.0;
|
||||
MPI_Allreduce(
|
||||
&local_I, &global_I, 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm()
|
||||
);
|
||||
return global_I;
|
||||
}
|
||||
|
||||
double get_mesh_volume(
|
||||
const fem::FEM& fem,
|
||||
const fem::FEM &fem,
|
||||
const mapping::COORDINATE_SPACE coordinate_space,
|
||||
const utils::DOMAINS domain
|
||||
) {
|
||||
mfem::ParMesh &mesh = *fem.mesh;
|
||||
const mapping::DomainMapper &map = *fem.mapping;
|
||||
const mfem::IntegrationRule &ir = *fem.int_rule;
|
||||
|
||||
const bool physical =
|
||||
(coordinate_space == mapping::COORDINATE_SPACE::PHYSICAL);
|
||||
|
||||
if (physical && !fem.has_mapping()) {
|
||||
MFEM_ABORT(
|
||||
"Physical volume requested but no domain mapping is available."
|
||||
);
|
||||
}
|
||||
|
||||
double local_volume = 0.0;
|
||||
|
||||
for (int e = 0; e < mesh.GetNE(); ++e) {
|
||||
@@ -150,15 +246,21 @@ namespace mean_field::analysis {
|
||||
case utils::DOMAINS::ALL:
|
||||
break;
|
||||
case utils::DOMAINS::STELLAR:
|
||||
if (attr == 3) continue;
|
||||
if (attr == 3)
|
||||
continue;
|
||||
break;
|
||||
case utils::DOMAINS::VACUUM:
|
||||
if (attr != 3) continue;
|
||||
if (attr != 3)
|
||||
continue;
|
||||
break;
|
||||
default:
|
||||
MFEM_ABORT("Unsupported domain type for volume computation.");
|
||||
}
|
||||
mfem::ElementTransformation *T = mesh.GetElementTransformation(e);
|
||||
const mfem::IntegrationRule &ir =
|
||||
get_density_rule<field::Density::Form::MassConservation>(
|
||||
fem, *T, {}, domain
|
||||
);
|
||||
|
||||
for (int q = 0; q < ir.GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint &ip = ir.IntPoint(q);
|
||||
@@ -167,7 +269,7 @@ namespace mean_field::analysis {
|
||||
double dV = ip.weight * T->Weight();
|
||||
|
||||
if (physical) {
|
||||
dV *= std::fabs(map.ComputeDetJ(*T, ip));
|
||||
dV *= std::fabs(fem.mapping->ComputeDetJ(*T, ip));
|
||||
}
|
||||
|
||||
local_volume += dV;
|
||||
@@ -175,8 +277,10 @@ namespace mean_field::analysis {
|
||||
}
|
||||
|
||||
double global_volume = 0.0;
|
||||
MPI_Allreduce(&local_volume, &global_volume, 1, MPI_DOUBLE, MPI_SUM,
|
||||
mesh.GetComm());
|
||||
MPI_Allreduce(
|
||||
&local_volume, &global_volume, 1, MPI_DOUBLE, MPI_SUM,
|
||||
mesh.GetComm()
|
||||
);
|
||||
return global_volume;
|
||||
}
|
||||
}
|
||||
} // namespace mean_field::analysis
|
||||
|
||||
@@ -1,190 +1,560 @@
|
||||
module;
|
||||
#include <string>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <expected>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <stroid/stroid.h>
|
||||
|
||||
module mean_field;
|
||||
|
||||
import :boundary.contexts;
|
||||
import :field.mfem;
|
||||
import :mapping.coefficients;
|
||||
import :utils.misc;
|
||||
import :utils.user;
|
||||
|
||||
|
||||
namespace mean_field::fem {
|
||||
FEM setup_fem(const std::string &filename, const utils::Args &args, const int extra_refine) {
|
||||
FEM setup_fem(
|
||||
const std::string &filename,
|
||||
const utils::Args &args,
|
||||
const int extraRefine
|
||||
) {
|
||||
FEM fem;
|
||||
|
||||
//==================================================================
|
||||
// Section 1: Mesh and FE Space Setup
|
||||
//==================================================================
|
||||
using GravityPotential = field::Gravity::Potential;
|
||||
using GravityFlux = field::Gravity::Flux;
|
||||
using DisplacementVector = field::Displacement::Vector;
|
||||
using DensityScalar = field::Density::Scalar;
|
||||
using EnthalpyScalar = field::Enthalpy::Scalar;
|
||||
|
||||
// =====================================================================
|
||||
// Section 1: Mesh construction
|
||||
// =====================================================================
|
||||
|
||||
fem.smesh = stroid::IO::LoadStroidMesh(filename).value();
|
||||
if (extra_refine > 0) {
|
||||
stroid::refinement::UniformRefinement(fem.smesh, extra_refine);
|
||||
|
||||
if (extraRefine > 0) {
|
||||
stroid::refinement::UniformRefinement(fem.smesh, extraRefine);
|
||||
}
|
||||
|
||||
fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh);
|
||||
int mpiSize = 1;
|
||||
MPI_Comm_size(MPI_COMM_WORLD, &mpiSize);
|
||||
|
||||
const std::unique_ptr<int[]> meshPartitioning(
|
||||
fem.smesh.mesh->GeneratePartitioning(mpiSize, 1)
|
||||
);
|
||||
|
||||
fem.mesh = std::make_unique<mfem::ParMesh>(
|
||||
MPI_COMM_WORLD, *fem.smesh.mesh, meshPartitioning.get(), 1
|
||||
);
|
||||
|
||||
fem.mesh->EnsureNodes();
|
||||
|
||||
const int geom_order = utils::get_mesh_order(*fem.mesh);
|
||||
const int dim = fem.mesh->Dimension();
|
||||
// =====================================================================
|
||||
// Section 2: Exterior compactification coordinate
|
||||
// =====================================================================
|
||||
|
||||
const int v_order = 2;
|
||||
const int rho_order = 2;
|
||||
const int p = rho_order ;
|
||||
if (fem.smesh.exterior_coordinate == nullptr) {
|
||||
throw std::runtime_error("Exterior coordinate not set.");
|
||||
}
|
||||
|
||||
const int cb_type = mfem::BasisType::GaussLobatto;
|
||||
const int ob_type = mfem::BasisType::IntegratedGLL;
|
||||
if (fem.smesh.exterior_coordinate->space == nullptr) {
|
||||
throw std::runtime_error("Space for exterior coordinate not set.");
|
||||
}
|
||||
|
||||
fem.RT_fec = std::make_unique<mfem::RT_FECollection>(p, dim, cb_type, ob_type);
|
||||
fem.RT_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.RT_fec.get());
|
||||
if (fem.smesh.exterior_coordinate->values == nullptr) {
|
||||
throw std::runtime_error("Values for exterior coordinate not set.");
|
||||
}
|
||||
|
||||
fem.H1_fec = std::make_unique<mfem::H1_FECollection>(v_order, dim);
|
||||
fem.L2_fec = std::make_unique<mfem::L2_FECollection>(rho_order, dim);
|
||||
const mfem::FiniteElementSpace &serialCoordinateSpace =
|
||||
*fem.smesh.exterior_coordinate->space;
|
||||
|
||||
// Gravity (Scalar H1) and Velocity (Vector H1)
|
||||
fem.H1_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.H1_fec.get());
|
||||
fem.Vec_H1_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.H1_fec.get(), dim,
|
||||
mfem::Ordering::byNODES);
|
||||
const mfem::GridFunction &serialCoordinate =
|
||||
*fem.smesh.exterior_coordinate->values;
|
||||
|
||||
// Density & Pressure (Scalar Discontinuous L2)
|
||||
fem.L2_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.L2_fec.get());
|
||||
if (serialCoordinate.FESpace() != &serialCoordinateSpace) {
|
||||
throw std::runtime_error(
|
||||
"Exterior coordinate values are not associated with the "
|
||||
"supplied finite-element space."
|
||||
);
|
||||
}
|
||||
|
||||
//==================================================================
|
||||
// Section 2: Domain Mapping
|
||||
//==================================================================
|
||||
auto [r_star_ref, r_inf_ref] = utils::discover_bounds(fem.mesh.get(), 3)
|
||||
.or_else([](const boundary::BoundsError &err)-> std::expected<boundary::Bounds, boundary::BoundsError> {
|
||||
throw std::runtime_error("Unable to determine vacuum domain reference boundary...");
|
||||
}).value();
|
||||
if (serialCoordinateSpace.GetMesh() != fem.smesh.mesh.get()) {
|
||||
throw std::runtime_error(
|
||||
"Exterior coordinate space is not associated with the "
|
||||
"loaded STROID mesh."
|
||||
);
|
||||
}
|
||||
|
||||
fem.mapping = std::make_unique<mapping::DomainMapper>(r_star_ref, r_inf_ref);
|
||||
if (serialCoordinateSpace.GetVDim() != 1) {
|
||||
throw std::runtime_error(
|
||||
"Exterior coordinate must be a scalar field."
|
||||
);
|
||||
}
|
||||
|
||||
//==================================================================
|
||||
// Section 3: Multi-physics Block-offsets
|
||||
//==================================================================
|
||||
fem.block_true_offsets.SetSize(3);
|
||||
fem.block_true_offsets[0] = 0;
|
||||
fem.block_true_offsets[1] = fem.Vec_H1_fes->GetTrueVSize();
|
||||
fem.block_true_offsets[2] = fem.block_true_offsets[1] + fem.L2_fes->GetTrueVSize();
|
||||
if (serialCoordinate.Size() != serialCoordinateSpace.GetVSize()) {
|
||||
throw std::runtime_error(
|
||||
"Exterior coordinate value count does not match its "
|
||||
"finite-element space."
|
||||
);
|
||||
}
|
||||
|
||||
fem.gravity_block_true_offsets.SetSize(3);
|
||||
fem.gravity_block_true_offsets[0] = 0;
|
||||
fem.gravity_block_true_offsets[1] = fem.RT_fes->GetTrueVSize();
|
||||
fem.gravity_block_true_offsets[2] = fem.gravity_block_true_offsets[1] + fem.L2_fes->GetTrueVSize();
|
||||
const int compactificationOrder =
|
||||
serialCoordinateSpace.GetMaxElementOrder();
|
||||
|
||||
//==================================================================
|
||||
// Section 4: Multipole BC setup.
|
||||
//==================================================================
|
||||
fem.com.SetSize(dim);
|
||||
const int dimension = fem.mesh->Dimension();
|
||||
|
||||
fem.compactificationFec = std::make_unique<mfem::H1_FECollection>(
|
||||
compactificationOrder, dimension
|
||||
);
|
||||
|
||||
fem.compactificationFes = std::make_unique<mfem::ParFiniteElementSpace>(
|
||||
fem.mesh.get(), fem.compactificationFec.get()
|
||||
);
|
||||
|
||||
mfem::ParGridFunction distributedCoordinate(
|
||||
fem.mesh.get(), &serialCoordinate, meshPartitioning.get()
|
||||
);
|
||||
|
||||
if (distributedCoordinate.Size() !=
|
||||
fem.compactificationFes->GetVSize()) {
|
||||
throw std::runtime_error(
|
||||
"Distributed exterior coordinate does not match the "
|
||||
"constructed parallel finite-element space."
|
||||
);
|
||||
}
|
||||
|
||||
fem.compactificationCoordinate =
|
||||
std::make_unique<mfem::ParGridFunction>(
|
||||
fem.compactificationFes.get()
|
||||
);
|
||||
|
||||
*fem.compactificationCoordinate = distributedCoordinate;
|
||||
|
||||
double localMinimum = std::numeric_limits<double>::infinity();
|
||||
|
||||
double localMaximum = -std::numeric_limits<double>::infinity();
|
||||
|
||||
for (int index = 0; index < fem.compactificationCoordinate->Size();
|
||||
++index) {
|
||||
const double value = (*fem.compactificationCoordinate)(index);
|
||||
|
||||
if (!std::isfinite(value)) {
|
||||
throw std::runtime_error(
|
||||
"Exterior coordinate contains a non-finite value."
|
||||
);
|
||||
}
|
||||
|
||||
localMinimum = std::min(localMinimum, value);
|
||||
|
||||
localMaximum = std::max(localMaximum, value);
|
||||
}
|
||||
|
||||
double globalMinimum = 0.0;
|
||||
double globalMaximum = 0.0;
|
||||
|
||||
MPI_Allreduce(
|
||||
&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN,
|
||||
MPI_COMM_WORLD
|
||||
);
|
||||
|
||||
MPI_Allreduce(
|
||||
&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX,
|
||||
MPI_COMM_WORLD
|
||||
);
|
||||
|
||||
constexpr double coordinateTolerance = 1.0e-12;
|
||||
|
||||
if (globalMinimum < -coordinateTolerance ||
|
||||
globalMaximum > 1.0 + coordinateTolerance) {
|
||||
throw std::runtime_error(
|
||||
"Exterior coordinate lies outside the expected "
|
||||
"interval [0, 1]."
|
||||
);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Section 3: Compile-time field realization
|
||||
// =====================================================================
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Gravity potential: scalar L2
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
fem.gravityPotentialFec =
|
||||
GravityField::make_fec<GravityPotential>(dimension);
|
||||
|
||||
fem.gravityPotentialFes = GravityField::make_fespace<GravityPotential>(
|
||||
*fem.mesh, *fem.gravityPotentialFec
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Gravity flux: H(div)/RT. Basis choices are encoded by field.mfem.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
fem.gravityFluxFec = GravityField::make_fec<GravityFlux>(dimension);
|
||||
|
||||
fem.gravityFluxFes = GravityField::make_fespace<GravityFlux>(
|
||||
*fem.mesh, *fem.gravityFluxFec
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Displacement: vector H1. Ordering is encoded by field.mfem.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
fem.displacementFec =
|
||||
DisplacementField::make_fec<DisplacementVector>(dimension);
|
||||
|
||||
fem.displacementFes =
|
||||
DisplacementField::make_fespace<DisplacementVector>(
|
||||
*fem.mesh, *fem.displacementFec
|
||||
);
|
||||
|
||||
fem.displacement =
|
||||
std::make_unique<mfem::ParGridFunction>(fem.displacementFes.get());
|
||||
|
||||
*fem.displacement = 0.0;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Density: scalar discontinuous L2
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
fem.densityFec = DensityField::make_fec<DensityScalar>(dimension);
|
||||
|
||||
fem.densityFes = DensityField::make_fespace<DensityScalar>(
|
||||
*fem.mesh, *fem.densityFec
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Specific enthalpy: scalar continuous H1
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
fem.enthalpyFec = EnthalpyField::make_fec<EnthalpyScalar>(dimension);
|
||||
|
||||
fem.enthalpyFes = EnthalpyField::make_fespace<EnthalpyScalar>(
|
||||
*fem.mesh, *fem.enthalpyFec
|
||||
);
|
||||
|
||||
// =====================================================================
|
||||
// Section 4: Domain mapping
|
||||
// =====================================================================
|
||||
|
||||
auto [stellarRadiusReference, infinityRadiusReference] =
|
||||
utils::discover_bounds(fem.mesh.get(), 3)
|
||||
.or_else(
|
||||
[](const boundary::BoundsError &)
|
||||
-> std::expected<
|
||||
boundary::Bounds, boundary::BoundsError> {
|
||||
throw std::runtime_error(
|
||||
"Unable to determine vacuum-domain reference "
|
||||
"boundaries."
|
||||
);
|
||||
}
|
||||
)
|
||||
.value();
|
||||
|
||||
fem.mapping = std::make_unique<mapping::DomainMapper>(
|
||||
*fem.displacement, stellarRadiusReference, infinityRadiusReference
|
||||
);
|
||||
|
||||
// =====================================================================
|
||||
// Section 5: Block offsets
|
||||
//
|
||||
// Legacy layouts only. New coupled operators use :utils.blocks forms.
|
||||
//
|
||||
// Main system: [Displacement | Density]
|
||||
// Gravity system: [Flux | Potential]
|
||||
// =====================================================================
|
||||
|
||||
fem.blockTrueOffsets.SetSize(3);
|
||||
fem.blockTrueOffsets[0] = 0;
|
||||
|
||||
fem.blockTrueOffsets[1] = fem.displacementFes->GetTrueVSize();
|
||||
|
||||
fem.blockTrueOffsets[2] =
|
||||
fem.blockTrueOffsets[1] + fem.densityFes->GetTrueVSize();
|
||||
|
||||
fem.gravityBlockTrueOffsets.SetSize(3);
|
||||
fem.gravityBlockTrueOffsets[0] = 0;
|
||||
|
||||
fem.gravityBlockTrueOffsets[1] = fem.gravityFluxFes->GetTrueVSize();
|
||||
|
||||
fem.gravityBlockTrueOffsets[2] =
|
||||
fem.gravityBlockTrueOffsets[1] +
|
||||
fem.gravityPotentialFes->GetTrueVSize();
|
||||
|
||||
// =====================================================================
|
||||
// Section 6: Multipole data
|
||||
// =====================================================================
|
||||
|
||||
fem.com.SetSize(dimension);
|
||||
fem.com = 0.0;
|
||||
fem.Q.SetSize(dim, dim);
|
||||
|
||||
fem.Q.SetSize(dimension, dimension);
|
||||
fem.Q = 0.0;
|
||||
|
||||
//==================================================================
|
||||
// Section 5: Integration Rules
|
||||
//==================================================================
|
||||
MFEM_ASSERT(fem.mesh->GetElementGeometry(0) == mfem::Geometry::CUBE,
|
||||
"Currently only hexahedral meshes are supported");
|
||||
const int element_order = fem.H1_fes->GetMaxElementOrder();
|
||||
fem.int_order = 2 * element_order + geom_order - 2 + args.quad_boost;
|
||||
// =====================================================================
|
||||
// Section 7: Essential boundaries and domain masks
|
||||
// =====================================================================
|
||||
|
||||
fem.int_rule = std::make_unique<mfem::IntegrationRule>(mfem::IntRules.Get(mfem::Geometry::CUBE, fem.int_order));
|
||||
fem.essentialDisplacementTdofs.SetSize(0);
|
||||
|
||||
//==================================================================
|
||||
// Section 6: Essential Boundaries & Domain Masks
|
||||
//==================================================================
|
||||
fem.ess_v_tdofs.SetSize(0);
|
||||
populate_element_mask(
|
||||
fem.mesh.get(), utils::DOMAINS::STELLAR,
|
||||
fem.gravityContext.stellar_mask
|
||||
);
|
||||
|
||||
populate_element_mask(fem.mesh.get(), utils::DOMAINS::STELLAR, fem.gravity_context.stellar_mask);
|
||||
const int boundaryAttributeCount = fem.mesh->bdr_attributes.Max();
|
||||
|
||||
const int n_bdr_attrs = fem.mesh->bdr_attributes.Max();
|
||||
fem.boundary_context.inf_bounds.SetSize(n_bdr_attrs);
|
||||
fem.boundary_context.stellar_bounds.SetSize(n_bdr_attrs);
|
||||
fem.boundaryContext.inf_bounds.SetSize(boundaryAttributeCount);
|
||||
|
||||
fem.boundary_context.inf_bounds = 0;
|
||||
fem.boundary_context.stellar_bounds = 0;
|
||||
fem.boundaryContext.stellar_bounds.SetSize(boundaryAttributeCount);
|
||||
|
||||
fem.boundary_context.inf_bounds[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
|
||||
fem.boundary_context.stellar_bounds[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1;
|
||||
fem.boundaryContext.inf_bounds = 0;
|
||||
fem.boundaryContext.stellar_bounds = 0;
|
||||
|
||||
//==================================================================
|
||||
// Section 7: Gravity Context Setup
|
||||
//==================================================================
|
||||
fem.gravity_context.minres = std::make_unique<mfem::MINRESSolver>(fem.mesh->GetComm());
|
||||
fem.gravity_context.minres->SetRelTol(1e-12);
|
||||
fem.gravity_context.minres->SetAbsTol(1e-12);
|
||||
fem.gravity_context.minres->SetMaxIter(1000);
|
||||
fem.gravity_context.minres->SetPrintLevel(0);
|
||||
fem.boundaryContext.inf_bounds
|
||||
[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
|
||||
|
||||
fem.gravity_context.prec_Phi = std::make_unique<mfem::HypreBoomerAMG>();
|
||||
fem.gravity_context.prec_Phi->SetPrintLevel(0);
|
||||
fem.boundaryContext.stellar_bounds
|
||||
[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1;
|
||||
|
||||
fem.gravity_context.block_prec = std::make_unique<mfem::BlockDiagonalPreconditioner>(fem.gravity_block_true_offsets);
|
||||
// =====================================================================
|
||||
// Section 8: Gravity solver context
|
||||
// =====================================================================
|
||||
|
||||
fem.gravityContext.minres =
|
||||
std::make_unique<mfem::MINRESSolver>(fem.mesh->GetComm());
|
||||
|
||||
fem.gravityContext.minres->SetRelTol(1.0e-12);
|
||||
fem.gravityContext.minres->SetAbsTol(1.0e-12);
|
||||
fem.gravityContext.minres->SetMaxIter(1000);
|
||||
fem.gravityContext.minres->SetPrintLevel(0);
|
||||
|
||||
fem.gravityContext.prec_Phi = std::make_unique<mfem::HypreBoomerAMG>();
|
||||
|
||||
fem.gravityContext.prec_Phi->SetPrintLevel(0);
|
||||
|
||||
fem.gravityContext.block_prec =
|
||||
std::make_unique<mfem::BlockDiagonalPreconditioner>(
|
||||
fem.gravityBlockTrueOffsets
|
||||
);
|
||||
|
||||
fem.gravityContext.minres->SetPreconditioner(
|
||||
*fem.gravityContext.block_prec
|
||||
);
|
||||
|
||||
// =====================================================================
|
||||
// Section 9: Vacuum true-DOF masks
|
||||
// =====================================================================
|
||||
|
||||
fem.gravity_context.minres->SetPreconditioner(*fem.gravity_context.block_prec);
|
||||
//=========================================================
|
||||
// Section 10: Set All vacuum elements true degrees of freedom
|
||||
//=========================================================
|
||||
{
|
||||
mfem::Array<int> vacuum_mask;
|
||||
utils::populate_element_mask(fem.mesh.get(), utils::DOMAINS::VACUUM, vacuum_mask);
|
||||
mfem::Array<int> vacuumMask;
|
||||
|
||||
utils::populate_domain_tdofs(fem.Vec_H1_fes.get(), vacuum_mask, fem.vacuum_tdof_v);
|
||||
utils::populate_domain_tdofs(fem.L2_fes.get(), vacuum_mask, fem.vacuum_tdof_rho);
|
||||
utils::populate_element_mask(
|
||||
fem.mesh.get(), utils::DOMAINS::VACUUM, vacuumMask
|
||||
);
|
||||
|
||||
utils::populate_domain_tdofs(
|
||||
fem.displacementFes.get(), vacuumMask,
|
||||
fem.vacuumDisplacementTdofs
|
||||
);
|
||||
|
||||
utils::populate_domain_tdofs(
|
||||
fem.densityFes.get(), vacuumMask, fem.vacuumDensityTdofs
|
||||
);
|
||||
|
||||
utils::populate_domain_tdofs(
|
||||
fem.enthalpyFes.get(), vacuumMask, fem.vacuumEnthalpyTdofs
|
||||
);
|
||||
}
|
||||
|
||||
const quadrature::QuadratureOptions& quadrature_options = args.quadrature;
|
||||
// =====================================================================
|
||||
// Section 10: Quadrature policy
|
||||
// =====================================================================
|
||||
|
||||
if (quadrature_options.validation.reject_negative_boosts && quadrature_options.global_boost < 0) {
|
||||
throw std::invalid_argument("Global quadrature boost cannot be negative.");
|
||||
const quadrature::QuadratureOptions &quadratureOptions =
|
||||
args.quadrature;
|
||||
|
||||
if (quadratureOptions.validation.reject_negative_boosts &&
|
||||
quadratureOptions.global_boost < 0) {
|
||||
throw std::invalid_argument(
|
||||
"Global quadrature boost cannot be negative."
|
||||
);
|
||||
}
|
||||
|
||||
quadrature::RuleSet quadrature_rule_set = quadrature::make_rule_set(quadrature_options.mode, quadrature_options.global_boost);
|
||||
quadrature::RuleSet quadratureRuleSet = quadrature::make_rule_set(
|
||||
quadratureOptions.mode, quadratureOptions.global_boost
|
||||
);
|
||||
|
||||
if (quadrature_options.fallback_fixed_order.has_value()) {
|
||||
if (*quadrature_options.fallback_fixed_order < 0) {
|
||||
throw std::invalid_argument("Fallback quadrature order cannot be negative.");
|
||||
}
|
||||
quadrature_rule_set.fallback.fixed_order = quadrature_options.fallback_fixed_order;
|
||||
if (quadratureOptions.fallback_fixed_order.has_value()) {
|
||||
if (*quadratureOptions.fallback_fixed_order < 0) {
|
||||
throw std::invalid_argument(
|
||||
"Fallback quadrature order cannot be negative."
|
||||
);
|
||||
}
|
||||
|
||||
auto apply_quadrature_options = [&quadrature_options](quadrature::RuleControl& rule_control, const quadrature::QuadratureTermOptions& term_options) {
|
||||
if (term_options.fixed_order.has_value() && *term_options.fixed_order < 0) {
|
||||
throw std::invalid_argument("Fixed quadrature order cannot be negative.");
|
||||
quadratureRuleSet.fallback.fixed_order =
|
||||
quadratureOptions.fallback_fixed_order;
|
||||
}
|
||||
|
||||
if (quadrature_options.validation.reject_negative_boosts && term_options.additional_boost < 0) {
|
||||
throw std::invalid_argument("Term quadrature boost cannot be negative.");
|
||||
auto apply_quadrature_options =
|
||||
[&quadratureOptions](
|
||||
quadrature::RuleControl &ruleControl,
|
||||
const quadrature::QuadratureTermOptions &termOptions
|
||||
) {
|
||||
if (termOptions.fixed_order.has_value() &&
|
||||
*termOptions.fixed_order < 0) {
|
||||
throw std::invalid_argument(
|
||||
"Fixed quadrature order cannot be negative."
|
||||
);
|
||||
}
|
||||
|
||||
rule_control.boost += term_options.additional_boost;
|
||||
if (quadratureOptions.validation.reject_negative_boosts &&
|
||||
termOptions.additional_boost < 0) {
|
||||
throw std::invalid_argument(
|
||||
"Term quadrature boost cannot be negative."
|
||||
);
|
||||
}
|
||||
|
||||
if (term_options.fixed_order.has_value()) {
|
||||
rule_control.fixed_order = term_options.fixed_order;
|
||||
ruleControl.boost += termOptions.additional_boost;
|
||||
|
||||
if (termOptions.fixed_order.has_value()) {
|
||||
ruleControl.fixed_order = termOptions.fixed_order;
|
||||
}
|
||||
};
|
||||
|
||||
apply_quadrature_options(quadrature_rule_set.gravity_hdiv_mass, quadrature_options.gravity_hdiv_mass);
|
||||
apply_quadrature_options(quadrature_rule_set.gravity_divergence, quadrature_options.gravity_divergence);
|
||||
apply_quadrature_options(quadrature_rule_set.gravity_source, quadrature_options.gravity_source);
|
||||
apply_quadrature_options(quadrature_rule_set.gravity_boundary, quadrature_options.gravity_boundary);
|
||||
apply_quadrature_options(quadrature_rule_set.density_projection, quadrature_options.density_projection);
|
||||
apply_quadrature_options(quadrature_rule_set.mass_conservation, quadrature_options.mass_conservation);
|
||||
apply_quadrature_options(quadrature_rule_set.center_of_mass, quadrature_options.center_of_mass);
|
||||
apply_quadrature_options(quadrature_rule_set.quadrupole, quadrature_options.quadrupole);
|
||||
apply_quadrature_options(quadrature_rule_set.gravitational_energy, quadrature_options.gravitational_energy);
|
||||
apply_quadrature_options(quadrature_rule_set.virial, quadrature_options.virial);
|
||||
apply_quadrature_options(quadrature_rule_set.error_norm, quadrature_options.error_norm);
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.gravity_hdiv_mass,
|
||||
quadratureOptions.gravity_hdiv_mass
|
||||
);
|
||||
|
||||
apply_quadrature_options(quadrature_rule_set.roles.discretization, quadrature_options.roles.discretization);
|
||||
apply_quadrature_options(quadrature_rule_set.roles.preconditioner, quadrature_options.roles.preconditioner);
|
||||
apply_quadrature_options(quadrature_rule_set.roles.diagnostic, quadrature_options.roles.diagnostic);
|
||||
apply_quadrature_options(quadrature_rule_set.roles.projection, quadrature_options.roles.projection);
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.gravity_divergence,
|
||||
quadratureOptions.gravity_divergence
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.gravity_source, quadratureOptions.gravity_source
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.gravity_boundary,
|
||||
quadratureOptions.gravity_boundary
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.centrifugal, quadratureOptions.centrifugal
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.density_projection,
|
||||
quadratureOptions.density_projection
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.eos_closure, quadratureOptions.eos_closure
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.hydrostatic_equilibrium,
|
||||
quadratureOptions.hydrostatic_equilibrium
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.isobaric_surface,
|
||||
quadratureOptions.isobaric_surface
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.mesh_extension, quadratureOptions.mesh_extension
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.mass_conservation,
|
||||
quadratureOptions.mass_conservation
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.mass_normalization,
|
||||
quadratureOptions.mass_normalization
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.center_of_mass, quadratureOptions.center_of_mass
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.quadrupole, quadratureOptions.quadrupole
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.gravitational_energy,
|
||||
quadratureOptions.gravitational_energy
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.pressure_integral,
|
||||
quadratureOptions.pressure_integral
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.pressure_force, quadratureOptions.pressure_force
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.virial, quadratureOptions.virial
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.error_norm, quadratureOptions.error_norm
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.roles.discretization,
|
||||
quadratureOptions.roles.discretization
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.roles.preconditioner,
|
||||
quadratureOptions.roles.preconditioner
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.roles.diagnostic,
|
||||
quadratureOptions.roles.diagnostic
|
||||
);
|
||||
|
||||
apply_quadrature_options(
|
||||
quadratureRuleSet.roles.projection,
|
||||
quadratureOptions.roles.projection
|
||||
);
|
||||
|
||||
fem.quadratureFactory = std::make_unique<quadrature::RuleFactory>(
|
||||
quadrature::Policy(std::move(quadratureRuleSet))
|
||||
);
|
||||
|
||||
// =====================================================================
|
||||
// Section 11: Stateless domain mapper
|
||||
// =====================================================================
|
||||
|
||||
auto exteriorDomain = std::make_unique<
|
||||
const mapping::compactification::KelvinCompactification>(
|
||||
args.kelvin_options
|
||||
);
|
||||
|
||||
fem.domainMapperStateless =
|
||||
std::make_unique<mapping::DomainMapperStateless>(
|
||||
args.domain_mapper_options, std::move(exteriorDomain)
|
||||
);
|
||||
|
||||
fem.quadrature_factory = std::make_unique<quadrature::RuleFactory>(quadrature::Policy(std::move(quadrature_rule_set)));
|
||||
return fem;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mean_field::fem
|
||||
@@ -4,7 +4,9 @@ module;
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::integrators {
|
||||
AdvectionIntegrator::AdvectionIntegrator(const mapping::DomainMapper &map) : m_map(map) {}
|
||||
AdvectionIntegrator::AdvectionIntegrator(const mapping::DomainMapper &map)
|
||||
: m_map(map) {
|
||||
}
|
||||
|
||||
void AdvectionIntegrator::AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
@@ -37,7 +39,8 @@ namespace mean_field::integrators {
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
|
||||
|
||||
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1);
|
||||
const mfem::IntegrationRule *ir =
|
||||
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1);
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); q++) {
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
@@ -80,7 +83,8 @@ namespace mean_field::integrators {
|
||||
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
r_v(i + c * dof_v) += shape_v(i) * rho_val * adv_val(c) * weight;
|
||||
r_v(i + c * dof_v) +=
|
||||
shape_v(i) * rho_val * adv_val(c) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,13 +109,16 @@ namespace mean_field::integrators {
|
||||
mfem::DenseMatrix *dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix *dv_drho = elmats(0, 1);
|
||||
|
||||
if (dv_dv) *dv_dv = 0.0;
|
||||
if (dv_drho) *dv_drho = 0.0;
|
||||
if (dv_dv)
|
||||
*dv_dv = 0.0;
|
||||
if (dv_drho)
|
||||
*dv_drho = 0.0;
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
|
||||
|
||||
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1);
|
||||
const mfem::IntegrationRule *ir =
|
||||
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1);
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); q++) {
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
@@ -164,7 +171,8 @@ namespace mean_field::integrators {
|
||||
double v_dot_grad_phi_j = 0.0;
|
||||
|
||||
for (int k = 0; k < dim; ++k) {
|
||||
v_dot_grad_phi_j += v_val(k) * dshape_v_phys(j, k);
|
||||
v_dot_grad_phi_j +=
|
||||
v_val(k) * dshape_v_phys(j, k);
|
||||
}
|
||||
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
@@ -172,14 +180,18 @@ namespace mean_field::integrators {
|
||||
int col = j + d * dof_v;
|
||||
|
||||
// \rho (\delta \vec{v} \cdot \nabla \vec{v})
|
||||
// \delta v is along direction 'd' for the cth component of advection
|
||||
// \delta v is along direction 'd' for the cth
|
||||
// component of advection
|
||||
double termA = shape_v(j) * grad_v(c, d);
|
||||
|
||||
// \rho(\vec{v} \cdot \nabla \delta \vec{v})
|
||||
// Only non-zero when the advected component matches the test component
|
||||
double termB = (c == d) ? v_dot_grad_phi_j : 0.0;
|
||||
// Only non-zero when the advected component
|
||||
// matches the test component
|
||||
double termB =
|
||||
(c == d) ? v_dot_grad_phi_j : 0.0;
|
||||
|
||||
(*dv_dv)(row, col) += shape_v(i) * rho_val * (termA + termB) * weight;
|
||||
(*dv_dv)(row, col) += shape_v(i) * rho_val *
|
||||
(termA + termB) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,4 +215,4 @@ namespace mean_field::integrators {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace mean_field::integrators
|
||||
@@ -4,18 +4,26 @@ module mean_field;
|
||||
|
||||
namespace mean_field::integrators {
|
||||
CentrifugalForceIntegrator::CentrifugalForceIntegrator(
|
||||
const mapping::DomainMapper& map,
|
||||
const mfem::Vector& omega
|
||||
) : m_map(map), m_omega(3) {
|
||||
const mapping::DomainMapper &map,
|
||||
const mfem::Vector &omega
|
||||
)
|
||||
: m_map(map),
|
||||
m_omega(3) {
|
||||
MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D");
|
||||
m_omega = omega;
|
||||
}
|
||||
|
||||
void CentrifugalForceIntegrator::SetOmega(const mfem::Vector& omega) {
|
||||
void CentrifugalForceIntegrator::SetOmega(const mfem::Vector &omega) {
|
||||
MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D");
|
||||
m_omega = omega;
|
||||
}
|
||||
|
||||
void CentrifugalForceIntegrator::SetIntegrationRule(
|
||||
const mfem::IntegrationRule &ir
|
||||
) {
|
||||
m_ir = &ir;
|
||||
}
|
||||
|
||||
void CentrifugalForceIntegrator::AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
@@ -26,18 +34,18 @@ namespace mean_field::integrators {
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
const mfem::FiniteElement *fe_v = el[0];
|
||||
const mfem::FiniteElement *fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& rho_dofs = *elfun[1];
|
||||
const mfem::Vector &rho_dofs = *elfun[1];
|
||||
|
||||
mfem::Vector& r_v = *elvec[0];
|
||||
r_v = 0.0;
|
||||
mfem::Vector &r_v = *elvec[0];
|
||||
r_v.SetSize(dof_v * dim);
|
||||
r_v = 0.0;
|
||||
if (elvec[1]) {
|
||||
elvec[1]->SetSize(dof_rho);
|
||||
*elvec[1] = 0.0;
|
||||
@@ -47,10 +55,15 @@ namespace mean_field::integrators {
|
||||
mfem::Vector x_phys(dim);
|
||||
mfem::Vector a(dim), b(dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
MFEM_VERIFY(
|
||||
m_ir, "CentrifugalForceIntegrator must be configured with an "
|
||||
"integration rule before assembly. Call "
|
||||
"SetIntegrationRule first."
|
||||
);
|
||||
const mfem::IntegrationRule *ir = m_ir;
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
@@ -89,30 +102,39 @@ namespace mean_field::integrators {
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
if (utils::is_vacuum(Tr, elmats)) {
|
||||
return;
|
||||
}
|
||||
const mfem::FiniteElement *fe_v = el[0];
|
||||
const mfem::FiniteElement *fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
mfem::DenseMatrix* dv_dv = elmats(0,0);
|
||||
mfem::DenseMatrix* dv_drho = elmats(0,1);
|
||||
mfem::DenseMatrix *dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix *dv_drho = elmats(0, 1);
|
||||
|
||||
if (dv_dv) *dv_dv = 0.0;
|
||||
if (elmats(1, 0)) *elmats(1, 0) = 0.0;
|
||||
if (elmats(1, 1)) *elmats(1, 1) = 0.0;
|
||||
if (dv_drho) *dv_drho = 0.0;
|
||||
if (!dv_drho) return;
|
||||
if (dv_dv)
|
||||
*dv_dv = 0.0;
|
||||
if (elmats(1, 0))
|
||||
*elmats(1, 0) = 0.0;
|
||||
if (elmats(1, 1))
|
||||
*elmats(1, 1) = 0.0;
|
||||
if (dv_drho)
|
||||
*dv_drho = 0.0;
|
||||
if (!dv_drho)
|
||||
return;
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::Vector x_phys(dim);
|
||||
mfem::Vector a(dim), b(dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
const mfem::IntegrationRule *ir =
|
||||
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
@@ -137,10 +159,11 @@ namespace mean_field::integrators {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const int row = i + c * dof_v;
|
||||
for (int j = 0; j < dof_rho; ++j) {
|
||||
(*dv_drho)(row, j) += shape_v(i) * shape_rho(j) * b(c) * weight;
|
||||
(*dv_drho)(row, j) +=
|
||||
shape_v(i) * shape_rho(j) * b(c) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace mean_field::integrators
|
||||
@@ -4,8 +4,12 @@ module;
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::integrators {
|
||||
CoriolisIntegrator::CoriolisIntegrator(const mapping::DomainMapper& map, const mfem::Vector& omega)
|
||||
: m_map(map), m_omega(omega) {
|
||||
CoriolisIntegrator::CoriolisIntegrator(
|
||||
const mapping::DomainMapper &map,
|
||||
const mfem::Vector &omega
|
||||
)
|
||||
: m_map(map),
|
||||
m_omega(omega) {
|
||||
m_omega_mat.SetSize(3, 3);
|
||||
m_omega_mat = 0.0;
|
||||
m_omega_mat(0, 1) = -m_omega(2);
|
||||
@@ -16,7 +20,8 @@ namespace mean_field::integrators {
|
||||
m_omega_mat(2, 1) = m_omega(0);
|
||||
}
|
||||
|
||||
void CoriolisIntegrator::AssembleElementVector(const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
void CoriolisIntegrator::AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array<mfem::Vector *> &elvec
|
||||
@@ -25,17 +30,17 @@ namespace mean_field::integrators {
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
const mfem::FiniteElement *fe_v = el[0];
|
||||
const mfem::FiniteElement *fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& v_dofs = *elfun[0];
|
||||
const mfem::Vector& rho_dofs = *elfun[1];
|
||||
const mfem::Vector &v_dofs = *elfun[0];
|
||||
const mfem::Vector &rho_dofs = *elfun[1];
|
||||
|
||||
mfem::Vector& r_v = *elvec[0];
|
||||
mfem::Vector &r_v = *elvec[0];
|
||||
r_v.SetSize(dof_v * dim);
|
||||
r_v = 0.0;
|
||||
if (elvec[1]) {
|
||||
@@ -44,10 +49,11 @@ namespace mean_field::integrators {
|
||||
}
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
const mfem::IntegrationRule *ir =
|
||||
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
@@ -56,11 +62,14 @@ namespace mean_field::integrators {
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) rho_val += rho_dofs(i) * shape_rho(i);
|
||||
for (int i = 0; i < dof_rho; ++i)
|
||||
rho_val += rho_dofs(i) * shape_rho(i);
|
||||
|
||||
mfem::Vector v_val(dim); v_val = 0.0;
|
||||
mfem::Vector v_val(dim);
|
||||
v_val = 0.0;
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) v_val(c) += v_dofs(i + c * dof_v) * shape_v(i);
|
||||
for (int c = 0; c < dim; ++c)
|
||||
v_val(c) += v_dofs(i + c * dof_v) * shape_v(i);
|
||||
}
|
||||
|
||||
mfem::Vector F_coriolis(dim);
|
||||
@@ -69,39 +78,44 @@ namespace mean_field::integrators {
|
||||
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
r_v(i + c * dof_v) += shape_v(i) * rho_val * F_coriolis(c) * weight;
|
||||
r_v(i + c * dof_v) +=
|
||||
shape_v(i) * rho_val * F_coriolis(c) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CoriolisIntegrator::AssembleElementGrad(const mfem::Array<const mfem::FiniteElement*> &el,
|
||||
void CoriolisIntegrator::AssembleElementGrad(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
const mfem::FiniteElement *fe_v = el[0];
|
||||
const mfem::FiniteElement *fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& v_dofs = *elfun[0];
|
||||
const mfem::Vector& rho_dofs = *elfun[1];
|
||||
const mfem::Vector &v_dofs = *elfun[0];
|
||||
const mfem::Vector &rho_dofs = *elfun[1];
|
||||
|
||||
mfem::DenseMatrix* dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix* dv_drho = elmats(0, 1);
|
||||
mfem::DenseMatrix *dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix *dv_drho = elmats(0, 1);
|
||||
|
||||
if (dv_dv) *dv_dv = 0.0;
|
||||
if (dv_drho) *dv_drho = 0.0;
|
||||
if (dv_dv)
|
||||
*dv_dv = 0.0;
|
||||
if (dv_drho)
|
||||
*dv_drho = 0.0;
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
const mfem::IntegrationRule *ir =
|
||||
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
@@ -110,11 +124,14 @@ namespace mean_field::integrators {
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) rho_val += rho_dofs(i) * shape_rho(i);
|
||||
for (int i = 0; i < dof_rho; ++i)
|
||||
rho_val += rho_dofs(i) * shape_rho(i);
|
||||
|
||||
mfem::Vector v_val(dim); v_val = 0.0;
|
||||
mfem::Vector v_val(dim);
|
||||
v_val = 0.0;
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) v_val(c) += v_dofs(i + c * dof_v) * shape_v(i);
|
||||
for (int c = 0; c < dim; ++c)
|
||||
v_val(c) += v_dofs(i + c * dof_v) * shape_v(i);
|
||||
}
|
||||
|
||||
mfem::Vector F_coriolis(dim);
|
||||
@@ -124,12 +141,14 @@ namespace mean_field::integrators {
|
||||
if (dv_dv) {
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
int row = i + c * dof_v;
|
||||
const int row = i + c * dof_v;
|
||||
for (int j = 0; j < dof_v; ++j) {
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
int col = j + d * dof_v;
|
||||
double coupling = m_omega_mat(c, d);
|
||||
(*dv_dv)(row, col) += shape_v(i) * shape_v(j) * 2.0 * rho_val * coupling * weight;
|
||||
(*dv_dv)(row, col) += shape_v(i) * shape_v(j) *
|
||||
2.0 * rho_val * coupling *
|
||||
weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,11 +161,12 @@ namespace mean_field::integrators {
|
||||
int row = i + c * dof_v;
|
||||
for (int j = 0; j < dof_rho; ++j) {
|
||||
int col = j;
|
||||
(*dv_drho)(row, col) += shape_v(i) * shape_rho(j) * F_coriolis(c) * weight;
|
||||
(*dv_drho)(row, col) += shape_v(i) * shape_rho(j) *
|
||||
F_coriolis(c) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace mean_field::integrators
|
||||
@@ -1,124 +1,341 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
module mean_field;
|
||||
import :solver.fields;
|
||||
|
||||
namespace {
|
||||
using namespace mean_field;
|
||||
|
||||
constexpr int velocity_block =
|
||||
solver::block_index(solver::FieldBlock::velocity);
|
||||
constexpr int density_block =
|
||||
solver::block_index(solver::FieldBlock::density);
|
||||
constexpr int gravity_gradient_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_gradient);
|
||||
constexpr int displacement_block =
|
||||
solver::block_index(solver::FieldBlock::displacement);
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::integrators {
|
||||
GravityForceIntegrator::GravityForceIntegrator(
|
||||
const mapping::DomainMapper& map,
|
||||
const mfem::GridFunction& phi
|
||||
): m_map(map), m_phi(&phi) {}
|
||||
GravityMomentumIntegrator::GravityMomentumIntegrator(
|
||||
const mapping::DomainMapper &map,
|
||||
const GravityForceJacobianMode jacobian_mode
|
||||
)
|
||||
: m_map(map),
|
||||
m_jacobian_mode(jacobian_mode) {
|
||||
}
|
||||
|
||||
void GravityForceIntegrator::SetPotential(const mfem::GridFunction& phi) { m_phi = φ };
|
||||
void GravityMomentumIntegrator::SetJacobianMode(
|
||||
const GravityForceJacobianMode jacobian_mode
|
||||
) {
|
||||
m_jacobian_mode = jacobian_mode;
|
||||
}
|
||||
|
||||
void GravityForceIntegrator::AssembleElementVector(
|
||||
void GravityMomentumIntegrator::SetIntegrationRule(
|
||||
const mfem::IntegrationRule &integration_rule
|
||||
) {
|
||||
m_integration_rule = &integration_rule;
|
||||
}
|
||||
|
||||
GravityForceJacobianMode
|
||||
GravityMomentumIntegrator::GetJacobianMode() const {
|
||||
return m_jacobian_mode;
|
||||
}
|
||||
|
||||
void GravityMomentumIntegrator::AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array<mfem::Vector *> &elvec
|
||||
) {
|
||||
|
||||
if (utils::is_vacuum(Tr, elvec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
MFEM_VERIFY(
|
||||
m_integration_rule,
|
||||
"GravityForceIntegrator must be configured with an "
|
||||
"integration rule before assembly."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
el.Size() > gravity_gradient_block,
|
||||
"GravityForceIntegrator requires velocity, density, and "
|
||||
"gravity-gradient finite elements."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
elfun.Size() > gravity_gradient_block,
|
||||
"GravityForceIntegrator requires velocity, density, and "
|
||||
"gravity-gradient element states."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
elvec.Size() > velocity_block && elvec[velocity_block],
|
||||
"GravityForceIntegrator requires a velocity residual block."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
el[velocity_block] && el[density_block] &&
|
||||
el[gravity_gradient_block],
|
||||
"GravityForceIntegrator received a null finite element."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
elfun[density_block] && elfun[gravity_gradient_block],
|
||||
"GravityForceIntegrator received a null element state."
|
||||
);
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const mfem::FiniteElement *velocity_element = el[velocity_block];
|
||||
const mfem::FiniteElement *density_element = el[density_block];
|
||||
const mfem::FiniteElement *gravity_gradient_element =
|
||||
el[gravity_gradient_block];
|
||||
|
||||
const int velocity_dofs_count = velocity_element->GetDof();
|
||||
const int density_dofs_count = density_element->GetDof();
|
||||
const int gravity_gradient_dofs_count =
|
||||
gravity_gradient_element->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& rho_dofs = *elfun[1];
|
||||
const mfem::Vector &density_dofs = *elfun[density_block];
|
||||
const mfem::Vector &gravity_gradient_dofs =
|
||||
*elfun[gravity_gradient_block];
|
||||
|
||||
mfem::Vector& r_v = *elvec[0];
|
||||
r_v.SetSize(dof_v * dim);
|
||||
r_v = 0.0;
|
||||
if (elvec[1]) {
|
||||
elvec[1]->SetSize(dof_rho);
|
||||
*elvec[1] = 0.0;
|
||||
MFEM_VERIFY(
|
||||
density_dofs.Size() == density_dofs_count,
|
||||
"GravityForceIntegrator received an incorrectly sized density "
|
||||
"state."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
gravity_gradient_dofs.Size() == gravity_gradient_dofs_count,
|
||||
"GravityForceIntegrator received an incorrectly sized "
|
||||
"gravity-gradient "
|
||||
"state."
|
||||
);
|
||||
|
||||
for (int block = 0; block < elvec.Size(); ++block) {
|
||||
if (elvec[block]) {
|
||||
*elvec[block] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::Vector grad_phi_ref(dim), grad_phi_phys(dim), grad_phi_elem(dim);
|
||||
mfem::Vector &velocity_residual = *elvec[velocity_block];
|
||||
velocity_residual.SetSize(dim * velocity_dofs_count);
|
||||
velocity_residual = 0.0;
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
m_phi->GetGradient(Tr, grad_phi_elem);
|
||||
mfem::DenseMatrix J_map(dim, dim), J_map_inv(dim, dim);
|
||||
m_map.ComputeJacobian(Tr, J_map);
|
||||
mfem::CalcInverse(J_map, J_map_inv);
|
||||
J_map_inv.MultTranspose(grad_phi_elem, grad_phi_phys);
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) {
|
||||
rho_val += rho_dofs(i) * shape_rho(i);
|
||||
if (elvec.Size() > density_block && elvec[density_block]) {
|
||||
elvec[density_block]->SetSize(density_dofs_count);
|
||||
*elvec[density_block] = 0.0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
r_v(i + c * dof_v) += shape_v(i) * rho_val * grad_phi_phys(c) * weight;
|
||||
if (elvec.Size() > gravity_gradient_block &&
|
||||
elvec[gravity_gradient_block]) {
|
||||
elvec[gravity_gradient_block]->SetSize(gravity_gradient_dofs_count);
|
||||
*elvec[gravity_gradient_block] = 0.0;
|
||||
}
|
||||
|
||||
mfem::Vector velocity_shape(velocity_dofs_count);
|
||||
mfem::Vector density_shape(density_dofs_count);
|
||||
mfem::DenseMatrix gravity_gradient_shape(
|
||||
gravity_gradient_dofs_count, dim
|
||||
);
|
||||
mfem::Vector gravity_gradient_element_value(dim);
|
||||
mfem::Vector gravity_gradient_physical_value(dim);
|
||||
|
||||
const mfem::IntegrationRule &integration_rule = *m_integration_rule;
|
||||
|
||||
for (int q = 0; q < integration_rule.GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint &integration_point =
|
||||
integration_rule.IntPoint(q);
|
||||
Tr.SetIntPoint(&integration_point);
|
||||
|
||||
const mapping::VolumeQuadratureContext context =
|
||||
m_map.GetQuadratureContext(Tr, integration_point);
|
||||
|
||||
velocity_element->CalcShape(integration_point, velocity_shape);
|
||||
density_element->CalcShape(integration_point, density_shape);
|
||||
gravity_gradient_element->CalcVShape(Tr, gravity_gradient_shape);
|
||||
|
||||
gravity_gradient_shape.MultTranspose(
|
||||
gravity_gradient_dofs, gravity_gradient_element_value
|
||||
);
|
||||
context.J_inv.MultTranspose(
|
||||
gravity_gradient_element_value, gravity_gradient_physical_value
|
||||
);
|
||||
|
||||
double density_value = 0.0;
|
||||
for (int i = 0; i < density_dofs_count; ++i) {
|
||||
density_value += density_dofs(i) * density_shape(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < velocity_dofs_count; ++i) {
|
||||
for (int component = 0; component < dim; ++component) {
|
||||
velocity_residual(i + component * velocity_dofs_count) +=
|
||||
velocity_shape(i) * density_value *
|
||||
gravity_gradient_physical_value(component) *
|
||||
context.weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GravityForceIntegrator::AssembleElementGrad(
|
||||
void GravityMomentumIntegrator::AssembleElementGrad(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
if (utils::is_vacuum(Tr, elmats)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
MFEM_VERIFY(
|
||||
m_integration_rule,
|
||||
"GravityForceIntegrator must be configured with an "
|
||||
"integration rule before assembly."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
el.Size() > gravity_gradient_block,
|
||||
"GravityForceIntegrator requires velocity, density, and "
|
||||
"gravity-gradient finite elements."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
elfun.Size() > gravity_gradient_block,
|
||||
"GravityForceIntegrator requires velocity, density, and "
|
||||
"gravity-gradient element states."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
el[velocity_block] && el[density_block] &&
|
||||
el[gravity_gradient_block],
|
||||
"GravityForceIntegrator received a null finite element."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
elfun[density_block] && elfun[gravity_gradient_block],
|
||||
"GravityForceIntegrator received a null element state."
|
||||
);
|
||||
|
||||
for (int row = 0; row < elmats.NumRows(); ++row) {
|
||||
for (int column = 0; column < elmats.NumCols(); ++column) {
|
||||
if (elmats(row, column)) {
|
||||
*elmats(row, column) = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_jacobian_mode == GravityForceJacobianMode::exact) {
|
||||
MFEM_ABORT(
|
||||
"Exact GravityForceIntegrator geometry Jacobian is unavailable "
|
||||
"until "
|
||||
"DomainMapper linearization is "
|
||||
"implemented."
|
||||
);
|
||||
}
|
||||
|
||||
const mfem::FiniteElement *velocity_element = el[velocity_block];
|
||||
const mfem::FiniteElement *density_element = el[density_block];
|
||||
const mfem::FiniteElement *gravity_gradient_element =
|
||||
el[gravity_gradient_block];
|
||||
|
||||
const int velocity_dofs_count = velocity_element->GetDof();
|
||||
const int density_dofs_count = density_element->GetDof();
|
||||
const int gravity_gradient_dofs_count =
|
||||
gravity_gradient_element->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
mfem::DenseMatrix* dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix* dv_drho = elmats(0, 1);
|
||||
const mfem::Vector &density_dofs = *elfun[density_block];
|
||||
const mfem::Vector &gravity_gradient_dofs =
|
||||
*elfun[gravity_gradient_block];
|
||||
|
||||
if (dv_dv) *dv_dv = 0.0;
|
||||
if (dv_drho) *dv_drho = 0.0;
|
||||
MFEM_VERIFY(
|
||||
density_dofs.Size() == density_dofs_count,
|
||||
"GravityForceIntegrator received an incorrectly sized density "
|
||||
"state."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
gravity_gradient_dofs.Size() == gravity_gradient_dofs_count,
|
||||
"GravityForceIntegrator received an incorrectly sized "
|
||||
"gravity-gradient "
|
||||
"state."
|
||||
);
|
||||
|
||||
if (!dv_drho) return;
|
||||
mfem::DenseMatrix *dv_drho = elmats(velocity_block, density_block);
|
||||
mfem::DenseMatrix *dv_dgrad_phi =
|
||||
m_jacobian_mode == GravityForceJacobianMode::field_coupled
|
||||
? elmats(velocity_block, gravity_gradient_block)
|
||||
: nullptr;
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::Vector grad_phi_ref(dim), grad_phi_phys(dim), grad_phi_elem(dim);
|
||||
if (!dv_drho && !dv_dgrad_phi) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
mfem::Vector velocity_shape(velocity_dofs_count);
|
||||
mfem::Vector density_shape(density_dofs_count);
|
||||
mfem::DenseMatrix gravity_gradient_shape(
|
||||
gravity_gradient_dofs_count, dim
|
||||
);
|
||||
mfem::Vector gravity_gradient_element_value(dim);
|
||||
mfem::Vector gravity_gradient_physical_value(dim);
|
||||
mfem::Vector gravity_basis_element(dim);
|
||||
mfem::Vector gravity_basis_physical(dim);
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
const mfem::IntegrationRule &integration_rule = *m_integration_rule;
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
m_phi->GetGradient(Tr, grad_phi_elem);
|
||||
mfem::DenseMatrix J_map(dim, dim), J_map_inv(dim, dim);
|
||||
m_map.ComputeJacobian(Tr, J_map);
|
||||
mfem::CalcInverse(J_map, J_map_inv);
|
||||
J_map_inv.MultTranspose(grad_phi_elem, grad_phi_phys);
|
||||
for (int q = 0; q < integration_rule.GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint &integration_point =
|
||||
integration_rule.IntPoint(q);
|
||||
Tr.SetIntPoint(&integration_point);
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
const mapping::VolumeQuadratureContext context =
|
||||
m_map.GetQuadratureContext(Tr, integration_point);
|
||||
|
||||
velocity_element->CalcShape(integration_point, velocity_shape);
|
||||
density_element->CalcShape(integration_point, density_shape);
|
||||
gravity_gradient_element->CalcVShape(Tr, gravity_gradient_shape);
|
||||
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const int row = i + c * dof_v;
|
||||
for (int j = 0; j < dof_rho; ++j) {
|
||||
(*dv_drho)(row, j) += shape_v(i) * shape_rho(j) * grad_phi_phys(c) * weight;
|
||||
gravity_gradient_shape.MultTranspose(
|
||||
gravity_gradient_dofs, gravity_gradient_element_value
|
||||
);
|
||||
context.J_inv.MultTranspose(
|
||||
gravity_gradient_element_value, gravity_gradient_physical_value
|
||||
);
|
||||
|
||||
double density_value = 0.0;
|
||||
for (int i = 0; i < density_dofs_count; ++i) {
|
||||
density_value += density_dofs(i) * density_shape(i);
|
||||
}
|
||||
|
||||
if (dv_drho) {
|
||||
for (int i = 0; i < velocity_dofs_count; ++i) {
|
||||
for (int component = 0; component < dim; ++component) {
|
||||
const int row = i + component * velocity_dofs_count;
|
||||
|
||||
for (int j = 0; j < density_dofs_count; ++j) {
|
||||
(*dv_drho)(row, j) +=
|
||||
velocity_shape(i) * density_shape(j) *
|
||||
gravity_gradient_physical_value(component) *
|
||||
context.weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dv_dgrad_phi) {
|
||||
for (int j = 0; j < gravity_gradient_dofs_count; ++j) {
|
||||
for (int component = 0; component < dim; ++component) {
|
||||
gravity_basis_element(component) =
|
||||
gravity_gradient_shape(j, component);
|
||||
}
|
||||
|
||||
context.J_inv.MultTranspose(
|
||||
gravity_basis_element, gravity_basis_physical
|
||||
);
|
||||
|
||||
for (int i = 0; i < velocity_dofs_count; ++i) {
|
||||
for (int component = 0; component < dim; ++component) {
|
||||
const int row = i + component * velocity_dofs_count;
|
||||
(*dv_dgrad_phi)(row, j) +=
|
||||
velocity_shape(i) * density_value *
|
||||
gravity_basis_physical(component) *
|
||||
context.weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace mean_field::integrators
|
||||
@@ -4,7 +4,10 @@ module;
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::integrators {
|
||||
ContinuityVolumeIntegrator::ContinuityVolumeIntegrator(const mapping::DomainMapper& map) : m_map(map) {};
|
||||
ContinuityVolumeIntegrator::ContinuityVolumeIntegrator(
|
||||
const mapping::DomainMapper &map
|
||||
)
|
||||
: m_map(map) { };
|
||||
|
||||
void ContinuityVolumeIntegrator::AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
@@ -26,25 +29,27 @@ namespace mean_field::integrators {
|
||||
const mfem::Vector v_dofs = *elfun[0];
|
||||
const mfem::Vector rho_dofs = *elfun[1];
|
||||
|
||||
void* data_rho_before = elvec[1] ? (void*)elvec[1]->GetData() : nullptr;
|
||||
void *data_rho_before =
|
||||
elvec[1] ? (void *)elvec[1]->GetData() : nullptr;
|
||||
int size_rho_before = elvec[1] ? elvec[1]->Size() : -1;
|
||||
|
||||
if (elvec[0]) {
|
||||
elvec[0]->SetSize(dof_v * dim);
|
||||
*elvec[0] = 0.0;
|
||||
}
|
||||
mfem::Vector& r_rho = *elvec[1];
|
||||
mfem::Vector &r_rho = *elvec[1];
|
||||
r_rho.SetSize(dof_rho);
|
||||
r_rho = 0.0;
|
||||
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::DenseMatrix dshape_rho_ref(dof_rho, dim), dshape_rho_phys(dof_rho, dim);
|
||||
mfem::DenseMatrix dshape_rho_ref(dof_rho, dim),
|
||||
dshape_rho_phys(dof_rho, dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
const mfem::IntegrationRule *ir =
|
||||
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
@@ -54,7 +59,8 @@ namespace mean_field::integrators {
|
||||
fe_rho->CalcDShape(ip, dshape_rho_ref);
|
||||
mfem::Mult(dshape_rho_ref, J_inv, dshape_rho_phys);
|
||||
|
||||
mfem::Vector v_val(dim); v_val = 0.0;
|
||||
mfem::Vector v_val(dim);
|
||||
v_val = 0.0;
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const int row = i + c * dof_v;
|
||||
@@ -90,25 +96,31 @@ namespace mean_field::integrators {
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& v_dofs = *elfun[0];
|
||||
const mfem::Vector& rho_dofs = *elfun[1];
|
||||
const mfem::Vector &v_dofs = *elfun[0];
|
||||
const mfem::Vector &rho_dofs = *elfun[1];
|
||||
|
||||
mfem::DenseMatrix* drho_dv = elmats(1, 0);
|
||||
mfem::DenseMatrix* drho_drho = elmats(1, 1);
|
||||
mfem::DenseMatrix *drho_dv = elmats(1, 0);
|
||||
mfem::DenseMatrix *drho_drho = elmats(1, 1);
|
||||
|
||||
if (elmats(0, 0)) *elmats(0, 0) = 0.0;
|
||||
if (elmats(0, 1)) *elmats(0, 1) = 0.0;
|
||||
if (elmats(0, 0))
|
||||
*elmats(0, 0) = 0.0;
|
||||
if (elmats(0, 1))
|
||||
*elmats(0, 1) = 0.0;
|
||||
|
||||
if (drho_dv) *drho_dv = 0.0;
|
||||
if (drho_drho) *drho_drho = 0.0;
|
||||
if (drho_dv)
|
||||
*drho_dv = 0.0;
|
||||
if (drho_drho)
|
||||
*drho_drho = 0.0;
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::DenseMatrix dshape_rho_ref(dof_rho, dim), dshape_rho_phys(dof_rho, dim);
|
||||
mfem::DenseMatrix dshape_rho_ref(dof_rho, dim),
|
||||
dshape_rho_phys(dof_rho, dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
const mfem::IntegrationRule *ir =
|
||||
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
@@ -118,7 +130,8 @@ namespace mean_field::integrators {
|
||||
fe_rho->CalcDShape(ip, dshape_rho_ref);
|
||||
mfem::Mult(dshape_rho_ref, J_inv, dshape_rho_phys);
|
||||
|
||||
mfem::Vector v_val(dim); v_val = 0.0;
|
||||
mfem::Vector v_val(dim);
|
||||
v_val = 0.0;
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const int row = i + c * dof_v;
|
||||
@@ -136,7 +149,8 @@ namespace mean_field::integrators {
|
||||
for (int j = 0; j < dof_v; ++j) {
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
const int col = j + d * dof_v;
|
||||
(*drho_dv)(i, col) -= dshape_rho_phys(i, d) * rho_val * shape_v(j) * weight;
|
||||
(*drho_dv)(i, col) -= dshape_rho_phys(i, d) *
|
||||
rho_val * shape_v(j) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,15 +163,19 @@ namespace mean_field::integrators {
|
||||
grad_psi_dot_v += dshape_rho_phys(i, c) * v_val(c);
|
||||
}
|
||||
for (int j = 0; j < dof_rho; ++j) {
|
||||
(*drho_drho)(i, j) -= grad_psi_dot_v * shape_rho(j) * weight;
|
||||
|
||||
(*drho_drho)(i, j) -=
|
||||
grad_psi_dot_v * shape_rho(j) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContinuityFaceIntegrator::ContinuityFaceIntegrator(const mapping::DomainMapper& map): m_map(map) {}
|
||||
ContinuityFaceIntegrator::ContinuityFaceIntegrator(
|
||||
const mapping::DomainMapper &map
|
||||
)
|
||||
: m_map(map) {
|
||||
}
|
||||
|
||||
void ContinuityFaceIntegrator::AssembleFaceVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el1,
|
||||
@@ -200,30 +218,39 @@ namespace mean_field::integrators {
|
||||
return; // Boundary face,
|
||||
}
|
||||
|
||||
const mfem::Vector &v_dofs = *elfun[0]; // Size: dim * dof_v_minus + dim*dof_v_plus
|
||||
const mfem::Vector &rho_dofs = *elfun[1]; // Size: dof_rho_minus + dof_rho_plus
|
||||
const mfem::Vector &v_dofs =
|
||||
*elfun[0]; // Size: dim * dof_v_minus + dim*dof_v_plus
|
||||
const mfem::Vector &rho_dofs =
|
||||
*elfun[1]; // Size: dof_rho_minus + dof_rho_plus
|
||||
|
||||
// Helpers to auto offset to the correct point in the dof array
|
||||
auto rho_minus_dof = [&](const int i) {return rho_dofs(i);};
|
||||
auto rho_plus_dof = [&](const int i) {return rho_dofs(i + dof_rho_minus);};
|
||||
auto v_minus_dof = [&](const int k, const int c) {return v_dofs(k + c * dof_v_minus);};
|
||||
auto rho_minus_dof = [&](const int i) { return rho_dofs(i); };
|
||||
auto rho_plus_dof = [&](const int i) {
|
||||
return rho_dofs(i + dof_rho_minus);
|
||||
};
|
||||
auto v_minus_dof = [&](const int k, const int c) {
|
||||
return v_dofs(k + c * dof_v_minus);
|
||||
};
|
||||
|
||||
const int p_v = fe_v_minus->GetOrder();
|
||||
const int p_rho = fe_rho_minus->GetOrder();
|
||||
const int int_order = 2 * std::max(p_v, p_rho) + 1;
|
||||
|
||||
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(Tr.GetGeometryType(), int_order);
|
||||
const mfem::IntegrationRule *ir =
|
||||
&mfem::IntRules.Get(Tr.GetGeometryType(), int_order);
|
||||
|
||||
mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus), shape_rho_plus(dof_rho_plus);
|
||||
mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus),
|
||||
shape_rho_plus(dof_rho_plus);
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& face_ip = ir->IntPoint(q);
|
||||
const mfem::IntegrationPoint &face_ip = ir->IntPoint(q);
|
||||
|
||||
Tr.SetAllIntPoints(&face_ip);
|
||||
const mfem::IntegrationPoint &ip_minus = Tr.GetElement1IntPoint();
|
||||
const mfem::IntegrationPoint &ip_plus = Tr.GetElement2IntPoint();
|
||||
|
||||
auto [n_unit, ds, v_dot_n_scale] = m_map.GetFaceQuadratureContext(Tr, face_ip);
|
||||
auto [n_unit, ds, v_dot_n_scale] =
|
||||
m_map.GetFaceQuadratureContext(Tr, face_ip);
|
||||
|
||||
fe_v_minus->CalcShape(ip_minus, shape_v_minus);
|
||||
fe_rho_minus->CalcShape(ip_minus, shape_rho_minus);
|
||||
@@ -251,12 +278,14 @@ namespace mean_field::integrators {
|
||||
}
|
||||
|
||||
// Upwind density
|
||||
// I use the convention that the flow is positive when moving from minus to plus
|
||||
// I use the convention that the flow is positive when moving from
|
||||
// minus to plus
|
||||
const double rho_up = (u_n >= 0) ? rho_minus_val : rho_plus_val;
|
||||
|
||||
const double flux_weighted = u_n * rho_up * ds;
|
||||
|
||||
// Note the normals need to be in opposite directions for these two fluxes
|
||||
// Note the normals need to be in opposite directions for these two
|
||||
// fluxes
|
||||
for (int i = 0; i < dof_rho_minus; ++i) {
|
||||
r_rho(i) += shape_rho_minus(i) * flux_weighted;
|
||||
}
|
||||
@@ -288,7 +317,8 @@ namespace mean_field::integrators {
|
||||
const int N_v_total = dim * (dof_v_minus + dof_v_plus);
|
||||
const int N_rho_total = dof_rho_minus + dof_rho_plus;
|
||||
|
||||
auto size_and_zero_mat = [&](mfem::DenseMatrix* mat, const int r_size, const int c_size) {
|
||||
auto size_and_zero_mat = [&](mfem::DenseMatrix *mat, const int r_size,
|
||||
const int c_size) {
|
||||
if (mat) {
|
||||
mat->SetSize(r_size, c_size);
|
||||
*mat = 0.0;
|
||||
@@ -300,34 +330,41 @@ namespace mean_field::integrators {
|
||||
size_and_zero_mat(elmats(1, 0), N_rho_total, N_v_total);
|
||||
size_and_zero_mat(elmats(1, 1), N_rho_total, N_rho_total);
|
||||
|
||||
if (skip_face(Tr)) return;
|
||||
if (skip_face(Tr))
|
||||
return;
|
||||
|
||||
mfem::DenseMatrix *drho_dv = elmats(1, 0);
|
||||
mfem::DenseMatrix *drho_drho = elmats(1, 1);
|
||||
|
||||
if (!drho_dv && !drho_drho) return;
|
||||
if (!drho_dv && !drho_drho)
|
||||
return;
|
||||
|
||||
const mfem::Vector &v_dofs = *elfun[0];
|
||||
const mfem::Vector &rho_dofs = *elfun[1];
|
||||
|
||||
const int int_order = 2 * std::max(fe_v_minus->GetOrder(), fe_rho_minus->GetOrder()) + 1;
|
||||
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(Tr.GetGeometryType(), int_order);
|
||||
const int int_order =
|
||||
2 * std::max(fe_v_minus->GetOrder(), fe_rho_minus->GetOrder()) + 1;
|
||||
const mfem::IntegrationRule *ir =
|
||||
&mfem::IntRules.Get(Tr.GetGeometryType(), int_order);
|
||||
|
||||
mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus), shape_rho_plus(dof_rho_plus);
|
||||
mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus),
|
||||
shape_rho_plus(dof_rho_plus);
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& face_ip = ir->IntPoint(q);
|
||||
const mfem::IntegrationPoint &face_ip = ir->IntPoint(q);
|
||||
Tr.SetAllIntPoints(&face_ip);
|
||||
const mfem::IntegrationPoint &ip_minus = Tr.GetElement1IntPoint();
|
||||
const mfem::IntegrationPoint &ip_plus = Tr.GetElement2IntPoint();
|
||||
|
||||
auto [n_unit, ds, v_dot_n_scale] = m_map.GetFaceQuadratureContext(Tr, face_ip);
|
||||
auto [n_unit, ds, v_dot_n_scale] =
|
||||
m_map.GetFaceQuadratureContext(Tr, face_ip);
|
||||
|
||||
fe_v_minus->CalcShape(ip_minus, shape_v_minus);
|
||||
fe_rho_minus->CalcShape(ip_minus, shape_rho_minus);
|
||||
fe_rho_plus->CalcShape(ip_plus, shape_rho_plus);
|
||||
|
||||
const double u_n = compute_u_n(v_dofs, shape_v_minus, n_unit, dof_v_minus, dim);
|
||||
const double u_n =
|
||||
compute_u_n(v_dofs, shape_v_minus, n_unit, dof_v_minus, dim);
|
||||
|
||||
double rho_minus_val = 0.0;
|
||||
for (int i = 0; i < dof_rho_minus; ++i) {
|
||||
@@ -353,7 +390,8 @@ namespace mean_field::integrators {
|
||||
(*drho_drho)(i, ip) += shape_rho_minus(i) * col_w;
|
||||
}
|
||||
for (int j = 0; j < dof_rho_plus; ++j) {
|
||||
(*drho_drho)(dof_rho_minus + j, ip) -= shape_rho_plus(j) * col_w;
|
||||
(*drho_drho)(dof_rho_minus + j, ip) -=
|
||||
shape_rho_plus(j) * col_w;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -361,10 +399,12 @@ namespace mean_field::integrators {
|
||||
const double col_w = u_w * shape_rho_plus(jp);
|
||||
const int col_idx = dof_rho_minus + jp;
|
||||
for (int i = 0; i < dof_rho_minus; ++i) {
|
||||
(*drho_drho)(i, col_idx) += shape_rho_minus(i) * col_w;
|
||||
(*drho_drho)(i, col_idx) +=
|
||||
shape_rho_minus(i) * col_w;
|
||||
}
|
||||
for (int j = 0; j < dof_rho_plus; ++j) {
|
||||
(*drho_drho)(dof_rho_minus + j, col_idx) -= shape_rho_plus(j) * col_w;
|
||||
(*drho_drho)(dof_rho_minus + j, col_idx) -=
|
||||
shape_rho_plus(j) * col_w;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,10 +418,12 @@ namespace mean_field::integrators {
|
||||
const int col_idx = k + c * dof_v_minus;
|
||||
const double col_w = n_c_rho_w * shape_v_minus(k);
|
||||
for (int i = 0; i < dof_rho_minus; ++i) {
|
||||
(*drho_dv)(i, col_idx) += shape_rho_minus(i) * col_w;
|
||||
(*drho_dv)(i, col_idx) +=
|
||||
shape_rho_minus(i) * col_w;
|
||||
}
|
||||
for (int j = 0; j < dof_rho_plus; ++j) {
|
||||
(*drho_dv)(dof_rho_minus + j, col_idx) -= shape_rho_plus(j) * col_w;
|
||||
(*drho_dv)(dof_rho_minus + j, col_idx) -=
|
||||
shape_rho_plus(j) * col_w;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -389,7 +431,9 @@ namespace mean_field::integrators {
|
||||
}
|
||||
}
|
||||
|
||||
bool ContinuityFaceIntegrator::skip_face(const mfem::FaceElementTransformations& Tr) {
|
||||
bool ContinuityFaceIntegrator::skip_face(
|
||||
const mfem::FaceElementTransformations &Tr
|
||||
) {
|
||||
constexpr int VACUUM_ATTR = 3;
|
||||
const int attr_minus = Tr.Elem1->Attribute;
|
||||
const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1;
|
||||
@@ -402,7 +446,13 @@ namespace mean_field::integrators {
|
||||
return false;
|
||||
}
|
||||
|
||||
double ContinuityFaceIntegrator::compute_u_n(const mfem::Vector& v_dofs, const mfem::Vector& shape_v_minus, const mfem::Vector& n_unit, int dof_v_minus, int dim) {
|
||||
double ContinuityFaceIntegrator::compute_u_n(
|
||||
const mfem::Vector &v_dofs,
|
||||
const mfem::Vector &shape_v_minus,
|
||||
const mfem::Vector &n_unit,
|
||||
int dof_v_minus,
|
||||
int dim
|
||||
) {
|
||||
double u_n = 0.0;
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
double v_c = 0.0;
|
||||
@@ -413,5 +463,4 @@ namespace mean_field::integrators {
|
||||
}
|
||||
return u_n;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mean_field::integrators
|
||||
|
||||
@@ -4,12 +4,18 @@ module mean_field;
|
||||
|
||||
namespace mean_field::integrators {
|
||||
ViscosityIntegrator::ViscosityIntegrator(
|
||||
const mapping::DomainMapper& map,
|
||||
const mapping::DomainMapper &map,
|
||||
const double mu,
|
||||
const int quad_boost
|
||||
) : m_map(map), m_mu(mu), m_quad_boost(quad_boost) {}
|
||||
)
|
||||
: m_map(map),
|
||||
m_mu(mu),
|
||||
m_quad_boost(quad_boost) {
|
||||
}
|
||||
|
||||
void ViscosityIntegrator::SetMu(const double mu) { m_mu = mu; }
|
||||
void ViscosityIntegrator::SetMu(const double mu) {
|
||||
m_mu = mu;
|
||||
}
|
||||
|
||||
void ViscosityIntegrator::AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
@@ -21,19 +27,19 @@ namespace mean_field::integrators {
|
||||
return;
|
||||
}
|
||||
|
||||
void* data_before = (void*)elvec[0]->GetData();
|
||||
void *data_before = (void *)elvec[0]->GetData();
|
||||
int size_before = elvec[0]->Size();
|
||||
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
const mfem::FiniteElement *fe_v = el[0];
|
||||
const mfem::FiniteElement *fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& v_dofs = *elfun[0];
|
||||
const mfem::Vector &v_dofs = *elfun[0];
|
||||
|
||||
mfem::Vector& r_v = *elvec[0];
|
||||
mfem::Vector &r_v = *elvec[0];
|
||||
r_v.SetSize(dof_v * dim);
|
||||
r_v = 0.0;
|
||||
if (elvec[1]) {
|
||||
@@ -43,12 +49,13 @@ namespace mean_field::integrators {
|
||||
|
||||
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + m_quad_boost);
|
||||
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(
|
||||
fe_v->GetGeomType(), 2 * fe_v->GetOrder() + m_quad_boost
|
||||
);
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
|
||||
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
@@ -57,12 +64,13 @@ namespace mean_field::integrators {
|
||||
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
|
||||
|
||||
// ∇v(c,j) = δj v_c
|
||||
mfem::DenseMatrix grad_v(dim, dim); grad_v = 0.0;
|
||||
mfem::DenseMatrix grad_v(dim, dim);
|
||||
grad_v = 0.0;
|
||||
for (int n = 0; n < dof_v; ++n) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const double vn_c = v_dofs(n + c * dof_v);
|
||||
for (int j = 0; j < dim; ++j) {
|
||||
grad_v( c, j) += vn_c * dshape_v_phys(n, j);
|
||||
grad_v(c, j) += vn_c * dshape_v_phys(n, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +88,8 @@ namespace mean_field::integrators {
|
||||
double acc = 0.0;
|
||||
for (int j = 0; j < dim; ++j) {
|
||||
double D_cj = grad_v(c, j) + grad_v(j, c);
|
||||
if (c == j) D_cj -= (2.0 / 3.0) * div_v;
|
||||
if (c == j)
|
||||
D_cj -= (2.0 / 3.0) * div_v;
|
||||
acc += dshape_v_phys(i, j) * D_cj;
|
||||
}
|
||||
r_v(i + c * dof_v) += mu_w * acc;
|
||||
@@ -95,27 +104,33 @@ namespace mean_field::integrators {
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
const mfem::FiniteElement *fe_v = el[0];
|
||||
const mfem::FiniteElement *fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
mfem::DenseMatrix* dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix* dv_drho = elmats(0, 1);
|
||||
mfem::DenseMatrix *dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix *dv_drho = elmats(0, 1);
|
||||
|
||||
if (dv_drho) *dv_drho =0.0;
|
||||
if (dv_dv) *dv_dv = 0.0;
|
||||
if (elmats(1, 0)) *elmats(1, 0) = 0.0;
|
||||
if (elmats(1, 1)) *elmats(1, 1) = 0.0;
|
||||
if (!dv_dv) return;
|
||||
if (dv_drho)
|
||||
*dv_drho = 0.0;
|
||||
if (dv_dv)
|
||||
*dv_dv = 0.0;
|
||||
if (elmats(1, 0))
|
||||
*elmats(1, 0) = 0.0;
|
||||
if (elmats(1, 1))
|
||||
*elmats(1, 1) = 0.0;
|
||||
if (!dv_dv)
|
||||
return;
|
||||
|
||||
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
const mfem::IntegrationRule *ir =
|
||||
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
@@ -138,19 +153,19 @@ namespace mean_field::integrators {
|
||||
const int col = n + d * dof_v;
|
||||
|
||||
double val = 0.0;
|
||||
if (c == d) val += dot_grad;
|
||||
if (c == d)
|
||||
val += dot_grad;
|
||||
|
||||
val += dshape_v_phys(i, d) * dshape_v_phys(n, c);
|
||||
|
||||
val -= (2.0 / 3.0) * dshape_v_phys(i, c) * dshape_v_phys(n, d);
|
||||
val -= (2.0 / 3.0) * dshape_v_phys(i, c) *
|
||||
dshape_v_phys(n, d);
|
||||
(*dv_dv)(row, col) += mu_w * val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} // namespace mean_field::integrators
|
||||
@@ -10,13 +10,17 @@ namespace mean_field::mapping {
|
||||
//////////////////////////////
|
||||
MappedScalarCoefficient::MappedScalarCoefficient(
|
||||
const DomainMapper &map,
|
||||
mfem::Coefficient &coeff,
|
||||
Coefficient &coeff,
|
||||
const COORDINATE_SPACE coord_space
|
||||
) : m_map(map),
|
||||
)
|
||||
: m_map(map),
|
||||
m_coeff(coeff),
|
||||
m_coord_space(coord_space) {};
|
||||
m_coord_space(coord_space) { };
|
||||
|
||||
double MappedScalarCoefficient::Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
|
||||
double MappedScalarCoefficient::Eval(
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) {
|
||||
T.SetIntPoint(&ip);
|
||||
double f_val = 0.0;
|
||||
|
||||
@@ -33,7 +37,11 @@ namespace mean_field::mapping {
|
||||
}
|
||||
}
|
||||
|
||||
double MappedScalarCoefficient::eval_at_point(mfem::Coefficient &c, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
|
||||
double MappedScalarCoefficient::eval_at_point(
|
||||
Coefficient &c,
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) {
|
||||
return c.Eval(T, ip);
|
||||
}
|
||||
|
||||
@@ -45,22 +53,26 @@ namespace mean_field::mapping {
|
||||
const DomainMapper &map,
|
||||
mfem::Coefficient &sigma,
|
||||
const int dim
|
||||
) : mfem::MatrixCoefficient(dim),
|
||||
)
|
||||
: MatrixCoefficient(dim),
|
||||
m_map(map),
|
||||
m_scalar(&sigma),
|
||||
m_tensor(nullptr) {
|
||||
};
|
||||
m_tensor(nullptr) { };
|
||||
|
||||
MappedDiffusionCoefficient::MappedDiffusionCoefficient(
|
||||
const DomainMapper &map,
|
||||
mfem::MatrixCoefficient &sigma
|
||||
) : mfem::MatrixCoefficient(sigma.GetHeight()),
|
||||
MatrixCoefficient &sigma
|
||||
)
|
||||
: MatrixCoefficient(sigma.GetHeight()),
|
||||
m_map(map),
|
||||
m_scalar(nullptr),
|
||||
m_tensor(&sigma) {
|
||||
};
|
||||
m_tensor(&sigma) { };
|
||||
|
||||
void MappedDiffusionCoefficient::Eval(mfem::DenseMatrix &K, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
|
||||
void MappedDiffusionCoefficient::Eval(
|
||||
mfem::DenseMatrix &K,
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) {
|
||||
const int dim = height;
|
||||
T.SetIntPoint(&ip);
|
||||
|
||||
@@ -90,13 +102,17 @@ namespace mean_field::mapping {
|
||||
///////////////////////////////
|
||||
MappedVectorCoefficient::MappedVectorCoefficient(
|
||||
const DomainMapper &map,
|
||||
mfem::VectorCoefficient &coeff
|
||||
) : mfem::VectorCoefficient(coeff.GetVDim()),
|
||||
VectorCoefficient &coeff
|
||||
)
|
||||
: VectorCoefficient(coeff.GetVDim()),
|
||||
m_map(map),
|
||||
m_coeff(coeff) {
|
||||
};
|
||||
m_coeff(coeff) { };
|
||||
|
||||
void MappedVectorCoefficient::Eval(mfem::Vector &V, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
|
||||
void MappedVectorCoefficient::Eval(
|
||||
mfem::Vector &V,
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) {
|
||||
const int dim = vdim;
|
||||
T.SetIntPoint(&ip);
|
||||
|
||||
@@ -118,24 +134,32 @@ namespace mean_field::mapping {
|
||||
PhysicalPositionFunctionCoefficient::PhysicalPositionFunctionCoefficient(
|
||||
const DomainMapper &map,
|
||||
Func f // std::function<double(const mfem::Vector&)>
|
||||
) : m_f(std::move(f)),
|
||||
m_map(map) {};
|
||||
)
|
||||
: m_f(std::move(f)),
|
||||
m_map(map) { };
|
||||
|
||||
double PhysicalPositionFunctionCoefficient::Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
|
||||
double PhysicalPositionFunctionCoefficient::Eval(
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) {
|
||||
T.SetIntPoint(&ip);
|
||||
mfem::Vector x;
|
||||
m_map.GetPhysicalPoint(T, ip, x);
|
||||
return m_f(x);
|
||||
}
|
||||
|
||||
MappedHDivMassCoefficient::MappedHDivMassCoefficient(const DomainMapper& map, const int dim)
|
||||
: mfem::MatrixCoefficient(dim),
|
||||
m_map(map) {}
|
||||
MappedHDivMassCoefficient::MappedHDivMassCoefficient(
|
||||
const DomainMapper &map,
|
||||
const int dim
|
||||
)
|
||||
: MatrixCoefficient(dim),
|
||||
m_map(map) {
|
||||
}
|
||||
|
||||
void MappedHDivMassCoefficient::Eval(
|
||||
mfem::DenseMatrix& matrix,
|
||||
mfem::ElementTransformation& transformation,
|
||||
const mfem::IntegrationPoint& integration_point
|
||||
mfem::DenseMatrix &matrix,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point
|
||||
) {
|
||||
transformation.SetIntPoint(&integration_point);
|
||||
|
||||
@@ -144,9 +168,12 @@ namespace mean_field::mapping {
|
||||
|
||||
const double map_determinant = map_jacobian.Det();
|
||||
|
||||
MFEM_VERIFY(map_determinant > 0.0, "Domain mapping has a non-positive Jacobian determinant.");
|
||||
MFEM_VERIFY(
|
||||
map_determinant > 0.0,
|
||||
"Domain mapping has a non-positive Jacobian determinant."
|
||||
);
|
||||
|
||||
mfem::MultAtB(map_jacobian, map_jacobian, matrix);
|
||||
matrix *= 1.0 / std::abs(map_determinant);
|
||||
}
|
||||
}
|
||||
} // namespace mean_field::mapping
|
||||
|
||||
266
libmeanfield/impl/mapping/compactification/kelvin.cpp
Normal file
266
libmeanfield/impl/mapping/compactification/kelvin.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
module;
|
||||
|
||||
#include <cmath>
|
||||
#include <mfem.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
module mean_field;
|
||||
|
||||
namespace {
|
||||
bool vector_is_finite(const mfem::Vector &vector) {
|
||||
for (int i = 0; i < vector.Size(); ++i) {
|
||||
if (!std::isfinite(vector(i)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool matrix_is_finite(const mfem::DenseMatrix &matrix) {
|
||||
for (int i = 0; i < matrix.Height(); ++i) {
|
||||
for (int j = 0; j < matrix.Width(); ++j) {
|
||||
if (!std::isfinite(matrix(i, j)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::mapping::compactification {
|
||||
KelvinCompactification::KelvinCompactification(
|
||||
options::KelvinCompactificationOptions options
|
||||
)
|
||||
: m_options(options) {
|
||||
if (!std::isfinite(m_options.r_star_ref) ||
|
||||
!std::isfinite(m_options.r_inf_ref)) {
|
||||
throw std::invalid_argument(
|
||||
"Kelvin compactification radii must be finite."
|
||||
);
|
||||
}
|
||||
|
||||
if (m_options.r_star_ref <= 0.0 ||
|
||||
m_options.r_inf_ref <= m_options.r_star_ref) {
|
||||
throw std::invalid_argument(
|
||||
"Kelvin compactification requires 0 < r_star_ref < r_inf_ref."
|
||||
);
|
||||
}
|
||||
|
||||
if (!std::isfinite(m_options.coordinate_tolerance) ||
|
||||
m_options.coordinate_tolerance < 0.0 ||
|
||||
m_options.coordinate_tolerance >= 1.0) {
|
||||
throw std::invalid_argument(
|
||||
"Kelvin compactification coordinate tolerance must be finite "
|
||||
"and lie "
|
||||
"in [0, 1)."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MappingStatus KelvinCompactification::ComputeRadialFactors(
|
||||
const double compactification_coordinate,
|
||||
RadialFactors &factors
|
||||
) const {
|
||||
if (!std::isfinite(compactification_coordinate))
|
||||
return MappingStatus::non_finite_input;
|
||||
|
||||
const double tolerance = m_options.coordinate_tolerance;
|
||||
|
||||
if (compactification_coordinate < -tolerance ||
|
||||
compactification_coordinate > 1.0 + tolerance) {
|
||||
return MappingStatus::outside_reference_domain;
|
||||
}
|
||||
|
||||
double coordinate = compactification_coordinate;
|
||||
if (coordinate < 0.0)
|
||||
coordinate = 0.0;
|
||||
|
||||
if (coordinate >= 1.0 - tolerance) {
|
||||
return MappingStatus::at_compactified_infinity;
|
||||
}
|
||||
|
||||
const double radial_extent = m_options.r_inf_ref - m_options.r_star_ref;
|
||||
const double computational_radius =
|
||||
m_options.r_star_ref + coordinate * radial_extent;
|
||||
|
||||
if (!std::isfinite(computational_radius) ||
|
||||
computational_radius <= 0.0) {
|
||||
return MappingStatus::invalid_reference_radius;
|
||||
}
|
||||
|
||||
const double one_minus_coordinate = 1.0 - coordinate;
|
||||
const double denominator = computational_radius * one_minus_coordinate;
|
||||
|
||||
if (!std::isfinite(denominator) || denominator <= 0.0) {
|
||||
return MappingStatus::non_finite_result;
|
||||
}
|
||||
|
||||
const double scale = m_options.r_star_ref / denominator;
|
||||
const double scale_derivative =
|
||||
scale *
|
||||
(1.0 / one_minus_coordinate - radial_extent / computational_radius);
|
||||
|
||||
if (!std::isfinite(scale) || !std::isfinite(scale_derivative)) {
|
||||
return MappingStatus::non_finite_result;
|
||||
}
|
||||
|
||||
factors.coordinate = coordinate;
|
||||
factors.computational_radius = computational_radius;
|
||||
factors.scale = scale;
|
||||
factors.scale_derivative = scale_derivative;
|
||||
|
||||
return MappingStatus::valid;
|
||||
}
|
||||
|
||||
MappingStatus KelvinCompactification::Evaluate(
|
||||
const ExteriorMapInput &input,
|
||||
ExteriorMapResult &result
|
||||
) const {
|
||||
const int dimension = input.reference_position.Size();
|
||||
|
||||
if (dimension <= 0 || input.displaced_position.Size() != dimension ||
|
||||
input.compactification_coordinate_gradient.Size() != dimension) {
|
||||
return MappingStatus::invalid_dimension;
|
||||
}
|
||||
|
||||
if (input.displacement_jacobian.Height() != dimension ||
|
||||
input.displacement_jacobian.Width() != dimension) {
|
||||
return MappingStatus::invalid_dimension;
|
||||
}
|
||||
|
||||
if (!vector_is_finite(input.reference_position) ||
|
||||
!vector_is_finite(input.displaced_position) ||
|
||||
!vector_is_finite(input.compactification_coordinate_gradient) ||
|
||||
!matrix_is_finite(input.displacement_jacobian)) {
|
||||
return MappingStatus::non_finite_input;
|
||||
}
|
||||
|
||||
RadialFactors factors;
|
||||
const MappingStatus factor_status =
|
||||
ComputeRadialFactors(input.compactification_coordinate, factors);
|
||||
if (factor_status != MappingStatus::valid)
|
||||
return factor_status;
|
||||
|
||||
result.physical_position.SetSize(dimension);
|
||||
result.mapping_jacobian.SetSize(dimension, dimension);
|
||||
|
||||
for (int i = 0; i < dimension; ++i) {
|
||||
result.physical_position(i) =
|
||||
factors.scale * input.displaced_position(i);
|
||||
|
||||
for (int j = 0; j < dimension; ++j) {
|
||||
const double scale_gradient =
|
||||
factors.scale_derivative *
|
||||
input.compactification_coordinate_gradient(j);
|
||||
result.mapping_jacobian(i, j) =
|
||||
factors.scale * input.displacement_jacobian(i, j) +
|
||||
input.displaced_position(i) * scale_gradient;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vector_is_finite(result.physical_position) ||
|
||||
!matrix_is_finite(result.mapping_jacobian)) {
|
||||
return MappingStatus::non_finite_result;
|
||||
}
|
||||
|
||||
const double mapping_determinant = result.mapping_jacobian.Det();
|
||||
|
||||
if (!std::isfinite(mapping_determinant))
|
||||
return MappingStatus::non_finite_result;
|
||||
if (mapping_determinant <= 0.0)
|
||||
return MappingStatus::non_positive_determinant;
|
||||
|
||||
return MappingStatus::valid;
|
||||
}
|
||||
|
||||
MappingStatus KelvinCompactification::EvaluateVariation(
|
||||
const ExteriorMapInput &input,
|
||||
const ExteriorMapResult &result,
|
||||
const ExteriorMapDirection &direction,
|
||||
ExteriorMapVariation &variation
|
||||
) const {
|
||||
const int dimension = input.reference_position.Size();
|
||||
|
||||
if (dimension <= 0 || input.displaced_position.Size() != dimension ||
|
||||
input.compactification_coordinate_gradient.Size() != dimension) {
|
||||
return MappingStatus::invalid_dimension;
|
||||
}
|
||||
|
||||
if (input.displacement_jacobian.Height() != dimension ||
|
||||
input.displacement_jacobian.Width() != dimension) {
|
||||
return MappingStatus::invalid_dimension;
|
||||
}
|
||||
|
||||
if (result.physical_position.Size() != dimension ||
|
||||
result.mapping_jacobian.Height() != dimension ||
|
||||
result.mapping_jacobian.Width() != dimension) {
|
||||
return MappingStatus::invalid_dimension;
|
||||
}
|
||||
|
||||
if (direction.displaced_position_variation.Size() != dimension ||
|
||||
direction.displacement_jacobian_variation.Height() != dimension ||
|
||||
direction.displacement_jacobian_variation.Width() != dimension) {
|
||||
return MappingStatus::invalid_dimension;
|
||||
}
|
||||
|
||||
if (!vector_is_finite(input.reference_position) ||
|
||||
!vector_is_finite(input.displaced_position) ||
|
||||
!vector_is_finite(input.compactification_coordinate_gradient) ||
|
||||
!matrix_is_finite(input.displacement_jacobian)) {
|
||||
return MappingStatus::non_finite_input;
|
||||
}
|
||||
|
||||
if (!vector_is_finite(result.physical_position) ||
|
||||
!matrix_is_finite(result.mapping_jacobian) ||
|
||||
!vector_is_finite(direction.displaced_position_variation) ||
|
||||
!matrix_is_finite(direction.displacement_jacobian_variation)) {
|
||||
return MappingStatus::non_finite_input;
|
||||
}
|
||||
|
||||
RadialFactors factors;
|
||||
const MappingStatus factor_status =
|
||||
ComputeRadialFactors(input.compactification_coordinate, factors);
|
||||
if (factor_status != MappingStatus::valid)
|
||||
return factor_status;
|
||||
|
||||
variation.physical_position_variation.SetSize(dimension);
|
||||
variation.mapping_jacobian_variation.SetSize(dimension, dimension);
|
||||
|
||||
for (int i = 0; i < dimension; ++i) {
|
||||
variation.physical_position_variation(i) =
|
||||
factors.scale * direction.displaced_position_variation(i);
|
||||
|
||||
for (int j = 0; j < dimension; ++j) {
|
||||
const double scale_gradient =
|
||||
factors.scale_derivative *
|
||||
input.compactification_coordinate_gradient(j);
|
||||
variation.mapping_jacobian_variation(i, j) =
|
||||
factors.scale *
|
||||
direction.displacement_jacobian_variation(i, j) +
|
||||
direction.displaced_position_variation(i) * scale_gradient;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vector_is_finite(variation.physical_position_variation) ||
|
||||
!matrix_is_finite(variation.mapping_jacobian_variation)) {
|
||||
return MappingStatus::non_finite_result;
|
||||
}
|
||||
|
||||
return MappingStatus::valid;
|
||||
}
|
||||
|
||||
std::string_view KelvinCompactification::GetName() const noexcept {
|
||||
return "KelvinCompactification";
|
||||
}
|
||||
|
||||
double KelvinCompactification::GetReferenceStellarRadius() const noexcept {
|
||||
return m_options.r_star_ref;
|
||||
}
|
||||
|
||||
double KelvinCompactification::GetReferenceInfinityRadius() const noexcept {
|
||||
return m_options.r_inf_ref;
|
||||
}
|
||||
|
||||
double KelvinCompactification::GetCoordinateTolerance() const noexcept {
|
||||
return m_options.coordinate_tolerance;
|
||||
}
|
||||
} // namespace mean_field::mapping::compactification
|
||||
@@ -2,26 +2,52 @@ module;
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
import :mapping.types;
|
||||
|
||||
namespace {
|
||||
double get_positive_map_jacobian(
|
||||
const mean_field::mapping::DomainMapper &domain_mapper,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
mfem::DenseMatrix &map_jacobian
|
||||
) {
|
||||
transformation.SetIntPoint(&integration_point);
|
||||
domain_mapper.ComputeJacobian(transformation, map_jacobian);
|
||||
|
||||
const double map_determinant = map_jacobian.Det();
|
||||
MFEM_VERIFY(
|
||||
map_determinant > 0.0,
|
||||
"Domain mapping has a non-positive Jacobian determinant."
|
||||
);
|
||||
return map_determinant;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::mapping {
|
||||
DomainMapper::DomainMapper(
|
||||
const double r_star_ref,
|
||||
const double r_inf_ref
|
||||
) : m_d(nullptr),
|
||||
)
|
||||
: m_d(nullptr),
|
||||
m_r_star_ref(r_star_ref),
|
||||
m_r_inf_ref(r_inf_ref) {
|
||||
InitAllScratchSpaces();
|
||||
CalcIsIdentity() ? m_displacement_is_identity = true
|
||||
: m_displacement_is_identity = false;
|
||||
}
|
||||
|
||||
DomainMapper::DomainMapper(
|
||||
const mfem::GridFunction &d,
|
||||
const double r_star_ref,
|
||||
const double r_inf_ref
|
||||
) : m_d(&d),
|
||||
)
|
||||
: m_d(&d),
|
||||
m_dim(d.FESpace()->GetMesh()->Dimension()),
|
||||
m_r_star_ref(r_star_ref),
|
||||
m_r_inf_ref(r_inf_ref) {
|
||||
InitAllScratchSpaces();
|
||||
CalcIsIdentity() ? m_displacement_is_identity = true
|
||||
: m_displacement_is_identity = false;
|
||||
}
|
||||
|
||||
bool DomainMapper::is_vacuum(const mfem::ElementTransformation &T) const {
|
||||
@@ -29,7 +55,9 @@ namespace mean_field::mapping {
|
||||
return T.Attribute == m_vacuum_attr;
|
||||
} else if (T.ElementType == mfem::ElementTransformation::BDR_ELEMENT) {
|
||||
return T.Attribute == m_vacuum_attr - 1;
|
||||
// TODO: In a more robust code this should really be read from the stroid API to ensure that the vacuum boundary is really 1 - the vacuum material attribute
|
||||
// TODO: In a more robust code this should really be read from the
|
||||
// stroid API to ensure that the vacuum boundary is really 1 - the
|
||||
// vacuum material attribute
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -37,28 +65,69 @@ namespace mean_field::mapping {
|
||||
void DomainMapper::SetDisplacement(const mfem::GridFunction &d) {
|
||||
if (m_dim != d.FESpace()->GetMesh()->Dimension()) {
|
||||
const std::string err_msg = std::format(
|
||||
"Dimension mismatch: DomainMapper is initialized for dimension {}, but provided displacement field has dimension {}.",
|
||||
m_dim, d.FESpace()->GetMesh()->Dimension());
|
||||
"Dimension mismatch: DomainMapper is initialized for dimension "
|
||||
"{}, "
|
||||
"but provided displacement field has "
|
||||
"dimension {}.",
|
||||
m_dim, d.FESpace()->GetMesh()->Dimension()
|
||||
);
|
||||
throw std::invalid_argument(err_msg);
|
||||
}
|
||||
m_d = &d;
|
||||
InvalidateCache();
|
||||
|
||||
CalcIsIdentity() ? m_displacement_is_identity = true
|
||||
: m_displacement_is_identity = false;
|
||||
}
|
||||
|
||||
bool DomainMapper::IsIdentity() const {
|
||||
return (m_d == nullptr);
|
||||
bool DomainMapper::HasCompactification() const noexcept {
|
||||
return std::isfinite(m_r_star_ref) && std::isfinite(m_r_inf_ref) &&
|
||||
m_r_star_ref > 0.0 && m_r_inf_ref > m_r_star_ref &&
|
||||
m_xi_clamp > 0.0 && m_xi_clamp < 1.0;
|
||||
}
|
||||
|
||||
bool DomainMapper::HasDisplacementField() const noexcept {
|
||||
return m_d != nullptr;
|
||||
}
|
||||
|
||||
bool DomainMapper::CalcIsIdentity() const {
|
||||
if (m_d == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const int local_identity = m_d->Normlinf() == 0.0 ? 1 : 0;
|
||||
|
||||
const auto *parallel_displacement =
|
||||
dynamic_cast<const mfem::ParGridFunction *>(m_d);
|
||||
|
||||
if (parallel_displacement == nullptr) {
|
||||
return local_identity == 1;
|
||||
}
|
||||
|
||||
int global_identity = 0;
|
||||
MPI_Allreduce(
|
||||
&local_identity, &global_identity, 1, MPI_INT, MPI_MIN,
|
||||
parallel_displacement->ParFESpace()->GetComm()
|
||||
);
|
||||
|
||||
return global_identity == 1;
|
||||
}
|
||||
|
||||
void DomainMapper::ResetDisplacement() {
|
||||
m_d = nullptr;
|
||||
InvalidateCache();
|
||||
CalcIsIdentity() ? m_displacement_is_identity = true
|
||||
: m_displacement_is_identity = false;
|
||||
}
|
||||
|
||||
void DomainMapper::ComputeJacobian(mfem::ElementTransformation &T, mfem::DenseMatrix &J) const {
|
||||
void DomainMapper::ComputeJacobian(
|
||||
mfem::ElementTransformation &T,
|
||||
mfem::DenseMatrix &J
|
||||
) const {
|
||||
J.SetSize(m_dim, m_dim);
|
||||
J = 0.0;
|
||||
m_J_D = 0.0;
|
||||
if (IsIdentity()) {
|
||||
if (!HasDisplacementField()) {
|
||||
for (int i = 0; i < m_dim; ++i) {
|
||||
m_J_D(i, i) = 1.0; // Identity mapping
|
||||
}
|
||||
@@ -76,7 +145,7 @@ namespace mean_field::mapping {
|
||||
if (is_vacuum(T)) {
|
||||
T.Transform(T.GetIntPoint(), m_x_ref);
|
||||
|
||||
if (IsIdentity()) {
|
||||
if (!HasDisplacementField()) {
|
||||
m_x_disp = m_x_ref;
|
||||
} else {
|
||||
m_shape.SetSize(m_fe->GetDof());
|
||||
@@ -91,15 +160,22 @@ namespace mean_field::mapping {
|
||||
}
|
||||
}
|
||||
|
||||
double DomainMapper::ComputeDetJ(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) const {
|
||||
if (IsIdentity() && !is_vacuum(T)) return 1.0; // If no mapping, the determinant of the Jacobian is 1
|
||||
double DomainMapper::ComputeDetJ(
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) const {
|
||||
if (!HasDisplacementField() && !is_vacuum(T))
|
||||
return 1.0; // If no mapping, the determinant of the Jacobian is 1
|
||||
T.SetIntPoint(&ip);
|
||||
mfem::DenseMatrix J;
|
||||
ComputeJacobian(T, J);
|
||||
return J.Det();
|
||||
}
|
||||
|
||||
void DomainMapper::ComputeMappedDiffusionTensor(mfem::ElementTransformation &T, mfem::DenseMatrix &D) const {
|
||||
void DomainMapper::ComputeMappedDiffusionTensor(
|
||||
mfem::ElementTransformation &T,
|
||||
mfem::DenseMatrix &D
|
||||
) const {
|
||||
ComputeJacobian(T, m_J_temp);
|
||||
const double detJ = m_J_temp.Det();
|
||||
mfem::CalcInverse(m_J_temp, m_JInv_temp);
|
||||
@@ -108,13 +184,19 @@ namespace mean_field::mapping {
|
||||
D *= fabs(detJ);
|
||||
}
|
||||
|
||||
void DomainMapper::ComputeInverseJacobian(mfem::ElementTransformation &T, mfem::DenseMatrix &JInv) const {
|
||||
void DomainMapper::ComputeInverseJacobian(
|
||||
mfem::ElementTransformation &T,
|
||||
mfem::DenseMatrix &JInv
|
||||
) const {
|
||||
ComputeJacobian(T, m_J_temp);
|
||||
JInv.SetSize(m_dim, m_dim);
|
||||
mfem::CalcInverse(m_J_temp, JInv);
|
||||
}
|
||||
|
||||
DomainMapper::VolumeQuadratureContext DomainMapper::GetQuadratureContext(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) const {
|
||||
VolumeQuadratureContext DomainMapper::GetQuadratureContext(
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) const {
|
||||
const int dim = T.GetSpaceDim();
|
||||
mfem::DenseMatrix J_map(dim, dim), J_inv(dim, dim);
|
||||
ComputeJacobian(T, J_map);
|
||||
@@ -126,23 +208,32 @@ namespace mean_field::mapping {
|
||||
return {.J_inv = J_inv, .detJ = detJ, .weight = weight};
|
||||
}
|
||||
|
||||
DomainMapper::FaceQuadratureContext DomainMapper::GetFaceQuadratureContext(mfem::FaceElementTransformations &T, const mfem::IntegrationPoint &ip) const {
|
||||
FaceQuadratureContext DomainMapper::GetFaceQuadratureContext(
|
||||
mfem::FaceElementTransformations &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) const {
|
||||
const int dim = T.GetSpaceDim();
|
||||
T.SetAllIntPoints(&ip);
|
||||
|
||||
mfem::Vector n_raw(dim);
|
||||
mfem::CalcOrtho(T.Jacobian(), n_raw);
|
||||
|
||||
if (IsIdentity()) {
|
||||
if (!HasDisplacementField() && !is_vacuum(T)) {
|
||||
const double n_raw_mag = n_raw.Norml2();
|
||||
mfem::Vector n_unit(dim);
|
||||
n_unit = n_raw;
|
||||
n_unit /= n_raw_mag;
|
||||
return FaceQuadratureContext{.normal=n_unit, .ds=ip.weight * n_raw_mag, .v_dot_n_scale = 1.0};
|
||||
return FaceQuadratureContext{
|
||||
.normal = n_unit,
|
||||
.ds = ip.weight * n_raw_mag,
|
||||
.v_dot_n_scale = 1.0
|
||||
};
|
||||
}
|
||||
|
||||
// Nanson's Formula (https://en.wikiversity.org/wiki/Continuum_mechanics/Volume_change_and_area_change)
|
||||
// Since the displacement field lives in H1 it should be irrelevant if we pick Elem1 or Elem2
|
||||
// Nanson's Formula
|
||||
// (https://en.wikiversity.org/wiki/Continuum_mechanics/Volume_change_and_area_change)
|
||||
// Since the displacement field lives in H1 it should be irrelevant if
|
||||
// we pick Elem1 or Elem2
|
||||
mfem::DenseMatrix J_map(dim, dim);
|
||||
ComputeJacobian(*T.Elem1, J_map);
|
||||
const double detJ_map = J_map.Det();
|
||||
@@ -166,14 +257,17 @@ namespace mean_field::mapping {
|
||||
.ds = ip.weight * n_raw_mag,
|
||||
.v_dot_n_scale = n_phys_mag / n_raw_mag
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void DomainMapper::GetPhysicalPoint(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip, mfem::Vector &x_phys) const {
|
||||
void DomainMapper::GetPhysicalPoint(
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip,
|
||||
mfem::Vector &x_phys
|
||||
) const {
|
||||
x_phys.SetSize(m_dim);
|
||||
T.Transform(ip, m_x_ref);
|
||||
|
||||
if (IsIdentity()) {
|
||||
if (!HasDisplacementField()) {
|
||||
x_phys = m_x_ref;
|
||||
} else {
|
||||
UpdateElementCache(T);
|
||||
@@ -189,11 +283,91 @@ namespace mean_field::mapping {
|
||||
}
|
||||
}
|
||||
|
||||
void DomainMapper::GetVectorValue(const int i, const mfem::IntegrationPoint &ip, mfem::Vector &val) const {
|
||||
void DomainMapper::GetVectorValue(
|
||||
const int i,
|
||||
const mfem::IntegrationPoint &ip,
|
||||
mfem::Vector &val
|
||||
) const {
|
||||
m_d->GetVectorValue(i, ip, val);
|
||||
}
|
||||
|
||||
const mfem::GridFunction *DomainMapper::GetDisplacement() const { return m_d; }
|
||||
void DomainMapper::MapHDivFluxToPhysical(
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
const mfem::Vector &reference_flux,
|
||||
mfem::Vector &physical_flux
|
||||
) const {
|
||||
MFEM_VERIFY(
|
||||
reference_flux.Size() == m_dim,
|
||||
"The reference H(div) flux has the wrong dimension."
|
||||
);
|
||||
|
||||
mfem::DenseMatrix map_jacobian(m_dim, m_dim);
|
||||
const double map_determinant = get_positive_map_jacobian(
|
||||
*this, transformation, integration_point, map_jacobian
|
||||
);
|
||||
|
||||
mfem::Vector mapped_flux(m_dim);
|
||||
map_jacobian.Mult(reference_flux, mapped_flux);
|
||||
mapped_flux /= map_determinant;
|
||||
|
||||
physical_flux = mapped_flux;
|
||||
}
|
||||
|
||||
void DomainMapper::MapPhysicalFluxToHDivReference(
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
const mfem::Vector &physical_flux,
|
||||
mfem::Vector &reference_flux
|
||||
) const {
|
||||
MFEM_VERIFY(
|
||||
physical_flux.Size() == m_dim,
|
||||
"The physical flux has the wrong dimension."
|
||||
);
|
||||
|
||||
mfem::DenseMatrix map_jacobian(m_dim, m_dim);
|
||||
const double map_determinant = get_positive_map_jacobian(
|
||||
*this, transformation, integration_point, map_jacobian
|
||||
);
|
||||
|
||||
mfem::DenseMatrix inverse_map_jacobian(m_dim, m_dim);
|
||||
mfem::CalcInverse(map_jacobian, inverse_map_jacobian);
|
||||
|
||||
mfem::Vector mapped_flux(m_dim);
|
||||
inverse_map_jacobian.Mult(physical_flux, mapped_flux);
|
||||
mapped_flux *= map_determinant;
|
||||
|
||||
reference_flux = mapped_flux;
|
||||
}
|
||||
|
||||
void DomainMapper::MapReferenceGradientToPhysical(
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
const mfem::Vector &reference_gradient,
|
||||
mfem::Vector &physical_gradient
|
||||
) const {
|
||||
MFEM_VERIFY(
|
||||
reference_gradient.Size() == m_dim,
|
||||
"The reference gradient has the wrong dimension."
|
||||
);
|
||||
|
||||
mfem::DenseMatrix map_jacobian(m_dim, m_dim);
|
||||
get_positive_map_jacobian(
|
||||
*this, transformation, integration_point, map_jacobian
|
||||
);
|
||||
|
||||
mfem::DenseMatrix inverse_map_jacobian(m_dim, m_dim);
|
||||
mfem::CalcInverse(map_jacobian, inverse_map_jacobian);
|
||||
|
||||
mfem::Vector mapped_gradient(m_dim);
|
||||
inverse_map_jacobian.MultTranspose(reference_gradient, mapped_gradient);
|
||||
|
||||
physical_gradient = mapped_gradient;
|
||||
}
|
||||
|
||||
const mfem::GridFunction *DomainMapper::GetDisplacement() const {
|
||||
return m_d;
|
||||
}
|
||||
|
||||
double DomainMapper::GetPhysInfRadius() const {
|
||||
return 1.0 - m_xi_clamp;
|
||||
@@ -208,7 +382,8 @@ namespace mean_field::mapping {
|
||||
}
|
||||
|
||||
double DomainMapper::GetCacheHitRate() const {
|
||||
return (static_cast<double>(m_cache_hits)) / static_cast<double>(m_cache_misses + m_cache_hits);
|
||||
return (static_cast<double>(m_cache_hits)) /
|
||||
static_cast<double>(m_cache_misses + m_cache_hits);
|
||||
}
|
||||
|
||||
void DomainMapper::ResetCacheStats() const {
|
||||
@@ -225,7 +400,10 @@ namespace mean_field::mapping {
|
||||
m_d_val.SetSize(m_dim);
|
||||
}
|
||||
|
||||
void DomainMapper::ApplyKelvinMapping(const mfem::Vector &x_ref, mfem::Vector &x_phys) const {
|
||||
void DomainMapper::ApplyKelvinMapping(
|
||||
const mfem::Vector &x_ref,
|
||||
mfem::Vector &x_phys
|
||||
) const {
|
||||
const double r_ref = x_ref.Norml2();
|
||||
double xi = (r_ref - m_r_star_ref) / (m_r_inf_ref - m_r_star_ref);
|
||||
xi = std::clamp(xi, 0.0, m_xi_clamp);
|
||||
@@ -233,8 +411,12 @@ namespace mean_field::mapping {
|
||||
x_phys *= factor;
|
||||
}
|
||||
|
||||
void DomainMapper::ComputeKelvinJacobian(const mfem::Vector &x_ref, const mfem::Vector &x_disp, const mfem::DenseMatrix &J_D,
|
||||
mfem::DenseMatrix &J) const {
|
||||
void DomainMapper::ComputeKelvinJacobian(
|
||||
const mfem::Vector &x_ref,
|
||||
const mfem::Vector &x_disp,
|
||||
const mfem::DenseMatrix &J_D,
|
||||
mfem::DenseMatrix &J
|
||||
) const {
|
||||
const double r_ref = x_ref.Norml2();
|
||||
const double delta_R = m_r_inf_ref - m_r_star_ref;
|
||||
|
||||
@@ -245,8 +427,9 @@ namespace mean_field::mapping {
|
||||
|
||||
const double k = m_r_star_ref / (r_ref * denom);
|
||||
|
||||
const double dk_dr = m_r_star_ref * ((1.0 / (delta_R * r_ref * denom * denom)) - (
|
||||
1.0 / (r_ref * r_ref * denom)));
|
||||
const double dk_dr =
|
||||
m_r_star_ref * ((1.0 / (delta_R * r_ref * denom * denom)) -
|
||||
(1.0 / (r_ref * r_ref * denom)));
|
||||
|
||||
J.SetSize(m_dim, m_dim);
|
||||
const double outer_factor = dk_dr / r_ref;
|
||||
@@ -262,10 +445,14 @@ namespace mean_field::mapping {
|
||||
m_cached_elem_id = -1;
|
||||
}
|
||||
|
||||
void DomainMapper::UpdateElementCache(const mfem::ElementTransformation &T) const {
|
||||
if (IsIdentity()) return;
|
||||
void DomainMapper::UpdateElementCache(
|
||||
const mfem::ElementTransformation &T
|
||||
) const {
|
||||
if (!HasDisplacementField())
|
||||
return;
|
||||
|
||||
if (T.ElementNo != m_cached_elem_id || T.ElementType != m_cached_elem_type) {
|
||||
if (T.ElementNo != m_cached_elem_id ||
|
||||
T.ElementType != m_cached_elem_type) {
|
||||
m_cache_misses++;
|
||||
m_cached_elem_id = T.ElementNo;
|
||||
m_cached_elem_type = T.ElementType;
|
||||
@@ -291,4 +478,4 @@ namespace mean_field::mapping {
|
||||
m_cache_hits++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace mean_field::mapping
|
||||
|
||||
916
libmeanfield/impl/mapping/domain_mapper_new.cpp
Normal file
916
libmeanfield/impl/mapping/domain_mapper_new.cpp
Normal file
@@ -0,0 +1,916 @@
|
||||
module;
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
module mean_field;
|
||||
import :mapping.types;
|
||||
import :mapping.compactification;
|
||||
import :utils.user;
|
||||
|
||||
namespace {
|
||||
bool vector_is_finite(const mfem::Vector &vector) {
|
||||
for (int i = 0; i < vector.Size(); ++i) {
|
||||
if (!std::isfinite(vector(i)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool matrix_is_finite(const mfem::DenseMatrix &matrix) {
|
||||
for (int i = 0; i < matrix.Height(); ++i) {
|
||||
for (int j = 0; j < matrix.Width(); ++j) {
|
||||
if (!std::isfinite(matrix(i, j)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::mapping {
|
||||
ElementCompactificationData::ElementCompactificationData(
|
||||
const mfem::FiniteElement &element,
|
||||
const mfem::Vector &dofs
|
||||
)
|
||||
: m_element(&element),
|
||||
m_dofs(dofs) {
|
||||
if (element.GetRangeType() != mfem::FiniteElement::SCALAR) {
|
||||
throw std::invalid_argument(
|
||||
"Compactification coordinate requires a scalar finite element."
|
||||
);
|
||||
}
|
||||
|
||||
if (element.GetMapType() != mfem::FiniteElement::VALUE) {
|
||||
throw std::invalid_argument(
|
||||
"Compactification coordinate requires a value-mapped scalar "
|
||||
"finite "
|
||||
"element."
|
||||
);
|
||||
}
|
||||
|
||||
if (element.GetDerivType() != mfem::FiniteElement::GRAD) {
|
||||
throw std::invalid_argument(
|
||||
"Compactification coordinate finite element must provide a "
|
||||
"gradient."
|
||||
);
|
||||
}
|
||||
|
||||
if (element.GetDof() <= 0) {
|
||||
throw std::invalid_argument(
|
||||
"Compactification coordinate finite element has no degrees of "
|
||||
"freedom."
|
||||
);
|
||||
}
|
||||
|
||||
if (dofs.Size() != element.GetDof()) {
|
||||
throw std::invalid_argument(
|
||||
"Compactification coordinate DOF count does not match its "
|
||||
"finite "
|
||||
"element."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mfem::FiniteElement &
|
||||
ElementCompactificationData::GetElement() const noexcept {
|
||||
return *m_element;
|
||||
}
|
||||
|
||||
const mfem::Vector &ElementCompactificationData::GetDofs() const noexcept {
|
||||
return m_dofs;
|
||||
}
|
||||
|
||||
int ElementCompactificationData::GetDofCount() const noexcept {
|
||||
return m_dofs.Size();
|
||||
}
|
||||
|
||||
ElementDisplacementData::ElementDisplacementData(
|
||||
const mfem::FiniteElement &element,
|
||||
const mfem::Vector &displacement_dofs,
|
||||
const mfem::Ordering::Type ordering
|
||||
)
|
||||
: m_element(&element),
|
||||
m_dimension(0),
|
||||
m_ordering(ordering) {
|
||||
const int dof_count = element.GetDof();
|
||||
if (dof_count <= 0)
|
||||
throw std::invalid_argument(
|
||||
"The displacement element must have at least one degree of "
|
||||
"freedom."
|
||||
);
|
||||
if (displacement_dofs.Size() <= 0 ||
|
||||
displacement_dofs.Size() % dof_count != 0) {
|
||||
throw std::invalid_argument(
|
||||
"The displacement vector size must be a positive multiple of "
|
||||
"the "
|
||||
"element degree-of-freedom count."
|
||||
);
|
||||
}
|
||||
|
||||
m_dimension = displacement_dofs.Size() / dof_count;
|
||||
m_dof_matrix.SetSize(dof_count, m_dimension);
|
||||
|
||||
if (ordering == mfem::Ordering::byNODES) {
|
||||
for (int component = 0; component < m_dimension; ++component) {
|
||||
for (int i = 0; i < dof_count; ++i) {
|
||||
m_dof_matrix(i, component) =
|
||||
displacement_dofs(i + component * dof_count);
|
||||
}
|
||||
}
|
||||
} else if (ordering == mfem::Ordering::byVDIM) {
|
||||
for (int i = 0; i < dof_count; ++i) {
|
||||
for (int component = 0; component < m_dimension; ++component) {
|
||||
m_dof_matrix(i, component) =
|
||||
displacement_dofs(component + i * m_dimension);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw std::invalid_argument(
|
||||
"Unsupported MFEM displacement ordering."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mfem::FiniteElement &
|
||||
ElementDisplacementData::GetElement() const noexcept {
|
||||
return *m_element;
|
||||
}
|
||||
|
||||
const mfem::DenseMatrix &
|
||||
ElementDisplacementData::GetDofMatrix() const noexcept {
|
||||
return m_dof_matrix;
|
||||
}
|
||||
|
||||
int ElementDisplacementData::GetDimension() const noexcept {
|
||||
return m_dimension;
|
||||
}
|
||||
|
||||
int ElementDisplacementData::GetDofCount() const noexcept {
|
||||
return m_element->GetDof();
|
||||
}
|
||||
|
||||
mfem::Ordering::Type ElementDisplacementData::GetOrdering() const noexcept {
|
||||
return m_ordering;
|
||||
}
|
||||
|
||||
ElementDisplacementData ElementDisplacementDataFromElementVDofs(
|
||||
const mfem::FiniteElement &element,
|
||||
const mfem::Vector &displacement_dofs
|
||||
) {
|
||||
return ElementDisplacementData(
|
||||
element, displacement_dofs, mfem::Ordering::byNODES
|
||||
);
|
||||
}
|
||||
|
||||
DomainMapperStateless::Workspace::Workspace(const int dimension) {
|
||||
SetDimension(dimension);
|
||||
}
|
||||
|
||||
void DomainMapperStateless::Workspace::SetDimension(const int dimension) {
|
||||
if (dimension <= 0) {
|
||||
throw std::invalid_argument(
|
||||
"Domain mapping workspace dimension must be positive."
|
||||
);
|
||||
}
|
||||
|
||||
m_dimension = dimension;
|
||||
|
||||
m_field_value.SetSize(dimension);
|
||||
m_field_jacobian.SetSize(dimension, dimension);
|
||||
|
||||
m_compactification_point.coordinate = 0.0;
|
||||
m_compactification_point.coordinate_gradient.SetSize(dimension);
|
||||
|
||||
m_reference_normal.SetSize(dimension);
|
||||
m_mapped_normal.SetSize(dimension);
|
||||
m_full_element_jacobian.SetSize(dimension, dimension);
|
||||
|
||||
m_vector_temp.SetSize(dimension);
|
||||
m_matrix_temp_1.SetSize(dimension, dimension);
|
||||
m_matrix_temp_2.SetSize(dimension, dimension);
|
||||
|
||||
m_exterior_result.physical_position.SetSize(dimension);
|
||||
m_exterior_result.mapping_jacobian.SetSize(dimension, dimension);
|
||||
|
||||
m_exterior_variation.physical_position_variation.SetSize(dimension);
|
||||
m_exterior_variation.mapping_jacobian_variation.SetSize(
|
||||
dimension, dimension
|
||||
);
|
||||
}
|
||||
|
||||
int DomainMapperStateless::Workspace::GetDimension() const noexcept {
|
||||
return m_dimension;
|
||||
}
|
||||
|
||||
DomainMapperStateless::DomainMapperStateless(
|
||||
const utils::DomainMapperStatelessOptions options,
|
||||
std::unique_ptr<const compactification::ExteriorDomainMap> exterior_map
|
||||
)
|
||||
: m_options(options),
|
||||
m_exterior_map(std::move(exterior_map)) {
|
||||
if (m_options.dimension <= 0)
|
||||
throw std::invalid_argument(
|
||||
"The domain-mapping dimension must be positive."
|
||||
);
|
||||
if (m_options.vacuum_element_attribute <= 0)
|
||||
throw std::invalid_argument(
|
||||
"The vacuum element attribute must be positive."
|
||||
);
|
||||
if (!m_exterior_map)
|
||||
throw std::invalid_argument(
|
||||
"DomainMapperStateless requires an exterior-domain mapping."
|
||||
);
|
||||
}
|
||||
|
||||
bool DomainMapperStateless::IsCompactifiedElement(
|
||||
const mfem::ElementTransformation &transformation
|
||||
) const noexcept {
|
||||
return transformation.Attribute == m_options.vacuum_element_attribute;
|
||||
}
|
||||
|
||||
int DomainMapperStateless::GetDimension() const noexcept {
|
||||
return m_options.dimension;
|
||||
}
|
||||
|
||||
int DomainMapperStateless::GetVacuumElementAttribute() const noexcept {
|
||||
return m_options.vacuum_element_attribute;
|
||||
}
|
||||
|
||||
const compactification::ExteriorDomainMap &
|
||||
DomainMapperStateless::GetExteriorMap() const noexcept {
|
||||
return *m_exterior_map;
|
||||
}
|
||||
|
||||
void DomainMapperStateless::ValidateElementData(
|
||||
const ElementMappingData &element_data
|
||||
) const {
|
||||
const ElementDisplacementData &displacement = element_data.displacement;
|
||||
const ElementCompactificationData &compactification =
|
||||
element_data.compactification;
|
||||
|
||||
if (displacement.GetDimension() != m_options.dimension) {
|
||||
throw std::invalid_argument(
|
||||
"Displacement field dimension does not match the domain mapper "
|
||||
"dimension."
|
||||
);
|
||||
}
|
||||
|
||||
if (displacement.GetElement().GetDim() != m_options.dimension) {
|
||||
throw std::invalid_argument(
|
||||
"Displacement finite element dimension does not match the "
|
||||
"domain "
|
||||
"mapper dimension."
|
||||
);
|
||||
}
|
||||
|
||||
if (compactification.GetElement().GetDim() != m_options.dimension) {
|
||||
throw std::invalid_argument(
|
||||
"Compactification finite element dimension does not match the "
|
||||
"domain "
|
||||
"mapper dimension."
|
||||
);
|
||||
}
|
||||
|
||||
if (displacement.GetElement().GetGeomType() !=
|
||||
compactification.GetElement().GetGeomType()) {
|
||||
throw std::invalid_argument(
|
||||
"Displacement and compactification finite elements have "
|
||||
"different "
|
||||
"geometries."
|
||||
);
|
||||
}
|
||||
|
||||
if (compactification.GetElement().GetRangeType() !=
|
||||
mfem::FiniteElement::SCALAR) {
|
||||
throw std::invalid_argument(
|
||||
"Compactification coordinate requires a scalar finite element."
|
||||
);
|
||||
}
|
||||
|
||||
if (compactification.GetElement().GetMapType() !=
|
||||
mfem::FiniteElement::VALUE) {
|
||||
throw std::invalid_argument(
|
||||
"Compactification coordinate requires a value-mapped finite "
|
||||
"element."
|
||||
);
|
||||
}
|
||||
|
||||
if (compactification.GetElement().GetDerivType() !=
|
||||
mfem::FiniteElement::GRAD) {
|
||||
throw std::invalid_argument(
|
||||
"Compactification coordinate finite element does not provide a "
|
||||
"gradient."
|
||||
);
|
||||
}
|
||||
|
||||
if (compactification.GetDofCount() !=
|
||||
compactification.GetElement().GetDof()) {
|
||||
throw std::invalid_argument(
|
||||
"Compactification coordinate DOF count does not match its "
|
||||
"finite "
|
||||
"element."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MappingStatus DomainMapperStateless::EvaluateCompactificationCoordinate(
|
||||
const ElementCompactificationData &compactification,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
Workspace &workspace,
|
||||
CompactificationPointData &point_data
|
||||
) const {
|
||||
const mfem::FiniteElement &element = compactification.GetElement();
|
||||
const mfem::Vector &dofs = compactification.GetDofs();
|
||||
const int dof_count = element.GetDof();
|
||||
|
||||
if (workspace.GetDimension() != m_options.dimension ||
|
||||
transformation.GetSpaceDim() != m_options.dimension ||
|
||||
element.GetDim() != m_options.dimension) {
|
||||
return MappingStatus::invalid_dimension;
|
||||
}
|
||||
|
||||
if (dofs.Size() != dof_count) {
|
||||
return MappingStatus::invalid_dimension;
|
||||
}
|
||||
|
||||
for (int i = 0; i < dofs.Size(); ++i) {
|
||||
if (!std::isfinite(dofs(i)))
|
||||
return MappingStatus::non_finite_input;
|
||||
}
|
||||
|
||||
transformation.SetIntPoint(&integration_point);
|
||||
|
||||
workspace.m_compactification_shape.SetSize(dof_count);
|
||||
workspace.m_compactification_dshape.SetSize(
|
||||
dof_count, m_options.dimension
|
||||
);
|
||||
|
||||
element.CalcShape(
|
||||
integration_point, workspace.m_compactification_shape
|
||||
);
|
||||
element.CalcPhysDShape(
|
||||
transformation, workspace.m_compactification_dshape
|
||||
);
|
||||
|
||||
point_data.coordinate = dofs * workspace.m_compactification_shape;
|
||||
point_data.coordinate_gradient.SetSize(m_options.dimension);
|
||||
workspace.m_compactification_dshape.MultTranspose(
|
||||
dofs, point_data.coordinate_gradient
|
||||
);
|
||||
|
||||
if (!std::isfinite(point_data.coordinate)) {
|
||||
return MappingStatus::non_finite_result;
|
||||
}
|
||||
|
||||
for (int d = 0; d < point_data.coordinate_gradient.Size(); ++d) {
|
||||
if (!std::isfinite(point_data.coordinate_gradient(d)))
|
||||
return MappingStatus::non_finite_result;
|
||||
}
|
||||
|
||||
return MappingStatus::valid;
|
||||
}
|
||||
|
||||
void DomainMapperStateless::EvaluateField(
|
||||
const ElementDisplacementData &field,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
Workspace &workspace,
|
||||
mfem::Vector &value,
|
||||
mfem::DenseMatrix &jacobian
|
||||
) const {
|
||||
transformation.SetIntPoint(&integration_point);
|
||||
|
||||
const mfem::FiniteElement &element = field.GetElement();
|
||||
const mfem::DenseMatrix &dof_matrix = field.GetDofMatrix();
|
||||
|
||||
workspace.m_shape.SetSize(element.GetDof());
|
||||
workspace.m_mesh_dshape.SetSize(element.GetDof(), m_options.dimension);
|
||||
|
||||
element.CalcShape(integration_point, workspace.m_shape);
|
||||
element.CalcPhysDShape(transformation, workspace.m_mesh_dshape);
|
||||
|
||||
value.SetSize(m_options.dimension);
|
||||
dof_matrix.MultTranspose(workspace.m_shape, value);
|
||||
|
||||
jacobian.SetSize(m_options.dimension, m_options.dimension);
|
||||
mfem::MultAtB(dof_matrix, workspace.m_mesh_dshape, jacobian);
|
||||
}
|
||||
|
||||
MappingStatus DomainMapperStateless::EvaluatePoint(
|
||||
const ElementMappingData &element_data,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
Workspace &workspace,
|
||||
MappingPointContext &context
|
||||
) const {
|
||||
ValidateElementData(element_data);
|
||||
|
||||
if (workspace.GetDimension() != m_options.dimension)
|
||||
throw std::invalid_argument(
|
||||
"The mapping workspace has the wrong dimension."
|
||||
);
|
||||
if (transformation.GetSpaceDim() != m_options.dimension)
|
||||
throw std::invalid_argument(
|
||||
"The element transformation has the wrong spatial dimension."
|
||||
);
|
||||
if (transformation.GetGeometryType() !=
|
||||
element_data.displacement.GetElement().GetGeomType())
|
||||
throw std::invalid_argument(
|
||||
"The element transformation geometry does not match the "
|
||||
"supplied "
|
||||
"element data."
|
||||
);
|
||||
|
||||
transformation.SetIntPoint(&integration_point);
|
||||
|
||||
context.reference_position.SetSize(m_options.dimension);
|
||||
transformation.Transform(integration_point, context.reference_position);
|
||||
|
||||
EvaluateField(
|
||||
element_data.displacement, transformation, integration_point,
|
||||
workspace, workspace.m_field_value, workspace.m_field_jacobian
|
||||
);
|
||||
|
||||
if (!vector_is_finite(context.reference_position) ||
|
||||
!vector_is_finite(workspace.m_field_value) ||
|
||||
!matrix_is_finite(workspace.m_field_jacobian)) {
|
||||
return MappingStatus::non_finite_input;
|
||||
}
|
||||
|
||||
context.displaced_position.SetSize(m_options.dimension);
|
||||
context.displaced_position = context.reference_position;
|
||||
context.displaced_position += workspace.m_field_value;
|
||||
|
||||
context.displacement_jacobian.SetSize(
|
||||
m_options.dimension, m_options.dimension
|
||||
);
|
||||
context.displacement_jacobian = workspace.m_field_jacobian;
|
||||
for (int i = 0; i < m_options.dimension; ++i)
|
||||
context.displacement_jacobian(i, i) += 1.0;
|
||||
|
||||
context.compactified = IsCompactifiedElement(transformation);
|
||||
|
||||
if (context.compactified) {
|
||||
const MappingStatus coordinate_status =
|
||||
EvaluateCompactificationCoordinate(
|
||||
element_data.compactification, transformation,
|
||||
integration_point, workspace,
|
||||
workspace.m_compactification_point
|
||||
);
|
||||
|
||||
if (coordinate_status != MappingStatus::valid)
|
||||
return coordinate_status;
|
||||
|
||||
const compactification::ExteriorMapInput exterior_input{
|
||||
.reference_position = context.reference_position,
|
||||
.displaced_position = context.displaced_position,
|
||||
.displacement_jacobian = context.displacement_jacobian,
|
||||
.compactification_coordinate =
|
||||
workspace.m_compactification_point.coordinate,
|
||||
.compactification_coordinate_gradient =
|
||||
workspace.m_compactification_point.coordinate_gradient
|
||||
};
|
||||
|
||||
const MappingStatus exterior_status = m_exterior_map->Evaluate(
|
||||
exterior_input, workspace.m_exterior_result
|
||||
);
|
||||
if (exterior_status != MappingStatus::valid)
|
||||
return exterior_status;
|
||||
|
||||
context.physical_position =
|
||||
workspace.m_exterior_result.physical_position;
|
||||
context.mapping_jacobian =
|
||||
workspace.m_exterior_result.mapping_jacobian;
|
||||
} else {
|
||||
context.physical_position = context.displaced_position;
|
||||
context.mapping_jacobian = context.displacement_jacobian;
|
||||
}
|
||||
|
||||
if (!vector_is_finite(context.physical_position) ||
|
||||
!matrix_is_finite(context.mapping_jacobian))
|
||||
return MappingStatus::non_finite_result;
|
||||
|
||||
context.mapping_determinant = context.mapping_jacobian.Det();
|
||||
if (!std::isfinite(context.mapping_determinant))
|
||||
return MappingStatus::non_finite_result;
|
||||
if (context.mapping_determinant <= 0.0)
|
||||
return MappingStatus::non_positive_determinant;
|
||||
|
||||
context.inverse_mapping_jacobian.SetSize(
|
||||
m_options.dimension, m_options.dimension
|
||||
);
|
||||
mfem::CalcInverse(
|
||||
context.mapping_jacobian, context.inverse_mapping_jacobian
|
||||
);
|
||||
|
||||
if (!matrix_is_finite(context.inverse_mapping_jacobian))
|
||||
return MappingStatus::non_finite_result;
|
||||
|
||||
return MappingStatus::valid;
|
||||
}
|
||||
|
||||
MappingStatus DomainMapperStateless::EvaluateVolume(
|
||||
const ElementMappingData &element_data,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
Workspace &workspace,
|
||||
VolumeMappingContext &context
|
||||
) const {
|
||||
const MappingStatus point_status = EvaluatePoint(
|
||||
element_data, transformation, integration_point, workspace,
|
||||
context.mapping
|
||||
);
|
||||
if (point_status != MappingStatus::valid)
|
||||
return point_status;
|
||||
|
||||
transformation.SetIntPoint(&integration_point);
|
||||
mfem::Mult(
|
||||
context.mapping.mapping_jacobian, transformation.Jacobian(),
|
||||
workspace.m_full_element_jacobian
|
||||
);
|
||||
|
||||
context.quadrature.J_inv.SetSize(
|
||||
m_options.dimension, m_options.dimension
|
||||
);
|
||||
mfem::CalcInverse(
|
||||
workspace.m_full_element_jacobian, context.quadrature.J_inv
|
||||
);
|
||||
|
||||
context.quadrature.detJ = context.mapping.mapping_determinant;
|
||||
context.quadrature.weight = integration_point.weight *
|
||||
transformation.Weight() *
|
||||
context.mapping.mapping_determinant;
|
||||
|
||||
if (!matrix_is_finite(context.quadrature.J_inv) ||
|
||||
!std::isfinite(context.quadrature.weight))
|
||||
return MappingStatus::non_finite_result;
|
||||
if (context.quadrature.weight <= 0.0)
|
||||
return MappingStatus::non_positive_determinant;
|
||||
|
||||
return MappingStatus::valid;
|
||||
}
|
||||
|
||||
mfem::ElementTransformation &
|
||||
DomainMapperStateless::SelectFaceElementTransformation(
|
||||
mfem::FaceElementTransformations &transformation,
|
||||
const FaceElementSide side
|
||||
) {
|
||||
if (side == FaceElementSide::element_1) {
|
||||
MFEM_VERIFY(
|
||||
transformation.Elem1 != nullptr,
|
||||
"The face does not have an element-1 transformation."
|
||||
);
|
||||
return *transformation.Elem1;
|
||||
}
|
||||
|
||||
MFEM_VERIFY(
|
||||
transformation.Elem2 != nullptr,
|
||||
"The face does not have an element-2 transformation."
|
||||
);
|
||||
return *transformation.Elem2;
|
||||
}
|
||||
|
||||
const mfem::IntegrationPoint &
|
||||
DomainMapperStateless::SelectFaceElementIntegrationPoint(
|
||||
mfem::FaceElementTransformations &transformation,
|
||||
const FaceElementSide side
|
||||
) {
|
||||
mfem::ElementTransformation &element_transformation =
|
||||
SelectFaceElementTransformation(transformation, side);
|
||||
return element_transformation.GetIntPoint();
|
||||
}
|
||||
|
||||
MappingStatus DomainMapperStateless::EvaluateFace(
|
||||
const ElementMappingData &element_data,
|
||||
mfem::FaceElementTransformations &transformation,
|
||||
const FaceElementSide side,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
Workspace &workspace,
|
||||
FaceMappingContext &context
|
||||
) const {
|
||||
transformation.SetAllIntPoints(&integration_point);
|
||||
mfem::ElementTransformation &element_transformation =
|
||||
SelectFaceElementTransformation(transformation, side);
|
||||
const mfem::IntegrationPoint &element_integration_point =
|
||||
SelectFaceElementIntegrationPoint(transformation, side);
|
||||
|
||||
const MappingStatus point_status = EvaluatePoint(
|
||||
element_data, element_transformation, element_integration_point,
|
||||
workspace, context.mapping
|
||||
);
|
||||
if (point_status != MappingStatus::valid)
|
||||
return point_status;
|
||||
|
||||
workspace.m_reference_normal.SetSize(m_options.dimension);
|
||||
mfem::CalcOrtho(
|
||||
transformation.Jacobian(), workspace.m_reference_normal
|
||||
);
|
||||
if (side == FaceElementSide::element_2)
|
||||
workspace.m_reference_normal *= -1.0;
|
||||
|
||||
const double reference_normal_magnitude =
|
||||
workspace.m_reference_normal.Norml2();
|
||||
if (!std::isfinite(reference_normal_magnitude) ||
|
||||
reference_normal_magnitude <= 0.0)
|
||||
return MappingStatus::non_finite_result;
|
||||
|
||||
context.reference_normal.SetSize(m_options.dimension);
|
||||
context.reference_normal = workspace.m_reference_normal;
|
||||
context.reference_normal /= reference_normal_magnitude;
|
||||
|
||||
context.mapping.inverse_mapping_jacobian.MultTranspose(
|
||||
workspace.m_reference_normal, workspace.m_mapped_normal
|
||||
);
|
||||
workspace.m_mapped_normal *= context.mapping.mapping_determinant;
|
||||
|
||||
const double mapped_normal_magnitude =
|
||||
workspace.m_mapped_normal.Norml2();
|
||||
if (!std::isfinite(mapped_normal_magnitude) ||
|
||||
mapped_normal_magnitude <= 0.0)
|
||||
return MappingStatus::non_finite_result;
|
||||
|
||||
context.quadrature.normal.SetSize(m_options.dimension);
|
||||
context.quadrature.normal = workspace.m_mapped_normal;
|
||||
context.quadrature.normal /= mapped_normal_magnitude;
|
||||
|
||||
context.reference_surface_weight =
|
||||
integration_point.weight * reference_normal_magnitude;
|
||||
context.physical_surface_weight =
|
||||
integration_point.weight * mapped_normal_magnitude;
|
||||
|
||||
context.quadrature.ds = context.reference_surface_weight;
|
||||
context.quadrature.v_dot_n_scale =
|
||||
mapped_normal_magnitude / reference_normal_magnitude;
|
||||
|
||||
if (!vector_is_finite(context.quadrature.normal) ||
|
||||
!std::isfinite(context.reference_surface_weight) ||
|
||||
!std::isfinite(context.physical_surface_weight) ||
|
||||
!std::isfinite(context.quadrature.v_dot_n_scale)) {
|
||||
return MappingStatus::non_finite_result;
|
||||
}
|
||||
|
||||
return MappingStatus::valid;
|
||||
}
|
||||
|
||||
MappingStatus DomainMapperStateless::EvaluatePointVariation(
|
||||
const ElementMappingData &element_data,
|
||||
const ElementDisplacementData &direction,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
const MappingPointContext &base_context,
|
||||
Workspace &workspace,
|
||||
MappingPointVariation &variation
|
||||
) const {
|
||||
ValidateElementData(element_data);
|
||||
const ElementMappingData direction_data{
|
||||
.displacement = direction,
|
||||
.compactification = element_data.compactification
|
||||
};
|
||||
ValidateElementData(direction_data);
|
||||
|
||||
if (element_data.displacement.GetDofCount() != direction.GetDofCount())
|
||||
throw std::invalid_argument(
|
||||
"The displacement and direction elements have different "
|
||||
"degree-of-freedom counts."
|
||||
);
|
||||
if (workspace.GetDimension() != m_options.dimension)
|
||||
throw std::invalid_argument(
|
||||
"The mapping workspace has the wrong dimension."
|
||||
);
|
||||
if (base_context.compactified != IsCompactifiedElement(transformation))
|
||||
throw std::invalid_argument(
|
||||
"The base mapping context does not match the current element "
|
||||
"domain."
|
||||
);
|
||||
|
||||
EvaluateField(
|
||||
direction, transformation, integration_point, workspace,
|
||||
workspace.m_field_value, workspace.m_field_jacobian
|
||||
);
|
||||
|
||||
if (!vector_is_finite(workspace.m_field_value) ||
|
||||
!matrix_is_finite(workspace.m_field_jacobian))
|
||||
return MappingStatus::non_finite_input;
|
||||
|
||||
variation.displacement_variation = workspace.m_field_value;
|
||||
variation.displacement_jacobian_variation = workspace.m_field_jacobian;
|
||||
|
||||
if (base_context.compactified) {
|
||||
const MappingStatus coordinate_status =
|
||||
EvaluateCompactificationCoordinate(
|
||||
element_data.compactification, transformation,
|
||||
integration_point, workspace,
|
||||
workspace.m_compactification_point
|
||||
);
|
||||
|
||||
if (coordinate_status != MappingStatus::valid)
|
||||
return coordinate_status;
|
||||
|
||||
const compactification::ExteriorMapInput exterior_input{
|
||||
.reference_position = base_context.reference_position,
|
||||
.displaced_position = base_context.displaced_position,
|
||||
.displacement_jacobian = base_context.displacement_jacobian,
|
||||
.compactification_coordinate =
|
||||
workspace.m_compactification_point.coordinate,
|
||||
.compactification_coordinate_gradient =
|
||||
workspace.m_compactification_point.coordinate_gradient
|
||||
};
|
||||
|
||||
workspace.m_exterior_result.physical_position =
|
||||
base_context.physical_position;
|
||||
workspace.m_exterior_result.mapping_jacobian =
|
||||
base_context.mapping_jacobian;
|
||||
|
||||
const compactification::ExteriorMapDirection exterior_direction{
|
||||
.displaced_position_variation =
|
||||
variation.displacement_variation,
|
||||
.displacement_jacobian_variation =
|
||||
variation.displacement_jacobian_variation
|
||||
};
|
||||
|
||||
// ReSharper disable once CppTooWideScopeInitStatement
|
||||
const MappingStatus exterior_status =
|
||||
m_exterior_map->EvaluateVariation(
|
||||
exterior_input, workspace.m_exterior_result,
|
||||
exterior_direction, workspace.m_exterior_variation
|
||||
);
|
||||
|
||||
if (exterior_status != MappingStatus::valid) {
|
||||
return exterior_status;
|
||||
}
|
||||
|
||||
variation.physical_position_variation =
|
||||
workspace.m_exterior_variation.physical_position_variation;
|
||||
variation.mapping_jacobian_variation =
|
||||
workspace.m_exterior_variation.mapping_jacobian_variation;
|
||||
} else {
|
||||
variation.physical_position_variation =
|
||||
variation.displacement_variation;
|
||||
variation.mapping_jacobian_variation =
|
||||
variation.displacement_jacobian_variation;
|
||||
}
|
||||
|
||||
mfem::Mult(
|
||||
base_context.inverse_mapping_jacobian,
|
||||
variation.mapping_jacobian_variation, workspace.m_matrix_temp_1
|
||||
);
|
||||
|
||||
double trace = 0.0;
|
||||
for (int i = 0; i < m_options.dimension; ++i)
|
||||
trace += workspace.m_matrix_temp_1(i, i);
|
||||
variation.mapping_determinant_variation =
|
||||
base_context.mapping_determinant * trace;
|
||||
|
||||
variation.inverse_mapping_jacobian_variation.SetSize(
|
||||
m_options.dimension, m_options.dimension
|
||||
);
|
||||
mfem::Mult(
|
||||
workspace.m_matrix_temp_1, base_context.inverse_mapping_jacobian,
|
||||
variation.inverse_mapping_jacobian_variation
|
||||
);
|
||||
variation.inverse_mapping_jacobian_variation *= -1.0;
|
||||
|
||||
if (!vector_is_finite(variation.physical_position_variation) ||
|
||||
!matrix_is_finite(variation.mapping_jacobian_variation) ||
|
||||
!matrix_is_finite(variation.inverse_mapping_jacobian_variation) ||
|
||||
!std::isfinite(variation.mapping_determinant_variation)) {
|
||||
return MappingStatus::non_finite_result;
|
||||
}
|
||||
|
||||
return MappingStatus::valid;
|
||||
}
|
||||
|
||||
MappingStatus DomainMapperStateless::EvaluateVolumeVariation(
|
||||
const ElementMappingData &element_data,
|
||||
const ElementDisplacementData &direction,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
const VolumeMappingContext &base_context,
|
||||
Workspace &workspace,
|
||||
VolumeMappingVariation &variation
|
||||
) const {
|
||||
const MappingStatus point_status = EvaluatePointVariation(
|
||||
element_data, direction, transformation, integration_point,
|
||||
base_context.mapping, workspace, variation.mapping
|
||||
);
|
||||
if (point_status != MappingStatus::valid)
|
||||
return point_status;
|
||||
|
||||
transformation.SetIntPoint(&integration_point);
|
||||
mfem::Mult(
|
||||
variation.mapping.mapping_jacobian_variation,
|
||||
transformation.Jacobian(), workspace.m_full_element_jacobian
|
||||
);
|
||||
mfem::Mult(
|
||||
base_context.quadrature.J_inv, workspace.m_full_element_jacobian,
|
||||
workspace.m_matrix_temp_1
|
||||
);
|
||||
|
||||
variation.inverse_element_jacobian_variation.SetSize(
|
||||
m_options.dimension, m_options.dimension
|
||||
);
|
||||
mfem::Mult(
|
||||
workspace.m_matrix_temp_1, base_context.quadrature.J_inv,
|
||||
variation.inverse_element_jacobian_variation
|
||||
);
|
||||
variation.inverse_element_jacobian_variation *= -1.0;
|
||||
|
||||
variation.weight_variation =
|
||||
integration_point.weight * transformation.Weight() *
|
||||
variation.mapping.mapping_determinant_variation;
|
||||
|
||||
if (!matrix_is_finite(variation.inverse_element_jacobian_variation) ||
|
||||
!std::isfinite(variation.weight_variation))
|
||||
return MappingStatus::non_finite_result;
|
||||
|
||||
return MappingStatus::valid;
|
||||
}
|
||||
|
||||
MappingStatus DomainMapperStateless::EvaluateFaceVariation(
|
||||
const ElementMappingData &element_data,
|
||||
const ElementDisplacementData &direction,
|
||||
mfem::FaceElementTransformations &transformation,
|
||||
const FaceElementSide side,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
const FaceMappingContext &base_context,
|
||||
Workspace &workspace,
|
||||
FaceMappingVariation &variation
|
||||
) const {
|
||||
transformation.SetAllIntPoints(&integration_point);
|
||||
mfem::ElementTransformation &element_transformation =
|
||||
SelectFaceElementTransformation(transformation, side);
|
||||
const mfem::IntegrationPoint &element_integration_point =
|
||||
SelectFaceElementIntegrationPoint(transformation, side);
|
||||
|
||||
const MappingStatus point_status = EvaluatePointVariation(
|
||||
element_data, direction, element_transformation,
|
||||
element_integration_point, base_context.mapping, workspace,
|
||||
variation.mapping
|
||||
);
|
||||
if (point_status != MappingStatus::valid)
|
||||
return point_status;
|
||||
|
||||
workspace.m_reference_normal.SetSize(m_options.dimension);
|
||||
mfem::CalcOrtho(
|
||||
transformation.Jacobian(), workspace.m_reference_normal
|
||||
);
|
||||
if (side == FaceElementSide::element_2)
|
||||
workspace.m_reference_normal *= -1.0;
|
||||
|
||||
const double reference_normal_magnitude =
|
||||
workspace.m_reference_normal.Norml2();
|
||||
if (!std::isfinite(reference_normal_magnitude) ||
|
||||
reference_normal_magnitude <= 0.0)
|
||||
return MappingStatus::non_finite_result;
|
||||
|
||||
base_context.mapping.inverse_mapping_jacobian.MultTranspose(
|
||||
workspace.m_reference_normal, workspace.m_vector_temp
|
||||
);
|
||||
workspace.m_mapped_normal = workspace.m_vector_temp;
|
||||
workspace.m_mapped_normal *= base_context.mapping.mapping_determinant;
|
||||
|
||||
variation.physical_normal_variation.SetSize(m_options.dimension);
|
||||
variation.mapping.inverse_mapping_jacobian_variation.MultTranspose(
|
||||
workspace.m_reference_normal, variation.physical_normal_variation
|
||||
);
|
||||
variation.physical_normal_variation *=
|
||||
base_context.mapping.mapping_determinant;
|
||||
variation.physical_normal_variation.Add(
|
||||
variation.mapping.mapping_determinant_variation,
|
||||
workspace.m_vector_temp
|
||||
);
|
||||
|
||||
const double mapped_normal_magnitude =
|
||||
workspace.m_mapped_normal.Norml2();
|
||||
if (!std::isfinite(mapped_normal_magnitude) ||
|
||||
mapped_normal_magnitude <= 0.0)
|
||||
return MappingStatus::non_finite_result;
|
||||
|
||||
const double mapped_normal_magnitude_variation =
|
||||
base_context.quadrature.normal *
|
||||
variation.physical_normal_variation;
|
||||
|
||||
variation.physical_normal_variation.Add(
|
||||
-mapped_normal_magnitude_variation, base_context.quadrature.normal
|
||||
);
|
||||
variation.physical_normal_variation /= mapped_normal_magnitude;
|
||||
|
||||
variation.physical_surface_weight_variation =
|
||||
integration_point.weight * mapped_normal_magnitude_variation;
|
||||
variation.normal_flux_scale_variation =
|
||||
mapped_normal_magnitude_variation / reference_normal_magnitude;
|
||||
|
||||
if (!vector_is_finite(variation.physical_normal_variation) ||
|
||||
!std::isfinite(variation.physical_surface_weight_variation) ||
|
||||
!std::isfinite(variation.normal_flux_scale_variation)) {
|
||||
return MappingStatus::non_finite_result;
|
||||
}
|
||||
|
||||
return MappingStatus::valid;
|
||||
}
|
||||
} // namespace mean_field::mapping
|
||||
276
libmeanfield/impl/mapping/transformations.cpp
Normal file
276
libmeanfield/impl/mapping/transformations.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
import :mapping.types;
|
||||
|
||||
namespace mean_field::mapping {
|
||||
void MapHDivFluxToPhysical(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &reference_flux,
|
||||
mfem::Vector &physical_flux
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
reference_flux.Size() == context.mapping_jacobian.Width(),
|
||||
"The reference H(div) flux has the wrong dimension."
|
||||
);
|
||||
|
||||
physical_flux.SetSize(reference_flux.Size());
|
||||
context.mapping_jacobian.Mult(reference_flux, physical_flux);
|
||||
physical_flux /= context.mapping_determinant;
|
||||
}
|
||||
|
||||
void MapPhysicalFluxToHDivReference(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &physical_flux,
|
||||
mfem::Vector &reference_flux
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
physical_flux.Size() == context.inverse_mapping_jacobian.Width(),
|
||||
"The physical H(div) flux has the wrong dimension."
|
||||
);
|
||||
|
||||
reference_flux.SetSize(physical_flux.Size());
|
||||
context.inverse_mapping_jacobian.Mult(physical_flux, reference_flux);
|
||||
reference_flux *= context.mapping_determinant;
|
||||
}
|
||||
|
||||
void MapReferenceGradientToPhysical(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &reference_gradient,
|
||||
mfem::Vector &physical_gradient
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
reference_gradient.Size() ==
|
||||
context.inverse_mapping_jacobian.Height(),
|
||||
"The reference scalar gradient has the wrong dimension."
|
||||
);
|
||||
|
||||
physical_gradient.SetSize(reference_gradient.Size());
|
||||
context.inverse_mapping_jacobian.MultTranspose(
|
||||
reference_gradient, physical_gradient
|
||||
);
|
||||
}
|
||||
|
||||
void MapPhysicalGradientToReference(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &physical_gradient,
|
||||
mfem::Vector &reference_gradient
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
physical_gradient.Size() == context.mapping_jacobian.Height(),
|
||||
"The physical scalar gradient has the wrong dimension."
|
||||
);
|
||||
|
||||
reference_gradient.SetSize(physical_gradient.Size());
|
||||
context.mapping_jacobian.MultTranspose(
|
||||
physical_gradient, reference_gradient
|
||||
);
|
||||
}
|
||||
|
||||
void MapReferenceVectorGradientToPhysical(
|
||||
const MappingPointContext &context,
|
||||
const mfem::DenseMatrix &reference_gradient,
|
||||
mfem::DenseMatrix &physical_gradient
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
reference_gradient.Width() ==
|
||||
context.inverse_mapping_jacobian.Height(),
|
||||
"The reference vector gradient has the wrong dimension."
|
||||
);
|
||||
|
||||
physical_gradient.SetSize(
|
||||
reference_gradient.Height(),
|
||||
context.inverse_mapping_jacobian.Width()
|
||||
);
|
||||
mfem::Mult(
|
||||
reference_gradient, context.inverse_mapping_jacobian,
|
||||
physical_gradient
|
||||
);
|
||||
}
|
||||
|
||||
void MapPhysicalVectorGradientToReference(
|
||||
const MappingPointContext &context,
|
||||
const mfem::DenseMatrix &physical_gradient,
|
||||
mfem::DenseMatrix &reference_gradient
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
physical_gradient.Width() == context.mapping_jacobian.Height(),
|
||||
"The physical vector gradient has the wrong dimension."
|
||||
);
|
||||
|
||||
reference_gradient.SetSize(
|
||||
physical_gradient.Height(), context.mapping_jacobian.Width()
|
||||
);
|
||||
mfem::Mult(
|
||||
physical_gradient, context.mapping_jacobian, reference_gradient
|
||||
);
|
||||
}
|
||||
|
||||
double MapHDivDivergenceToPhysical(
|
||||
const MappingPointContext &context,
|
||||
const double reference_divergence
|
||||
) {
|
||||
return reference_divergence / context.mapping_determinant;
|
||||
}
|
||||
|
||||
void ComputeHDivMassTensor(
|
||||
const MappingPointContext &context,
|
||||
mfem::DenseMatrix &mass_tensor
|
||||
) {
|
||||
const int dimension = context.mapping_jacobian.Height();
|
||||
|
||||
MFEM_VERIFY(
|
||||
context.mapping_jacobian.Width() == dimension,
|
||||
"The mapping Jacobian must be square."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
context.mapping_determinant > 0.0,
|
||||
"The mapping determinant must be positive."
|
||||
);
|
||||
|
||||
mass_tensor.SetSize(dimension, dimension);
|
||||
mfem::MultAtB(
|
||||
context.mapping_jacobian, context.mapping_jacobian, mass_tensor
|
||||
);
|
||||
mass_tensor *= 1 / context.mapping_determinant;
|
||||
}
|
||||
|
||||
void ComputeScalarDiffusionTensor(
|
||||
const MappingPointContext &context,
|
||||
mfem::DenseMatrix &diffusion_tensor
|
||||
) {
|
||||
const int dimension = context.inverse_mapping_jacobian.Height();
|
||||
|
||||
MFEM_VERIFY(
|
||||
context.inverse_mapping_jacobian.Width() == dimension,
|
||||
"The inverse mapping Jacobian must be square."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
context.mapping_determinant > 0.0,
|
||||
"The mapping determinant must be positive."
|
||||
);
|
||||
|
||||
diffusion_tensor.SetSize(dimension, dimension);
|
||||
mfem::MultABt(
|
||||
context.inverse_mapping_jacobian, context.inverse_mapping_jacobian,
|
||||
diffusion_tensor
|
||||
);
|
||||
diffusion_tensor *= context.mapping_determinant;
|
||||
}
|
||||
|
||||
void MapHCurlFieldToPhysical(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &reference_field,
|
||||
mfem::Vector &physical_field
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
reference_field.Size() == context.inverse_mapping_jacobian.Height(),
|
||||
"The reference H(curl) field has the wrong dimension."
|
||||
);
|
||||
|
||||
physical_field.SetSize(reference_field.Size());
|
||||
context.inverse_mapping_jacobian.MultTranspose(
|
||||
reference_field, physical_field
|
||||
);
|
||||
}
|
||||
|
||||
void MapPhysicalFieldToHCurlReference(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &physical_field,
|
||||
mfem::Vector &reference_field
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
physical_field.Size() == context.mapping_jacobian.Height(),
|
||||
"The physical H(curl) field has the wrong dimension."
|
||||
);
|
||||
|
||||
reference_field.SetSize(physical_field.Size());
|
||||
context.mapping_jacobian.MultTranspose(physical_field, reference_field);
|
||||
}
|
||||
|
||||
void MapHCurlCurlToPhysical(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &reference_curl,
|
||||
mfem::Vector &physical_curl
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
reference_curl.Size() == context.mapping_jacobian.Width(),
|
||||
"The reference H(curl) curl has the wrong dimension."
|
||||
);
|
||||
|
||||
physical_curl.SetSize(reference_curl.Size());
|
||||
context.mapping_jacobian.Mult(reference_curl, physical_curl);
|
||||
physical_curl /= context.mapping_determinant;
|
||||
}
|
||||
|
||||
void MapPhysicalCurlToHCurlReference(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &physical_curl,
|
||||
mfem::Vector &reference_curl
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
physical_curl.Size() == context.inverse_mapping_jacobian.Width(),
|
||||
"The physical H(curl) curl has the wrong dimension."
|
||||
);
|
||||
|
||||
reference_curl.SetSize(physical_curl.Size());
|
||||
context.inverse_mapping_jacobian.Mult(physical_curl, reference_curl);
|
||||
reference_curl *= context.mapping_determinant;
|
||||
}
|
||||
|
||||
// TODO: Investigate these
|
||||
void ComputeHCurlMassTensor(
|
||||
const MappingPointContext &context,
|
||||
mfem::DenseMatrix &mass_tensor
|
||||
) {
|
||||
ComputeScalarDiffusionTensor(context, mass_tensor);
|
||||
}
|
||||
|
||||
void ComputeHCurlCurlTensor(
|
||||
const MappingPointContext &context,
|
||||
mfem::DenseMatrix &curl_tensor
|
||||
) {
|
||||
ComputeHDivMassTensor(context, curl_tensor);
|
||||
}
|
||||
|
||||
void ComputeHDivMassTensorVariation(
|
||||
const MappingPointContext &context,
|
||||
const MappingPointVariation &variation,
|
||||
mfem::DenseMatrix &mass_tensor_variation
|
||||
) {
|
||||
const double determinant = context.mapping_determinant;
|
||||
const double determinant_variation =
|
||||
variation.mapping_determinant_variation;
|
||||
const int dimension = context.inverse_mapping_jacobian.Width();
|
||||
|
||||
mass_tensor_variation.SetSize(dimension, dimension);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(determinant) && determinant > 0.0,
|
||||
"The mapping determinant must be positive and finite."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(determinant_variation),
|
||||
"The mapping determinant variation must be finite."
|
||||
);
|
||||
|
||||
mfem::DenseMatrix determinant_correction(dimension, dimension);
|
||||
ComputeHDivMassTensor(context, determinant_correction);
|
||||
determinant_correction *= determinant_variation / determinant;
|
||||
|
||||
mfem::DenseMatrix right_jacobian_variation(dimension, dimension);
|
||||
mfem::MultAtB(
|
||||
context.mapping_jacobian, variation.mapping_jacobian_variation,
|
||||
right_jacobian_variation
|
||||
);
|
||||
mfem::MultAtB(
|
||||
variation.mapping_jacobian_variation, context.mapping_jacobian,
|
||||
mass_tensor_variation
|
||||
);
|
||||
|
||||
mass_tensor_variation += right_jacobian_variation;
|
||||
mass_tensor_variation *= 1 / determinant;
|
||||
mass_tensor_variation -= determinant_correction;
|
||||
}
|
||||
} // namespace mean_field::mapping
|
||||
@@ -0,0 +1,135 @@
|
||||
module;
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
import :operators.context.barotropic_closure_linearization;
|
||||
|
||||
namespace mean_field::operators::context::barotropic {
|
||||
BarotropicClosureLinearizationContext::
|
||||
BarotropicClosureLinearizationContext(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope
|
||||
)
|
||||
: m_f(f),
|
||||
m_operator(
|
||||
f,
|
||||
domainMapper,
|
||||
barotrope
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
m_f.densityFes != nullptr,
|
||||
"The closure linearization context requires the "
|
||||
"density finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
m_f.enthalpyFes != nullptr,
|
||||
"The closure linearization context requires the "
|
||||
"enthalpy finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
m_f.displacementFes != nullptr,
|
||||
"The closure linearization context requires the "
|
||||
"displacement finite-element space."
|
||||
);
|
||||
}
|
||||
|
||||
void BarotropicClosureLinearizationContext::Prepare(
|
||||
const mfem::Vector &baseDensityTrue,
|
||||
const mfem::Vector &baseEnthalpyTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
const BarotropicClosureRevisions &revisions
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
baseDensityTrue.Size() == m_f.densityFes->GetTrueVSize(),
|
||||
"The closure base-density vector has the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
baseEnthalpyTrue.Size() == m_f.enthalpyFes->GetTrueVSize(),
|
||||
"The closure base-enthalpy vector has the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacementTrue.Size() == m_f.displacementFes->GetTrueVSize(),
|
||||
"The closure displacement vector has the wrong size."
|
||||
);
|
||||
|
||||
if (m_isPrepared && revisions == m_revisions) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_operator.Prepare(baseDensityTrue, baseEnthalpyTrue, displacementTrue);
|
||||
|
||||
m_baseDensityTrue = baseDensityTrue;
|
||||
m_baseEnthalpyTrue = baseEnthalpyTrue;
|
||||
m_displacementTrue = displacementTrue;
|
||||
|
||||
m_revisions = revisions;
|
||||
m_isPrepared = true;
|
||||
++m_preparationCount;
|
||||
}
|
||||
|
||||
bool BarotropicClosureLinearizationContext::IsPrepared() const noexcept {
|
||||
return m_isPrepared;
|
||||
}
|
||||
|
||||
bool BarotropicClosureLinearizationContext::MatchesRevisions(
|
||||
const BarotropicClosureRevisions &revisions
|
||||
) const noexcept {
|
||||
return m_isPrepared && revisions == m_revisions;
|
||||
}
|
||||
|
||||
std::uint64_t BarotropicClosureLinearizationContext::
|
||||
GetPreparationCount() const noexcept {
|
||||
return m_preparationCount;
|
||||
}
|
||||
|
||||
const BarotropicClosureRevisions &
|
||||
BarotropicClosureLinearizationContext::GetRevisions() const {
|
||||
VerifyPrepared();
|
||||
return m_revisions;
|
||||
}
|
||||
|
||||
const mfem::Vector &
|
||||
BarotropicClosureLinearizationContext::GetBaseDensityTrue() const {
|
||||
VerifyPrepared();
|
||||
return m_baseDensityTrue;
|
||||
}
|
||||
|
||||
const mfem::Vector &
|
||||
BarotropicClosureLinearizationContext::GetBaseEnthalpyTrue() const {
|
||||
VerifyPrepared();
|
||||
return m_baseEnthalpyTrue;
|
||||
}
|
||||
|
||||
const mfem::Vector &
|
||||
BarotropicClosureLinearizationContext::GetDisplacementTrue() const {
|
||||
VerifyPrepared();
|
||||
return m_displacementTrue;
|
||||
}
|
||||
|
||||
const PreparedBarotropicClosureOperator &
|
||||
BarotropicClosureLinearizationContext::GetOperator() const noexcept {
|
||||
return m_operator;
|
||||
}
|
||||
|
||||
void BarotropicClosureLinearizationContext::BuildResidual(
|
||||
mfem::Vector &residual
|
||||
) const {
|
||||
VerifyPrepared();
|
||||
m_operator.BuildResidual(residual);
|
||||
}
|
||||
|
||||
void BarotropicClosureLinearizationContext::VerifyPrepared() const {
|
||||
MFEM_VERIFY(
|
||||
m_isPrepared, "The barotropic-closure linearization context "
|
||||
"has not been prepared."
|
||||
);
|
||||
}
|
||||
} // namespace mean_field::operators::context::barotropic
|
||||
442
libmeanfield/impl/operators/contexts/gravity_field_context.cpp
Normal file
442
libmeanfield/impl/operators/contexts/gravity_field_context.cpp
Normal file
@@ -0,0 +1,442 @@
|
||||
module;
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
import :operators.context.gravity_field;
|
||||
|
||||
namespace {
|
||||
void validate_displacement(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mfem::Vector &displacement_true
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr,
|
||||
"GravityFieldGeometryContext requires the "
|
||||
"displacement finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
displacement_true.Size() == f.displacementFes->GetTrueVSize(),
|
||||
"GravityFieldGeometryContext received a displacement vector with "
|
||||
"the "
|
||||
"wrong size."
|
||||
);
|
||||
|
||||
for (int i = 0; i < displacement_true.Size(); ++i) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(displacement_true(i)),
|
||||
"GravityFieldGeometryContext received a non-finite "
|
||||
"displacement "
|
||||
"value."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void validate_linearization_state(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::operators::context::gravity_field::
|
||||
GravityFieldStateView &state
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
f.densityFes != nullptr, "GravityFieldLinearizationContext "
|
||||
"requires the density finite-element "
|
||||
"space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityPotentialFes != nullptr,
|
||||
"GravityFieldLinearizationContext requires the gravity-potential "
|
||||
"finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityFluxFes != nullptr,
|
||||
"GravityFieldLinearizationContext requires the "
|
||||
"gravity-gradient finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr,
|
||||
"GravityFieldLinearizationContext requires "
|
||||
"the displacement finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
state.density.Size() == f.densityFes->GetTrueVSize(),
|
||||
"GravityFieldLinearizationContext received a density vector with "
|
||||
"the "
|
||||
"wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
state.displacement.Size() == f.displacementFes->GetTrueVSize(),
|
||||
"GravityFieldLinearizationContext received a displacement vector "
|
||||
"with "
|
||||
"the wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
state.gravity_gradient.Size() == f.gravityFluxFes->GetTrueVSize(),
|
||||
"GravityFieldLinearizationContext received a gravity-gradient "
|
||||
"vector "
|
||||
"with the wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
state.gravity_potential.Size() ==
|
||||
f.gravityPotentialFes->GetTrueVSize(),
|
||||
"GravityFieldLinearizationContext received a gravity-potential "
|
||||
"vector "
|
||||
"with the wrong size."
|
||||
);
|
||||
|
||||
for (int i = 0; i < state.density.Size(); ++i) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(state.density(i)),
|
||||
"GravityFieldLinearizationContext received a non-finite "
|
||||
"density "
|
||||
"value."
|
||||
);
|
||||
}
|
||||
|
||||
for (int i = 0; i < state.displacement.Size(); ++i) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(state.displacement(i)),
|
||||
"GravityFieldLinearizationContext received a non-finite "
|
||||
"displacement "
|
||||
"value."
|
||||
);
|
||||
}
|
||||
|
||||
for (int i = 0; i < state.gravity_gradient.Size(); ++i) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(state.gravity_gradient(i)),
|
||||
"GravityFieldLinearizationContext received a non-finite "
|
||||
"gravity-gradient value."
|
||||
);
|
||||
}
|
||||
|
||||
for (int i = 0; i < state.gravity_potential.Size(); ++i) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(state.gravity_potential(i)),
|
||||
"GravityFieldLinearizationContext received a non-finite "
|
||||
"gravity-potential value."
|
||||
);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators::context::gravity_field {
|
||||
GravityFieldGeometryContext::GravityFieldGeometryContext(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper
|
||||
)
|
||||
: m_fem(f),
|
||||
m_domain_mapper(domain_mapper) {
|
||||
MFEM_VERIFY(
|
||||
f.mesh != nullptr, "GravityFieldGeometryContext requires a mesh."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityFluxFes != nullptr,
|
||||
"GravityFieldGeometryContext requires the "
|
||||
"gravity-gradient finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.densityFes != nullptr,
|
||||
"GravityFieldGeometryContext requires the density finite-element "
|
||||
"space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityPotentialFes != nullptr,
|
||||
"GravityFieldGeometryContext requires the gravity-potential "
|
||||
"finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr,
|
||||
"GravityFieldGeometryContext requires the "
|
||||
"displacement finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.compactificationFes != nullptr,
|
||||
"GravityFieldGeometryContext requires the compactification "
|
||||
"finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.compactificationCoordinate != nullptr,
|
||||
"GravityFieldGeometryContext requires the compactification "
|
||||
"coordinate."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.quadratureFactory != nullptr,
|
||||
"GravityFieldGeometryContext requires the quadrature-rule factory."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
domain_mapper.GetDimension() == f.mesh->Dimension(),
|
||||
"The stateless domain-mapper dimension does not match the mesh "
|
||||
"dimension."
|
||||
);
|
||||
}
|
||||
|
||||
GravityFieldGeometryPreparation GravityFieldGeometryContext::Prepare(
|
||||
const mfem::Vector &displacement_true,
|
||||
const DiscretizationRevision discretization_revision,
|
||||
const DisplacementRevision displacement_revision
|
||||
) {
|
||||
validate_displacement(m_fem, displacement_true);
|
||||
|
||||
if (m_is_prepared) {
|
||||
MFEM_VERIFY(
|
||||
discretization_revision >= m_discretization_revision,
|
||||
"GravityFieldGeometryContext received an older discretization "
|
||||
"revision."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
displacement_revision >= m_displacement_revision,
|
||||
"GravityFieldGeometryContext received an older displacement "
|
||||
"revision."
|
||||
);
|
||||
}
|
||||
|
||||
const bool discretization_changed =
|
||||
!m_is_prepared ||
|
||||
discretization_revision != m_discretization_revision;
|
||||
const bool displacement_changed =
|
||||
!m_is_prepared || displacement_revision != m_displacement_revision;
|
||||
|
||||
GravityFieldGeometryPreparation preparation;
|
||||
|
||||
if (!discretization_changed && !displacement_changed) {
|
||||
return preparation;
|
||||
}
|
||||
|
||||
if (discretization_changed) {
|
||||
auto mass_operator =
|
||||
std::make_unique<PreparedMappedHDivMassOperator>(
|
||||
m_fem, m_domain_mapper
|
||||
);
|
||||
auto source_operator =
|
||||
std::make_unique<PreparedMappedGravitySourceOperator>(
|
||||
m_fem, m_domain_mapper
|
||||
);
|
||||
|
||||
mass_operator->Prepare(displacement_true);
|
||||
source_operator->Prepare(displacement_true);
|
||||
|
||||
m_mass_operator = std::move(mass_operator);
|
||||
m_source_operator = std::move(source_operator);
|
||||
|
||||
preparation.reconstructed_operators = true;
|
||||
preparation.rebuilt_mass_operator = true;
|
||||
preparation.rebuilt_source_operator = true;
|
||||
} else {
|
||||
MFEM_VERIFY(
|
||||
m_mass_operator != nullptr, "GravityFieldGeometryContext has "
|
||||
"no prepared H(div) mass operator."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_source_operator != nullptr,
|
||||
"GravityFieldGeometryContext has no prepared gravity source "
|
||||
"operator."
|
||||
);
|
||||
|
||||
m_mass_operator->Prepare(displacement_true);
|
||||
m_source_operator->Prepare(displacement_true);
|
||||
|
||||
preparation.rebuilt_mass_operator = true;
|
||||
preparation.rebuilt_source_operator = true;
|
||||
}
|
||||
|
||||
m_displacement_true = displacement_true;
|
||||
m_discretization_revision = discretization_revision;
|
||||
m_displacement_revision = displacement_revision;
|
||||
m_is_prepared = true;
|
||||
|
||||
preparation.refreshed_variation_state = true;
|
||||
|
||||
return preparation;
|
||||
}
|
||||
|
||||
const PreparedMappedHDivMassOperator &
|
||||
GravityFieldGeometryContext::GetMassOperator() const {
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared,
|
||||
"GravityFieldGeometryContext must be prepared before "
|
||||
"accessing its mass operator."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_mass_operator != nullptr,
|
||||
"GravityFieldGeometryContext has no prepared H(div) mass operator."
|
||||
);
|
||||
return *m_mass_operator;
|
||||
}
|
||||
|
||||
const PreparedMappedGravitySourceOperator &
|
||||
GravityFieldGeometryContext::GetSourceOperator() const {
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared,
|
||||
"GravityFieldGeometryContext must be prepared before "
|
||||
"accessing its source operator."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_source_operator != nullptr, "GravityFieldGeometryContext has no "
|
||||
"prepared gravity source operator."
|
||||
);
|
||||
return *m_source_operator;
|
||||
}
|
||||
|
||||
const mfem::Vector &GravityFieldGeometryContext::GetDisplacement() const {
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared,
|
||||
"GravityFieldGeometryContext must be prepared before "
|
||||
"accessing its displacement."
|
||||
);
|
||||
return m_displacement_true;
|
||||
}
|
||||
|
||||
DiscretizationRevision
|
||||
GravityFieldGeometryContext::GetDiscretizationRevision() const noexcept {
|
||||
return m_discretization_revision;
|
||||
}
|
||||
|
||||
DisplacementRevision
|
||||
GravityFieldGeometryContext::GetDisplacementRevision() const noexcept {
|
||||
return m_displacement_revision;
|
||||
}
|
||||
|
||||
bool GravityFieldGeometryContext::IsPrepared() const noexcept {
|
||||
return m_is_prepared;
|
||||
}
|
||||
|
||||
GravityFieldLinearizationContext::GravityFieldLinearizationContext(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper
|
||||
)
|
||||
: m_fem(f),
|
||||
m_geometry_context(
|
||||
f,
|
||||
domain_mapper
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
f.densityFes != nullptr, "GravityFieldLinearizationContext "
|
||||
"requires the density finite-element "
|
||||
"space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityPotentialFes != nullptr,
|
||||
"GravityFieldLinearizationContext requires the gravity-potential "
|
||||
"finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityFluxFes != nullptr,
|
||||
"GravityFieldLinearizationContext requires the "
|
||||
"gravity-gradient finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr,
|
||||
"GravityFieldLinearizationContext requires "
|
||||
"the displacement finite-element space."
|
||||
);
|
||||
}
|
||||
|
||||
GravityFieldPreparationReport GravityFieldLinearizationContext::Prepare(
|
||||
const GravityFieldStateView &state,
|
||||
const GravityFieldRevisions &revisions
|
||||
) {
|
||||
validate_linearization_state(m_fem, state);
|
||||
|
||||
if (m_is_prepared) {
|
||||
MFEM_VERIFY(
|
||||
revisions.discretization >= m_revisions.discretization,
|
||||
"GravityFieldLinearizationContext received an older "
|
||||
"discretization "
|
||||
"revision."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
revisions.displacement >= m_revisions.displacement,
|
||||
"GravityFieldLinearizationContext received an older "
|
||||
"displacement "
|
||||
"revision."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
revisions.density >= m_revisions.density,
|
||||
"GravityFieldLinearizationContext received an older density "
|
||||
"revision."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
revisions.gravity_gradient >= m_revisions.gravity_gradient,
|
||||
"GravityFieldLinearizationContext received an older "
|
||||
"gravity-gradient "
|
||||
"revision."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
revisions.gravity_potential >= m_revisions.gravity_potential,
|
||||
"GravityFieldLinearizationContext received an older "
|
||||
"gravity-potential revision."
|
||||
);
|
||||
}
|
||||
|
||||
const bool discretization_changed =
|
||||
!m_is_prepared ||
|
||||
revisions.discretization != m_revisions.discretization;
|
||||
const bool density_changed = !m_is_prepared || discretization_changed ||
|
||||
revisions.density != m_revisions.density;
|
||||
const bool gravity_gradient_changed =
|
||||
!m_is_prepared || discretization_changed ||
|
||||
revisions.gravity_gradient != m_revisions.gravity_gradient;
|
||||
|
||||
GravityFieldPreparationReport report;
|
||||
|
||||
report.geometry = m_geometry_context.Prepare(
|
||||
state.displacement, revisions.discretization, revisions.displacement
|
||||
);
|
||||
|
||||
if (density_changed) {
|
||||
m_density_true = state.density;
|
||||
report.updated_density = true;
|
||||
}
|
||||
|
||||
if (gravity_gradient_changed) {
|
||||
m_gravity_gradient_true = state.gravity_gradient;
|
||||
report.updated_gravity_gradient = true;
|
||||
}
|
||||
|
||||
m_revisions = revisions;
|
||||
m_is_prepared = true;
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
const GravityFieldGeometryContext &
|
||||
GravityFieldLinearizationContext::GetGeometryContext() const {
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared, "GravityFieldLinearizationContext must be prepared "
|
||||
"before accessing its geometry context."
|
||||
);
|
||||
return m_geometry_context;
|
||||
}
|
||||
|
||||
const mfem::Vector &GravityFieldLinearizationContext::GetDensity() const {
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared, "GravityFieldLinearizationContext must be prepared "
|
||||
"before accessing its density."
|
||||
);
|
||||
return m_density_true;
|
||||
}
|
||||
|
||||
const mfem::Vector &
|
||||
GravityFieldLinearizationContext::GetGravityGradient() const {
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared, "GravityFieldLinearizationContext must be prepared "
|
||||
"before accessing its gravity gradient."
|
||||
);
|
||||
return m_gravity_gradient_true;
|
||||
}
|
||||
|
||||
const GravityFieldRevisions &
|
||||
GravityFieldLinearizationContext::GetRevisions() const {
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared, "GravityFieldLinearizationContext must be prepared "
|
||||
"before accessing its revisions."
|
||||
);
|
||||
return m_revisions;
|
||||
}
|
||||
|
||||
bool GravityFieldLinearizationContext::IsPrepared() const noexcept {
|
||||
return m_is_prepared;
|
||||
}
|
||||
} // namespace mean_field::operators::context::gravity_field
|
||||
@@ -0,0 +1,309 @@
|
||||
module;
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
|
||||
import :operators.context.hydrostatic_equilibrium;
|
||||
|
||||
namespace {
|
||||
void validate_finite_vector(
|
||||
const mfem::Vector &vector,
|
||||
const char *message
|
||||
) {
|
||||
for (int i = 0; i < vector.Size(); ++i) {
|
||||
MFEM_VERIFY(std::isfinite(vector(i)), message);
|
||||
}
|
||||
}
|
||||
|
||||
void validate_state(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::operators::context::hydrostatic::
|
||||
HydrostaticEquilibriumStateView &state
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
f.enthalpyFes != nullptr,
|
||||
"HydrostaticEquilibriumContext requires the "
|
||||
"enthalpy finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.gravityPotentialFes != nullptr,
|
||||
"HydrostaticEquilibriumContext requires the "
|
||||
"gravity-potential finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr,
|
||||
"HydrostaticEquilibriumContext requires the "
|
||||
"displacement finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
state.enthalpy.Size() == f.enthalpyFes->GetTrueVSize(),
|
||||
"HydrostaticEquilibriumContext received an "
|
||||
"enthalpy vector with the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
state.gravityPotential.Size() ==
|
||||
f.gravityPotentialFes->GetTrueVSize(),
|
||||
"HydrostaticEquilibriumContext received a "
|
||||
"gravity-potential vector with the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
state.displacement.Size() == f.displacementFes->GetTrueVSize(),
|
||||
"HydrostaticEquilibriumContext received a "
|
||||
"displacement vector with the wrong size."
|
||||
);
|
||||
|
||||
validate_finite_vector(
|
||||
state.enthalpy, "HydrostaticEquilibriumContext received a "
|
||||
"non-finite enthalpy value."
|
||||
);
|
||||
|
||||
validate_finite_vector(
|
||||
state.gravityPotential, "HydrostaticEquilibriumContext received a "
|
||||
"non-finite gravity-potential value."
|
||||
);
|
||||
|
||||
validate_finite_vector(
|
||||
state.displacement, "HydrostaticEquilibriumContext received a "
|
||||
"non-finite displacement value."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(state.bernoulliConstant),
|
||||
"HydrostaticEquilibriumContext received a "
|
||||
"non-finite Bernoulli constant."
|
||||
);
|
||||
}
|
||||
|
||||
template <typename Stamp>
|
||||
void validate_dependency_transition(
|
||||
const Stamp &prepared,
|
||||
const Stamp &requested,
|
||||
const char *message
|
||||
) {
|
||||
MFEM_VERIFY(requested.CanFollow(prepared), message);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators::context::hydrostatic {
|
||||
HydrostaticEquilibriumContext::HydrostaticEquilibriumContext(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper
|
||||
)
|
||||
: m_f(f),
|
||||
m_domainMapper(domainMapper) {
|
||||
MFEM_VERIFY(
|
||||
m_f.mesh != nullptr,
|
||||
"HydrostaticEquilibriumContext requires a mesh."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
m_f.enthalpyFes != nullptr,
|
||||
"HydrostaticEquilibriumContext requires the "
|
||||
"enthalpy finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
m_f.gravityPotentialFes != nullptr,
|
||||
"HydrostaticEquilibriumContext requires the "
|
||||
"gravity-potential finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
m_f.displacementFes != nullptr,
|
||||
"HydrostaticEquilibriumContext requires the "
|
||||
"displacement finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
m_domainMapper.GetDimension() == m_f.mesh->Dimension(),
|
||||
"The hydrostatic context's stateless "
|
||||
"domain-mapper dimension does not match the mesh "
|
||||
"dimension."
|
||||
);
|
||||
}
|
||||
|
||||
HydrostaticPreparationReport HydrostaticEquilibriumContext::Prepare(
|
||||
const HydrostaticEquilibriumStateView &state,
|
||||
const HydrostaticEquilibriumDependencies &dependencies
|
||||
) {
|
||||
validate_state(m_f, state);
|
||||
|
||||
if (m_isPrepared) {
|
||||
validate_dependency_transition(
|
||||
m_dependencies.discretization, dependencies.discretization,
|
||||
"HydrostaticEquilibriumContext received an older "
|
||||
"discretization revision for the same identity."
|
||||
);
|
||||
|
||||
validate_dependency_transition(
|
||||
m_dependencies.enthalpy, dependencies.enthalpy,
|
||||
"HydrostaticEquilibriumContext received an older "
|
||||
"enthalpy revision for the same identity."
|
||||
);
|
||||
|
||||
validate_dependency_transition(
|
||||
m_dependencies.gravityPotential, dependencies.gravityPotential,
|
||||
"HydrostaticEquilibriumContext received an older "
|
||||
"gravity-potential revision for the same identity."
|
||||
);
|
||||
|
||||
validate_dependency_transition(
|
||||
m_dependencies.displacement, dependencies.displacement,
|
||||
"HydrostaticEquilibriumContext received an older "
|
||||
"displacement revision for the same identity."
|
||||
);
|
||||
|
||||
validate_dependency_transition(
|
||||
m_dependencies.rotation, dependencies.rotation,
|
||||
"HydrostaticEquilibriumContext received an older "
|
||||
"rotation revision for the same identity."
|
||||
);
|
||||
|
||||
validate_dependency_transition(
|
||||
m_dependencies.bernoulliConstant,
|
||||
dependencies.bernoulliConstant,
|
||||
"HydrostaticEquilibriumContext received an older "
|
||||
"Bernoulli-constant revision for the same identity."
|
||||
);
|
||||
}
|
||||
|
||||
const bool staticChanged =
|
||||
!m_isPrepared ||
|
||||
dependencies.discretization != m_dependencies.discretization;
|
||||
|
||||
const bool enthalpyChanged =
|
||||
!m_isPrepared || dependencies.enthalpy != m_dependencies.enthalpy;
|
||||
|
||||
const bool gravityPotentialChanged =
|
||||
!m_isPrepared ||
|
||||
dependencies.gravityPotential != m_dependencies.gravityPotential;
|
||||
|
||||
const bool displacementChanged =
|
||||
!m_isPrepared ||
|
||||
dependencies.displacement != m_dependencies.displacement;
|
||||
|
||||
const bool rotationChanged =
|
||||
!m_isPrepared || dependencies.rotation != m_dependencies.rotation;
|
||||
|
||||
const bool bernoulliConstantChanged =
|
||||
!m_isPrepared ||
|
||||
dependencies.bernoulliConstant != m_dependencies.bernoulliConstant;
|
||||
|
||||
const bool geometryPreparationRequired =
|
||||
staticChanged || displacementChanged;
|
||||
|
||||
const bool rotationPreparationRequired =
|
||||
geometryPreparationRequired || rotationChanged;
|
||||
|
||||
const bool baseStatePreparationRequired =
|
||||
rotationPreparationRequired || enthalpyChanged ||
|
||||
gravityPotentialChanged || bernoulliConstantChanged;
|
||||
|
||||
HydrostaticPreparationReport report;
|
||||
|
||||
report.preparedStaticDependencies = staticChanged;
|
||||
report.preparedGeometryState = geometryPreparationRequired;
|
||||
report.preparedRotationDependencies = rotationPreparationRequired;
|
||||
report.preparedBaseState = baseStatePreparationRequired;
|
||||
|
||||
if (staticChanged || enthalpyChanged) {
|
||||
m_baseEnthalpyTrue = state.enthalpy;
|
||||
report.updatedEnthalpy = true;
|
||||
}
|
||||
|
||||
if (staticChanged || gravityPotentialChanged) {
|
||||
m_baseGravityPotentialTrue = state.gravityPotential;
|
||||
|
||||
report.updatedGravityPotential = true;
|
||||
}
|
||||
|
||||
if (geometryPreparationRequired) {
|
||||
m_displacementTrue = state.displacement;
|
||||
report.updatedDisplacement = true;
|
||||
}
|
||||
|
||||
if (staticChanged || bernoulliConstantChanged) {
|
||||
m_bernoulliConstant = state.bernoulliConstant;
|
||||
report.updatedBernoulliConstant = true;
|
||||
}
|
||||
|
||||
if (report.preparedStaticDependencies) {
|
||||
++m_statistics.staticPreparations;
|
||||
}
|
||||
|
||||
if (report.preparedGeometryState) {
|
||||
++m_statistics.geometryPreparations;
|
||||
}
|
||||
|
||||
if (report.preparedRotationDependencies) {
|
||||
++m_statistics.rotationPreparations;
|
||||
}
|
||||
|
||||
if (report.preparedBaseState) {
|
||||
++m_statistics.baseStatePreparations;
|
||||
}
|
||||
|
||||
m_dependencies = dependencies;
|
||||
m_isPrepared = true;
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
bool HydrostaticEquilibriumContext::IsPrepared() const noexcept {
|
||||
return m_isPrepared;
|
||||
}
|
||||
|
||||
bool HydrostaticEquilibriumContext::MatchesDependencies(
|
||||
const HydrostaticEquilibriumDependencies &dependencies
|
||||
) const noexcept {
|
||||
return m_isPrepared && dependencies == m_dependencies;
|
||||
}
|
||||
|
||||
const HydrostaticEquilibriumDependencies &
|
||||
HydrostaticEquilibriumContext::GetDependencies() const {
|
||||
VerifyPrepared();
|
||||
return m_dependencies;
|
||||
}
|
||||
|
||||
const HydrostaticPreparationStatistics &
|
||||
HydrostaticEquilibriumContext::GetPreparationStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
const mfem::Vector &
|
||||
HydrostaticEquilibriumContext::GetBaseEnthalpyTrue() const {
|
||||
VerifyPrepared();
|
||||
return m_baseEnthalpyTrue;
|
||||
}
|
||||
|
||||
const mfem::Vector &
|
||||
HydrostaticEquilibriumContext::GetBaseGravityPotentialTrue() const {
|
||||
VerifyPrepared();
|
||||
return m_baseGravityPotentialTrue;
|
||||
}
|
||||
|
||||
const mfem::Vector &
|
||||
HydrostaticEquilibriumContext::GetDisplacementTrue() const {
|
||||
VerifyPrepared();
|
||||
return m_displacementTrue;
|
||||
}
|
||||
|
||||
double HydrostaticEquilibriumContext::GetBernoulliConstant() const {
|
||||
VerifyPrepared();
|
||||
return m_bernoulliConstant;
|
||||
}
|
||||
|
||||
void HydrostaticEquilibriumContext::VerifyPrepared() const {
|
||||
MFEM_VERIFY(
|
||||
m_isPrepared, "HydrostaticEquilibriumContext has not been prepared."
|
||||
);
|
||||
}
|
||||
} // namespace mean_field::operators::context::hydrostatic
|
||||
790
libmeanfield/impl/operators/gravity_field.cpp
Normal file
790
libmeanfield/impl/operators/gravity_field.cpp
Normal file
@@ -0,0 +1,790 @@
|
||||
module;
|
||||
#include "profile.h"
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
import :operators.gravity_field;
|
||||
import :solver.fields;
|
||||
import :operators.kernels.gravity_field;
|
||||
|
||||
namespace {
|
||||
using namespace mean_field;
|
||||
int get_state_width(const mfem::Array<int> &state_true_offsets) {
|
||||
MFEM_VERIFY(
|
||||
state_true_offsets.Size() >= 2,
|
||||
"The coupled state requires at least one block."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
state_true_offsets[0] == 0,
|
||||
"The coupled state offsets must begin at zero."
|
||||
);
|
||||
|
||||
for (int i = 0; i < state_true_offsets.Size() - 1; ++i) {
|
||||
MFEM_VERIFY(
|
||||
state_true_offsets[i + 1] >= state_true_offsets[i],
|
||||
"The coupled state offsets must be nondecreasing."
|
||||
);
|
||||
}
|
||||
|
||||
MFEM_VERIFY(
|
||||
state_true_offsets.Last() > 0, "The coupled state cannot be empty."
|
||||
);
|
||||
return state_true_offsets.Last();
|
||||
}
|
||||
|
||||
int get_gravity_residual_height(const fem::FEM &f) {
|
||||
MFEM_VERIFY(
|
||||
f.gravityFluxFes != nullptr,
|
||||
"GravityFieldOperator requires the gravity-gradient finite-element "
|
||||
"space (RT: Raviart-Thomas)."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityPotentialFes != nullptr,
|
||||
"GravityFieldOperator requires the gravity-potential "
|
||||
"finite-element "
|
||||
"space (L2: Lebesgue "
|
||||
"space of square-integrable functions)."
|
||||
);
|
||||
return f.gravityFluxFes->GetTrueVSize() +
|
||||
f.gravityPotentialFes->GetTrueVSize();
|
||||
}
|
||||
|
||||
mfem::Array<int> make_gravity_residual_offsets(const fem::FEM &f) {
|
||||
mfem::Array<int> offsets(operators::gravity_residual_block_count + 1);
|
||||
offsets[0] = 0;
|
||||
offsets[1] = f.gravityFluxFes->GetTrueVSize();
|
||||
offsets[2] = offsets[1] + f.gravityPotentialFes->GetTrueVSize();
|
||||
return offsets;
|
||||
}
|
||||
|
||||
template <int index>
|
||||
int get_state_block_size(
|
||||
const mfem::Array<int> &state_true_offsets,
|
||||
const utils::blocks::value_block<index>
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
index + 1 < state_true_offsets.Size(),
|
||||
"Value block is not present in the state offsets."
|
||||
);
|
||||
return state_true_offsets[index + 1] - state_true_offsets[index];
|
||||
}
|
||||
|
||||
void validate_state_offsets(
|
||||
const fem::FEM &f,
|
||||
const mfem::Array<int> &state_true_offsets
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
f.densityFes != nullptr,
|
||||
"GravityFieldOperator requires the density finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr, "GravityFieldOperator requires the "
|
||||
"displacement finite-element space."
|
||||
);
|
||||
|
||||
using form = utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto density_block = utils::blocks::get_value_block<form>(
|
||||
utils::blocks::density_field.mass_term
|
||||
);
|
||||
constexpr auto displacement_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::displacement_field.geometry_term
|
||||
);
|
||||
constexpr auto gravity_gradient_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto gravity_potential_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
state_true_offsets.Size() == form::value_block_count + 1,
|
||||
"The gravity state offsets do not match gravity_field_form."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
get_state_block_size(state_true_offsets, density_block) ==
|
||||
f.densityFes->GetTrueVSize(),
|
||||
"The density block does not match the density finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
get_state_block_size(state_true_offsets, displacement_block) ==
|
||||
f.displacementFes->GetTrueVSize(),
|
||||
"The displacement block does not match the displacement "
|
||||
"finite-element "
|
||||
"space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
get_state_block_size(state_true_offsets, gravity_gradient_block) ==
|
||||
f.gravityFluxFes->GetTrueVSize(),
|
||||
"The gravity-gradient block does not match the RT finite-element "
|
||||
"space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
get_state_block_size(state_true_offsets, gravity_potential_block) ==
|
||||
f.gravityPotentialFes->GetTrueVSize(),
|
||||
"The gravity-potential block does not match the potential "
|
||||
"finite-element space."
|
||||
);
|
||||
}
|
||||
|
||||
void validate_gravity_context(const fem::FEM &f) {
|
||||
MFEM_VERIFY(
|
||||
f.gravityContext.b_form != nullptr,
|
||||
"GravityFieldOperator requires the divergence operator."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityContext.BT != nullptr,
|
||||
"GravityFieldOperator requires the transpose divergence operator."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.quadratureFactory != nullptr,
|
||||
"GravityFieldOperator requires the quadrature-rule factory."
|
||||
);
|
||||
}
|
||||
|
||||
template <int index>
|
||||
mfem::Vector make_read_only_value_view(
|
||||
const mfem::Vector &vector,
|
||||
const mfem::Array<int> &offsets,
|
||||
const utils::blocks::value_block<index>
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
index + 1 < offsets.Size(),
|
||||
"Value block is not present in the supplied offset array."
|
||||
);
|
||||
|
||||
const int begin = offsets[index];
|
||||
const int size = offsets[index + 1] - begin;
|
||||
|
||||
MFEM_VERIFY(
|
||||
vector.Size() == offsets.Last(),
|
||||
"Vector size does not match the value-block offsets."
|
||||
);
|
||||
return mfem::Vector(
|
||||
const_cast<mfem::real_t *>(vector.GetData()) + begin, size
|
||||
);
|
||||
}
|
||||
|
||||
template <int index>
|
||||
mfem::Vector make_read_only_residual_view(
|
||||
const mfem::Vector &vector,
|
||||
const mfem::Array<int> &offsets,
|
||||
const utils::blocks::residual_block<index> block
|
||||
) {
|
||||
const int block_id = block;
|
||||
const int begin = offsets[block_id];
|
||||
const int size = offsets[block_id + 1] - begin;
|
||||
|
||||
MFEM_VERIFY(
|
||||
vector.Size() == offsets.Last(),
|
||||
"The vector does not match the residual-block layout."
|
||||
);
|
||||
|
||||
mfem::Vector view;
|
||||
view.MakeRef(const_cast<mfem::Vector &>(vector), begin, size);
|
||||
return view;
|
||||
}
|
||||
|
||||
template <int index>
|
||||
mfem::Vector make_residual_view(
|
||||
mfem::Vector &vector,
|
||||
const mfem::Array<int> &offsets,
|
||||
const utils::blocks::residual_block<index>
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
index + 1 < offsets.Size(),
|
||||
"Residual block is not present in the supplied offset array."
|
||||
);
|
||||
|
||||
const int begin = offsets[index];
|
||||
const int size = offsets[index + 1] - begin;
|
||||
|
||||
MFEM_VERIFY(
|
||||
vector.Size() == offsets.Last(),
|
||||
"Vector size does not match the residual-block offsets."
|
||||
);
|
||||
return mfem::Vector(vector.GetData() + begin, size);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators {
|
||||
GravityFieldOperator::GravityFieldOperator(
|
||||
fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper,
|
||||
context::gravity_field::GravityFieldLinearizationContext
|
||||
&linearization_context,
|
||||
const mfem::Array<int> &state_true_offsets,
|
||||
GravityFieldJacobianOperator &jacobian
|
||||
)
|
||||
: Operator(
|
||||
get_gravity_residual_height(f),
|
||||
get_state_width(state_true_offsets)
|
||||
),
|
||||
m_fem(f),
|
||||
m_domain_mapper(domain_mapper),
|
||||
m_linearization_context(linearization_context),
|
||||
m_state_true_offsets(state_true_offsets),
|
||||
m_residual_true_offsets(make_gravity_residual_offsets(f)),
|
||||
m_jacobian(jacobian) {
|
||||
MFEM_VERIFY(f.mesh != nullptr, "GravityFieldOperator requires a mesh.");
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr, "GravityFieldOperator requires the "
|
||||
"displacement finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.smesh.exterior_coordinate != nullptr,
|
||||
"GravityFieldOperator requires the STROID exterior coordinate."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.smesh.exterior_coordinate->space != nullptr,
|
||||
"GravityFieldOperator requires the exterior-coordinate "
|
||||
"finite-element "
|
||||
"space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.smesh.exterior_coordinate->values != nullptr,
|
||||
"GravityFieldOperator requires the exterior-coordinate values."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
domain_mapper.GetDimension() == f.mesh->Dimension(),
|
||||
"GravityFieldOperator received a domain mapper with the wrong "
|
||||
"dimension."
|
||||
);
|
||||
|
||||
validate_state_offsets(f, m_state_true_offsets);
|
||||
validate_gravity_context(f);
|
||||
|
||||
bool has_vacuum_domain = false;
|
||||
|
||||
for (int i = 0; i < f.mesh->attributes.Size(); ++i) {
|
||||
if (f.mesh->attributes[i] ==
|
||||
domain_mapper.GetVacuumElementAttribute()) {
|
||||
has_vacuum_domain = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MFEM_VERIFY(
|
||||
has_vacuum_domain,
|
||||
"GravityFieldOperator requires a compactified vacuum domain."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_residual_true_offsets.Last() == Height(),
|
||||
"The gravity residual offsets do not match the operator height."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_state_true_offsets.Last() == Width(),
|
||||
"The coupled state offsets do not match the operator width."
|
||||
);
|
||||
}
|
||||
|
||||
context::gravity_field::GravityFieldPreparationReport
|
||||
GravityFieldOperator::Prepare(
|
||||
const mfem::Vector &state,
|
||||
const context::gravity_field::GravityFieldRevisions &revisions
|
||||
) {
|
||||
using form = utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto density_block = utils::blocks::get_value_block<form>(
|
||||
utils::blocks::density_field.mass_term
|
||||
);
|
||||
constexpr auto displacement_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::displacement_field.geometry_term
|
||||
);
|
||||
constexpr auto gravity_gradient_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto gravity_potential_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
state.Size() == Width(), "GravityFieldOperator received a "
|
||||
"preparation state with the wrong size."
|
||||
);
|
||||
|
||||
const mfem::Vector density = make_read_only_value_view(
|
||||
state, m_state_true_offsets, density_block
|
||||
);
|
||||
const mfem::Vector displacement = make_read_only_value_view(
|
||||
state, m_state_true_offsets, displacement_block
|
||||
);
|
||||
const mfem::Vector gravity_gradient = make_read_only_value_view(
|
||||
state, m_state_true_offsets, gravity_gradient_block
|
||||
);
|
||||
const mfem::Vector gravity_potential = make_read_only_value_view(
|
||||
state, m_state_true_offsets, gravity_potential_block
|
||||
);
|
||||
|
||||
return m_linearization_context.Prepare(
|
||||
{.density = density,
|
||||
.displacement = displacement,
|
||||
.gravity_gradient = gravity_gradient,
|
||||
.gravity_potential = gravity_potential},
|
||||
revisions
|
||||
);
|
||||
}
|
||||
|
||||
const mfem::Array<int> &
|
||||
GravityFieldOperator::GetStateTrueOffsets() const noexcept {
|
||||
return m_state_true_offsets;
|
||||
}
|
||||
|
||||
const mfem::Array<int> &
|
||||
GravityFieldOperator::GetResidualTrueOffsets() const noexcept {
|
||||
return m_residual_true_offsets;
|
||||
}
|
||||
|
||||
void GravityFieldOperator::ApplyGravityUnknowns(
|
||||
const mfem::Vector &gravity_gradient,
|
||||
const mfem::Vector &gravity_potential,
|
||||
const context::gravity_field::GravityFieldGeometryContext
|
||||
&geometry_context,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
using form = utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto gravity_gradient_residual_block =
|
||||
utils::blocks::get_residual_block<form>(
|
||||
utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto gravity_poisson_residual_block =
|
||||
utils::blocks::get_residual_block<form>(
|
||||
utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
geometry_context.IsPrepared(),
|
||||
"GravityFieldOperator received an unprepared geometry context."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
gravity_gradient.Size() == m_fem.gravityFluxFes->GetTrueVSize(),
|
||||
"GravityFieldOperator received a gravity-gradient vector with the "
|
||||
"wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
gravity_potential.Size() ==
|
||||
m_fem.gravityPotentialFes->GetTrueVSize(),
|
||||
"GravityFieldOperator received a gravity-potential vector with the "
|
||||
"wrong size."
|
||||
);
|
||||
|
||||
action.SetSize(Height());
|
||||
action = 0.0;
|
||||
|
||||
mfem::Vector gravity_gradient_action = make_residual_view(
|
||||
action, m_residual_true_offsets, gravity_gradient_residual_block
|
||||
);
|
||||
mfem::Vector gravity_poisson_action = make_residual_view(
|
||||
action, m_residual_true_offsets, gravity_poisson_residual_block
|
||||
);
|
||||
mfem::Vector transpose_divergence_action(
|
||||
gravity_gradient_action.Size()
|
||||
);
|
||||
|
||||
geometry_context.GetMassOperator().Mult(
|
||||
gravity_gradient, gravity_gradient_action
|
||||
);
|
||||
m_fem.gravityContext.BT->Mult(
|
||||
gravity_potential, transpose_divergence_action
|
||||
);
|
||||
gravity_gradient_action += transpose_divergence_action;
|
||||
m_fem.gravityContext.b_form->Mult(
|
||||
gravity_gradient, gravity_poisson_action
|
||||
);
|
||||
}
|
||||
|
||||
void GravityFieldOperator::ApplyDensitySource(
|
||||
const mfem::Vector &density,
|
||||
const context::gravity_field::GravityFieldGeometryContext
|
||||
&geometry_context,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
using form = utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto gravity_poisson_residual_block =
|
||||
utils::blocks::get_residual_block<form>(
|
||||
utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
geometry_context.IsPrepared(),
|
||||
"GravityFieldOperator received an unprepared geometry context."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
density.Size() == m_fem.densityFes->GetTrueVSize(),
|
||||
"GravityFieldOperator received a density vector with the wrong "
|
||||
"size."
|
||||
);
|
||||
|
||||
action.SetSize(Height());
|
||||
action = 0.0;
|
||||
|
||||
mfem::Vector gravity_poisson_action = make_residual_view(
|
||||
action, m_residual_true_offsets, gravity_poisson_residual_block
|
||||
);
|
||||
geometry_context.GetSourceOperator().Mult(
|
||||
density, gravity_poisson_action
|
||||
);
|
||||
}
|
||||
|
||||
void GravityFieldOperator::Mult(
|
||||
const mfem::Vector &state,
|
||||
mfem::Vector &residual
|
||||
) const {
|
||||
MEAN_FIELD_PROFILE_SCOPE("GravityFieldOperator::Mult");
|
||||
|
||||
using form = utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto density_block = utils::blocks::get_value_block<form>(
|
||||
utils::blocks::density_field.mass_term
|
||||
);
|
||||
constexpr auto gravity_gradient_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto gravity_potential_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
state.Size() == Width(),
|
||||
"GravityFieldOperator received a state with the wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_linearization_context.IsPrepared(),
|
||||
"GravityFieldOperator must be prepared before Mult is called."
|
||||
);
|
||||
|
||||
const mfem::Vector density = make_read_only_value_view(
|
||||
state, m_state_true_offsets, density_block
|
||||
);
|
||||
const mfem::Vector gravity_gradient = make_read_only_value_view(
|
||||
state, m_state_true_offsets, gravity_gradient_block
|
||||
);
|
||||
const mfem::Vector gravity_potential = make_read_only_value_view(
|
||||
state, m_state_true_offsets, gravity_potential_block
|
||||
);
|
||||
const context::gravity_field::GravityFieldGeometryContext
|
||||
&geometry_context = m_linearization_context.GetGeometryContext();
|
||||
|
||||
mfem::Vector source;
|
||||
|
||||
ApplyGravityUnknowns(
|
||||
gravity_gradient, gravity_potential, geometry_context, residual
|
||||
);
|
||||
ApplyDensitySource(density, geometry_context, source);
|
||||
|
||||
residual -= source;
|
||||
}
|
||||
|
||||
context::gravity_field::GravityFieldLinearizationContext &
|
||||
GravityFieldOperator::GetLinearizationContext() noexcept {
|
||||
return m_linearization_context;
|
||||
}
|
||||
|
||||
const context::gravity_field::GravityFieldLinearizationContext &
|
||||
GravityFieldOperator::GetLinearizationContext() const noexcept {
|
||||
return m_linearization_context;
|
||||
}
|
||||
|
||||
mfem::Operator &
|
||||
GravityFieldOperator::GetGradient(const mfem::Vector &state) const {
|
||||
MFEM_VERIFY(
|
||||
state.Size() == Width(), "GravityFieldOperator received a "
|
||||
"linearization state with the wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_linearization_context.IsPrepared(),
|
||||
"GravityFieldOperator must be prepared before GetGradient is "
|
||||
"called."
|
||||
);
|
||||
|
||||
return m_jacobian;
|
||||
}
|
||||
ReducedGravityFieldOperator::ReducedGravityFieldOperator(
|
||||
GravityFieldOperator &gravity_field_operator,
|
||||
context::gravity_field::GravityFieldGeometryContext
|
||||
&gravity_field_geometry_context,
|
||||
const mfem::Vector &displacement
|
||||
)
|
||||
: Operator(
|
||||
gravity_field_operator.Height(),
|
||||
gravity_field_operator.Height()
|
||||
),
|
||||
m_gravity_field_operator(gravity_field_operator),
|
||||
m_gravity_true_offsets(
|
||||
gravity_field_operator.GetResidualTrueOffsets()
|
||||
),
|
||||
m_gravity_field_geometry_context(gravity_field_geometry_context) {
|
||||
using form = utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto gravity_gradient_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto gravity_potential_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
constexpr auto gravity_gradient_residual_block =
|
||||
utils::blocks::get_residual_block<form>(
|
||||
utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto gravity_poisson_residual_block =
|
||||
utils::blocks::get_residual_block<form>(
|
||||
utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
|
||||
const mfem::Array<int> &state_offsets =
|
||||
m_gravity_field_operator.GetStateTrueOffsets();
|
||||
|
||||
MFEM_VERIFY(
|
||||
state_offsets.Size() == form::value_block_count + 1,
|
||||
"ReducedGravityFieldOperator received an invalid full-state layout."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_gravity_true_offsets.Size() == form::residual_block_count + 1,
|
||||
"ReducedGravityFieldOperator received an invalid gravity-residual "
|
||||
"layout."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
state_offsets[0] == 0, "The full-state offsets must begin at zero."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_gravity_true_offsets[0] == 0,
|
||||
"The reduced gravity offsets must begin at zero."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
state_offsets.Last() == m_gravity_field_operator.Width(),
|
||||
"The full-state offsets do not match the gravity-field operator "
|
||||
"width."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_gravity_true_offsets.Last() == m_gravity_field_operator.Height(),
|
||||
"The reduced gravity offsets do not match the gravity-field "
|
||||
"operator "
|
||||
"height."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
Width() == Height(), "ReducedGravityFieldOperator must be square."
|
||||
);
|
||||
|
||||
const int full_gradient_size =
|
||||
state_offsets[static_cast<int>(gravity_gradient_block) + 1] -
|
||||
state_offsets[gravity_gradient_block];
|
||||
const int full_potential_size =
|
||||
state_offsets[static_cast<int>(gravity_potential_block) + 1] -
|
||||
state_offsets[gravity_potential_block];
|
||||
const int reduced_gradient_size =
|
||||
m_gravity_true_offsets
|
||||
[static_cast<int>(gravity_gradient_residual_block) + 1] -
|
||||
m_gravity_true_offsets[gravity_gradient_residual_block];
|
||||
const int reduced_potential_size =
|
||||
m_gravity_true_offsets
|
||||
[static_cast<int>(gravity_poisson_residual_block) + 1] -
|
||||
m_gravity_true_offsets[gravity_poisson_residual_block];
|
||||
|
||||
MFEM_VERIFY(
|
||||
full_gradient_size == reduced_gradient_size,
|
||||
"The reduced gravity-gradient block does not match the full-state "
|
||||
"gravity-gradient block."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
full_potential_size == reduced_potential_size,
|
||||
"The reduced gravity-potential block does not match the Poisson "
|
||||
"residual block."
|
||||
);
|
||||
|
||||
SetDisplacement(displacement);
|
||||
}
|
||||
|
||||
void ReducedGravityFieldOperator::SetDisplacement(
|
||||
const mfem::Vector &displacement
|
||||
) {
|
||||
ValidateDisplacement(displacement);
|
||||
|
||||
context::gravity_field::DiscretizationRevision discretization_revision;
|
||||
context::gravity_field::DisplacementRevision displacement_revision;
|
||||
|
||||
if (m_gravity_field_geometry_context.IsPrepared()) {
|
||||
discretization_revision =
|
||||
m_gravity_field_geometry_context.GetDiscretizationRevision();
|
||||
displacement_revision =
|
||||
m_gravity_field_geometry_context.GetDisplacementRevision();
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacement_revision.value <
|
||||
std::numeric_limits<std::uint64_t>::max(),
|
||||
"The reduced gravity displacement revision has overflowed."
|
||||
);
|
||||
++displacement_revision.value;
|
||||
}
|
||||
|
||||
m_gravity_field_geometry_context.Prepare(
|
||||
displacement, discretization_revision, displacement_revision
|
||||
);
|
||||
}
|
||||
|
||||
const mfem::Vector &ReducedGravityFieldOperator::GetDisplacement() const {
|
||||
return m_gravity_field_geometry_context.GetDisplacement();
|
||||
}
|
||||
|
||||
void ReducedGravityFieldOperator::BuildRightHandSide(
|
||||
const mfem::Vector &density,
|
||||
mfem::Vector &right_hand_side
|
||||
) const {
|
||||
ValidateDensity(density);
|
||||
|
||||
m_gravity_field_operator.ApplyDensitySource(
|
||||
density, m_gravity_field_geometry_context, right_hand_side
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
right_hand_side.Size() == Height(),
|
||||
"ReducedGravityFieldOperator produced a right-hand side with the "
|
||||
"wrong "
|
||||
"size."
|
||||
);
|
||||
}
|
||||
|
||||
void ReducedGravityFieldOperator::Mult(
|
||||
const mfem::Vector &gravity_state,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
MEAN_FIELD_PROFILE_SCOPE("ReducedGravityFieldOperator::Mult");
|
||||
|
||||
using form = utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto gravity_gradient_residual_block =
|
||||
utils::blocks::get_residual_block<form>(
|
||||
utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto gravity_poisson_residual_block =
|
||||
utils::blocks::get_residual_block<form>(
|
||||
utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
|
||||
ValidateGravityState(gravity_state);
|
||||
|
||||
const mfem::Vector gravity_gradient_true = make_read_only_residual_view(
|
||||
gravity_state, m_gravity_true_offsets,
|
||||
gravity_gradient_residual_block
|
||||
);
|
||||
const mfem::Vector gravity_potential_true =
|
||||
make_read_only_residual_view(
|
||||
gravity_state, m_gravity_true_offsets,
|
||||
gravity_poisson_residual_block
|
||||
);
|
||||
|
||||
m_gravity_field_operator.ApplyGravityUnknowns(
|
||||
gravity_gradient_true, gravity_potential_true,
|
||||
m_gravity_field_geometry_context, action
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
action.Size() == Height(), "ReducedGravityFieldOperator produced "
|
||||
"an action with the wrong size."
|
||||
);
|
||||
}
|
||||
|
||||
GravityFieldOperator &
|
||||
ReducedGravityFieldOperator::GetGravityFieldOperator() noexcept {
|
||||
return m_gravity_field_operator;
|
||||
}
|
||||
|
||||
const GravityFieldOperator &
|
||||
ReducedGravityFieldOperator::GetGravityFieldOperator() const noexcept {
|
||||
return m_gravity_field_operator;
|
||||
}
|
||||
|
||||
context::gravity_field::GravityFieldGeometryContext &
|
||||
ReducedGravityFieldOperator::GetGeometryContext() noexcept {
|
||||
return m_gravity_field_geometry_context;
|
||||
}
|
||||
|
||||
const context::gravity_field::GravityFieldGeometryContext &
|
||||
ReducedGravityFieldOperator::GetGeometryContext() const noexcept {
|
||||
return m_gravity_field_geometry_context;
|
||||
}
|
||||
|
||||
const mfem::Array<int> &
|
||||
ReducedGravityFieldOperator::GetGravityTrueOffsets() const noexcept {
|
||||
return m_gravity_true_offsets;
|
||||
}
|
||||
|
||||
void ReducedGravityFieldOperator::ValidateDisplacement(
|
||||
const mfem::Vector &displacement
|
||||
) const {
|
||||
using form = utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto displacement_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::displacement_field.geometry_term
|
||||
);
|
||||
|
||||
const mfem::Array<int> &state_offsets =
|
||||
m_gravity_field_operator.GetStateTrueOffsets();
|
||||
const int expected_size =
|
||||
state_offsets[static_cast<int>(displacement_block) + 1] -
|
||||
state_offsets[displacement_block];
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacement.Size() == expected_size,
|
||||
"ReducedGravityFieldOperator received a displacement with the "
|
||||
"wrong "
|
||||
"size."
|
||||
);
|
||||
|
||||
for (int i = 0; i < displacement.Size(); ++i) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(displacement(i)),
|
||||
"ReducedGravityFieldOperator received a non-finite "
|
||||
"displacement "
|
||||
"value."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ReducedGravityFieldOperator::ValidateDensity(
|
||||
const mfem::Vector &density
|
||||
) const {
|
||||
using form = utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto density_block = utils::blocks::get_value_block<form>(
|
||||
utils::blocks::density_field.mass_term
|
||||
);
|
||||
|
||||
const mfem::Array<int> &state_offsets =
|
||||
m_gravity_field_operator.GetStateTrueOffsets();
|
||||
const int expected_size =
|
||||
state_offsets[static_cast<int>(density_block) + 1] -
|
||||
state_offsets[density_block];
|
||||
|
||||
MFEM_VERIFY(
|
||||
density.Size() == expected_size,
|
||||
"ReducedGravityFieldOperator received a density with the wrong "
|
||||
"size."
|
||||
);
|
||||
}
|
||||
|
||||
void ReducedGravityFieldOperator::ValidateGravityState(
|
||||
const mfem::Vector &gravity_state
|
||||
) const {
|
||||
MFEM_VERIFY(
|
||||
gravity_state.Size() == Width(),
|
||||
"ReducedGravityFieldOperator received "
|
||||
"a gravity state with the wrong size."
|
||||
);
|
||||
}
|
||||
} // namespace mean_field::operators
|
||||
317
libmeanfield/impl/operators/gravity_field_jacobian.cpp
Normal file
317
libmeanfield/impl/operators/gravity_field_jacobian.cpp
Normal file
@@ -0,0 +1,317 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
import :operators.gravity_field_jacobian;
|
||||
import :operators.kernels.gravity_field;
|
||||
import :utils.blocks;
|
||||
|
||||
namespace {
|
||||
template <int index>
|
||||
mfem::Vector make_read_only_value_view(
|
||||
const mfem::Vector &vector,
|
||||
const mfem::Array<int> &offsets,
|
||||
const mean_field::utils::blocks::value_block<index>
|
||||
) {
|
||||
const int offset = offsets[index];
|
||||
const int size = offsets[index + 1] - offset;
|
||||
return mfem::Vector(
|
||||
const_cast<mfem::real_t *>(vector.GetData()) + offset, size
|
||||
);
|
||||
}
|
||||
|
||||
template <int index>
|
||||
mfem::Vector make_residual_view(
|
||||
mfem::Vector &vector,
|
||||
const mfem::Array<int> &offsets,
|
||||
const mean_field::utils::blocks::residual_block<index>
|
||||
) {
|
||||
const int offset = offsets[index];
|
||||
const int size = offsets[index + 1] - offset;
|
||||
return mfem::Vector(vector.GetData() + offset, size);
|
||||
}
|
||||
|
||||
template <int index>
|
||||
int get_block_size(
|
||||
const mfem::Array<int> &offsets,
|
||||
const mean_field::utils::blocks::value_block<index>
|
||||
) {
|
||||
return offsets[index + 1] - offsets[index];
|
||||
}
|
||||
|
||||
template <int index>
|
||||
int get_block_size(
|
||||
const mfem::Array<int> &offsets,
|
||||
const mean_field::utils::blocks::residual_block<index>
|
||||
) {
|
||||
return offsets[index + 1] - offsets[index];
|
||||
}
|
||||
|
||||
void validate_offsets(
|
||||
const mfem::Array<int> &offsets,
|
||||
const int block_count,
|
||||
const char *message
|
||||
) {
|
||||
MFEM_VERIFY(offsets.Size() == block_count + 1, message);
|
||||
MFEM_VERIFY(offsets[0] == 0, "Block offsets must begin at zero.");
|
||||
|
||||
for (int i = 0; i < block_count; ++i)
|
||||
MFEM_VERIFY(
|
||||
offsets[i + 1] >= offsets[i],
|
||||
"Block offsets must be nondecreasing."
|
||||
);
|
||||
}
|
||||
|
||||
void validate_layout(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mfem::Array<int> &state_offsets,
|
||||
const mfem::Array<int> &residual_offsets
|
||||
) {
|
||||
using form = mean_field::utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto density_block =
|
||||
mean_field::utils::blocks::get_value_block<form>(
|
||||
mean_field::utils::blocks::density_field.mass_term
|
||||
);
|
||||
constexpr auto displacement_block =
|
||||
mean_field::utils::blocks::get_value_block<form>(
|
||||
mean_field::utils::blocks::displacement_field.geometry_term
|
||||
);
|
||||
constexpr auto gravity_gradient_block =
|
||||
mean_field::utils::blocks::get_value_block<form>(
|
||||
mean_field::utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto gravity_potential_block =
|
||||
mean_field::utils::blocks::get_value_block<form>(
|
||||
mean_field::utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
constexpr auto gravity_gradient_residual_block =
|
||||
mean_field::utils::blocks::get_residual_block<form>(
|
||||
mean_field::utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto gravity_poisson_residual_block =
|
||||
mean_field::utils::blocks::get_residual_block<form>(
|
||||
mean_field::utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
|
||||
validate_offsets(
|
||||
state_offsets, form::value_block_count,
|
||||
"Gravity Jacobian state offsets do not match the gravity field "
|
||||
"form."
|
||||
);
|
||||
validate_offsets(
|
||||
residual_offsets, form::residual_block_count,
|
||||
"Gravity Jacobian residual offsets do not match the gravity field "
|
||||
"form."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
get_block_size(state_offsets, density_block) ==
|
||||
f.densityFes->GetTrueVSize(),
|
||||
"The Jacobian density block has the wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
get_block_size(state_offsets, displacement_block) ==
|
||||
f.displacementFes->GetTrueVSize(),
|
||||
"The Jacobian displacement block has the wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
get_block_size(state_offsets, gravity_gradient_block) ==
|
||||
f.gravityFluxFes->GetTrueVSize(),
|
||||
"The Jacobian gravity-gradient block has the wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
get_block_size(state_offsets, gravity_potential_block) ==
|
||||
f.gravityPotentialFes->GetTrueVSize(),
|
||||
"The Jacobian gravity-potential block has the wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
get_block_size(residual_offsets, gravity_gradient_residual_block) ==
|
||||
f.gravityFluxFes->GetTrueVSize(),
|
||||
"The Jacobian gradient-residual block has the wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
get_block_size(residual_offsets, gravity_poisson_residual_block) ==
|
||||
f.gravityPotentialFes->GetTrueVSize(),
|
||||
"The Jacobian Poisson-residual block has the wrong size."
|
||||
);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators {
|
||||
GravityFieldJacobianOperator::GravityFieldJacobianOperator(
|
||||
fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper,
|
||||
const context::gravity_field::GravityFieldLinearizationContext
|
||||
&linearization_context,
|
||||
const mfem::Array<int> &state_true_offsets,
|
||||
const mfem::Array<int> &residual_true_offsets
|
||||
)
|
||||
: Operator(
|
||||
residual_true_offsets.Last(),
|
||||
state_true_offsets.Last()
|
||||
),
|
||||
m_fem(f),
|
||||
m_domain_mapper(domain_mapper),
|
||||
m_linearization_context(linearization_context),
|
||||
m_state_true_offsets(state_true_offsets),
|
||||
m_residual_true_offsets(residual_true_offsets) {
|
||||
MFEM_VERIFY(
|
||||
f.densityFes != nullptr,
|
||||
"GravityFieldJacobianOperator requires the density finite-element "
|
||||
"space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityPotentialFes != nullptr,
|
||||
"GravityFieldJacobianOperator requires the gravity-potential "
|
||||
"finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityFluxFes != nullptr,
|
||||
"GravityFieldJacobianOperator requires the "
|
||||
"gravity-gradient finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr,
|
||||
"GravityFieldJacobianOperator requires the "
|
||||
"displacement finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityContext.b_form != nullptr,
|
||||
"GravityFieldJacobianOperator requires the divergence operator."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityContext.BT != nullptr,
|
||||
"GravityFieldJacobianOperator requires the transpose divergence "
|
||||
"operator."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.quadratureFactory != nullptr,
|
||||
"GravityFieldJacobianOperator requires the quadrature-rule factory."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
domain_mapper.GetDimension() == f.mesh->Dimension(),
|
||||
"GravityFieldJacobianOperator received a domain mapper with the "
|
||||
"wrong "
|
||||
"dimension."
|
||||
);
|
||||
|
||||
validate_layout(f, m_state_true_offsets, m_residual_true_offsets);
|
||||
}
|
||||
|
||||
void GravityFieldJacobianOperator::Mult(
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
MFEM_VERIFY(
|
||||
m_linearization_context.IsPrepared(),
|
||||
"GravityFieldJacobianOperator requires a prepared linearization "
|
||||
"context."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
direction.Size() == Width(),
|
||||
"GravityFieldJacobianOperator received a direction with the wrong "
|
||||
"size."
|
||||
);
|
||||
|
||||
using form = utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto density_block = utils::blocks::get_value_block<form>(
|
||||
utils::blocks::density_field.mass_term
|
||||
);
|
||||
constexpr auto displacement_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::displacement_field.geometry_term
|
||||
);
|
||||
constexpr auto gravity_gradient_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto gravity_potential_block =
|
||||
utils::blocks::get_value_block<form>(
|
||||
utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
constexpr auto gravity_gradient_residual_block =
|
||||
utils::blocks::get_residual_block<form>(
|
||||
utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
constexpr auto gravity_poisson_residual_block =
|
||||
utils::blocks::get_residual_block<form>(
|
||||
utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
|
||||
const context::gravity_field::GravityFieldGeometryContext
|
||||
&geometry_context = m_linearization_context.GetGeometryContext();
|
||||
const mfem::Vector &density = m_linearization_context.GetDensity();
|
||||
const mfem::Vector &displacement = geometry_context.GetDisplacement();
|
||||
const mfem::Vector &gravity_gradient =
|
||||
m_linearization_context.GetGravityGradient();
|
||||
|
||||
const mfem::Vector density_direction = make_read_only_value_view(
|
||||
direction, m_state_true_offsets, density_block
|
||||
);
|
||||
const mfem::Vector displacement_direction = make_read_only_value_view(
|
||||
direction, m_state_true_offsets, displacement_block
|
||||
);
|
||||
const mfem::Vector gravity_gradient_direction =
|
||||
make_read_only_value_view(
|
||||
direction, m_state_true_offsets, gravity_gradient_block
|
||||
);
|
||||
const mfem::Vector gravity_potential_direction =
|
||||
make_read_only_value_view(
|
||||
direction, m_state_true_offsets, gravity_potential_block
|
||||
);
|
||||
|
||||
action.SetSize(Height());
|
||||
action = 0.0;
|
||||
|
||||
mfem::Vector gravity_gradient_action = make_residual_view(
|
||||
action, m_residual_true_offsets, gravity_gradient_residual_block
|
||||
);
|
||||
mfem::Vector gravity_poisson_action = make_residual_view(
|
||||
action, m_residual_true_offsets, gravity_poisson_residual_block
|
||||
);
|
||||
|
||||
mfem::Vector transpose_divergence_action;
|
||||
mfem::Vector source_action;
|
||||
mfem::Vector mass_variation_action;
|
||||
mfem::Vector source_variation_action;
|
||||
|
||||
geometry_context.GetMassOperator().Mult(
|
||||
gravity_gradient_direction, gravity_gradient_action
|
||||
);
|
||||
geometry_context.GetSourceOperator().Mult(
|
||||
density_direction, source_action
|
||||
);
|
||||
|
||||
kernels::apply_mapped_hdiv_mass_variation(
|
||||
m_fem, m_domain_mapper, gravity_gradient, displacement,
|
||||
displacement_direction, mass_variation_action
|
||||
);
|
||||
|
||||
kernels::apply_mapped_source_variation(
|
||||
m_fem, m_domain_mapper, density, displacement,
|
||||
displacement_direction, source_variation_action
|
||||
);
|
||||
|
||||
transpose_divergence_action.SetSize(gravity_gradient_action.Size());
|
||||
m_fem.gravityContext.BT->Mult(
|
||||
gravity_potential_direction, transpose_divergence_action
|
||||
);
|
||||
|
||||
gravity_gradient_action += transpose_divergence_action;
|
||||
gravity_gradient_action += mass_variation_action;
|
||||
|
||||
m_fem.gravityContext.b_form->Mult(
|
||||
gravity_gradient_direction, gravity_poisson_action
|
||||
);
|
||||
|
||||
gravity_poisson_action -= source_action;
|
||||
gravity_poisson_action -= source_variation_action;
|
||||
}
|
||||
|
||||
const context::gravity_field::GravityFieldLinearizationContext &
|
||||
GravityFieldJacobianOperator::GetLinearizationContext() const noexcept {
|
||||
return m_linearization_context;
|
||||
}
|
||||
} // namespace mean_field::operators
|
||||
@@ -0,0 +1,811 @@
|
||||
module;
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
|
||||
import :operators.kernels.barotropic_closure;
|
||||
|
||||
namespace {
|
||||
enum class ClosureAction { residual, density, enthalpy };
|
||||
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &trueVector,
|
||||
mfem::Vector &localVector
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
|
||||
"True vector has the wrong size."
|
||||
);
|
||||
|
||||
localVector.SetSize(finiteElementSpace.GetVSize());
|
||||
|
||||
const mfem::Operator *prolongation =
|
||||
finiteElementSpace.GetProlongationMatrix();
|
||||
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->Mult(trueVector, localVector);
|
||||
} else {
|
||||
localVector = trueVector;
|
||||
}
|
||||
}
|
||||
|
||||
void local_to_true(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &localVector,
|
||||
mfem::Vector &trueVector
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
localVector.Size() == finiteElementSpace.GetVSize(),
|
||||
"Local vector has the wrong size."
|
||||
);
|
||||
|
||||
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
|
||||
trueVector = 0.0;
|
||||
|
||||
const mfem::Operator *prolongation =
|
||||
finiteElementSpace.GetProlongationMatrix();
|
||||
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->MultTranspose(localVector, trueVector);
|
||||
} else {
|
||||
trueVector = localVector;
|
||||
}
|
||||
}
|
||||
|
||||
int get_eos_extra_order(
|
||||
const mean_field::physics::PolytropicBarotrope &barotrope
|
||||
) {
|
||||
const double extraOrder =
|
||||
(barotrope.polytropic_index() - 1.0) *
|
||||
static_cast<double>(
|
||||
mean_field::field::Enthalpy::Scalar::familyOrder
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(extraOrder) && extraOrder >= 0.0 &&
|
||||
extraOrder <=
|
||||
static_cast<double>(std::numeric_limits<int>::max()),
|
||||
"The EOS effective polynomial order is invalid."
|
||||
);
|
||||
|
||||
return static_cast<int>(std::ceil(extraOrder));
|
||||
}
|
||||
|
||||
const mfem::IntegrationRule &get_eos_rule(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::FiniteElement &densityElement,
|
||||
const mfem::FiniteElement &enthalpyElement,
|
||||
const mfem::ElementTransformation &transformation
|
||||
) {
|
||||
using EnthalpyField =
|
||||
mean_field::field::Field<mean_field::field::Enthalpy>;
|
||||
|
||||
MFEM_VERIFY(
|
||||
densityElement.GetOrder() ==
|
||||
mean_field::field::Density::Scalar::familyOrder,
|
||||
"The EOS test element does not match the "
|
||||
"registered density field."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
enthalpyElement.GetOrder() ==
|
||||
mean_field::field::Enthalpy::Scalar::familyOrder,
|
||||
"The EOS trial element does not match the "
|
||||
"registered enthalpy field."
|
||||
);
|
||||
|
||||
const mean_field::quadrature::Query query = EnthalpyField::make_query<
|
||||
mean_field::field::Enthalpy::Form::EosClosureSource>(
|
||||
mean_field::quadrature::QuadratureRole::discretization,
|
||||
transformation.OrderW(),
|
||||
std::array<int, 1>{get_eos_extra_order(barotrope)},
|
||||
mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
const auto resolution =
|
||||
f.quadratureFactory->get(query, transformation.GetGeometryType());
|
||||
|
||||
MFEM_VERIFY(
|
||||
resolution.integration_rule != nullptr,
|
||||
"The quadrature policy did not return an "
|
||||
"EOS-closure integration rule."
|
||||
);
|
||||
|
||||
return *resolution.integration_rule;
|
||||
}
|
||||
|
||||
void validate_common_inputs(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::mapping::DomainMapperStateless &domainMapper,
|
||||
const mfem::Vector &displacementTrue
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
f.mesh != nullptr, "The EOS closure kernel requires a mesh."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.densityFes != nullptr,
|
||||
"The EOS closure kernel requires the density "
|
||||
"finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.enthalpyFes != nullptr,
|
||||
"The EOS closure kernel requires the enthalpy "
|
||||
"finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr,
|
||||
"The EOS closure kernel requires the displacement "
|
||||
"finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.compactificationFes != nullptr,
|
||||
"The EOS closure kernel requires the "
|
||||
"compactification finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.compactificationCoordinate != nullptr,
|
||||
"The EOS closure kernel requires the "
|
||||
"compactification coordinate."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.quadratureFactory != nullptr,
|
||||
"The EOS closure kernel requires the quadrature "
|
||||
"rule factory."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
|
||||
"The displacement vector has the wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
domainMapper.GetDimension() == f.mesh->Dimension(),
|
||||
"The domain-mapper dimension does not match "
|
||||
"the mesh dimension."
|
||||
);
|
||||
}
|
||||
|
||||
void apply_closure_action(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::mapping::DomainMapperStateless &domainMapper,
|
||||
const mean_field::physics::PolytropicBarotrope &barotrope,
|
||||
const ClosureAction closureAction,
|
||||
const mfem::Vector *densityInputTrue,
|
||||
const mfem::Vector *baseEnthalpyTrue,
|
||||
const mfem::Vector *enthalpyVariationTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &action
|
||||
) {
|
||||
validate_common_inputs(f, domainMapper, displacementTrue);
|
||||
|
||||
if (closureAction == ClosureAction::residual ||
|
||||
closureAction == ClosureAction::density) {
|
||||
MFEM_VERIFY(
|
||||
densityInputTrue != nullptr &&
|
||||
densityInputTrue->Size() == f.densityFes->GetTrueVSize(),
|
||||
"The density input has the wrong size."
|
||||
);
|
||||
}
|
||||
|
||||
if (closureAction == ClosureAction::residual ||
|
||||
closureAction == ClosureAction::enthalpy) {
|
||||
MFEM_VERIFY(
|
||||
baseEnthalpyTrue != nullptr &&
|
||||
baseEnthalpyTrue->Size() == f.enthalpyFes->GetTrueVSize(),
|
||||
"The base enthalpy has the wrong size."
|
||||
);
|
||||
}
|
||||
|
||||
if (closureAction == ClosureAction::enthalpy) {
|
||||
MFEM_VERIFY(
|
||||
enthalpyVariationTrue != nullptr &&
|
||||
enthalpyVariationTrue->Size() ==
|
||||
f.enthalpyFes->GetTrueVSize(),
|
||||
"The enthalpy variation has the wrong size."
|
||||
);
|
||||
}
|
||||
|
||||
mfem::Vector densityInputLocal;
|
||||
mfem::Vector baseEnthalpyLocal;
|
||||
mfem::Vector enthalpyVariationLocal;
|
||||
mfem::Vector displacementLocal;
|
||||
|
||||
if (densityInputTrue != nullptr) {
|
||||
true_to_local(*f.densityFes, *densityInputTrue, densityInputLocal);
|
||||
}
|
||||
|
||||
if (baseEnthalpyTrue != nullptr) {
|
||||
true_to_local(*f.enthalpyFes, *baseEnthalpyTrue, baseEnthalpyLocal);
|
||||
}
|
||||
|
||||
if (enthalpyVariationTrue != nullptr) {
|
||||
true_to_local(
|
||||
*f.enthalpyFes, *enthalpyVariationTrue, enthalpyVariationLocal
|
||||
);
|
||||
}
|
||||
|
||||
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
|
||||
|
||||
mfem::Vector localAction(f.densityFes->GetVSize());
|
||||
localAction = 0.0;
|
||||
|
||||
mean_field::mapping::DomainMapperStateless::Workspace workspace(
|
||||
f.mesh->Dimension()
|
||||
);
|
||||
|
||||
mfem::Array<int> densityDofs;
|
||||
mfem::Array<int> enthalpyDofs;
|
||||
mfem::Array<int> displacementDofs;
|
||||
mfem::Array<int> compactificationDofs;
|
||||
|
||||
mfem::Vector elementDensityInput;
|
||||
mfem::Vector elementBaseEnthalpy;
|
||||
mfem::Vector elementEnthalpyVariation;
|
||||
mfem::Vector elementDisplacement;
|
||||
mfem::Vector elementCompactification;
|
||||
mfem::Vector elementAction;
|
||||
|
||||
mfem::Vector densityShape;
|
||||
mfem::Vector enthalpyShape;
|
||||
|
||||
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
|
||||
|
||||
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
||||
mfem::ElementTransformation *transformation =
|
||||
f.mesh->GetElementTransformation(elementId);
|
||||
|
||||
MFEM_VERIFY(
|
||||
transformation != nullptr,
|
||||
"The EOS closure kernel received a null "
|
||||
"element transformation."
|
||||
);
|
||||
|
||||
if (transformation->Attribute == vacuumAttribute) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement &densityElement =
|
||||
*f.densityFes->GetFE(elementId);
|
||||
const mfem::FiniteElement &enthalpyElement =
|
||||
*f.enthalpyFes->GetFE(elementId);
|
||||
const mfem::FiniteElement &displacementElement =
|
||||
*f.displacementFes->GetFE(elementId);
|
||||
const mfem::FiniteElement &compactificationElement =
|
||||
*f.compactificationFes->GetFE(elementId);
|
||||
|
||||
mfem::DofTransformation *densityDofTransformation =
|
||||
f.densityFes->GetElementDofs(elementId, densityDofs);
|
||||
|
||||
mfem::DofTransformation *enthalpyDofTransformation =
|
||||
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
|
||||
|
||||
mfem::DofTransformation *displacementDofTransformation =
|
||||
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
|
||||
|
||||
mfem::DofTransformation *compactificationDofTransformation =
|
||||
f.compactificationFes->GetElementDofs(
|
||||
elementId, compactificationDofs
|
||||
);
|
||||
|
||||
if (densityInputTrue != nullptr) {
|
||||
densityInputLocal.GetSubVector(
|
||||
densityDofs, elementDensityInput
|
||||
);
|
||||
|
||||
if (densityDofTransformation != nullptr) {
|
||||
densityDofTransformation->InvTransformPrimal(
|
||||
elementDensityInput
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (baseEnthalpyTrue != nullptr) {
|
||||
baseEnthalpyLocal.GetSubVector(
|
||||
enthalpyDofs, elementBaseEnthalpy
|
||||
);
|
||||
|
||||
if (enthalpyDofTransformation != nullptr) {
|
||||
enthalpyDofTransformation->InvTransformPrimal(
|
||||
elementBaseEnthalpy
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (enthalpyVariationTrue != nullptr) {
|
||||
enthalpyVariationLocal.GetSubVector(
|
||||
enthalpyDofs, elementEnthalpyVariation
|
||||
);
|
||||
|
||||
if (enthalpyDofTransformation != nullptr) {
|
||||
enthalpyDofTransformation->InvTransformPrimal(
|
||||
elementEnthalpyVariation
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
displacementLocal.GetSubVector(
|
||||
displacementDofs, elementDisplacement
|
||||
);
|
||||
|
||||
f.compactificationCoordinate->GetSubVector(
|
||||
compactificationDofs, elementCompactification
|
||||
);
|
||||
|
||||
if (displacementDofTransformation != nullptr) {
|
||||
displacementDofTransformation->InvTransformPrimal(
|
||||
elementDisplacement
|
||||
);
|
||||
}
|
||||
|
||||
if (compactificationDofTransformation != nullptr) {
|
||||
compactificationDofTransformation->InvTransformPrimal(
|
||||
elementCompactification
|
||||
);
|
||||
}
|
||||
|
||||
const mean_field::mapping::ElementDisplacementData
|
||||
displacementData = mean_field::mapping::
|
||||
ElementDisplacementDataFromElementVDofs(
|
||||
displacementElement, elementDisplacement
|
||||
);
|
||||
|
||||
const mean_field::mapping::ElementCompactificationData
|
||||
compactificationData(
|
||||
compactificationElement, elementCompactification
|
||||
);
|
||||
|
||||
const mean_field::mapping::ElementMappingData mappingData{
|
||||
.displacement = displacementData,
|
||||
.compactification = compactificationData
|
||||
};
|
||||
|
||||
densityShape.SetSize(densityElement.GetDof());
|
||||
enthalpyShape.SetSize(enthalpyElement.GetDof());
|
||||
elementAction.SetSize(densityElement.GetDof());
|
||||
elementAction = 0.0;
|
||||
|
||||
const mfem::IntegrationRule &integrationRule = get_eos_rule(
|
||||
f, barotrope, densityElement, enthalpyElement, *transformation
|
||||
);
|
||||
|
||||
for (int quadratureIndex = 0;
|
||||
quadratureIndex < integrationRule.GetNPoints();
|
||||
++quadratureIndex) {
|
||||
const mfem::IntegrationPoint &integrationPoint =
|
||||
integrationRule.IntPoint(quadratureIndex);
|
||||
|
||||
transformation->SetIntPoint(&integrationPoint);
|
||||
|
||||
mean_field::mapping::VolumeMappingContext mappingContext;
|
||||
|
||||
const mean_field::mapping::MappingStatus mappingStatus =
|
||||
domainMapper.EvaluateVolume(
|
||||
mappingData, *transformation, integrationPoint,
|
||||
workspace, mappingContext
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
mappingStatus == mean_field::mapping::MappingStatus::valid,
|
||||
"Stateless mapping failed in the EOS "
|
||||
"closure kernel. Element: "
|
||||
<< elementId
|
||||
<< ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadratureIndex
|
||||
<< ", status: " << static_cast<int>(mappingStatus)
|
||||
);
|
||||
|
||||
densityElement.CalcShape(integrationPoint, densityShape);
|
||||
|
||||
double integrand = 0.0;
|
||||
|
||||
if (closureAction == ClosureAction::density) {
|
||||
integrand = elementDensityInput * densityShape;
|
||||
} else {
|
||||
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
|
||||
|
||||
const double baseEnthalpy =
|
||||
elementBaseEnthalpy * enthalpyShape;
|
||||
|
||||
if (closureAction == ClosureAction::residual) {
|
||||
const double density =
|
||||
elementDensityInput * densityShape;
|
||||
|
||||
integrand =
|
||||
density -
|
||||
barotrope.density_from_enthalpy(baseEnthalpy);
|
||||
} else {
|
||||
const double enthalpyVariation =
|
||||
elementEnthalpyVariation * enthalpyShape;
|
||||
|
||||
integrand = -barotrope.density_derivative_from_enthalpy(
|
||||
baseEnthalpy
|
||||
) *
|
||||
enthalpyVariation;
|
||||
}
|
||||
}
|
||||
|
||||
const double weightedIntegrand =
|
||||
mappingContext.quadrature.weight * integrand;
|
||||
|
||||
for (int densityDof = 0; densityDof < densityElement.GetDof();
|
||||
++densityDof) {
|
||||
elementAction(densityDof) +=
|
||||
weightedIntegrand * densityShape(densityDof);
|
||||
}
|
||||
}
|
||||
|
||||
if (densityDofTransformation != nullptr) {
|
||||
densityDofTransformation->TransformDual(elementAction);
|
||||
}
|
||||
|
||||
localAction.AddElementVector(densityDofs, elementAction);
|
||||
}
|
||||
|
||||
local_to_true(*f.densityFes, localAction, action);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators::kernels {
|
||||
void apply_barotropic_closure(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::Vector &densityTrue,
|
||||
const mfem::Vector &enthalpyTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &residual
|
||||
) {
|
||||
apply_closure_action(
|
||||
f, domainMapper, barotrope, ClosureAction::residual, &densityTrue,
|
||||
&enthalpyTrue, nullptr, displacementTrue, residual
|
||||
);
|
||||
}
|
||||
|
||||
void apply_barotropic_closure_density_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::Vector &densityVariationTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &action
|
||||
) {
|
||||
apply_closure_action(
|
||||
f, domainMapper, barotrope, ClosureAction::density,
|
||||
&densityVariationTrue, nullptr, nullptr, displacementTrue, action
|
||||
);
|
||||
}
|
||||
|
||||
void apply_barotropic_closure_enthalpy_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::Vector &baseEnthalpyTrue,
|
||||
const mfem::Vector &enthalpyVariationTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &action
|
||||
) {
|
||||
apply_closure_action(
|
||||
f, domainMapper, barotrope, ClosureAction::enthalpy, nullptr,
|
||||
&baseEnthalpyTrue, &enthalpyVariationTrue, displacementTrue, action
|
||||
);
|
||||
}
|
||||
|
||||
void apply_barotropic_closure_displacement_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::Vector &baseDensityTrue,
|
||||
const mfem::Vector &baseEnthalpyTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
const mfem::Vector &displacementVariationTrue,
|
||||
mfem::Vector &action
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
f.mesh != nullptr, "The barotropic-closure displacement action "
|
||||
"requires a mesh."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.densityFes != nullptr,
|
||||
"The barotropic-closure displacement action "
|
||||
"requires the density finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.enthalpyFes != nullptr,
|
||||
"The barotropic-closure displacement action "
|
||||
"requires the enthalpy finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr,
|
||||
"The barotropic-closure displacement action "
|
||||
"requires the displacement finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.compactificationFes != nullptr,
|
||||
"The barotropic-closure displacement action "
|
||||
"requires the compactification finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.compactificationCoordinate != nullptr,
|
||||
"The barotropic-closure displacement action "
|
||||
"requires the compactification coordinate."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.quadratureFactory != nullptr,
|
||||
"The barotropic-closure displacement action "
|
||||
"requires the quadrature-rule factory."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
baseDensityTrue.Size() == f.densityFes->GetTrueVSize(),
|
||||
"The base-density vector has the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
baseEnthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(),
|
||||
"The base-enthalpy vector has the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
|
||||
"The displacement vector has the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacementVariationTrue.Size() ==
|
||||
f.displacementFes->GetTrueVSize(),
|
||||
"The displacement-variation vector has the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
domainMapper.GetDimension() == f.mesh->Dimension(),
|
||||
"The domain-mapper dimension does not match the "
|
||||
"mesh dimension."
|
||||
);
|
||||
|
||||
mfem::Vector baseDensityLocal;
|
||||
mfem::Vector baseEnthalpyLocal;
|
||||
mfem::Vector displacementLocal;
|
||||
mfem::Vector displacementVariationLocal;
|
||||
|
||||
true_to_local(*f.densityFes, baseDensityTrue, baseDensityLocal);
|
||||
|
||||
true_to_local(*f.enthalpyFes, baseEnthalpyTrue, baseEnthalpyLocal);
|
||||
|
||||
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
|
||||
|
||||
true_to_local(
|
||||
*f.displacementFes, displacementVariationTrue,
|
||||
displacementVariationLocal
|
||||
);
|
||||
|
||||
mfem::Vector localAction(f.densityFes->GetVSize());
|
||||
localAction = 0.0;
|
||||
|
||||
mapping::DomainMapperStateless::Workspace workspace(
|
||||
f.mesh->Dimension()
|
||||
);
|
||||
|
||||
mfem::Array<int> densityDofs;
|
||||
mfem::Array<int> enthalpyDofs;
|
||||
mfem::Array<int> displacementDofs;
|
||||
mfem::Array<int> compactificationDofs;
|
||||
|
||||
mfem::Vector elementBaseDensity;
|
||||
mfem::Vector elementBaseEnthalpy;
|
||||
mfem::Vector elementDisplacement;
|
||||
mfem::Vector elementDisplacementVariation;
|
||||
mfem::Vector elementCompactification;
|
||||
|
||||
mfem::Vector densityShape;
|
||||
mfem::Vector enthalpyShape;
|
||||
mfem::Vector elementAction;
|
||||
|
||||
mapping::VolumeMappingContext mappingContext;
|
||||
mapping::VolumeMappingVariation mappingVariation;
|
||||
|
||||
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
|
||||
|
||||
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
||||
mfem::ElementTransformation *transformation =
|
||||
f.mesh->GetElementTransformation(elementId);
|
||||
|
||||
MFEM_VERIFY(
|
||||
transformation != nullptr,
|
||||
"The barotropic-closure displacement action "
|
||||
"received a null element transformation."
|
||||
);
|
||||
|
||||
if (transformation->Attribute == vacuumAttribute) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement &densityElement =
|
||||
*f.densityFes->GetFE(elementId);
|
||||
|
||||
const mfem::FiniteElement &enthalpyElement =
|
||||
*f.enthalpyFes->GetFE(elementId);
|
||||
|
||||
const mfem::FiniteElement &displacementElement =
|
||||
*f.displacementFes->GetFE(elementId);
|
||||
|
||||
const mfem::FiniteElement &compactificationElement =
|
||||
*f.compactificationFes->GetFE(elementId);
|
||||
|
||||
mfem::DofTransformation *densityDofTransformation =
|
||||
f.densityFes->GetElementDofs(elementId, densityDofs);
|
||||
|
||||
mfem::DofTransformation *enthalpyDofTransformation =
|
||||
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
|
||||
|
||||
mfem::DofTransformation *displacementDofTransformation =
|
||||
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
|
||||
|
||||
mfem::DofTransformation *compactificationDofTransformation =
|
||||
f.compactificationFes->GetElementDofs(
|
||||
elementId, compactificationDofs
|
||||
);
|
||||
|
||||
baseDensityLocal.GetSubVector(densityDofs, elementBaseDensity);
|
||||
|
||||
baseEnthalpyLocal.GetSubVector(enthalpyDofs, elementBaseEnthalpy);
|
||||
|
||||
displacementLocal.GetSubVector(
|
||||
displacementDofs, elementDisplacement
|
||||
);
|
||||
|
||||
displacementVariationLocal.GetSubVector(
|
||||
displacementDofs, elementDisplacementVariation
|
||||
);
|
||||
|
||||
f.compactificationCoordinate->GetSubVector(
|
||||
compactificationDofs, elementCompactification
|
||||
);
|
||||
|
||||
if (densityDofTransformation != nullptr) {
|
||||
densityDofTransformation->InvTransformPrimal(
|
||||
elementBaseDensity
|
||||
);
|
||||
}
|
||||
|
||||
if (enthalpyDofTransformation != nullptr) {
|
||||
enthalpyDofTransformation->InvTransformPrimal(
|
||||
elementBaseEnthalpy
|
||||
);
|
||||
}
|
||||
|
||||
if (displacementDofTransformation != nullptr) {
|
||||
displacementDofTransformation->InvTransformPrimal(
|
||||
elementDisplacement
|
||||
);
|
||||
|
||||
displacementDofTransformation->InvTransformPrimal(
|
||||
elementDisplacementVariation
|
||||
);
|
||||
}
|
||||
|
||||
if (compactificationDofTransformation != nullptr) {
|
||||
compactificationDofTransformation->InvTransformPrimal(
|
||||
elementCompactification
|
||||
);
|
||||
}
|
||||
|
||||
const mapping::ElementDisplacementData displacementData =
|
||||
mapping::ElementDisplacementDataFromElementVDofs(
|
||||
displacementElement, elementDisplacement
|
||||
);
|
||||
|
||||
const mapping::ElementDisplacementData displacementVariationData =
|
||||
mapping::ElementDisplacementDataFromElementVDofs(
|
||||
displacementElement, elementDisplacementVariation
|
||||
);
|
||||
|
||||
const mapping::ElementCompactificationData compactificationData(
|
||||
compactificationElement, elementCompactification
|
||||
);
|
||||
|
||||
const mapping::ElementMappingData mappingData{
|
||||
.displacement = displacementData,
|
||||
.compactification = compactificationData
|
||||
};
|
||||
|
||||
densityShape.SetSize(densityElement.GetDof());
|
||||
|
||||
enthalpyShape.SetSize(enthalpyElement.GetDof());
|
||||
|
||||
elementAction.SetSize(densityElement.GetDof());
|
||||
elementAction = 0.0;
|
||||
|
||||
const mfem::IntegrationRule &integrationRule = get_eos_rule(
|
||||
f, barotrope, densityElement, enthalpyElement, *transformation
|
||||
);
|
||||
|
||||
for (int quadraturePoint = 0;
|
||||
quadraturePoint < integrationRule.GetNPoints();
|
||||
++quadraturePoint) {
|
||||
const mfem::IntegrationPoint &integrationPoint =
|
||||
integrationRule.IntPoint(quadraturePoint);
|
||||
|
||||
transformation->SetIntPoint(&integrationPoint);
|
||||
|
||||
const mapping::MappingStatus mappingStatus =
|
||||
domainMapper.EvaluateVolume(
|
||||
mappingData, *transformation, integrationPoint,
|
||||
workspace, mappingContext
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
mappingStatus == mapping::MappingStatus::valid,
|
||||
"The base mapping is invalid while applying "
|
||||
"the barotropic-closure displacement action. "
|
||||
"Element: "
|
||||
<< elementId
|
||||
<< ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadraturePoint
|
||||
<< ", status: " << static_cast<int>(mappingStatus)
|
||||
);
|
||||
|
||||
const mapping::MappingStatus variationStatus =
|
||||
domainMapper.EvaluateVolumeVariation(
|
||||
mappingData, displacementVariationData, *transformation,
|
||||
integrationPoint, mappingContext, workspace,
|
||||
mappingVariation
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
variationStatus == mapping::MappingStatus::valid,
|
||||
"The mapping variation is invalid while "
|
||||
"applying the barotropic-closure "
|
||||
"displacement action. Element: "
|
||||
<< elementId
|
||||
<< ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadraturePoint
|
||||
<< ", status: " << static_cast<int>(variationStatus)
|
||||
);
|
||||
|
||||
densityElement.CalcShape(integrationPoint, densityShape);
|
||||
|
||||
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
|
||||
|
||||
const double densityValue = elementBaseDensity * densityShape;
|
||||
|
||||
const double enthalpyValue =
|
||||
elementBaseEnthalpy * enthalpyShape;
|
||||
|
||||
const double closureValue =
|
||||
densityValue -
|
||||
barotrope.density_from_enthalpy(enthalpyValue);
|
||||
|
||||
const double geometryActionValue =
|
||||
closureValue * mappingVariation.weight_variation;
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(closureValue) &&
|
||||
std::isfinite(geometryActionValue),
|
||||
"The barotropic-closure displacement action "
|
||||
"encountered a non-finite quadrature value."
|
||||
);
|
||||
|
||||
elementAction.Add(geometryActionValue, densityShape);
|
||||
}
|
||||
|
||||
if (densityDofTransformation != nullptr) {
|
||||
densityDofTransformation->TransformDual(elementAction);
|
||||
}
|
||||
|
||||
localAction.AddElementVector(densityDofs, elementAction);
|
||||
}
|
||||
|
||||
local_to_true(*f.densityFes, localAction, action);
|
||||
}
|
||||
} // namespace mean_field::operators::kernels
|
||||
1030
libmeanfield/impl/operators/kernels/gravity_kernels.cpp
Normal file
1030
libmeanfield/impl/operators/kernels/gravity_kernels.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,775 @@
|
||||
module;
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <mfem.hpp>
|
||||
#include <optional>
|
||||
|
||||
module mean_field;
|
||||
|
||||
import :operators.kernels.hydrostatic_equilibrium;
|
||||
|
||||
namespace {
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &trueVector,
|
||||
mfem::Vector &localVector
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
|
||||
"True vector has the wrong size."
|
||||
);
|
||||
|
||||
localVector.SetSize(finiteElementSpace.GetVSize());
|
||||
|
||||
const mfem::Operator *prolongation =
|
||||
finiteElementSpace.GetProlongationMatrix();
|
||||
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->Mult(trueVector, localVector);
|
||||
} else {
|
||||
localVector = trueVector;
|
||||
}
|
||||
}
|
||||
|
||||
void local_to_true(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &localVector,
|
||||
mfem::Vector &trueVector
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
localVector.Size() == finiteElementSpace.GetVSize(),
|
||||
"Local vector has the wrong size."
|
||||
);
|
||||
|
||||
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
|
||||
|
||||
trueVector = 0.0;
|
||||
|
||||
const mfem::Operator *prolongation =
|
||||
finiteElementSpace.GetProlongationMatrix();
|
||||
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->MultTranspose(localVector, trueVector);
|
||||
} else {
|
||||
trueVector = localVector;
|
||||
}
|
||||
}
|
||||
|
||||
void validate_fem(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::mapping::DomainMapperStateless &domainMapper
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
f.mesh != nullptr, "The hydrostatic kernel requires a mesh."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.enthalpyFes != nullptr, "The hydrostatic kernel requires the "
|
||||
"enthalpy finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.gravityPotentialFes != nullptr,
|
||||
"The hydrostatic kernel requires the "
|
||||
"gravity-potential finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr, "The hydrostatic kernel requires the "
|
||||
"displacement finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.compactificationFes != nullptr,
|
||||
"The hydrostatic kernel requires the "
|
||||
"compactification finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.compactificationCoordinate != nullptr,
|
||||
"The hydrostatic kernel requires the "
|
||||
"compactification coordinate."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.quadratureFactory != nullptr,
|
||||
"The hydrostatic kernel requires the "
|
||||
"quadrature-rule factory."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.mesh->Dimension() == 3,
|
||||
"The rigid-rotation hydrostatic kernel "
|
||||
"currently requires a three-dimensional mesh."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
domainMapper.GetDimension() == f.mesh->Dimension(),
|
||||
"The domain-mapper dimension does not match "
|
||||
"the mesh dimension."
|
||||
);
|
||||
}
|
||||
|
||||
const mfem::IntegrationRule &get_hydrostatic_rule(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mfem::FiniteElement &enthalpyElement,
|
||||
const mfem::FiniteElement &potentialElement,
|
||||
const mfem::ElementTransformation &transformation
|
||||
) {
|
||||
using EnthalpyField =
|
||||
mean_field::field::Field<mean_field::field::Enthalpy>;
|
||||
|
||||
MFEM_VERIFY(
|
||||
enthalpyElement.GetOrder() ==
|
||||
mean_field::field::Enthalpy::Scalar::familyOrder,
|
||||
"The hydrostatic test element does not match "
|
||||
"the registered enthalpy field."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
potentialElement.GetOrder() ==
|
||||
mean_field::field::Gravity::Potential::familyOrder,
|
||||
"The hydrostatic potential element does not "
|
||||
"match the registered gravity-potential field."
|
||||
);
|
||||
|
||||
const auto enthalpyQuery = EnthalpyField::make_query<
|
||||
mean_field::field::Enthalpy::Form::EquilibriumEnthalpy>(
|
||||
mean_field::quadrature::QuadratureRole::discretization,
|
||||
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
const auto gravityQuery = EnthalpyField::make_query<
|
||||
mean_field::field::Enthalpy::Form::EquilibriumGravity>(
|
||||
mean_field::quadrature::QuadratureRole::discretization,
|
||||
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
const auto rotationQuery = EnthalpyField::make_query<
|
||||
mean_field::field::Enthalpy::Form::EquilibriumRotation>(
|
||||
mean_field::quadrature::QuadratureRole::discretization,
|
||||
transformation.OrderW(), std::array<int, 1>{2},
|
||||
mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
const auto constantQuery = EnthalpyField::make_query<
|
||||
mean_field::field::Enthalpy::Form::EquilibriumConstant>(
|
||||
mean_field::quadrature::QuadratureRole::discretization,
|
||||
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
int integrationOrder = 0;
|
||||
|
||||
const auto update_order = [&f, &transformation, &integrationOrder](
|
||||
const mean_field::quadrature::Query &query
|
||||
) {
|
||||
const auto rule = f.quadratureFactory->get(
|
||||
query, transformation.GetGeometryType()
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
rule.integration_rule != nullptr,
|
||||
"The quadrature policy did not return "
|
||||
"a hydrostatic-equilibrium rule."
|
||||
);
|
||||
|
||||
integrationOrder =
|
||||
std::max(integrationOrder, rule.resolution.order);
|
||||
};
|
||||
|
||||
update_order(enthalpyQuery);
|
||||
update_order(gravityQuery);
|
||||
update_order(rotationQuery);
|
||||
update_order(constantQuery);
|
||||
|
||||
return mfem::IntRules.Get(
|
||||
transformation.GetGeometryType(), integrationOrder
|
||||
);
|
||||
}
|
||||
|
||||
struct HydrostaticAssemblyRequest {
|
||||
const mean_field::physics::RigidRotation *rotation{nullptr};
|
||||
|
||||
const mfem::Vector *baseEnthalpyTrue{nullptr};
|
||||
const mfem::Vector *basePotentialTrue{nullptr};
|
||||
|
||||
const mfem::Vector *enthalpyVariationTrue{nullptr};
|
||||
const mfem::Vector *potentialVariationTrue{nullptr};
|
||||
const mfem::Vector *displacementVariationTrue{nullptr};
|
||||
|
||||
double bernoulliConstant{0.0};
|
||||
double constantVariation{0.0};
|
||||
|
||||
bool buildResidual{false};
|
||||
};
|
||||
|
||||
void assemble_hydrostatic_form(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::mapping::DomainMapperStateless &domainMapper,
|
||||
const mfem::Vector &displacementTrue,
|
||||
const HydrostaticAssemblyRequest &request,
|
||||
mfem::Vector &result
|
||||
) {
|
||||
validate_fem(f, domainMapper);
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
|
||||
"The hydrostatic displacement vector has "
|
||||
"the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(request.bernoulliConstant),
|
||||
"The Bernoulli constant is non-finite."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(request.constantVariation),
|
||||
"The Bernoulli-constant variation is non-finite."
|
||||
);
|
||||
|
||||
const bool requiresBaseState =
|
||||
request.buildResidual ||
|
||||
request.displacementVariationTrue != nullptr;
|
||||
|
||||
if (requiresBaseState) {
|
||||
MFEM_VERIFY(
|
||||
request.rotation != nullptr,
|
||||
"The hydrostatic residual or geometry "
|
||||
"action requires the rotation model."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
request.baseEnthalpyTrue != nullptr,
|
||||
"The hydrostatic residual or geometry "
|
||||
"action requires the base enthalpy."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
request.basePotentialTrue != nullptr,
|
||||
"The hydrostatic residual or geometry "
|
||||
"action requires the base potential."
|
||||
);
|
||||
}
|
||||
|
||||
if (request.baseEnthalpyTrue != nullptr) {
|
||||
MFEM_VERIFY(
|
||||
request.baseEnthalpyTrue->Size() ==
|
||||
f.enthalpyFes->GetTrueVSize(),
|
||||
"The base enthalpy vector has the wrong size."
|
||||
);
|
||||
}
|
||||
|
||||
if (request.basePotentialTrue != nullptr) {
|
||||
MFEM_VERIFY(
|
||||
request.basePotentialTrue->Size() ==
|
||||
f.gravityPotentialFes->GetTrueVSize(),
|
||||
"The base potential vector has the wrong size."
|
||||
);
|
||||
}
|
||||
|
||||
if (request.enthalpyVariationTrue != nullptr) {
|
||||
MFEM_VERIFY(
|
||||
request.enthalpyVariationTrue->Size() ==
|
||||
f.enthalpyFes->GetTrueVSize(),
|
||||
"The enthalpy variation has the wrong size."
|
||||
);
|
||||
}
|
||||
|
||||
if (request.potentialVariationTrue != nullptr) {
|
||||
MFEM_VERIFY(
|
||||
request.potentialVariationTrue->Size() ==
|
||||
f.gravityPotentialFes->GetTrueVSize(),
|
||||
"The potential variation has the wrong size."
|
||||
);
|
||||
}
|
||||
|
||||
if (request.displacementVariationTrue != nullptr) {
|
||||
MFEM_VERIFY(
|
||||
request.displacementVariationTrue->Size() ==
|
||||
f.displacementFes->GetTrueVSize(),
|
||||
"The displacement variation has the wrong size."
|
||||
);
|
||||
}
|
||||
|
||||
mfem::Vector displacementLocal;
|
||||
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
|
||||
|
||||
mfem::Vector baseEnthalpyLocal;
|
||||
mfem::Vector basePotentialLocal;
|
||||
mfem::Vector enthalpyVariationLocal;
|
||||
mfem::Vector potentialVariationLocal;
|
||||
mfem::Vector displacementVariationLocal;
|
||||
|
||||
if (request.baseEnthalpyTrue != nullptr) {
|
||||
true_to_local(
|
||||
*f.enthalpyFes, *request.baseEnthalpyTrue, baseEnthalpyLocal
|
||||
);
|
||||
}
|
||||
|
||||
if (request.basePotentialTrue != nullptr) {
|
||||
true_to_local(
|
||||
*f.gravityPotentialFes, *request.basePotentialTrue,
|
||||
basePotentialLocal
|
||||
);
|
||||
}
|
||||
|
||||
if (request.enthalpyVariationTrue != nullptr) {
|
||||
true_to_local(
|
||||
*f.enthalpyFes, *request.enthalpyVariationTrue,
|
||||
enthalpyVariationLocal
|
||||
);
|
||||
}
|
||||
|
||||
if (request.potentialVariationTrue != nullptr) {
|
||||
true_to_local(
|
||||
*f.gravityPotentialFes, *request.potentialVariationTrue,
|
||||
potentialVariationLocal
|
||||
);
|
||||
}
|
||||
|
||||
if (request.displacementVariationTrue != nullptr) {
|
||||
true_to_local(
|
||||
*f.displacementFes, *request.displacementVariationTrue,
|
||||
displacementVariationLocal
|
||||
);
|
||||
}
|
||||
|
||||
mfem::Vector localResult(f.enthalpyFes->GetVSize());
|
||||
|
||||
localResult = 0.0;
|
||||
|
||||
mean_field::mapping::DomainMapperStateless::Workspace workspace(
|
||||
f.mesh->Dimension()
|
||||
);
|
||||
|
||||
mfem::Array<int> enthalpyDofs;
|
||||
mfem::Array<int> potentialDofs;
|
||||
mfem::Array<int> displacementDofs;
|
||||
mfem::Array<int> compactificationDofs;
|
||||
|
||||
mfem::Vector elementBaseEnthalpy;
|
||||
mfem::Vector elementBasePotential;
|
||||
mfem::Vector elementEnthalpyVariation;
|
||||
mfem::Vector elementPotentialVariation;
|
||||
mfem::Vector elementDisplacement;
|
||||
mfem::Vector elementDisplacementVariation;
|
||||
mfem::Vector elementCompactification;
|
||||
mfem::Vector elementResult;
|
||||
|
||||
mfem::Vector enthalpyShape;
|
||||
mfem::Vector potentialShape;
|
||||
|
||||
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
|
||||
|
||||
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
||||
mfem::ElementTransformation *transformation =
|
||||
f.mesh->GetElementTransformation(elementId);
|
||||
|
||||
MFEM_VERIFY(
|
||||
transformation != nullptr,
|
||||
"The hydrostatic kernel received a null "
|
||||
"element transformation."
|
||||
);
|
||||
|
||||
if (transformation->Attribute == vacuumAttribute) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement &enthalpyElement =
|
||||
*f.enthalpyFes->GetFE(elementId);
|
||||
|
||||
const mfem::FiniteElement &potentialElement =
|
||||
*f.gravityPotentialFes->GetFE(elementId);
|
||||
|
||||
const mfem::FiniteElement &displacementElement =
|
||||
*f.displacementFes->GetFE(elementId);
|
||||
|
||||
const mfem::FiniteElement &compactificationElement =
|
||||
*f.compactificationFes->GetFE(elementId);
|
||||
|
||||
mfem::DofTransformation *enthalpyDofTransformation =
|
||||
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
|
||||
|
||||
mfem::DofTransformation *potentialDofTransformation =
|
||||
f.gravityPotentialFes->GetElementDofs(elementId, potentialDofs);
|
||||
|
||||
mfem::DofTransformation *displacementDofTransformation =
|
||||
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
|
||||
|
||||
mfem::DofTransformation *compactificationDofTransformation =
|
||||
f.compactificationFes->GetElementDofs(
|
||||
elementId, compactificationDofs
|
||||
);
|
||||
|
||||
displacementLocal.GetSubVector(
|
||||
displacementDofs, elementDisplacement
|
||||
);
|
||||
|
||||
f.compactificationCoordinate->GetSubVector(
|
||||
compactificationDofs, elementCompactification
|
||||
);
|
||||
|
||||
if (request.baseEnthalpyTrue != nullptr) {
|
||||
baseEnthalpyLocal.GetSubVector(
|
||||
enthalpyDofs, elementBaseEnthalpy
|
||||
);
|
||||
}
|
||||
|
||||
if (request.basePotentialTrue != nullptr) {
|
||||
basePotentialLocal.GetSubVector(
|
||||
potentialDofs, elementBasePotential
|
||||
);
|
||||
}
|
||||
|
||||
if (request.enthalpyVariationTrue != nullptr) {
|
||||
enthalpyVariationLocal.GetSubVector(
|
||||
enthalpyDofs, elementEnthalpyVariation
|
||||
);
|
||||
}
|
||||
|
||||
if (request.potentialVariationTrue != nullptr) {
|
||||
potentialVariationLocal.GetSubVector(
|
||||
potentialDofs, elementPotentialVariation
|
||||
);
|
||||
}
|
||||
|
||||
if (request.displacementVariationTrue != nullptr) {
|
||||
displacementVariationLocal.GetSubVector(
|
||||
displacementDofs, elementDisplacementVariation
|
||||
);
|
||||
}
|
||||
|
||||
if (enthalpyDofTransformation != nullptr) {
|
||||
if (request.baseEnthalpyTrue != nullptr) {
|
||||
enthalpyDofTransformation->InvTransformPrimal(
|
||||
elementBaseEnthalpy
|
||||
);
|
||||
}
|
||||
|
||||
if (request.enthalpyVariationTrue != nullptr) {
|
||||
enthalpyDofTransformation->InvTransformPrimal(
|
||||
elementEnthalpyVariation
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (potentialDofTransformation != nullptr) {
|
||||
if (request.basePotentialTrue != nullptr) {
|
||||
potentialDofTransformation->InvTransformPrimal(
|
||||
elementBasePotential
|
||||
);
|
||||
}
|
||||
|
||||
if (request.potentialVariationTrue != nullptr) {
|
||||
potentialDofTransformation->InvTransformPrimal(
|
||||
elementPotentialVariation
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (displacementDofTransformation != nullptr) {
|
||||
displacementDofTransformation->InvTransformPrimal(
|
||||
elementDisplacement
|
||||
);
|
||||
|
||||
if (request.displacementVariationTrue != nullptr) {
|
||||
displacementDofTransformation->InvTransformPrimal(
|
||||
elementDisplacementVariation
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (compactificationDofTransformation != nullptr) {
|
||||
compactificationDofTransformation->InvTransformPrimal(
|
||||
elementCompactification
|
||||
);
|
||||
}
|
||||
|
||||
const mean_field::mapping::ElementDisplacementData
|
||||
displacementData = mean_field::mapping::
|
||||
ElementDisplacementDataFromElementVDofs(
|
||||
displacementElement, elementDisplacement
|
||||
);
|
||||
|
||||
const mean_field::mapping::ElementCompactificationData
|
||||
compactificationData(
|
||||
compactificationElement, elementCompactification
|
||||
);
|
||||
|
||||
const mean_field::mapping::ElementMappingData mappingData{
|
||||
.displacement = displacementData,
|
||||
.compactification = compactificationData
|
||||
};
|
||||
|
||||
std::optional<mean_field::mapping::ElementDisplacementData>
|
||||
displacementVariationData;
|
||||
|
||||
if (request.displacementVariationTrue != nullptr) {
|
||||
displacementVariationData.emplace(
|
||||
mean_field::mapping::
|
||||
ElementDisplacementDataFromElementVDofs(
|
||||
displacementElement, elementDisplacementVariation
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
elementResult.SetSize(enthalpyElement.GetDof());
|
||||
|
||||
elementResult = 0.0;
|
||||
|
||||
enthalpyShape.SetSize(enthalpyElement.GetDof());
|
||||
|
||||
potentialShape.SetSize(potentialElement.GetDof());
|
||||
|
||||
const mfem::IntegrationRule &integrationRule = get_hydrostatic_rule(
|
||||
f, enthalpyElement, potentialElement, *transformation
|
||||
);
|
||||
|
||||
for (int quadraturePoint = 0;
|
||||
quadraturePoint < integrationRule.GetNPoints();
|
||||
++quadraturePoint) {
|
||||
const mfem::IntegrationPoint &integrationPoint =
|
||||
integrationRule.IntPoint(quadraturePoint);
|
||||
|
||||
transformation->SetIntPoint(&integrationPoint);
|
||||
|
||||
mean_field::mapping::VolumeMappingContext mappingContext;
|
||||
|
||||
const mean_field::mapping::MappingStatus mappingStatus =
|
||||
domainMapper.EvaluateVolume(
|
||||
mappingData, *transformation, integrationPoint,
|
||||
workspace, mappingContext
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
mappingStatus == mean_field::mapping::MappingStatus::valid,
|
||||
"The base mapping is invalid in the "
|
||||
"hydrostatic kernel. Element: "
|
||||
<< elementId
|
||||
<< ", quadrature point: " << quadraturePoint
|
||||
<< ", status: " << static_cast<int>(mappingStatus)
|
||||
);
|
||||
|
||||
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
|
||||
|
||||
potentialElement.CalcShape(integrationPoint, potentialShape);
|
||||
|
||||
double baseIntegrand = 0.0;
|
||||
|
||||
if (requiresBaseState) {
|
||||
const double enthalpyValue =
|
||||
elementBaseEnthalpy * enthalpyShape;
|
||||
|
||||
const double potentialValue =
|
||||
elementBasePotential * potentialShape;
|
||||
|
||||
const double rotationPotential =
|
||||
request.rotation->potential(
|
||||
mappingContext.mapping.physical_position
|
||||
);
|
||||
|
||||
baseIntegrand = enthalpyValue + potentialValue -
|
||||
rotationPotential -
|
||||
request.bernoulliConstant;
|
||||
}
|
||||
|
||||
if (request.buildResidual) {
|
||||
elementResult.Add(
|
||||
mappingContext.quadrature.weight * baseIntegrand,
|
||||
enthalpyShape
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
double materialVariation = -request.constantVariation;
|
||||
|
||||
if (request.enthalpyVariationTrue != nullptr) {
|
||||
materialVariation +=
|
||||
elementEnthalpyVariation * enthalpyShape;
|
||||
}
|
||||
|
||||
if (request.potentialVariationTrue != nullptr) {
|
||||
materialVariation +=
|
||||
elementPotentialVariation * potentialShape;
|
||||
}
|
||||
|
||||
double weightedVariation =
|
||||
mappingContext.quadrature.weight * materialVariation;
|
||||
|
||||
if (request.displacementVariationTrue != nullptr) {
|
||||
mean_field::mapping::VolumeMappingVariation
|
||||
mappingVariation;
|
||||
|
||||
const mean_field::mapping::MappingStatus variationStatus =
|
||||
domainMapper.EvaluateVolumeVariation(
|
||||
mappingData, *displacementVariationData,
|
||||
*transformation, integrationPoint, mappingContext,
|
||||
workspace, mappingVariation
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
variationStatus ==
|
||||
mean_field::mapping::MappingStatus::valid,
|
||||
"The mapping variation is invalid "
|
||||
"in the hydrostatic kernel."
|
||||
);
|
||||
|
||||
const double rotationVariation =
|
||||
request.rotation->potential_directional_derivative(
|
||||
mappingContext.mapping.physical_position,
|
||||
mappingVariation.mapping.physical_position_variation
|
||||
);
|
||||
|
||||
weightedVariation +=
|
||||
baseIntegrand * mappingVariation.weight_variation -
|
||||
rotationVariation * mappingContext.quadrature.weight;
|
||||
}
|
||||
|
||||
elementResult.Add(weightedVariation, enthalpyShape);
|
||||
}
|
||||
|
||||
if (enthalpyDofTransformation != nullptr) {
|
||||
enthalpyDofTransformation->TransformDual(elementResult);
|
||||
}
|
||||
|
||||
localResult.AddElementVector(enthalpyDofs, elementResult);
|
||||
}
|
||||
|
||||
local_to_true(*f.enthalpyFes, localResult, result);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators::kernels {
|
||||
void apply_hydrostatic_equilibrium(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::RigidRotation &rotation,
|
||||
const mfem::Vector &enthalpyTrue,
|
||||
const mfem::Vector &potentialTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
const double bernoulliConstant,
|
||||
mfem::Vector &residual
|
||||
) {
|
||||
HydrostaticAssemblyRequest request;
|
||||
|
||||
request.rotation = &rotation;
|
||||
request.baseEnthalpyTrue = &enthalpyTrue;
|
||||
request.basePotentialTrue = &potentialTrue;
|
||||
request.bernoulliConstant = bernoulliConstant;
|
||||
request.buildResidual = true;
|
||||
|
||||
assemble_hydrostatic_form(
|
||||
f, domainMapper, displacementTrue, request, residual
|
||||
);
|
||||
}
|
||||
|
||||
void apply_hydrostatic_equilibrium_enthalpy_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const mfem::Vector &enthalpyVariationTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &action
|
||||
) {
|
||||
HydrostaticAssemblyRequest request;
|
||||
|
||||
request.enthalpyVariationTrue = &enthalpyVariationTrue;
|
||||
|
||||
assemble_hydrostatic_form(
|
||||
f, domainMapper, displacementTrue, request, action
|
||||
);
|
||||
}
|
||||
|
||||
void apply_hydrostatic_equilibrium_potential_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const mfem::Vector &potentialVariationTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &action
|
||||
) {
|
||||
HydrostaticAssemblyRequest request;
|
||||
|
||||
request.potentialVariationTrue = &potentialVariationTrue;
|
||||
|
||||
assemble_hydrostatic_form(
|
||||
f, domainMapper, displacementTrue, request, action
|
||||
);
|
||||
}
|
||||
|
||||
void apply_hydrostatic_equilibrium_constant_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const double constantVariation,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &action
|
||||
) {
|
||||
HydrostaticAssemblyRequest request;
|
||||
|
||||
request.constantVariation = constantVariation;
|
||||
|
||||
assemble_hydrostatic_form(
|
||||
f, domainMapper, displacementTrue, request, action
|
||||
);
|
||||
}
|
||||
|
||||
void apply_hydrostatic_equilibrium_displacement_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::RigidRotation &rotation,
|
||||
const mfem::Vector &baseEnthalpyTrue,
|
||||
const mfem::Vector &basePotentialTrue,
|
||||
const mfem::Vector &baseDisplacementTrue,
|
||||
const double baseBernoulliConstant,
|
||||
const mfem::Vector &displacementVariationTrue,
|
||||
mfem::Vector &action
|
||||
) {
|
||||
HydrostaticAssemblyRequest request;
|
||||
|
||||
request.rotation = &rotation;
|
||||
request.baseEnthalpyTrue = &baseEnthalpyTrue;
|
||||
request.basePotentialTrue = &basePotentialTrue;
|
||||
request.displacementVariationTrue = &displacementVariationTrue;
|
||||
request.bernoulliConstant = baseBernoulliConstant;
|
||||
|
||||
assemble_hydrostatic_form(
|
||||
f, domainMapper, baseDisplacementTrue, request, action
|
||||
);
|
||||
}
|
||||
|
||||
void apply_hydrostatic_equilibrium_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::RigidRotation &rotation,
|
||||
const mfem::Vector &baseEnthalpyTrue,
|
||||
const mfem::Vector &basePotentialTrue,
|
||||
const mfem::Vector &baseDisplacementTrue,
|
||||
const double baseBernoulliConstant,
|
||||
const mfem::Vector &enthalpyVariationTrue,
|
||||
const mfem::Vector &potentialVariationTrue,
|
||||
const double constantVariation,
|
||||
const mfem::Vector &displacementVariationTrue,
|
||||
mfem::Vector &action
|
||||
) {
|
||||
HydrostaticAssemblyRequest request;
|
||||
|
||||
request.rotation = &rotation;
|
||||
request.baseEnthalpyTrue = &baseEnthalpyTrue;
|
||||
request.basePotentialTrue = &basePotentialTrue;
|
||||
request.enthalpyVariationTrue = &enthalpyVariationTrue;
|
||||
request.potentialVariationTrue = &potentialVariationTrue;
|
||||
request.displacementVariationTrue = &displacementVariationTrue;
|
||||
request.bernoulliConstant = baseBernoulliConstant;
|
||||
request.constantVariation = constantVariation;
|
||||
|
||||
assemble_hydrostatic_form(
|
||||
f, domainMapper, baseDisplacementTrue, request, action
|
||||
);
|
||||
}
|
||||
} // namespace mean_field::operators::kernels
|
||||
464
libmeanfield/impl/operators/kernels/pressure_force_kernels.cpp
Normal file
464
libmeanfield/impl/operators/kernels/pressure_force_kernels.cpp
Normal file
@@ -0,0 +1,464 @@
|
||||
module;
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
|
||||
import :operators.kernels.pressure_force;
|
||||
|
||||
namespace {
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &trueVector,
|
||||
mfem::Vector &localVector
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
|
||||
"The pressure-force true vector has the wrong size."
|
||||
);
|
||||
|
||||
localVector.SetSize(finiteElementSpace.GetVSize());
|
||||
|
||||
const mfem::Operator *prolongation =
|
||||
finiteElementSpace.GetProlongationMatrix();
|
||||
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->Mult(trueVector, localVector);
|
||||
} else {
|
||||
localVector = trueVector;
|
||||
}
|
||||
}
|
||||
|
||||
void local_to_true(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &localVector,
|
||||
mfem::Vector &trueVector
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
localVector.Size() == finiteElementSpace.GetVSize(),
|
||||
"The pressure-force local vector has the wrong size."
|
||||
);
|
||||
|
||||
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
|
||||
trueVector = 0.0;
|
||||
|
||||
const mfem::Operator *prolongation =
|
||||
finiteElementSpace.GetProlongationMatrix();
|
||||
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->MultTranspose(localVector, trueVector);
|
||||
} else {
|
||||
trueVector = localVector;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] int vector_dof_index(
|
||||
const mfem::Ordering::Type ordering,
|
||||
const int scalarDof,
|
||||
const int component,
|
||||
const int scalarDofCount,
|
||||
const int dimension
|
||||
) {
|
||||
if (ordering == mfem::Ordering::byNODES) {
|
||||
return scalarDof + component * scalarDofCount;
|
||||
}
|
||||
|
||||
if (ordering == mfem::Ordering::byVDIM) {
|
||||
return component + scalarDof * dimension;
|
||||
}
|
||||
MFEM_ABORT("The displacement space uses an unsupported ordering.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
[[nodiscard]] int get_pressure_extra_order(
|
||||
const mean_field::physics::PolytropicBarotrope &barotrope
|
||||
) {
|
||||
/*
|
||||
* Pressure has the enthalpy dependence
|
||||
*
|
||||
* P(h) proportional to h^(n + 1).
|
||||
*
|
||||
* The registered enthalpy operand already contributes one factor
|
||||
* of the enthalpy polynomial order. The remaining dynamic
|
||||
* contribution is therefore n times that order.
|
||||
*/
|
||||
const double extraOrder =
|
||||
barotrope.polytropic_index() *
|
||||
static_cast<double>(
|
||||
mean_field::field::Enthalpy::Scalar::familyOrder
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(extraOrder) && extraOrder >= 0.0 &&
|
||||
extraOrder <=
|
||||
static_cast<double>(std::numeric_limits<int>::max()),
|
||||
"The pressure EOS effective polynomial order is invalid."
|
||||
);
|
||||
|
||||
return static_cast<int>(std::ceil(extraOrder));
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::IntegrationRule &get_pressure_force_rule(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::FiniteElement &enthalpyElement,
|
||||
const mfem::FiniteElement &displacementElement,
|
||||
const mfem::ElementTransformation &transformation
|
||||
) {
|
||||
using EnthalpyField =
|
||||
mean_field::field::Field<mean_field::field::Enthalpy>;
|
||||
|
||||
MFEM_VERIFY(
|
||||
enthalpyElement.GetOrder() ==
|
||||
mean_field::field::Enthalpy::Scalar::familyOrder,
|
||||
"The pressure-force enthalpy element does not match the "
|
||||
"registered enthalpy field."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacementElement.GetOrder() ==
|
||||
mean_field::field::Displacement::Vector::familyOrder,
|
||||
"The pressure-force test element does not match the "
|
||||
"registered displacement field."
|
||||
);
|
||||
|
||||
const mean_field::quadrature::Query query = EnthalpyField::make_query<
|
||||
mean_field::field::Enthalpy::Form::PressureForce>(
|
||||
mean_field::quadrature::QuadratureRole::discretization,
|
||||
transformation.OrderW(),
|
||||
std::array<int, 1>{get_pressure_extra_order(barotrope)},
|
||||
mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
const mean_field::quadrature::MfemRule rule =
|
||||
f.quadratureFactory->get(query, transformation.GetGeometryType());
|
||||
|
||||
MFEM_VERIFY(
|
||||
rule.integration_rule != nullptr,
|
||||
"The quadrature policy did not return a pressure-force "
|
||||
"integration rule."
|
||||
);
|
||||
|
||||
return *rule.integration_rule;
|
||||
}
|
||||
|
||||
void validate_inputs(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::mapping::DomainMapperStateless &domainMapper,
|
||||
const mfem::Vector &enthalpyTrue,
|
||||
const mfem::Vector &displacementTrue
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
f.mesh != nullptr, "The pressure-force kernel requires a mesh."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.enthalpyFes != nullptr,
|
||||
"The pressure-force kernel requires the enthalpy "
|
||||
"finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr,
|
||||
"The pressure-force kernel requires the displacement "
|
||||
"finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.compactificationFes != nullptr,
|
||||
"The pressure-force kernel requires the compactification "
|
||||
"finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.compactificationCoordinate != nullptr,
|
||||
"The pressure-force kernel requires the compactification "
|
||||
"coordinate."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.quadratureFactory != nullptr,
|
||||
"The pressure-force kernel requires the quadrature "
|
||||
"rule factory."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
enthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(),
|
||||
"The pressure-force enthalpy vector has the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
|
||||
"The pressure-force displacement vector has the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
domainMapper.GetDimension() == f.mesh->Dimension(),
|
||||
"The pressure-force domain-mapper dimension does not match "
|
||||
"the mesh dimension."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes->GetVDim() == f.mesh->Dimension(),
|
||||
"The displacement vector dimension does not match the "
|
||||
"mesh dimension."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes->GetOrdering() == mfem::Ordering::byNODES,
|
||||
"The pressure-force kernel requires the registered byNODES "
|
||||
"displacement ordering."
|
||||
);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators::kernels {
|
||||
void apply_pressure_force_residual(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::Vector &enthalpyTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &residualTrue
|
||||
) {
|
||||
validate_inputs(f, domainMapper, enthalpyTrue, displacementTrue);
|
||||
|
||||
mfem::Vector enthalpyLocal;
|
||||
mfem::Vector displacementLocal;
|
||||
|
||||
true_to_local(*f.enthalpyFes, enthalpyTrue, enthalpyLocal);
|
||||
|
||||
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
|
||||
|
||||
mfem::Vector localResidual(f.displacementFes->GetVSize());
|
||||
localResidual = 0.0;
|
||||
|
||||
mapping::DomainMapperStateless::Workspace workspace(
|
||||
f.mesh->Dimension()
|
||||
);
|
||||
|
||||
mfem::Array<int> enthalpyDofs;
|
||||
mfem::Array<int> displacementDofs;
|
||||
mfem::Array<int> compactificationDofs;
|
||||
|
||||
mfem::Vector elementEnthalpy;
|
||||
mfem::Vector elementDisplacement;
|
||||
mfem::Vector elementCompactification;
|
||||
mfem::Vector elementResidual;
|
||||
mfem::Vector enthalpyShape;
|
||||
|
||||
mfem::DenseMatrix displacementDShapeReference;
|
||||
mfem::DenseMatrix displacementDShapePhysical;
|
||||
|
||||
mapping::VolumeMappingContext mappingContext;
|
||||
|
||||
const int dimension = f.mesh->Dimension();
|
||||
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
|
||||
|
||||
const mfem::Ordering::Type displacementOrdering =
|
||||
f.displacementFes->GetOrdering();
|
||||
|
||||
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
|
||||
mfem::ElementTransformation *transformation =
|
||||
f.mesh->GetElementTransformation(elementId);
|
||||
|
||||
MFEM_VERIFY(
|
||||
transformation != nullptr,
|
||||
"The pressure-force kernel received a null element "
|
||||
"transformation."
|
||||
);
|
||||
|
||||
/*
|
||||
* Skip vacuum before constructing or evaluating any mapping
|
||||
* data for the element.
|
||||
*/
|
||||
if (transformation->Attribute == vacuumAttribute) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement &enthalpyElement =
|
||||
*f.enthalpyFes->GetFE(elementId);
|
||||
|
||||
const mfem::FiniteElement &displacementElement =
|
||||
*f.displacementFes->GetFE(elementId);
|
||||
|
||||
const mfem::FiniteElement &compactificationElement =
|
||||
*f.compactificationFes->GetFE(elementId);
|
||||
|
||||
mfem::DofTransformation *enthalpyDofTransformation =
|
||||
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
|
||||
|
||||
mfem::DofTransformation *displacementDofTransformation =
|
||||
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
|
||||
|
||||
mfem::DofTransformation *compactificationDofTransformation =
|
||||
f.compactificationFes->GetElementDofs(
|
||||
elementId, compactificationDofs
|
||||
);
|
||||
|
||||
enthalpyLocal.GetSubVector(enthalpyDofs, elementEnthalpy);
|
||||
|
||||
displacementLocal.GetSubVector(
|
||||
displacementDofs, elementDisplacement
|
||||
);
|
||||
|
||||
f.compactificationCoordinate->GetSubVector(
|
||||
compactificationDofs, elementCompactification
|
||||
);
|
||||
|
||||
if (enthalpyDofTransformation != nullptr) {
|
||||
enthalpyDofTransformation->InvTransformPrimal(elementEnthalpy);
|
||||
}
|
||||
|
||||
if (displacementDofTransformation != nullptr) {
|
||||
displacementDofTransformation->InvTransformPrimal(
|
||||
elementDisplacement
|
||||
);
|
||||
}
|
||||
|
||||
if (compactificationDofTransformation != nullptr) {
|
||||
compactificationDofTransformation->InvTransformPrimal(
|
||||
elementCompactification
|
||||
);
|
||||
}
|
||||
|
||||
const mapping::ElementDisplacementData displacementData =
|
||||
mapping::ElementDisplacementDataFromElementVDofs(
|
||||
displacementElement, elementDisplacement
|
||||
);
|
||||
|
||||
const mapping::ElementCompactificationData compactificationData(
|
||||
compactificationElement, elementCompactification
|
||||
);
|
||||
|
||||
const mapping::ElementMappingData mappingData{
|
||||
.displacement = displacementData,
|
||||
.compactification = compactificationData
|
||||
};
|
||||
|
||||
const int scalarDisplacementDofCount = displacementElement.GetDof();
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacementDofs.Size() ==
|
||||
scalarDisplacementDofCount * dimension,
|
||||
"The pressure-force element displacement vector has "
|
||||
"the wrong size."
|
||||
);
|
||||
|
||||
enthalpyShape.SetSize(enthalpyElement.GetDof());
|
||||
|
||||
displacementDShapeReference.SetSize(
|
||||
scalarDisplacementDofCount, dimension
|
||||
);
|
||||
|
||||
displacementDShapePhysical.SetSize(
|
||||
scalarDisplacementDofCount, dimension
|
||||
);
|
||||
|
||||
elementResidual.SetSize(displacementDofs.Size());
|
||||
elementResidual = 0.0;
|
||||
|
||||
const mfem::IntegrationRule &integrationRule =
|
||||
get_pressure_force_rule(
|
||||
f, barotrope, enthalpyElement, displacementElement,
|
||||
*transformation
|
||||
);
|
||||
|
||||
for (int quadratureIndex = 0;
|
||||
quadratureIndex < integrationRule.GetNPoints();
|
||||
++quadratureIndex) {
|
||||
const mfem::IntegrationPoint &integrationPoint =
|
||||
integrationRule.IntPoint(quadratureIndex);
|
||||
|
||||
transformation->SetIntPoint(&integrationPoint);
|
||||
|
||||
const mapping::MappingStatus mappingStatus =
|
||||
domainMapper.EvaluateVolume(
|
||||
mappingData, *transformation, integrationPoint,
|
||||
workspace, mappingContext
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
mappingStatus == mapping::MappingStatus::valid,
|
||||
"Stateless mapping failed in the pressure-force "
|
||||
"kernel. Element: "
|
||||
<< elementId
|
||||
<< ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadratureIndex
|
||||
<< ", status: " << static_cast<int>(mappingStatus)
|
||||
);
|
||||
|
||||
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
|
||||
|
||||
const double enthalpyValue = elementEnthalpy * enthalpyShape;
|
||||
|
||||
const double pressureValue =
|
||||
barotrope.pressure_from_enthalpy(enthalpyValue);
|
||||
|
||||
displacementElement.CalcDShape(
|
||||
integrationPoint, displacementDShapeReference
|
||||
);
|
||||
|
||||
/*
|
||||
* Row i of DShape is grad_reference(N_i). Multiplication
|
||||
* by the complete inverse element Jacobian gives
|
||||
*
|
||||
* grad_physical(N_i)
|
||||
* = grad_reference(N_i) J^{-1}.
|
||||
*/
|
||||
mfem::Mult(
|
||||
displacementDShapeReference,
|
||||
mappingContext.quadrature.J_inv, displacementDShapePhysical
|
||||
);
|
||||
|
||||
const double weightedPressure =
|
||||
pressureValue * mappingContext.quadrature.weight;
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(pressureValue) &&
|
||||
std::isfinite(weightedPressure),
|
||||
"The pressure-force kernel encountered a non-finite "
|
||||
"quadrature value."
|
||||
);
|
||||
|
||||
/*
|
||||
* For the vector basis N_i e_c,
|
||||
*
|
||||
* div(N_i e_c) = partial_c N_i.
|
||||
*
|
||||
* Therefore
|
||||
*
|
||||
* R_(i,c)
|
||||
* = -integral P partial_c N_i dV.
|
||||
*/
|
||||
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount;
|
||||
++scalarDof) {
|
||||
for (int component = 0; component < dimension;
|
||||
++component) {
|
||||
const int vectorDof = vector_dof_index(
|
||||
displacementOrdering, scalarDof, component,
|
||||
scalarDisplacementDofCount, dimension
|
||||
);
|
||||
|
||||
elementResidual(vectorDof) -=
|
||||
weightedPressure *
|
||||
displacementDShapePhysical(scalarDof, component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (displacementDofTransformation != nullptr) {
|
||||
displacementDofTransformation->TransformDual(elementResidual);
|
||||
}
|
||||
|
||||
localResidual.AddElementVector(displacementDofs, elementResidual);
|
||||
}
|
||||
|
||||
local_to_true(*f.displacementFes, localResidual, residualTrue);
|
||||
}
|
||||
} // namespace mean_field::operators::kernels
|
||||
718
libmeanfield/impl/operators/prepared_barotropic_closure.cpp
Normal file
718
libmeanfield/impl/operators/prepared_barotropic_closure.cpp
Normal file
@@ -0,0 +1,718 @@
|
||||
module;
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
|
||||
import :operators.prepared_barotropic_closure;
|
||||
|
||||
namespace {
|
||||
int get_density_size(const mean_field::fem::FEM &f) {
|
||||
MFEM_VERIFY(
|
||||
f.densityFes != nullptr,
|
||||
"PreparedBarotropicClosureOperator requires the "
|
||||
"density finite-element space."
|
||||
);
|
||||
|
||||
return f.densityFes->GetTrueVSize();
|
||||
}
|
||||
|
||||
int get_enthalpy_size(const mean_field::fem::FEM &f) {
|
||||
MFEM_VERIFY(
|
||||
f.enthalpyFes != nullptr,
|
||||
"PreparedBarotropicClosureOperator requires the "
|
||||
"enthalpy finite-element space."
|
||||
);
|
||||
|
||||
return f.enthalpyFes->GetTrueVSize();
|
||||
}
|
||||
|
||||
void validate_finite_vector(
|
||||
const mfem::Vector &vector,
|
||||
const char *message
|
||||
) {
|
||||
for (int i = 0; i < vector.Size(); ++i) {
|
||||
MFEM_VERIFY(std::isfinite(vector(i)), message);
|
||||
}
|
||||
}
|
||||
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &trueVector,
|
||||
mfem::Vector &localVector
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
|
||||
"True vector has the wrong size."
|
||||
);
|
||||
|
||||
localVector.SetSize(finiteElementSpace.GetVSize());
|
||||
|
||||
const mfem::Operator *prolongation =
|
||||
finiteElementSpace.GetProlongationMatrix();
|
||||
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->Mult(trueVector, localVector);
|
||||
} else {
|
||||
localVector = trueVector;
|
||||
}
|
||||
}
|
||||
|
||||
void local_to_true(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const mfem::Vector &localVector,
|
||||
mfem::Vector &trueVector
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
localVector.Size() == finiteElementSpace.GetVSize(),
|
||||
"Local vector has the wrong size."
|
||||
);
|
||||
|
||||
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
|
||||
trueVector = 0.0;
|
||||
|
||||
const mfem::Operator *prolongation =
|
||||
finiteElementSpace.GetProlongationMatrix();
|
||||
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->MultTranspose(localVector, trueVector);
|
||||
} else {
|
||||
trueVector = localVector;
|
||||
}
|
||||
}
|
||||
|
||||
int get_eos_extra_order(
|
||||
const mean_field::physics::PolytropicBarotrope &barotrope
|
||||
) {
|
||||
const double extraOrder =
|
||||
(barotrope.polytropic_index() - 1.0) *
|
||||
static_cast<double>(
|
||||
mean_field::field::Enthalpy::Scalar::familyOrder
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(extraOrder) && extraOrder >= 0.0 &&
|
||||
extraOrder <=
|
||||
static_cast<double>(std::numeric_limits<int>::max()),
|
||||
"The EOS effective polynomial order is invalid."
|
||||
);
|
||||
|
||||
return static_cast<int>(std::ceil(extraOrder));
|
||||
}
|
||||
|
||||
const mfem::IntegrationRule &get_eos_rule(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::FiniteElement &densityElement,
|
||||
const mfem::FiniteElement &enthalpyElement,
|
||||
const mfem::ElementTransformation &transformation
|
||||
) {
|
||||
using EnthalpyField =
|
||||
mean_field::field::Field<mean_field::field::Enthalpy>;
|
||||
|
||||
MFEM_VERIFY(
|
||||
densityElement.GetOrder() ==
|
||||
mean_field::field::Density::Scalar::familyOrder,
|
||||
"The prepared EOS test element does not match "
|
||||
"the registered density field."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
enthalpyElement.GetOrder() ==
|
||||
mean_field::field::Enthalpy::Scalar::familyOrder,
|
||||
"The prepared EOS trial element does not match "
|
||||
"the registered enthalpy field."
|
||||
);
|
||||
|
||||
const mean_field::quadrature::Query query = EnthalpyField::make_query<
|
||||
mean_field::field::Enthalpy::Form::EosClosureSource>(
|
||||
mean_field::quadrature::QuadratureRole::discretization,
|
||||
transformation.OrderW(),
|
||||
std::array<int, 1>{get_eos_extra_order(barotrope)},
|
||||
mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
const auto resolution =
|
||||
f.quadratureFactory->get(query, transformation.GetGeometryType());
|
||||
|
||||
MFEM_VERIFY(
|
||||
resolution.integration_rule != nullptr,
|
||||
"The quadrature policy did not return a prepared "
|
||||
"EOS-closure integration rule."
|
||||
);
|
||||
|
||||
return *resolution.integration_rule;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators {
|
||||
PreparedBarotropicClosureOperator::PreparedBarotropicClosureOperator(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope
|
||||
)
|
||||
: mfem::Operator(
|
||||
f.densityFes->GetTrueVSize(),
|
||||
f.densityFes->GetTrueVSize() + f.enthalpyFes->GetTrueVSize() +
|
||||
f.displacementFes->GetTrueVSize()
|
||||
),
|
||||
m_fem(f),
|
||||
m_domainMapper(domainMapper),
|
||||
m_barotrope(barotrope),
|
||||
m_densitySize(f.densityFes->GetTrueVSize()),
|
||||
m_enthalpySize(f.enthalpyFes->GetTrueVSize()) {
|
||||
MFEM_VERIFY(
|
||||
m_fem.densityFes != nullptr,
|
||||
"PreparedBarotropicClosureOperator requires "
|
||||
"a density finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
m_fem.enthalpyFes != nullptr,
|
||||
"PreparedBarotropicClosureOperator requires "
|
||||
"an enthalpy finite-element space."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
m_fem.displacementFes != nullptr,
|
||||
"PreparedBarotropicClosureOperator requires "
|
||||
"a displacement finite-element space."
|
||||
);
|
||||
}
|
||||
|
||||
void PreparedBarotropicClosureOperator::Prepare(
|
||||
const mfem::Vector &baseDensityTrue,
|
||||
const mfem::Vector &baseEnthalpyTrue,
|
||||
const mfem::Vector &displacementTrue
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
baseDensityTrue.Size() == m_densitySize,
|
||||
"PreparedBarotropicClosureOperator received a "
|
||||
"base-density vector with the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
baseEnthalpyTrue.Size() == m_enthalpySize,
|
||||
"PreparedBarotropicClosureOperator received a "
|
||||
"base-enthalpy vector with the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacementTrue.Size() == m_fem.displacementFes->GetTrueVSize(),
|
||||
"PreparedBarotropicClosureOperator received a "
|
||||
"displacement vector with the wrong size."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
baseDensityTrue.Size() == m_fem.densityFes->GetTrueVSize(),
|
||||
"The base density true vector has the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
baseEnthalpyTrue.Size() == m_fem.enthalpyFes->GetTrueVSize(),
|
||||
"The base enthalpy true vector has the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacementTrue.Size() == m_fem.displacementFes->GetTrueVSize(),
|
||||
"The base displacement true vector has the wrong size."
|
||||
);
|
||||
|
||||
validate_finite_vector(
|
||||
baseDensityTrue, "PreparedBarotropicClosureOperator received a "
|
||||
"non-finite base-density value."
|
||||
);
|
||||
|
||||
validate_finite_vector(
|
||||
baseEnthalpyTrue, "PreparedBarotropicClosureOperator received a "
|
||||
"non-finite base-enthalpy value."
|
||||
);
|
||||
|
||||
validate_finite_vector(
|
||||
displacementTrue, "PreparedBarotropicClosureOperator received a "
|
||||
"non-finite displacement value."
|
||||
);
|
||||
|
||||
m_isPrepared = false;
|
||||
m_elements.clear();
|
||||
m_elements.reserve(m_fem.mesh->GetNE());
|
||||
|
||||
mfem::Vector baseDensityLocal;
|
||||
mfem::Vector baseEnthalpyLocal;
|
||||
mfem::Vector displacementLocal;
|
||||
|
||||
true_to_local(*m_fem.densityFes, baseDensityTrue, baseDensityLocal);
|
||||
|
||||
true_to_local(*m_fem.enthalpyFes, baseEnthalpyTrue, baseEnthalpyLocal);
|
||||
|
||||
true_to_local(
|
||||
*m_fem.displacementFes, displacementTrue, displacementLocal
|
||||
);
|
||||
|
||||
mapping::DomainMapperStateless::Workspace workspace(
|
||||
m_fem.mesh->Dimension()
|
||||
);
|
||||
|
||||
mfem::Array<int> displacementDofs;
|
||||
mfem::Array<int> compactificationDofs;
|
||||
|
||||
mfem::Vector elementBaseDensity;
|
||||
mfem::Vector elementBaseEnthalpy;
|
||||
mfem::Vector elementDisplacement;
|
||||
mfem::Vector elementCompactification;
|
||||
|
||||
mfem::Vector densityShape;
|
||||
mfem::Vector enthalpyShape;
|
||||
|
||||
const int vacuumAttribute = m_domainMapper.GetVacuumElementAttribute();
|
||||
|
||||
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
|
||||
mfem::ElementTransformation *transformation =
|
||||
m_fem.mesh->GetElementTransformation(elementId);
|
||||
|
||||
MFEM_VERIFY(
|
||||
transformation != nullptr,
|
||||
"PreparedBarotropicClosureOperator received "
|
||||
"a null element transformation."
|
||||
);
|
||||
|
||||
if (transformation->Attribute == vacuumAttribute) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_elements.emplace_back();
|
||||
ElementPAData &data = m_elements.back();
|
||||
|
||||
data.densityDofTransformation =
|
||||
m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
|
||||
|
||||
data.enthalpyDofTransformation =
|
||||
m_fem.enthalpyFes->GetElementDofs(elementId, data.enthalpyDofs);
|
||||
|
||||
mfem::DofTransformation *displacementDofTransformation =
|
||||
m_fem.displacementFes->GetElementVDofs(
|
||||
elementId, displacementDofs
|
||||
);
|
||||
|
||||
mfem::DofTransformation *compactificationDofTransformation =
|
||||
m_fem.compactificationFes->GetElementDofs(
|
||||
elementId, compactificationDofs
|
||||
);
|
||||
|
||||
baseDensityLocal.GetSubVector(data.densityDofs, elementBaseDensity);
|
||||
|
||||
baseEnthalpyLocal.GetSubVector(
|
||||
data.enthalpyDofs, elementBaseEnthalpy
|
||||
);
|
||||
|
||||
displacementLocal.GetSubVector(
|
||||
displacementDofs, elementDisplacement
|
||||
);
|
||||
|
||||
m_fem.compactificationCoordinate->GetSubVector(
|
||||
compactificationDofs, elementCompactification
|
||||
);
|
||||
|
||||
if (data.densityDofTransformation != nullptr) {
|
||||
data.densityDofTransformation->InvTransformPrimal(
|
||||
elementBaseDensity
|
||||
);
|
||||
}
|
||||
|
||||
if (data.enthalpyDofTransformation != nullptr) {
|
||||
data.enthalpyDofTransformation->InvTransformPrimal(
|
||||
elementBaseEnthalpy
|
||||
);
|
||||
}
|
||||
|
||||
if (displacementDofTransformation != nullptr) {
|
||||
displacementDofTransformation->InvTransformPrimal(
|
||||
elementDisplacement
|
||||
);
|
||||
}
|
||||
|
||||
if (compactificationDofTransformation != nullptr) {
|
||||
compactificationDofTransformation->InvTransformPrimal(
|
||||
elementCompactification
|
||||
);
|
||||
}
|
||||
|
||||
const mfem::FiniteElement &densityElement =
|
||||
*m_fem.densityFes->GetFE(elementId);
|
||||
|
||||
const mfem::FiniteElement &enthalpyElement =
|
||||
*m_fem.enthalpyFes->GetFE(elementId);
|
||||
|
||||
const mfem::FiniteElement &displacementElement =
|
||||
*m_fem.displacementFes->GetFE(elementId);
|
||||
|
||||
const mfem::FiniteElement &compactificationElement =
|
||||
*m_fem.compactificationFes->GetFE(elementId);
|
||||
|
||||
const mapping::ElementDisplacementData displacementData =
|
||||
mapping::ElementDisplacementDataFromElementVDofs(
|
||||
displacementElement, elementDisplacement
|
||||
);
|
||||
|
||||
const mapping::ElementCompactificationData compactificationData(
|
||||
compactificationElement, elementCompactification
|
||||
);
|
||||
|
||||
const mapping::ElementMappingData mappingData{
|
||||
.displacement = displacementData,
|
||||
.compactification = compactificationData
|
||||
};
|
||||
|
||||
const mfem::IntegrationRule &integrationRule = get_eos_rule(
|
||||
m_fem, m_barotrope, densityElement, enthalpyElement,
|
||||
*transformation
|
||||
);
|
||||
|
||||
const int quadraturePointCount = integrationRule.GetNPoints();
|
||||
|
||||
const int densityDofCount = densityElement.GetDof();
|
||||
|
||||
const int enthalpyDofCount = enthalpyElement.GetDof();
|
||||
|
||||
data.densityBasis.SetSize(quadraturePointCount, densityDofCount);
|
||||
|
||||
data.enthalpyBasis.SetSize(quadraturePointCount, enthalpyDofCount);
|
||||
|
||||
data.weightedResidual.SetSize(quadraturePointCount);
|
||||
|
||||
data.quadratureWeights.SetSize(quadraturePointCount);
|
||||
|
||||
data.weightedEnthalpyDerivative.SetSize(quadraturePointCount);
|
||||
|
||||
densityShape.SetSize(densityDofCount);
|
||||
|
||||
enthalpyShape.SetSize(enthalpyDofCount);
|
||||
|
||||
for (int quadraturePoint = 0;
|
||||
quadraturePoint < quadraturePointCount; ++quadraturePoint) {
|
||||
const mfem::IntegrationPoint &integrationPoint =
|
||||
integrationRule.IntPoint(quadraturePoint);
|
||||
|
||||
transformation->SetIntPoint(&integrationPoint);
|
||||
|
||||
mapping::VolumeMappingContext mappingContext;
|
||||
|
||||
const mapping::MappingStatus mappingStatus =
|
||||
m_domainMapper.EvaluateVolume(
|
||||
mappingData, *transformation, integrationPoint,
|
||||
workspace, mappingContext
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
mappingStatus == mapping::MappingStatus::valid,
|
||||
"Stateless mapping failed while preparing "
|
||||
"the barotropic closure operator. Element: "
|
||||
<< elementId
|
||||
<< ", attribute: " << transformation->Attribute
|
||||
<< ", quadrature point: " << quadraturePoint
|
||||
<< ", status: " << static_cast<int>(mappingStatus)
|
||||
);
|
||||
|
||||
densityElement.CalcShape(integrationPoint, densityShape);
|
||||
|
||||
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
|
||||
|
||||
for (int densityDof = 0; densityDof < densityDofCount;
|
||||
++densityDof) {
|
||||
data.densityBasis(quadraturePoint, densityDof) =
|
||||
densityShape(densityDof);
|
||||
}
|
||||
|
||||
for (int enthalpyDof = 0; enthalpyDof < enthalpyDofCount;
|
||||
++enthalpyDof) {
|
||||
data.enthalpyBasis(quadraturePoint, enthalpyDof) =
|
||||
enthalpyShape(enthalpyDof);
|
||||
}
|
||||
|
||||
const double density = elementBaseDensity * densityShape;
|
||||
|
||||
const double enthalpy = elementBaseEnthalpy * enthalpyShape;
|
||||
|
||||
const double quadratureWeight =
|
||||
mappingContext.quadrature.weight;
|
||||
|
||||
const double eosDensity =
|
||||
m_barotrope.density_from_enthalpy(enthalpy);
|
||||
|
||||
const double enthalpyDerivative =
|
||||
m_barotrope.density_derivative_from_enthalpy(enthalpy);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(quadratureWeight) && quadratureWeight > 0.0 &&
|
||||
std::isfinite(eosDensity) &&
|
||||
std::isfinite(enthalpyDerivative),
|
||||
"PreparedBarotropicClosureOperator "
|
||||
"encountered invalid quadrature data."
|
||||
);
|
||||
|
||||
data.quadratureWeights(quadraturePoint) = quadratureWeight;
|
||||
|
||||
data.weightedResidual(quadraturePoint) =
|
||||
quadratureWeight * (density - eosDensity);
|
||||
|
||||
data.weightedEnthalpyDerivative(quadraturePoint) =
|
||||
quadratureWeight * enthalpyDerivative;
|
||||
}
|
||||
}
|
||||
|
||||
MFEM_VERIFY(
|
||||
!m_elements.empty(), "PreparedBarotropicClosureOperator found no "
|
||||
"stellar elements."
|
||||
);
|
||||
|
||||
m_baseDensityTrue = baseDensityTrue;
|
||||
m_baseEnthalpyTrue = baseEnthalpyTrue;
|
||||
m_baseDisplacementTrue = displacementTrue;
|
||||
|
||||
m_isPrepared = true;
|
||||
++m_preparationCount;
|
||||
}
|
||||
|
||||
void PreparedBarotropicClosureOperator::BuildResidual(
|
||||
mfem::Vector &residual
|
||||
) const {
|
||||
MFEM_VERIFY(
|
||||
m_isPrepared, "PreparedBarotropicClosureOperator must be "
|
||||
"prepared before BuildResidual is called."
|
||||
);
|
||||
|
||||
mfem::Vector localResidual(m_fem.densityFes->GetVSize());
|
||||
localResidual = 0.0;
|
||||
|
||||
mfem::Vector elementResidual;
|
||||
|
||||
for (const ElementPAData &data : m_elements) {
|
||||
elementResidual.SetSize(data.densityDofs.Size());
|
||||
|
||||
data.densityBasis.MultTranspose(
|
||||
data.weightedResidual, elementResidual
|
||||
);
|
||||
|
||||
if (data.densityDofTransformation != nullptr) {
|
||||
data.densityDofTransformation->TransformDual(elementResidual);
|
||||
}
|
||||
|
||||
localResidual.AddElementVector(data.densityDofs, elementResidual);
|
||||
}
|
||||
|
||||
local_to_true(*m_fem.densityFes, localResidual, residual);
|
||||
}
|
||||
|
||||
void PreparedBarotropicClosureOperator::Mult(
|
||||
const mfem::Vector &densityVariationTrue,
|
||||
const mfem::Vector &enthalpyVariationTrue,
|
||||
const mfem::Vector &displacementVariationTrue,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
VerifyPrepared();
|
||||
|
||||
MFEM_VERIFY(
|
||||
densityVariationTrue.Size() == m_densitySize,
|
||||
"The density-variation true vector has "
|
||||
"the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
enthalpyVariationTrue.Size() == m_enthalpySize,
|
||||
"The enthalpy-variation true vector has "
|
||||
"the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacementVariationTrue.Size() ==
|
||||
m_fem.displacementFes->GetTrueVSize(),
|
||||
"The displacement-variation true vector has "
|
||||
"the wrong size."
|
||||
);
|
||||
|
||||
Mult(densityVariationTrue, enthalpyVariationTrue, action);
|
||||
|
||||
mfem::Vector displacementAction;
|
||||
|
||||
kernels::apply_barotropic_closure_displacement_action(
|
||||
m_fem, m_domainMapper, m_barotrope, m_baseDensityTrue,
|
||||
m_baseEnthalpyTrue, m_baseDisplacementTrue,
|
||||
displacementVariationTrue, displacementAction
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
displacementAction.Size() == m_densitySize,
|
||||
"The barotropic-closure displacement action "
|
||||
"returned a vector with the wrong size."
|
||||
);
|
||||
|
||||
action += displacementAction;
|
||||
}
|
||||
|
||||
void PreparedBarotropicClosureOperator::Mult(
|
||||
const mfem::Vector &combinedVariation,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
VerifyPrepared();
|
||||
|
||||
const int displacementSize = m_fem.displacementFes->GetTrueVSize();
|
||||
|
||||
const int combinedSize =
|
||||
m_densitySize + m_enthalpySize + displacementSize;
|
||||
|
||||
MFEM_VERIFY(
|
||||
combinedVariation.Size() == combinedSize,
|
||||
"The combined barotropic-closure variation "
|
||||
"vector has the wrong size. Expected "
|
||||
<< combinedSize << " entries but received "
|
||||
<< combinedVariation.Size() << "."
|
||||
);
|
||||
|
||||
mfem::real_t *combinedData =
|
||||
const_cast<mfem::real_t *>(combinedVariation.HostRead());
|
||||
|
||||
const mfem::Vector densityVariationTrue(combinedData, m_densitySize);
|
||||
|
||||
const mfem::Vector enthalpyVariationTrue(
|
||||
combinedData + m_densitySize, m_enthalpySize
|
||||
);
|
||||
|
||||
const mfem::Vector displacementVariationTrue(
|
||||
combinedData + m_densitySize + m_enthalpySize, displacementSize
|
||||
);
|
||||
|
||||
Mult(
|
||||
densityVariationTrue, enthalpyVariationTrue,
|
||||
displacementVariationTrue, action
|
||||
);
|
||||
}
|
||||
|
||||
void PreparedBarotropicClosureOperator::Mult(
|
||||
const mfem::Vector &densityVariationTrue,
|
||||
const mfem::Vector &enthalpyVariationTrue,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
MFEM_VERIFY(
|
||||
m_isPrepared, "PreparedBarotropicClosureOperator must be "
|
||||
"prepared before Mult is called."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
densityVariationTrue.Size() == m_densitySize,
|
||||
"PreparedBarotropicClosureOperator received a "
|
||||
"density variation with the wrong size."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
enthalpyVariationTrue.Size() == m_enthalpySize,
|
||||
"PreparedBarotropicClosureOperator received an "
|
||||
"enthalpy variation with the wrong size."
|
||||
);
|
||||
|
||||
mfem::Vector densityVariationLocal;
|
||||
mfem::Vector enthalpyVariationLocal;
|
||||
|
||||
true_to_local(
|
||||
*m_fem.densityFes, densityVariationTrue, densityVariationLocal
|
||||
);
|
||||
|
||||
true_to_local(
|
||||
*m_fem.enthalpyFes, enthalpyVariationTrue, enthalpyVariationLocal
|
||||
);
|
||||
|
||||
mfem::Vector localAction(m_fem.densityFes->GetVSize());
|
||||
localAction = 0.0;
|
||||
|
||||
mfem::Vector elementDensityVariation;
|
||||
mfem::Vector elementEnthalpyVariation;
|
||||
mfem::Vector quadratureDensityVariation;
|
||||
mfem::Vector quadratureEnthalpyVariation;
|
||||
mfem::Vector quadratureAction;
|
||||
mfem::Vector elementAction;
|
||||
|
||||
for (const ElementPAData &data : m_elements) {
|
||||
densityVariationLocal.GetSubVector(
|
||||
data.densityDofs, elementDensityVariation
|
||||
);
|
||||
|
||||
enthalpyVariationLocal.GetSubVector(
|
||||
data.enthalpyDofs, elementEnthalpyVariation
|
||||
);
|
||||
|
||||
if (data.densityDofTransformation != nullptr) {
|
||||
data.densityDofTransformation->InvTransformPrimal(
|
||||
elementDensityVariation
|
||||
);
|
||||
}
|
||||
|
||||
if (data.enthalpyDofTransformation != nullptr) {
|
||||
data.enthalpyDofTransformation->InvTransformPrimal(
|
||||
elementEnthalpyVariation
|
||||
);
|
||||
}
|
||||
|
||||
quadratureDensityVariation.SetSize(data.quadratureWeights.Size());
|
||||
|
||||
quadratureEnthalpyVariation.SetSize(data.quadratureWeights.Size());
|
||||
|
||||
quadratureAction.SetSize(data.quadratureWeights.Size());
|
||||
|
||||
data.densityBasis.Mult(
|
||||
elementDensityVariation, quadratureDensityVariation
|
||||
);
|
||||
|
||||
data.enthalpyBasis.Mult(
|
||||
elementEnthalpyVariation, quadratureEnthalpyVariation
|
||||
);
|
||||
|
||||
for (int quadraturePoint = 0;
|
||||
quadraturePoint < quadratureAction.Size(); ++quadraturePoint) {
|
||||
quadratureAction(quadraturePoint) =
|
||||
data.quadratureWeights(quadraturePoint) *
|
||||
quadratureDensityVariation(quadraturePoint) -
|
||||
data.weightedEnthalpyDerivative(quadraturePoint) *
|
||||
quadratureEnthalpyVariation(quadraturePoint);
|
||||
}
|
||||
|
||||
elementAction.SetSize(data.densityDofs.Size());
|
||||
|
||||
data.densityBasis.MultTranspose(quadratureAction, elementAction);
|
||||
|
||||
if (data.densityDofTransformation != nullptr) {
|
||||
data.densityDofTransformation->TransformDual(elementAction);
|
||||
}
|
||||
|
||||
localAction.AddElementVector(data.densityDofs, elementAction);
|
||||
}
|
||||
|
||||
local_to_true(*m_fem.densityFes, localAction, action);
|
||||
}
|
||||
|
||||
bool PreparedBarotropicClosureOperator::IsPrepared() const noexcept {
|
||||
return m_isPrepared;
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
PreparedBarotropicClosureOperator::GetPreparationCount() const noexcept {
|
||||
return m_preparationCount;
|
||||
}
|
||||
|
||||
int PreparedBarotropicClosureOperator::GetDensitySize() const noexcept {
|
||||
return m_densitySize;
|
||||
}
|
||||
|
||||
int PreparedBarotropicClosureOperator::GetEnthalpySize() const noexcept {
|
||||
return m_enthalpySize;
|
||||
}
|
||||
|
||||
void PreparedBarotropicClosureOperator::VerifyPrepared() const {
|
||||
MFEM_VERIFY(
|
||||
m_isPrepared, "PreparedBarotropicClosureOperator must be "
|
||||
"prepared before this operation is called."
|
||||
);
|
||||
}
|
||||
} // namespace mean_field::operators
|
||||
617
libmeanfield/impl/operators/prepared_gravity_source.cpp
Normal file
617
libmeanfield/impl/operators/prepared_gravity_source.cpp
Normal file
@@ -0,0 +1,617 @@
|
||||
module;
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
#include <numbers>
|
||||
|
||||
module mean_field;
|
||||
import :operators.prepared_gravity_source;
|
||||
|
||||
namespace {
|
||||
int get_operator_height(const mean_field::fem::FEM &f) {
|
||||
MFEM_VERIFY(
|
||||
f.gravityPotentialFes != nullptr,
|
||||
"PreparedMappedGravitySourceOperator requires the "
|
||||
"gravity-potential "
|
||||
"finite-element space."
|
||||
);
|
||||
return f.gravityPotentialFes->GetTrueVSize();
|
||||
}
|
||||
|
||||
int get_operator_width(const mean_field::fem::FEM &f) {
|
||||
MFEM_VERIFY(
|
||||
f.densityFes != nullptr,
|
||||
"PreparedMappedGravitySourceOperator requires the density "
|
||||
"finite-element space."
|
||||
);
|
||||
return f.densityFes->GetTrueVSize();
|
||||
}
|
||||
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finite_element_space,
|
||||
const mfem::Vector &true_vector,
|
||||
mfem::Vector &local_vector
|
||||
) {
|
||||
local_vector.SetSize(finite_element_space.GetVSize());
|
||||
|
||||
const mfem::Operator *prolongation =
|
||||
finite_element_space.GetProlongationMatrix();
|
||||
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->Mult(true_vector, local_vector);
|
||||
} else {
|
||||
local_vector = true_vector;
|
||||
}
|
||||
}
|
||||
|
||||
void local_to_true(
|
||||
const mfem::ParFiniteElementSpace &finite_element_space,
|
||||
const mfem::Vector &local_vector,
|
||||
mfem::Vector &true_vector
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
local_vector.Size() == finite_element_space.GetVSize(),
|
||||
"Local vector has the wrong size."
|
||||
);
|
||||
|
||||
true_vector.SetSize(finite_element_space.GetTrueVSize());
|
||||
true_vector = 0.0;
|
||||
|
||||
const mfem::Operator *prolongation =
|
||||
finite_element_space.GetProlongationMatrix();
|
||||
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->MultTranspose(local_vector, true_vector);
|
||||
} else {
|
||||
true_vector = local_vector;
|
||||
}
|
||||
}
|
||||
|
||||
const mfem::IntegrationRule &get_source_rule(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mfem::FiniteElement &density_element,
|
||||
const mfem::FiniteElement &potential_element,
|
||||
const mfem::ElementTransformation &transformation
|
||||
) {
|
||||
using GravityField =
|
||||
mean_field::field::Field<mean_field::field::Gravity>;
|
||||
MFEM_VERIFY(
|
||||
density_element.GetOrder() ==
|
||||
mean_field::field::Density::Scalar::familyOrder,
|
||||
"The prepared source trial element does not match the registered "
|
||||
"density field."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
potential_element.GetOrder() ==
|
||||
mean_field::field::Gravity::Potential::familyOrder,
|
||||
"The prepared source test element does not match the registered "
|
||||
"gravity potential."
|
||||
);
|
||||
const mean_field::quadrature::Query query = GravityField::make_query<
|
||||
mean_field::field::Gravity::Form::SourceProjection>(
|
||||
mean_field::quadrature::QuadratureRole::discretization,
|
||||
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
|
||||
mean_field::quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
return *f.quadratureFactory
|
||||
->get(query, transformation.GetGeometryType())
|
||||
.integration_rule;
|
||||
}
|
||||
|
||||
class FrozenMappedGravitySourceCoefficient final
|
||||
: public mfem::Coefficient {
|
||||
public:
|
||||
FrozenMappedGravitySourceCoefficient(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::mapping::DomainMapperStateless &domain_mapper,
|
||||
const mfem::Vector &displacement_true
|
||||
)
|
||||
: m_fem(f),
|
||||
m_domain_mapper(domain_mapper),
|
||||
m_workspace(domain_mapper.GetDimension()) {
|
||||
true_to_local(
|
||||
*m_fem.displacementFes, displacement_true, m_displacement_local
|
||||
);
|
||||
}
|
||||
|
||||
double Eval(
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point
|
||||
) override {
|
||||
transformation.SetIntPoint(&integration_point);
|
||||
|
||||
const int element_id = transformation.ElementNo;
|
||||
MFEM_VERIFY(
|
||||
element_id >= 0 && element_id < m_fem.mesh->GetNE(),
|
||||
"Mapped gravity source coefficient received an invalid element "
|
||||
"ID."
|
||||
);
|
||||
if (transformation.Attribute ==
|
||||
m_domain_mapper.GetVacuumElementAttribute()) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
LoadElement(element_id);
|
||||
const mean_field::mapping::ElementMappingData mapping_data{
|
||||
.displacement = *m_displacement_data,
|
||||
.compactification = *m_compactification_data
|
||||
};
|
||||
|
||||
mean_field::mapping::VolumeMappingContext mapping_context;
|
||||
|
||||
const mean_field::mapping::MappingStatus status =
|
||||
m_domain_mapper.EvaluateVolume(
|
||||
mapping_data, transformation, integration_point,
|
||||
m_workspace, mapping_context
|
||||
);
|
||||
|
||||
if (status != mean_field::mapping::MappingStatus::valid) {
|
||||
const mfem::FiniteElement &displacement_element =
|
||||
*m_fem.displacementFes->GetFE(element_id);
|
||||
const mfem::FiniteElement &compactification_element =
|
||||
*m_fem.compactificationFes->GetFE(element_id);
|
||||
|
||||
mfem::Vector displacement_shape(displacement_element.GetDof());
|
||||
mfem::Vector compactification_shape(
|
||||
compactification_element.GetDof()
|
||||
);
|
||||
mfem::Vector reference_position(m_domain_mapper.GetDimension());
|
||||
mfem::Vector displacement_value(m_domain_mapper.GetDimension());
|
||||
|
||||
displacement_element.CalcShape(
|
||||
integration_point, displacement_shape
|
||||
);
|
||||
compactification_element.CalcShape(
|
||||
integration_point, compactification_shape
|
||||
);
|
||||
transformation.Transform(integration_point, reference_position);
|
||||
m_displacement_data->GetDofMatrix().MultTranspose(
|
||||
displacement_shape, displacement_value
|
||||
);
|
||||
|
||||
const double compactification_coordinate =
|
||||
m_compactification_data->GetDofs() * compactification_shape;
|
||||
|
||||
MFEM_ABORT(
|
||||
"Stateless domain mapping failed while preparing the "
|
||||
"gravity "
|
||||
"source operator."
|
||||
<< "\nMapping status = " << static_cast<int>(status)
|
||||
<< "\nElement ID = " << element_id
|
||||
<< "\nElement attribute = " << transformation.Attribute
|
||||
<< "\nIntegration-point index = " << integration_point.index
|
||||
<< "\nIntegration point = <" << integration_point.x << ", "
|
||||
<< integration_point.y << ", " << integration_point.z << ">"
|
||||
<< "\nReference position = <" << reference_position(0)
|
||||
<< ", " << reference_position(1) << ", "
|
||||
<< reference_position(2) << ">"
|
||||
<< "\nReference radius = " << reference_position.Norml2()
|
||||
<< "\nDisplacement value = <" << displacement_value(0)
|
||||
<< ", " << displacement_value(1) << ", "
|
||||
<< displacement_value(2) << ">"
|
||||
<< "\nDisplacement magnitude = "
|
||||
<< displacement_value.Norml2()
|
||||
<< "\nCompactification coordinate = "
|
||||
<< compactification_coordinate
|
||||
<< "\nDisplacement ordering = "
|
||||
<< static_cast<int>(m_fem.displacementFes->GetOrdering())
|
||||
);
|
||||
}
|
||||
const double mapping_determinant =
|
||||
mapping_context.mapping.mapping_determinant;
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
|
||||
"Prepared gravity source operator encountered a non-positive "
|
||||
"or "
|
||||
"non-finite mapping determinant."
|
||||
);
|
||||
|
||||
return 4.0 * std::numbers::pi * mean_field::utils::G *
|
||||
mapping_determinant;
|
||||
}
|
||||
|
||||
private:
|
||||
void LoadElement(const int element_id) {
|
||||
if (element_id == m_cached_element_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement &displacement_element =
|
||||
*m_fem.displacementFes->GetFE(element_id);
|
||||
const mfem::FiniteElement &compactification_element =
|
||||
*m_fem.compactificationFes->GetFE(element_id);
|
||||
|
||||
mfem::DofTransformation *displacement_dof_transformation =
|
||||
m_fem.displacementFes->GetElementVDofs(
|
||||
element_id, m_displacement_dofs
|
||||
);
|
||||
mfem::DofTransformation *compactification_dof_transformation =
|
||||
m_fem.compactificationFes->GetElementDofs(
|
||||
element_id, m_compactification_dofs
|
||||
);
|
||||
|
||||
m_displacement_local.GetSubVector(
|
||||
m_displacement_dofs, m_element_displacement
|
||||
);
|
||||
m_fem.compactificationCoordinate->GetSubVector(
|
||||
m_compactification_dofs, m_element_compactification
|
||||
);
|
||||
|
||||
if (displacement_dof_transformation != nullptr) {
|
||||
displacement_dof_transformation->InvTransformPrimal(
|
||||
m_element_displacement
|
||||
);
|
||||
}
|
||||
|
||||
if (compactification_dof_transformation != nullptr) {
|
||||
compactification_dof_transformation->InvTransformPrimal(
|
||||
m_element_compactification
|
||||
);
|
||||
}
|
||||
|
||||
m_displacement_data = std::make_unique<
|
||||
mean_field::mapping::ElementDisplacementData>(
|
||||
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
|
||||
displacement_element, m_element_displacement
|
||||
)
|
||||
);
|
||||
|
||||
m_compactification_data = std::make_unique<
|
||||
mean_field::mapping::ElementCompactificationData>(
|
||||
compactification_element, m_element_compactification
|
||||
);
|
||||
|
||||
m_cached_element_id = element_id;
|
||||
}
|
||||
|
||||
const mean_field::fem::FEM &m_fem;
|
||||
const mean_field::mapping::DomainMapperStateless &m_domain_mapper;
|
||||
|
||||
mfem::Vector m_displacement_local;
|
||||
|
||||
mfem::Array<int> m_displacement_dofs;
|
||||
mfem::Array<int> m_compactification_dofs;
|
||||
|
||||
mfem::Vector m_element_displacement;
|
||||
mfem::Vector m_element_compactification;
|
||||
|
||||
std::unique_ptr<mean_field::mapping::ElementDisplacementData>
|
||||
m_displacement_data;
|
||||
std::unique_ptr<mean_field::mapping::ElementCompactificationData>
|
||||
m_compactification_data;
|
||||
|
||||
mean_field::mapping::DomainMapperStateless::Workspace m_workspace;
|
||||
int m_cached_element_id{-1};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators {
|
||||
PreparedMappedGravitySourceOperator::PreparedMappedGravitySourceOperator(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper
|
||||
)
|
||||
: Operator(
|
||||
get_operator_height(f),
|
||||
get_operator_width(f)
|
||||
),
|
||||
m_fem(f),
|
||||
m_domain_mapper(domain_mapper) {
|
||||
MFEM_VERIFY(
|
||||
f.mesh != nullptr,
|
||||
"PreparedMappedGravitySourceOperator requires a mesh."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.densityFes != nullptr,
|
||||
"PreparedMappedGravitySourceOperator requires the density "
|
||||
"finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityPotentialFes != nullptr,
|
||||
"PreparedMappedGravitySourceOperator requires the "
|
||||
"gravity-potential "
|
||||
"finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr,
|
||||
"PreparedMappedGravitySourceOperator requires "
|
||||
"the displacement finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.compactificationFes != nullptr,
|
||||
"PreparedMappedGravitySourceOperator requires the compactification "
|
||||
"finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.compactificationCoordinate != nullptr,
|
||||
"PreparedMappedGravitySourceOperator requires the compactification "
|
||||
"coordinate."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.quadratureFactory != nullptr,
|
||||
"PreparedMappedGravitySourceOperator "
|
||||
"requires the quadrature-rule factory."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
domain_mapper.GetDimension() == f.mesh->Dimension(),
|
||||
"The stateless domain-mapper dimension does not match the mesh "
|
||||
"dimension."
|
||||
);
|
||||
|
||||
utils::populate_element_mask(
|
||||
f.mesh.get(), utils::DOMAINS::STELLAR, m_stellar_marker
|
||||
);
|
||||
}
|
||||
|
||||
void PreparedMappedGravitySourceOperator::Prepare(
|
||||
const mfem::Vector &displacement_true
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
displacement_true.Size() == m_fem.displacementFes->GetTrueVSize(),
|
||||
"PreparedMappedGravitySourceOperator received a displacement "
|
||||
"vector "
|
||||
"with the wrong size."
|
||||
);
|
||||
|
||||
for (int i = 0; i < displacement_true.Size(); ++i) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(displacement_true(i)),
|
||||
"PreparedMappedGravitySourceOperator received a non-finite "
|
||||
"displacement value."
|
||||
);
|
||||
}
|
||||
|
||||
m_is_prepared = false;
|
||||
m_elements.clear();
|
||||
m_elements.reserve(m_fem.mesh->GetNE());
|
||||
|
||||
FrozenMappedGravitySourceCoefficient source_coefficient(
|
||||
m_fem, m_domain_mapper, displacement_true
|
||||
);
|
||||
|
||||
for (int element_id = 0; element_id < m_fem.mesh->GetNE();
|
||||
++element_id) {
|
||||
const int attribute = m_fem.mesh->GetAttribute(element_id);
|
||||
|
||||
if (attribute <= 0 || attribute > m_stellar_marker.Size() ||
|
||||
m_stellar_marker[attribute - 1] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_elements.emplace_back();
|
||||
ElementPAData &data = m_elements.back();
|
||||
|
||||
data.element_id = element_id;
|
||||
|
||||
data.density_dof_transformation =
|
||||
m_fem.densityFes->GetElementDofs(element_id, data.density_dofs);
|
||||
|
||||
data.potential_dof_transformation =
|
||||
m_fem.gravityPotentialFes->GetElementDofs(
|
||||
element_id, data.potential_dofs
|
||||
);
|
||||
|
||||
const mfem::FiniteElement &density_element =
|
||||
*m_fem.densityFes->GetFE(element_id);
|
||||
|
||||
const mfem::FiniteElement &potential_element =
|
||||
*m_fem.gravityPotentialFes->GetFE(element_id);
|
||||
|
||||
mfem::ElementTransformation &transformation =
|
||||
*m_fem.mesh->GetElementTransformation(element_id);
|
||||
|
||||
const mfem::IntegrationRule &integration_rule = get_source_rule(
|
||||
m_fem, density_element, potential_element, transformation
|
||||
);
|
||||
|
||||
const int quadrature_point_count = integration_rule.GetNPoints();
|
||||
|
||||
const int density_dof_count = density_element.GetDof();
|
||||
|
||||
const int potential_dof_count = potential_element.GetDof();
|
||||
|
||||
data.density_basis.SetSize(
|
||||
quadrature_point_count, density_dof_count
|
||||
);
|
||||
|
||||
data.potential_basis.SetSize(
|
||||
quadrature_point_count, potential_dof_count
|
||||
);
|
||||
|
||||
data.quadrature_data.SetSize(quadrature_point_count);
|
||||
|
||||
mfem::Vector density_shape(density_dof_count);
|
||||
mfem::Vector potential_shape(potential_dof_count);
|
||||
|
||||
for (int quadrature_point = 0;
|
||||
quadrature_point < quadrature_point_count;
|
||||
++quadrature_point) {
|
||||
const mfem::IntegrationPoint &integration_point =
|
||||
integration_rule.IntPoint(quadrature_point);
|
||||
|
||||
transformation.SetIntPoint(&integration_point);
|
||||
|
||||
// CalcPhysShape matches the scalar mixed-mass discretization,
|
||||
// including the finite-element map type.
|
||||
density_element.CalcPhysShape(transformation, density_shape);
|
||||
|
||||
potential_element.CalcPhysShape(
|
||||
transformation, potential_shape
|
||||
);
|
||||
|
||||
for (int i = 0; i < density_dof_count; ++i) {
|
||||
data.density_basis(quadrature_point, i) = density_shape(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < potential_dof_count; ++i) {
|
||||
data.potential_basis(quadrature_point, i) =
|
||||
potential_shape(i);
|
||||
}
|
||||
|
||||
const double coefficient_value =
|
||||
source_coefficient.Eval(transformation, integration_point);
|
||||
|
||||
transformation.SetIntPoint(&integration_point);
|
||||
|
||||
const double quadrature_value = integration_point.weight *
|
||||
transformation.Weight() *
|
||||
coefficient_value;
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(quadrature_value) && quadrature_value > 0.0,
|
||||
"Prepared gravity source operator encountered invalid "
|
||||
"quadrature data on element "
|
||||
<< element_id << ", quadrature point "
|
||||
<< quadrature_point << "."
|
||||
);
|
||||
|
||||
data.quadrature_data(quadrature_point) = quadrature_value;
|
||||
}
|
||||
}
|
||||
|
||||
MFEM_VERIFY(
|
||||
!m_elements.empty(),
|
||||
"PreparedMappedGravitySourceOperator found no stellar elements."
|
||||
);
|
||||
|
||||
m_is_prepared = true;
|
||||
++m_preparation_count;
|
||||
}
|
||||
void PreparedMappedGravitySourceOperator::Mult(
|
||||
const mfem::Vector &density_true,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared,
|
||||
"PreparedMappedGravitySourceOperator must be prepared before "
|
||||
"Mult is called."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
density_true.Size() == Width(),
|
||||
"PreparedMappedGravitySourceOperator received a density vector "
|
||||
"with the wrong size."
|
||||
);
|
||||
|
||||
mfem::Vector density_local;
|
||||
|
||||
true_to_local(*m_fem.densityFes, density_true, density_local);
|
||||
|
||||
mfem::Vector local_action(m_fem.gravityPotentialFes->GetVSize());
|
||||
local_action = 0.0;
|
||||
|
||||
mfem::Vector element_density;
|
||||
mfem::Vector quadrature_density;
|
||||
mfem::Vector element_action;
|
||||
|
||||
for (const ElementPAData &data : m_elements) {
|
||||
density_local.GetSubVector(data.density_dofs, element_density);
|
||||
|
||||
if (data.density_dof_transformation != nullptr) {
|
||||
data.density_dof_transformation->InvTransformPrimal(
|
||||
element_density
|
||||
);
|
||||
}
|
||||
|
||||
quadrature_density.SetSize(data.quadrature_data.Size());
|
||||
|
||||
// B_density * x_e
|
||||
data.density_basis.Mult(element_density, quadrature_density);
|
||||
|
||||
// D * B_density * x_e
|
||||
for (int q = 0; q < quadrature_density.Size(); ++q) {
|
||||
quadrature_density(q) *= data.quadrature_data(q);
|
||||
}
|
||||
|
||||
element_action.SetSize(data.potential_dofs.Size());
|
||||
|
||||
// B_potential^T * D * B_density * x_e
|
||||
data.potential_basis.MultTranspose(
|
||||
quadrature_density, element_action
|
||||
);
|
||||
|
||||
if (data.potential_dof_transformation != nullptr) {
|
||||
data.potential_dof_transformation->TransformDual(
|
||||
element_action
|
||||
);
|
||||
}
|
||||
|
||||
local_action.AddElementVector(data.potential_dofs, element_action);
|
||||
}
|
||||
|
||||
local_to_true(*m_fem.gravityPotentialFes, local_action, action);
|
||||
}
|
||||
|
||||
void PreparedMappedGravitySourceOperator::MultTranspose(
|
||||
const mfem::Vector &potential_true,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared,
|
||||
"PreparedMappedGravitySourceOperator must be prepared before "
|
||||
"MultTranspose is called."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
potential_true.Size() == Height(),
|
||||
"PreparedMappedGravitySourceOperator received a potential vector "
|
||||
"with the wrong size."
|
||||
);
|
||||
|
||||
mfem::Vector potential_local;
|
||||
|
||||
true_to_local(
|
||||
*m_fem.gravityPotentialFes, potential_true, potential_local
|
||||
);
|
||||
|
||||
mfem::Vector local_action(m_fem.densityFes->GetVSize());
|
||||
local_action = 0.0;
|
||||
|
||||
mfem::Vector element_potential;
|
||||
mfem::Vector quadrature_potential;
|
||||
mfem::Vector element_action;
|
||||
|
||||
for (const ElementPAData &data : m_elements) {
|
||||
potential_local.GetSubVector(
|
||||
data.potential_dofs, element_potential
|
||||
);
|
||||
|
||||
if (data.potential_dof_transformation != nullptr) {
|
||||
data.potential_dof_transformation->InvTransformPrimal(
|
||||
element_potential
|
||||
);
|
||||
}
|
||||
|
||||
quadrature_potential.SetSize(data.quadrature_data.Size());
|
||||
|
||||
data.potential_basis.Mult(element_potential, quadrature_potential);
|
||||
|
||||
for (int q = 0; q < quadrature_potential.Size(); ++q) {
|
||||
quadrature_potential(q) *= data.quadrature_data(q);
|
||||
}
|
||||
|
||||
element_action.SetSize(data.density_dofs.Size());
|
||||
|
||||
data.density_basis.MultTranspose(
|
||||
quadrature_potential, element_action
|
||||
);
|
||||
|
||||
if (data.density_dof_transformation != nullptr) {
|
||||
data.density_dof_transformation->TransformDual(element_action);
|
||||
}
|
||||
|
||||
local_action.AddElementVector(data.density_dofs, element_action);
|
||||
}
|
||||
|
||||
local_to_true(*m_fem.densityFes, local_action, action);
|
||||
}
|
||||
bool PreparedMappedGravitySourceOperator::IsPrepared() const noexcept {
|
||||
return m_is_prepared;
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept {
|
||||
return m_preparation_count;
|
||||
}
|
||||
} // namespace mean_field::operators
|
||||
441
libmeanfield/impl/operators/prepared_hdiv_mass.cpp
Normal file
441
libmeanfield/impl/operators/prepared_hdiv_mass.cpp
Normal file
@@ -0,0 +1,441 @@
|
||||
module;
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
import :operators.prepared_hdiv_mass;
|
||||
|
||||
namespace {
|
||||
int get_operator_size(const mean_field::fem::FEM &f) {
|
||||
MFEM_VERIFY(
|
||||
f.gravityFluxFes != nullptr,
|
||||
"PreparedMappedHDivMassOperator requires the "
|
||||
"gravity-gradient finite-element space."
|
||||
);
|
||||
return f.gravityFluxFes->GetTrueVSize();
|
||||
}
|
||||
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finite_element_space,
|
||||
const mfem::Vector &true_vector,
|
||||
mfem::Vector &local_vector
|
||||
) {
|
||||
local_vector.SetSize(finite_element_space.GetVSize());
|
||||
|
||||
const mfem::Operator *prolongation =
|
||||
finite_element_space.GetProlongationMatrix();
|
||||
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->Mult(true_vector, local_vector);
|
||||
} else {
|
||||
local_vector = true_vector;
|
||||
}
|
||||
}
|
||||
|
||||
int find_representative_element(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mfem::Array<int> &marker
|
||||
) {
|
||||
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
|
||||
const int attribute = f.mesh->GetAttribute(element_id);
|
||||
|
||||
if (attribute > 0 && attribute <= marker.Size() &&
|
||||
marker[attribute - 1] != 0) {
|
||||
return element_id;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void validate_uniform_domain_discretization(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mfem::Array<int> &marker,
|
||||
const int representative_element_id
|
||||
) {
|
||||
const mfem::FiniteElement &representative_element =
|
||||
*f.gravityFluxFes->GetFE(representative_element_id);
|
||||
const mfem::ElementTransformation &representative_transformation =
|
||||
*f.mesh->GetElementTransformation(representative_element_id);
|
||||
|
||||
for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) {
|
||||
const int attribute = f.mesh->GetAttribute(element_id);
|
||||
|
||||
if (attribute <= 0 || attribute > marker.Size() ||
|
||||
marker[attribute - 1] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement &element =
|
||||
*f.gravityFluxFes->GetFE(element_id);
|
||||
const mfem::ElementTransformation &transformation =
|
||||
*f.mesh->GetElementTransformation(element_id);
|
||||
|
||||
MFEM_VERIFY(
|
||||
element.GetGeomType() == representative_element.GetGeomType(),
|
||||
"Prepared H(div) mass domains currently require a uniform "
|
||||
"element "
|
||||
"geometry."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
element.GetOrder() == representative_element.GetOrder(),
|
||||
"Prepared H(div) mass domains currently require a uniform "
|
||||
"finite-element order."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
transformation.OrderW() ==
|
||||
representative_transformation.OrderW(),
|
||||
"Prepared H(div) mass domains currently require a uniform "
|
||||
"geometry-weight order."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FrozenMappedHDivMassCoefficient final
|
||||
: public mfem::MatrixCoefficient {
|
||||
public:
|
||||
FrozenMappedHDivMassCoefficient(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::mapping::DomainMapperStateless &domain_mapper,
|
||||
const mfem::Vector &displacement_true,
|
||||
bool elevates_vacuum
|
||||
)
|
||||
: MatrixCoefficient(domain_mapper.GetDimension()),
|
||||
m_fem(f),
|
||||
m_domain_mapper(domain_mapper),
|
||||
m_workspace(domain_mapper.GetDimension()),
|
||||
m_elevates_vacuum(elevates_vacuum) {
|
||||
true_to_local(
|
||||
*m_fem.displacementFes, displacement_true, m_displacement_local
|
||||
);
|
||||
}
|
||||
|
||||
void Eval(
|
||||
mfem::DenseMatrix &mass_tensor,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point
|
||||
) override {
|
||||
transformation.SetIntPoint(&integration_point);
|
||||
|
||||
const int element_id = transformation.ElementNo;
|
||||
MFEM_VERIFY(
|
||||
element_id >= 0 && element_id < m_fem.mesh->GetNE(),
|
||||
"Mapped H(div) mass coefficient received an invalid element ID."
|
||||
);
|
||||
|
||||
const bool element_is_vacuum =
|
||||
transformation.Attribute ==
|
||||
m_domain_mapper.GetVacuumElementAttribute();
|
||||
|
||||
if (element_is_vacuum != m_elevates_vacuum) {
|
||||
mass_tensor.SetSize(m_domain_mapper.GetDimension());
|
||||
mass_tensor = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
LoadElement(element_id);
|
||||
|
||||
const mean_field::mapping::ElementMappingData mapping_data{
|
||||
.displacement = *m_displacement_data,
|
||||
.compactification = *m_compactification_data
|
||||
};
|
||||
|
||||
mean_field::mapping::VolumeMappingContext mapping_context;
|
||||
|
||||
const mean_field::mapping::MappingStatus status =
|
||||
m_domain_mapper.EvaluateVolume(
|
||||
mapping_data, transformation, integration_point,
|
||||
m_workspace, mapping_context
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
status == mean_field::mapping::MappingStatus::valid,
|
||||
"Stateless domain mapping failed while preparing the H(div) "
|
||||
"mass "
|
||||
"operator. Mapping status = "
|
||||
<< static_cast<int>(status)
|
||||
<< ", element ID = " << element_id
|
||||
<< ", element attribute = " << transformation.Attribute
|
||||
<< ", coefficient domain = "
|
||||
<< (m_elevates_vacuum ? "vacuum" : "stellar")
|
||||
);
|
||||
|
||||
const mfem::DenseMatrix &mapping_jacobian =
|
||||
mapping_context.mapping.mapping_jacobian;
|
||||
const double mapping_determinant =
|
||||
mapping_context.mapping.mapping_determinant;
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(mapping_determinant) && mapping_determinant > 0.0,
|
||||
"Prepared H(div) mass operator encountered a non-positive or "
|
||||
"non-finite mapping determinant."
|
||||
);
|
||||
|
||||
mfem::MultAtB(mapping_jacobian, mapping_jacobian, mass_tensor);
|
||||
mass_tensor *= 1.0 / mapping_determinant;
|
||||
}
|
||||
|
||||
private:
|
||||
void LoadElement(const int element_id) {
|
||||
if (element_id == m_cached_element_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement &displacement_element =
|
||||
*m_fem.displacementFes->GetFE(element_id);
|
||||
const mfem::FiniteElement &compactification_element =
|
||||
*m_fem.compactificationFes->GetFE(element_id);
|
||||
|
||||
mfem::DofTransformation *displacement_dof_transformation =
|
||||
m_fem.displacementFes->GetElementVDofs(
|
||||
element_id, m_displacement_dofs
|
||||
);
|
||||
mfem::DofTransformation *compactification_dof_transformation =
|
||||
m_fem.compactificationFes->GetElementDofs(
|
||||
element_id, m_compactification_dofs
|
||||
);
|
||||
|
||||
m_displacement_local.GetSubVector(
|
||||
m_displacement_dofs, m_element_displacement
|
||||
);
|
||||
m_fem.compactificationCoordinate->GetSubVector(
|
||||
m_compactification_dofs, m_element_compactification
|
||||
);
|
||||
|
||||
if (displacement_dof_transformation != nullptr) {
|
||||
displacement_dof_transformation->InvTransformPrimal(
|
||||
m_element_displacement
|
||||
);
|
||||
}
|
||||
|
||||
if (compactification_dof_transformation != nullptr) {
|
||||
compactification_dof_transformation->InvTransformPrimal(
|
||||
m_element_compactification
|
||||
);
|
||||
}
|
||||
|
||||
m_displacement_data = std::make_unique<
|
||||
mean_field::mapping::ElementDisplacementData>(
|
||||
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
|
||||
displacement_element, m_element_displacement
|
||||
)
|
||||
);
|
||||
|
||||
m_compactification_data = std::make_unique<
|
||||
mean_field::mapping::ElementCompactificationData>(
|
||||
compactification_element, m_element_compactification
|
||||
);
|
||||
|
||||
m_cached_element_id = element_id;
|
||||
}
|
||||
|
||||
const mean_field::fem::FEM &m_fem;
|
||||
const mean_field::mapping::DomainMapperStateless &m_domain_mapper;
|
||||
|
||||
mfem::Vector m_displacement_local;
|
||||
|
||||
mfem::Array<int> m_displacement_dofs;
|
||||
mfem::Array<int> m_compactification_dofs;
|
||||
|
||||
mfem::Vector m_element_displacement;
|
||||
mfem::Vector m_element_compactification;
|
||||
|
||||
std::unique_ptr<mean_field::mapping::ElementDisplacementData>
|
||||
m_displacement_data;
|
||||
std::unique_ptr<mean_field::mapping::ElementCompactificationData>
|
||||
m_compactification_data;
|
||||
|
||||
mean_field::mapping::DomainMapperStateless::Workspace m_workspace;
|
||||
int m_cached_element_id{-1};
|
||||
bool m_elevates_vacuum;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::operators {
|
||||
PreparedMappedHDivMassOperator::PreparedMappedHDivMassOperator(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper
|
||||
)
|
||||
: Operator(get_operator_size(f)),
|
||||
m_fem(f),
|
||||
m_domain_mapper(domain_mapper) {
|
||||
MFEM_VERIFY(
|
||||
f.mesh != nullptr, "PreparedMappedHDivMassOperator requires a mesh."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityFluxFes != nullptr,
|
||||
"PreparedMappedHDivMassOperator requires the "
|
||||
"gravity-gradient finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr,
|
||||
"PreparedMappedHDivMassOperator requires the "
|
||||
"displacement finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.compactificationFes != nullptr,
|
||||
"PreparedMappedHDivMassOperator requires the compactification "
|
||||
"finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.compactificationCoordinate != nullptr,
|
||||
"PreparedMappedHDivMassOperator requires the compactification "
|
||||
"coordinate."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.quadratureFactory != nullptr,
|
||||
"PreparedMappedHDivMassOperator requires the quadrature-rule "
|
||||
"factory."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
domain_mapper.GetDimension() == f.mesh->Dimension(),
|
||||
"The stateless domain-mapper dimension does not match the mesh "
|
||||
"dimension."
|
||||
);
|
||||
|
||||
utils::populate_element_mask(
|
||||
f.mesh.get(), utils::DOMAINS::STELLAR, m_stellar_marker
|
||||
);
|
||||
utils::populate_element_mask(
|
||||
f.mesh.get(), utils::DOMAINS::VACUUM, m_vacuum_marker
|
||||
);
|
||||
|
||||
const int stellar_element_id =
|
||||
find_representative_element(f, m_stellar_marker);
|
||||
const int vacuum_element_id =
|
||||
find_representative_element(f, m_vacuum_marker);
|
||||
|
||||
MFEM_VERIFY(
|
||||
stellar_element_id >= 0, "PreparedMappedHDivMassOperator requires "
|
||||
"at least one stellar element."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
vacuum_element_id >= 0,
|
||||
"PreparedMappedHDivMassOperator requires at "
|
||||
"least one compactified vacuum element."
|
||||
);
|
||||
|
||||
validate_uniform_domain_discretization(
|
||||
f, m_stellar_marker, stellar_element_id
|
||||
);
|
||||
validate_uniform_domain_discretization(
|
||||
f, m_vacuum_marker, vacuum_element_id
|
||||
);
|
||||
}
|
||||
|
||||
void PreparedMappedHDivMassOperator::Prepare(
|
||||
const mfem::Vector &displacement_true
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
displacement_true.Size() == m_fem.displacementFes->GetTrueVSize(),
|
||||
"PreparedMappedHDivMassOperator received a displacement vector "
|
||||
"with "
|
||||
"the wrong size."
|
||||
);
|
||||
|
||||
for (int i = 0; i < displacement_true.Size(); ++i) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(displacement_true(i)),
|
||||
"PreparedMappedHDivMassOperator received a non-finite "
|
||||
"displacement "
|
||||
"value."
|
||||
);
|
||||
}
|
||||
|
||||
const int stellar_element_id =
|
||||
find_representative_element(m_fem, m_stellar_marker);
|
||||
const int vacuum_element_id =
|
||||
find_representative_element(m_fem, m_vacuum_marker);
|
||||
|
||||
const mfem::FiniteElement &stellar_element =
|
||||
*m_fem.gravityFluxFes->GetFE(stellar_element_id);
|
||||
const mfem::FiniteElement &vacuum_element =
|
||||
*m_fem.gravityFluxFes->GetFE(vacuum_element_id);
|
||||
|
||||
mfem::ElementTransformation &stellar_transformation =
|
||||
*m_fem.mesh->GetElementTransformation(stellar_element_id);
|
||||
mfem::ElementTransformation &vacuum_transformation =
|
||||
*m_fem.mesh->GetElementTransformation(vacuum_element_id);
|
||||
|
||||
m_mass_form.reset();
|
||||
m_stellar_mass_coefficient.reset();
|
||||
m_vacuum_mass_coefficient.reset();
|
||||
|
||||
m_stellar_mass_coefficient =
|
||||
std::make_unique<FrozenMappedHDivMassCoefficient>(
|
||||
m_fem, m_domain_mapper, displacement_true, false
|
||||
);
|
||||
m_vacuum_mass_coefficient =
|
||||
std::make_unique<FrozenMappedHDivMassCoefficient>(
|
||||
m_fem, m_domain_mapper, displacement_true, true
|
||||
);
|
||||
|
||||
m_mass_form =
|
||||
std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
|
||||
m_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
||||
|
||||
auto stellar_integrator =
|
||||
std::make_unique<mfem::VectorFEMassIntegrator>(
|
||||
*m_stellar_mass_coefficient
|
||||
);
|
||||
auto vacuum_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(
|
||||
*m_vacuum_mass_coefficient
|
||||
);
|
||||
|
||||
m_fem.quadratureFactory->configure_gravity_hdiv_mass(
|
||||
*stellar_integrator, quadrature::QuadratureRole::discretization,
|
||||
stellar_element, stellar_transformation, utils::DOMAINS::STELLAR,
|
||||
quadrature::MappingKind::general
|
||||
);
|
||||
|
||||
m_fem.quadratureFactory->configure_gravity_hdiv_mass(
|
||||
*vacuum_integrator, quadrature::QuadratureRole::discretization,
|
||||
vacuum_element, vacuum_transformation, utils::DOMAINS::VACUUM,
|
||||
quadrature::MappingKind::kelvin
|
||||
);
|
||||
|
||||
m_mass_form->AddDomainIntegrator(
|
||||
stellar_integrator.release(), m_stellar_marker
|
||||
);
|
||||
m_mass_form->AddDomainIntegrator(
|
||||
vacuum_integrator.release(), m_vacuum_marker
|
||||
);
|
||||
m_mass_form->Assemble();
|
||||
|
||||
m_is_prepared = true;
|
||||
++m_preparation_count;
|
||||
}
|
||||
|
||||
void PreparedMappedHDivMassOperator::Mult(
|
||||
const mfem::Vector &gravity_gradient_true,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
|
||||
"before Mult is called."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_mass_form != nullptr, "PreparedMappedHDivMassOperator has no "
|
||||
"assembled partial-assembly form."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
gravity_gradient_true.Size() == Width(),
|
||||
"PreparedMappedHDivMassOperator received a gravity-gradient vector "
|
||||
"with the wrong size."
|
||||
);
|
||||
|
||||
action.SetSize(Height());
|
||||
m_mass_form->Mult(gravity_gradient_true, action);
|
||||
}
|
||||
|
||||
bool PreparedMappedHDivMassOperator::IsPrepared() const noexcept {
|
||||
return m_is_prepared;
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept {
|
||||
return m_preparation_count;
|
||||
}
|
||||
} // namespace mean_field::operators
|
||||
1432
libmeanfield/impl/operators/prepared_hydrostatic_equilibrium.cpp
Normal file
1432
libmeanfield/impl/operators/prepared_hydrostatic_equilibrium.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,55 @@
|
||||
module;
|
||||
#include "mfem.hpp"
|
||||
#include <source_location>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
#include <source_location>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <format>
|
||||
|
||||
module mean_field;
|
||||
import :mapping.coefficients;
|
||||
import :analysis.integral;
|
||||
|
||||
namespace {
|
||||
double centrifugal_potential(const mfem::Vector &phys_x, const double omega) {
|
||||
double centrifugal_potential(
|
||||
const mfem::Vector &phys_x,
|
||||
const double omega
|
||||
) {
|
||||
const double s2 = std::pow(phys_x(0), 2) + std::pow(phys_x(1), 2);
|
||||
return -0.5 * s2 * std::pow(omega, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void grid_function_to_true_dofs(
|
||||
const mfem::ParFiniteElementSpace &finite_element_space,
|
||||
const mfem::GridFunction &grid_function,
|
||||
mfem::Vector &true_dofs
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
grid_function.Size() == finite_element_space.GetVSize(),
|
||||
"The grid function does not match the requested finite-element "
|
||||
"space."
|
||||
);
|
||||
|
||||
true_dofs.SetSize(finite_element_space.GetTrueVSize());
|
||||
|
||||
const mfem::Operator *restriction =
|
||||
finite_element_space.GetRestrictionMatrix();
|
||||
|
||||
if (restriction != nullptr) {
|
||||
restriction->Mult(grid_function, true_dofs);
|
||||
} else {
|
||||
MFEM_VERIFY(
|
||||
grid_function.Size() == true_dofs.Size(),
|
||||
"A finite-element space without a restriction operator must "
|
||||
"have "
|
||||
"matching local and true sizes."
|
||||
);
|
||||
|
||||
true_dofs = grid_function;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::physics {
|
||||
GravitySolution grav_potential(
|
||||
@@ -24,68 +58,106 @@ namespace mean_field::physics {
|
||||
const mfem::GridFunction &rho,
|
||||
const bool phi_warm
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
f.densityFes != nullptr && rho.FESpace() == f.densityFes.get(),
|
||||
"Gravity solve requires rho to use the registered density space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityPotentialFes != nullptr,
|
||||
"Gravity solve requires the registered gravity-potential space."
|
||||
);
|
||||
|
||||
mfem::Array<int> outer_bdr_marker(f.mesh->bdr_attributes.Max());
|
||||
outer_bdr_marker = 0;
|
||||
outer_bdr_marker[1] = 1;
|
||||
|
||||
mfem::ParLinearForm g_rhs(f.RT_fes.get());
|
||||
mfem::ParLinearForm g_rhs(f.gravityFluxFes.get());
|
||||
|
||||
// ReSharper disable once CppTooWideScope
|
||||
std::unique_ptr<mfem::Coefficient> boundary_potential_coeff;
|
||||
|
||||
if (!f.has_mapping()) { // We only need to explicitly add a boundary integrator if a mapping is not being used. In the case where the outer domain has been compactified the φ=0 boundary condition is the natural condition and MFEM automatically handles this
|
||||
auto boundary_potential = [&f](const mfem::Vector& x_physical) {
|
||||
if (!f.has_mapping()) { // We only need to explicitly add a boundary
|
||||
// integrator if a mapping is not being used. In
|
||||
// the case where the outer domain has been
|
||||
// compactified the φ=0 boundary condition is
|
||||
// the natural condition and MFEM automatically
|
||||
// handles this
|
||||
auto boundary_potential = [&f](const mfem::Vector &x_physical) {
|
||||
return l2_multipole_potential(f, utils::MASS, x_physical);
|
||||
};
|
||||
|
||||
boundary_potential_coeff = std::make_unique<mfem::FunctionCoefficient>(boundary_potential);
|
||||
auto boundary_integrator = std::make_unique<mfem::VectorFEBoundaryFluxLFIntegrator>(*boundary_potential_coeff);
|
||||
const mfem::FiniteElement& boundary_element = *f.RT_fes->GetTypicalTraceElement();
|
||||
boundary_potential_coeff =
|
||||
std::make_unique<mfem::FunctionCoefficient>(boundary_potential);
|
||||
auto boundary_integrator =
|
||||
std::make_unique<mfem::VectorFEBoundaryFluxLFIntegrator>(
|
||||
*boundary_potential_coeff
|
||||
);
|
||||
const mfem::FiniteElement &boundary_element =
|
||||
*f.gravityFluxFes->GetTypicalTraceElement();
|
||||
|
||||
f.quadrature_factory->configure_gravity_boundary(*boundary_integrator, quadrature::QuadratureRole::discretization, boundary_element, utils::DOMAINS::VACUUM, quadrature::MappingKind::none);
|
||||
g_rhs.AddBoundaryIntegrator(boundary_integrator.release(), outer_bdr_marker);
|
||||
f.quadratureFactory->configure_gravity_boundary(
|
||||
*boundary_integrator,
|
||||
quadrature::QuadratureRole::discretization, boundary_element,
|
||||
utils::DOMAINS::VACUUM, quadrature::MappingKind::none
|
||||
);
|
||||
g_rhs.AddBoundaryIntegrator(
|
||||
boundary_integrator.release(), outer_bdr_marker
|
||||
);
|
||||
}
|
||||
|
||||
g_rhs.Assemble();
|
||||
mfem::GridFunctionCoefficient rho_coeff(&rho);
|
||||
mfem::ConstantCoefficient G4pi(4.0 * M_PI * utils::G);
|
||||
mfem::ProductCoefficient source_coeff(G4pi, rho_coeff);
|
||||
mfem::ParLinearForm f_rhs(f.L2_fes.get());
|
||||
mfem::ParLinearForm f_rhs(f.gravityPotentialFes.get());
|
||||
|
||||
std::unique_ptr<mfem::Coefficient> mapped_source_coeff;
|
||||
mfem::Coefficient* active_source_coeff = &source_coeff;
|
||||
quadrature::MappingKind source_mapping_kind = quadrature::MappingKind::none;
|
||||
mfem::Coefficient *active_source_coeff = &source_coeff;
|
||||
quadrature::MappingKind source_mapping_kind =
|
||||
quadrature::MappingKind::none;
|
||||
|
||||
if (f.has_mapping()) {
|
||||
mapped_source_coeff = std::make_unique<mapping::MappedScalarCoefficient>(*f.mapping, source_coeff);
|
||||
mapped_source_coeff =
|
||||
std::make_unique<mapping::MappedScalarCoefficient>(
|
||||
*f.mapping, source_coeff
|
||||
);
|
||||
active_source_coeff = mapped_source_coeff.get();
|
||||
source_mapping_kind = quadrature::MappingKind::general;
|
||||
}
|
||||
|
||||
auto source_integrator = std::make_unique<mfem::DomainLFIntegrator>(*active_source_coeff);
|
||||
const mfem::FiniteElement& source_test_element = *f.L2_fes->GetTypicalFE();
|
||||
const mfem::ElementTransformation& source_transformation = *f.mesh->GetElementTransformation(0);
|
||||
const int source_coefficient_order = f.L2_fes->GetMaxElementOrder();
|
||||
auto source_integrator =
|
||||
std::make_unique<mfem::DomainLFIntegrator>(*active_source_coeff);
|
||||
const mfem::FiniteElement &source_test_element =
|
||||
*f.gravityPotentialFes->GetTypicalFE();
|
||||
const mfem::ElementTransformation &source_transformation =
|
||||
*f.mesh->GetElementTransformation(0);
|
||||
const int source_coefficient_order = f.densityFes->GetMaxElementOrder();
|
||||
|
||||
f.quadrature_factory->configure_gravity_source(*source_integrator, quadrature::QuadratureRole::discretization, source_test_element, source_transformation, source_coefficient_order, utils::DOMAINS::STELLAR, source_mapping_kind);
|
||||
f_rhs.AddDomainIntegrator(source_integrator.release(), f.gravity_context.stellar_mask);
|
||||
f.quadratureFactory->configure_gravity_source(
|
||||
*source_integrator, quadrature::QuadratureRole::discretization,
|
||||
source_test_element, source_transformation,
|
||||
source_coefficient_order, utils::DOMAINS::STELLAR,
|
||||
source_mapping_kind
|
||||
);
|
||||
f_rhs.AddDomainIntegrator(
|
||||
source_integrator.release(), f.gravityContext.stellar_mask
|
||||
);
|
||||
f_rhs.Assemble();
|
||||
|
||||
mfem::BlockVector RHS(f.gravity_block_true_offsets);
|
||||
mfem::BlockVector RHS(f.gravityBlockTrueOffsets);
|
||||
RHS.GetBlock(0) = *g_rhs.ParallelAssemble();
|
||||
RHS.GetBlock(1) = *f_rhs.ParallelAssemble();
|
||||
|
||||
mfem::BlockVector X(f.gravity_block_true_offsets);
|
||||
mfem::BlockVector X(f.gravityBlockTrueOffsets);
|
||||
X = 0.0;
|
||||
f.gravity_context.minres->SetOperator(*f.gravity_context.block_A);
|
||||
f.gravity_context.minres->Mult(RHS, X);
|
||||
f.gravityContext.minres->SetOperator(*f.gravityContext.block_A);
|
||||
f.gravityContext.minres->Mult(RHS, X);
|
||||
|
||||
GravitySolution solution(f);
|
||||
solution.gradPhi.SetFromTrueDofs(X.GetBlock(0));
|
||||
solution.phi.SetFromTrueDofs(X.GetBlock(1));
|
||||
|
||||
return solution;
|
||||
|
||||
}
|
||||
|
||||
mfem::GridFunction get_potential(
|
||||
@@ -105,18 +177,21 @@ namespace mean_field::physics {
|
||||
|
||||
std::unique_ptr<mfem::Coefficient> centrifugal_coeff;
|
||||
if (fem.has_mapping()) {
|
||||
centrifugal_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(*fem.mapping, rot);
|
||||
centrifugal_coeff = std::make_unique<
|
||||
mapping::PhysicalPositionFunctionCoefficient>(
|
||||
*fem.mapping, rot
|
||||
);
|
||||
} else {
|
||||
centrifugal_coeff = std::make_unique<mfem::FunctionCoefficient>(rot);
|
||||
centrifugal_coeff =
|
||||
std::make_unique<mfem::FunctionCoefficient>(rot);
|
||||
}
|
||||
|
||||
mfem::GridFunction centrifugal_gf(fem.H1_fes.get());
|
||||
mfem::GridFunction centrifugal_gf(fem.gravityPotentialFes.get());
|
||||
centrifugal_gf.ProjectCoefficient(*centrifugal_coeff);
|
||||
|
||||
phi.phi += centrifugal_gf;
|
||||
}
|
||||
return phi.phi;
|
||||
|
||||
}
|
||||
|
||||
mfem::DenseMatrix compute_quadrupole_moment_tensor(
|
||||
@@ -128,11 +203,23 @@ namespace mean_field::physics {
|
||||
mfem::DenseMatrix local_Q(dim, dim);
|
||||
local_Q = 0.0;
|
||||
|
||||
for (int i = 0; i < fem.H1_fes->GetNE(); ++i) {
|
||||
if (fem.mesh->GetAttribute(i) == 3) continue;
|
||||
for (int i = 0; i < fem.mesh->GetNE(); ++i) {
|
||||
if (fem.mesh->GetAttribute(i) == 3)
|
||||
continue;
|
||||
|
||||
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
|
||||
const mfem::IntegrationRule &ir = *fem.int_rule;
|
||||
mfem::ElementTransformation *trans =
|
||||
fem.mesh->GetElementTransformation(i);
|
||||
using DensityField = field::Field<field::Density>;
|
||||
const quadrature::Query query =
|
||||
DensityField::make_query<field::Density::Form::Quadrupole>(
|
||||
quadrature::QuadratureRole::diagnostic, trans->OrderW(),
|
||||
std::array<int, 1>{2}, utils::DOMAINS::STELLAR,
|
||||
fem.has_mapping() ? quadrature::MappingKind::general
|
||||
: quadrature::MappingKind::none
|
||||
);
|
||||
const mfem::IntegrationRule &ir =
|
||||
*fem.quadratureFactory->get(query, trans->GetGeometryType())
|
||||
.integration_rule;
|
||||
|
||||
for (int j = 0; j < ir.GetNPoints(); ++j) {
|
||||
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
|
||||
@@ -164,7 +251,8 @@ namespace mean_field::physics {
|
||||
for (int m = 0; m < dim; ++m) {
|
||||
for (int n = 0; n < dim; ++n) {
|
||||
const double delta = (m == n) ? 1.0 : 0.0;
|
||||
const double contrib = 3.0 * x_prime(m) * x_prime(n) - delta * r_sq;
|
||||
const double contrib =
|
||||
3.0 * x_prime(m) * x_prime(n) - delta * r_sq;
|
||||
local_Q(m, n) += rho_val * contrib * weight;
|
||||
}
|
||||
}
|
||||
@@ -172,7 +260,10 @@ namespace mean_field::physics {
|
||||
}
|
||||
|
||||
mfem::DenseMatrix global_Q(dim, dim);
|
||||
MPI_Allreduce(local_Q.GetData(), global_Q.GetData(), dim * dim, MPI_DOUBLE, MPI_SUM, fem.H1_fes->GetComm());
|
||||
MPI_Allreduce(
|
||||
local_Q.GetData(), global_Q.GetData(), dim * dim, MPI_DOUBLE,
|
||||
MPI_SUM, fem.mesh->GetComm()
|
||||
);
|
||||
|
||||
return global_Q;
|
||||
}
|
||||
@@ -183,7 +274,8 @@ namespace mean_field::physics {
|
||||
const mfem::Vector &phys_x
|
||||
) {
|
||||
const double r = phys_x.Norml2();
|
||||
if (r < 1e-12) return 0.0;
|
||||
if (r < 1e-12)
|
||||
return 0.0;
|
||||
|
||||
const int dim = fem.mesh->Dimension();
|
||||
|
||||
@@ -197,7 +289,8 @@ namespace mean_field::physics {
|
||||
}
|
||||
}
|
||||
|
||||
const double l2_contrib = -(utils::G / (2.0 * std::pow(r, 3))) * l2_mult_factor;
|
||||
const double l2_contrib =
|
||||
-(utils::G / (2.0 * std::pow(r, 3))) * l2_mult_factor;
|
||||
|
||||
const double l0_contrib = -utils::G * total_mass / r;
|
||||
|
||||
@@ -211,87 +304,331 @@ namespace mean_field::physics {
|
||||
// ==========================================
|
||||
// 1. Partially Assemble the High-Order Mass Block
|
||||
// ==========================================
|
||||
f.gravity_context.m_form = std::make_unique<mfem::ParBilinearForm>(f.RT_fes.get());
|
||||
f.gravity_context.m_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
||||
f.gravityContext.m_form =
|
||||
std::make_unique<mfem::ParBilinearForm>(f.gravityFluxFes.get());
|
||||
f.gravityContext.m_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
||||
|
||||
std::unique_ptr<mfem::VectorFEMassIntegrator> hdiv_mass_integrator;
|
||||
|
||||
if (f.has_mapping()) {
|
||||
f.gravity_context.mapped_hdiv_mass_coeff = std::make_unique<mapping::MappedHDivMassCoefficient>(*f.mapping, f.mesh->Dimension());
|
||||
hdiv_mass_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(*f.gravity_context.mapped_hdiv_mass_coeff);
|
||||
f.gravityContext.mapped_hdiv_mass_coeff =
|
||||
std::make_unique<mapping::MappedHDivMassCoefficient>(
|
||||
*f.mapping, f.mesh->Dimension()
|
||||
);
|
||||
hdiv_mass_integrator =
|
||||
std::make_unique<mfem::VectorFEMassIntegrator>(
|
||||
*f.gravityContext.mapped_hdiv_mass_coeff
|
||||
);
|
||||
} else {
|
||||
f.gravity_context.mapped_hdiv_mass_coeff.reset();
|
||||
hdiv_mass_integrator = std::make_unique<mfem::VectorFEMassIntegrator>();
|
||||
f.gravityContext.mapped_hdiv_mass_coeff.reset();
|
||||
hdiv_mass_integrator =
|
||||
std::make_unique<mfem::VectorFEMassIntegrator>();
|
||||
}
|
||||
|
||||
const mfem::FiniteElement& hdiv_element = *f.RT_fes->GetTypicalFE();
|
||||
const mfem::ElementTransformation& hdiv_transformation = *f.mesh->GetElementTransformation(0);
|
||||
const quadrature::MappingKind mapping_kind = f.has_mapping() ? quadrature::MappingKind::general : quadrature::MappingKind::none;
|
||||
const mfem::FiniteElement &hdiv_element =
|
||||
*f.gravityFluxFes->GetTypicalFE();
|
||||
const mfem::ElementTransformation &hdiv_transformation =
|
||||
*f.mesh->GetElementTransformation(0);
|
||||
const quadrature::MappingKind mapping_kind =
|
||||
f.has_mapping() ? quadrature::MappingKind::general
|
||||
: quadrature::MappingKind::none;
|
||||
|
||||
f.quadrature_factory->configure_gravity_hdiv_mass(*hdiv_mass_integrator, quadrature::QuadratureRole::discretization, hdiv_element, hdiv_transformation, utils::DOMAINS::ALL, mapping_kind);
|
||||
f.gravity_context.m_form->AddDomainIntegrator(hdiv_mass_integrator.release());
|
||||
f.gravity_context.m_form->Assemble();
|
||||
f.quadratureFactory->configure_gravity_hdiv_mass(
|
||||
*hdiv_mass_integrator, quadrature::QuadratureRole::discretization,
|
||||
hdiv_element, hdiv_transformation, utils::DOMAINS::ALL, mapping_kind
|
||||
);
|
||||
f.gravityContext.m_form->AddDomainIntegrator(
|
||||
hdiv_mass_integrator.release()
|
||||
);
|
||||
|
||||
f.gravityContext.m_form->Assemble();
|
||||
|
||||
// ==========================================
|
||||
// 2. Partially Assemble the High-Order Divergence Block
|
||||
// ==========================================
|
||||
f.gravity_context.b_form = std::make_unique<mfem::ParMixedBilinearForm>(f.RT_fes.get(), f.L2_fes.get());
|
||||
f.gravity_context.b_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
||||
f.gravityContext.b_form = std::make_unique<mfem::ParMixedBilinearForm>(
|
||||
f.gravityFluxFes.get(), f.gravityPotentialFes.get()
|
||||
);
|
||||
f.gravityContext.b_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
||||
|
||||
auto divergence_discretization_integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
|
||||
const mfem::FiniteElement& divergence_discretization_test_element = *f.L2_fes->GetTypicalFE();
|
||||
auto divergence_discretization_integrator =
|
||||
std::make_unique<mfem::VectorFEDivergenceIntegrator>();
|
||||
const mfem::FiniteElement &divergence_discretization_test_element =
|
||||
*f.gravityPotentialFes->GetTypicalFE();
|
||||
|
||||
f.quadrature_factory->configure_gravity_divergence(*divergence_discretization_integrator, quadrature::QuadratureRole::discretization, hdiv_element, divergence_discretization_test_element, hdiv_transformation, utils::DOMAINS::ALL, quadrature::MappingKind::none);
|
||||
f.gravity_context.b_form->AddDomainIntegrator(divergence_discretization_integrator.release());
|
||||
f.gravity_context.b_form->Assemble();
|
||||
f.quadratureFactory->configure_gravity_divergence(
|
||||
*divergence_discretization_integrator,
|
||||
quadrature::QuadratureRole::discretization, hdiv_element,
|
||||
divergence_discretization_test_element, hdiv_transformation,
|
||||
utils::DOMAINS::ALL, quadrature::MappingKind::none
|
||||
);
|
||||
f.gravityContext.b_form->AddDomainIntegrator(
|
||||
divergence_discretization_integrator.release()
|
||||
);
|
||||
|
||||
f.gravityContext.b_form->Assemble();
|
||||
|
||||
MFEM_VERIFY(
|
||||
f.domainMapperStateless != nullptr,
|
||||
"Gravity source partial assembly requires the stateless domain "
|
||||
"mapper."
|
||||
);
|
||||
|
||||
mfem::Vector displacement_true(f.displacementFes->GetTrueVSize());
|
||||
displacement_true = 0.0;
|
||||
|
||||
const mfem::GridFunction *active_displacement =
|
||||
f.mapping->GetDisplacement();
|
||||
|
||||
if (active_displacement != nullptr) {
|
||||
grid_function_to_true_dofs(
|
||||
*f.displacementFes, *active_displacement, displacement_true
|
||||
);
|
||||
}
|
||||
|
||||
auto source_form =
|
||||
std::make_unique<operators::PreparedMappedGravitySourceOperator>(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
|
||||
source_form->Prepare(displacement_true);
|
||||
|
||||
f.gravityContext.source_form = std::move(source_form);
|
||||
// ==========================================
|
||||
// 3. Assemble Global Block Operator
|
||||
// ==========================================
|
||||
f.gravity_context.BT = std::make_unique<mfem::TransposeOperator>(f.gravity_context.b_form.get());
|
||||
f.gravityContext.BT = std::make_unique<mfem::TransposeOperator>(
|
||||
f.gravityContext.b_form.get()
|
||||
);
|
||||
|
||||
f.gravity_context.block_A = std::make_unique<mfem::BlockOperator>(f.gravity_block_true_offsets);
|
||||
f.gravity_context.block_A->SetBlock(0, 0, f.gravity_context.m_form.get());
|
||||
f.gravity_context.block_A->SetBlock(0, 1, f.gravity_context.BT.get());
|
||||
f.gravity_context.block_A->SetBlock(1, 0, f.gravity_context.b_form.get());
|
||||
f.gravityContext.block_A =
|
||||
std::make_unique<mfem::BlockOperator>(f.gravityBlockTrueOffsets);
|
||||
f.gravityContext.block_A->SetBlock(0, 0, f.gravityContext.m_form.get());
|
||||
f.gravityContext.block_A->SetBlock(0, 1, f.gravityContext.BT.get());
|
||||
f.gravityContext.block_A->SetBlock(1, 0, f.gravityContext.b_form.get());
|
||||
|
||||
// ==========================================
|
||||
// 4. Construct a mapped Schur preconditioner
|
||||
// ==========================================
|
||||
mfem::Vector mass_diagonal(f.RT_fes->GetTrueVSize());
|
||||
f.gravity_context.m_form->AssembleDiagonal(mass_diagonal);
|
||||
mfem::Vector mass_diagonal(f.gravityFluxFes->GetTrueVSize());
|
||||
f.gravityContext.m_form->AssembleDiagonal(mass_diagonal);
|
||||
|
||||
mfem::Vector inverse_mass_diagonal(mass_diagonal);
|
||||
|
||||
for (int i = 0; i < inverse_mass_diagonal.Size(); ++i) {
|
||||
MFEM_VERIFY(std::isfinite(inverse_mass_diagonal(i)) && inverse_mass_diagonal(i) > 0.0, "Mapped RT mass matrix has a non-positive or non-finite diagonal entry.");
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(inverse_mass_diagonal(i)) &&
|
||||
inverse_mass_diagonal(i) > 0.0,
|
||||
"Mapped RT mass matrix has a non-positive or non-finite "
|
||||
"diagonal "
|
||||
"entry."
|
||||
);
|
||||
inverse_mass_diagonal(i) = 1.0 / inverse_mass_diagonal(i);
|
||||
}
|
||||
|
||||
mfem::ParMixedBilinearForm b_preconditioner(f.RT_fes.get(), f.L2_fes.get());
|
||||
auto divergence_preconditioner_integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
|
||||
mfem::ParMixedBilinearForm b_preconditioner(
|
||||
f.gravityFluxFes.get(), f.gravityPotentialFes.get()
|
||||
);
|
||||
auto divergence_preconditioner_integrator =
|
||||
std::make_unique<mfem::VectorFEDivergenceIntegrator>();
|
||||
|
||||
const mfem::FiniteElement& divergence_trial_element = *f.RT_fes->GetTypicalFE();
|
||||
const mfem::FiniteElement& divergence_test_element = *f.L2_fes->GetTypicalFE();
|
||||
const mfem::ElementTransformation& divergence_transformation = *f.mesh->GetElementTransformation(0);
|
||||
const mfem::FiniteElement &divergence_trial_element =
|
||||
*f.gravityFluxFes->GetTypicalFE();
|
||||
const mfem::FiniteElement &divergence_test_element =
|
||||
*f.gravityPotentialFes->GetTypicalFE();
|
||||
const mfem::ElementTransformation &divergence_transformation =
|
||||
*f.mesh->GetElementTransformation(0);
|
||||
|
||||
f.quadrature_factory->configure_gravity_divergence(*divergence_preconditioner_integrator, quadrature::QuadratureRole::preconditioner, divergence_trial_element, divergence_test_element, divergence_transformation, utils::DOMAINS::ALL, quadrature::MappingKind::none);
|
||||
b_preconditioner.AddDomainIntegrator(divergence_preconditioner_integrator.release());
|
||||
f.quadratureFactory->configure_gravity_divergence(
|
||||
*divergence_preconditioner_integrator,
|
||||
quadrature::QuadratureRole::preconditioner,
|
||||
divergence_trial_element, divergence_test_element,
|
||||
divergence_transformation, utils::DOMAINS::ALL,
|
||||
quadrature::MappingKind::none
|
||||
);
|
||||
b_preconditioner.AddDomainIntegrator(
|
||||
divergence_preconditioner_integrator.release()
|
||||
);
|
||||
b_preconditioner.Assemble();
|
||||
b_preconditioner.Finalize();
|
||||
std::unique_ptr<mfem::HypreParMatrix> b_matrix(b_preconditioner.ParallelAssemble());
|
||||
std::unique_ptr<mfem::HypreParMatrix> inverse_mass_b_transpose(b_matrix->Transpose());
|
||||
std::unique_ptr<mfem::HypreParMatrix> b_matrix(
|
||||
b_preconditioner.ParallelAssemble()
|
||||
);
|
||||
std::unique_ptr<mfem::HypreParMatrix> inverse_mass_b_transpose(
|
||||
b_matrix->Transpose()
|
||||
);
|
||||
|
||||
inverse_mass_b_transpose->ScaleRows(inverse_mass_diagonal);
|
||||
|
||||
f.gravity_context.Schur.reset(mfem::ParMult(b_matrix.get(), inverse_mass_b_transpose.get()));
|
||||
f.gravityContext.Schur.reset(
|
||||
mfem::ParMult(b_matrix.get(), inverse_mass_b_transpose.get())
|
||||
);
|
||||
|
||||
// ==========================================
|
||||
// 5. Wire Up the preconditioners
|
||||
// ==========================================
|
||||
f.gravity_context.prec_M = std::make_unique<mfem::OperatorJacobiSmoother>(mass_diagonal, empty_tdofs);
|
||||
f.gravity_context.prec_Phi->SetOperator(*f.gravity_context.Schur);
|
||||
f.gravity_context.block_prec->SetDiagonalBlock(0, f.gravity_context.prec_M.get());
|
||||
f.gravity_context.block_prec->SetDiagonalBlock(1, f.gravity_context.prec_Phi.get());
|
||||
f.gravityContext.prec_M =
|
||||
std::make_unique<mfem::OperatorJacobiSmoother>(
|
||||
mass_diagonal, empty_tdofs
|
||||
);
|
||||
f.gravityContext.prec_Phi->SetOperator(*f.gravityContext.Schur);
|
||||
f.gravityContext.block_prec->SetDiagonalBlock(
|
||||
0, f.gravityContext.prec_M.get()
|
||||
);
|
||||
f.gravityContext.block_prec->SetDiagonalBlock(
|
||||
1, f.gravityContext.prec_Phi.get()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
GravitySolution grav_potential_new(
|
||||
fem::FEM &f,
|
||||
const utils::Args &args,
|
||||
const mfem::GridFunction &rho,
|
||||
const mfem::GridFunction &displacement
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
f.mesh != nullptr,
|
||||
"Gravity initialization requires a parallel mesh."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.densityFes != nullptr,
|
||||
"Gravity initialization requires the density finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityPotentialFes != nullptr,
|
||||
"Gravity initialization requires the gravity-potential "
|
||||
"finite-element "
|
||||
"space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityFluxFes != nullptr,
|
||||
"Gravity initialization requires the "
|
||||
"gravity-gradient finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.displacementFes != nullptr, "Gravity initialization requires the "
|
||||
"displacement finite-element space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.domainMapperStateless != nullptr,
|
||||
"Gravity initialization requires the stateless domain mapper."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityContext.b_form != nullptr,
|
||||
"Gravity initialization requires the divergence operator."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityContext.BT != nullptr,
|
||||
"Gravity initialization requires the transpose divergence operator."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
f.gravityContext.block_prec != nullptr,
|
||||
"Gravity initialization requires the gravity block preconditioner."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
rho.FESpace() == f.densityFes.get(),
|
||||
"Gravity initialization requires density to use the FEM density "
|
||||
"space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
displacement.FESpace() == f.displacementFes.get(),
|
||||
"Gravity initialization requires displacement to use the FEM "
|
||||
"Vec_H1 "
|
||||
"space."
|
||||
);
|
||||
|
||||
using form = utils::blocks::gravity_field_form;
|
||||
|
||||
constexpr auto gravity_gradient_residual_block =
|
||||
utils::blocks::get_residual_block<form>(
|
||||
utils::blocks::gravity_field.gradient_term
|
||||
);
|
||||
|
||||
constexpr auto gravity_poisson_residual_block =
|
||||
utils::blocks::get_residual_block<form>(
|
||||
utils::blocks::gravity_field.poisson_term
|
||||
);
|
||||
|
||||
const std::array<int, form::value_block_count> value_sizes{
|
||||
f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(),
|
||||
f.gravityFluxFes->GetTrueVSize(),
|
||||
f.gravityPotentialFes->GetTrueVSize()
|
||||
};
|
||||
|
||||
const std::array<int, form::residual_block_count> residual_sizes{
|
||||
f.gravityFluxFes->GetTrueVSize(),
|
||||
f.gravityPotentialFes->GetTrueVSize()
|
||||
};
|
||||
|
||||
const utils::blocks::form_layout<form> layout(
|
||||
value_sizes, residual_sizes
|
||||
);
|
||||
|
||||
mfem::Vector density_true;
|
||||
mfem::Vector displacement_true;
|
||||
|
||||
grid_function_to_true_dofs(*f.densityFes, rho, density_true);
|
||||
grid_function_to_true_dofs(
|
||||
*f.displacementFes, displacement, displacement_true
|
||||
);
|
||||
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext
|
||||
linearization_context(f, *f.domainMapperStateless);
|
||||
|
||||
operators::GravityFieldJacobianOperator gravity_jacobian(
|
||||
f, *f.domainMapperStateless, linearization_context,
|
||||
layout.value_offsets(), layout.residual_offsets()
|
||||
);
|
||||
|
||||
operators::GravityFieldOperator gravity_operator(
|
||||
f, *f.domainMapperStateless, linearization_context,
|
||||
layout.value_offsets(), gravity_jacobian
|
||||
);
|
||||
|
||||
operators::context::gravity_field::GravityFieldGeometryContext
|
||||
reduced_geometry_context(f, *f.domainMapperStateless);
|
||||
|
||||
operators::ReducedGravityFieldOperator reduced_operator(
|
||||
gravity_operator, reduced_geometry_context, displacement_true
|
||||
);
|
||||
|
||||
mfem::Vector right_hand_side;
|
||||
reduced_operator.BuildRightHandSide(density_true, right_hand_side);
|
||||
|
||||
MFEM_VERIFY(
|
||||
right_hand_side.Size() == reduced_operator.Height(),
|
||||
"The reduced gravity right-hand side has the wrong size."
|
||||
);
|
||||
|
||||
mfem::BlockVector gravity_state(
|
||||
reduced_operator.GetGravityTrueOffsets()
|
||||
);
|
||||
gravity_state = 0.0;
|
||||
|
||||
mfem::MINRESSolver minres(f.mesh->GetComm());
|
||||
minres.SetOperator(reduced_operator);
|
||||
minres.SetPreconditioner(*f.gravityContext.block_prec);
|
||||
minres.SetRelTol(args.p.rtol);
|
||||
minres.SetAbsTol(args.p.atol);
|
||||
minres.SetMaxIter(args.p.max_iters);
|
||||
minres.SetPrintLevel(1);
|
||||
minres.Mult(right_hand_side, gravity_state);
|
||||
|
||||
MFEM_VERIFY(
|
||||
minres.GetConverged(),
|
||||
"The reduced gravity solve failed to converge."
|
||||
);
|
||||
|
||||
GravitySolution solution(f);
|
||||
|
||||
solution.gradPhi.SetFromTrueDofs(
|
||||
gravity_state.GetBlock(gravity_gradient_residual_block)
|
||||
);
|
||||
|
||||
solution.phi.SetFromTrueDofs(
|
||||
gravity_state.GetBlock(gravity_poisson_residual_block)
|
||||
);
|
||||
|
||||
return solution;
|
||||
}
|
||||
} // namespace mean_field::physics
|
||||
|
||||
@@ -1,17 +1,32 @@
|
||||
module;
|
||||
#include "mean_field.h"
|
||||
#include <array>
|
||||
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::physics {
|
||||
double compute_moment_of_inertia(const fem::FEM &fem, const mfem::GridFunction &rho_ref) {
|
||||
double compute_moment_of_inertia(
|
||||
const fem::FEM &fem,
|
||||
const mfem::GridFunction &rho_ref
|
||||
) {
|
||||
double local_I = 0.0;
|
||||
|
||||
for (int i = 0; i < fem.mesh->GetNE(); i++) {
|
||||
if (fem.mesh->GetAttribute(i) == 3) continue;
|
||||
if (fem.mesh->GetAttribute(i) == 3)
|
||||
continue;
|
||||
|
||||
mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(i);
|
||||
const mfem::IntegrationRule &ir = *fem.int_rule;
|
||||
mfem::ElementTransformation *T =
|
||||
fem.mesh->GetElementTransformation(i);
|
||||
using DensityField = field::Field<field::Density>;
|
||||
const quadrature::Query query =
|
||||
DensityField::make_query<field::Density::Form::Quadrupole>(
|
||||
quadrature::QuadratureRole::diagnostic, T->OrderW(),
|
||||
std::array<int, 1>{2}, utils::DOMAINS::STELLAR,
|
||||
quadrature::MappingKind::general
|
||||
);
|
||||
const mfem::IntegrationRule &ir =
|
||||
*fem.quadratureFactory->get(query, T->GetGeometryType())
|
||||
.integration_rule;
|
||||
|
||||
for (int j = 0; j < ir.GetNPoints(); j++) {
|
||||
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
|
||||
@@ -22,7 +37,8 @@ namespace mean_field::physics {
|
||||
mfem::Vector x_phys;
|
||||
fem.mapping->GetPhysicalPoint(*T, ip, x_phys);
|
||||
|
||||
const double r_cyl_sq = x_phys(0) * x_phys(0) + x_phys(1) * x_phys(1);
|
||||
const double r_cyl_sq =
|
||||
x_phys(0) * x_phys(0) + x_phys(1) * x_phys(1);
|
||||
const double detJ = std::fabs(fem.mapping->ComputeDetJ(*T, ip));
|
||||
const double weight = T->Weight() * ip.weight * detJ;
|
||||
|
||||
@@ -31,8 +47,10 @@ namespace mean_field::physics {
|
||||
}
|
||||
|
||||
double global_I = 0.0;
|
||||
MPI_Allreduce(&local_I, &global_I, 1, MPI_DOUBLE, MPI_SUM, fem.H1_fes->GetComm());
|
||||
MPI_Allreduce(
|
||||
&local_I, &global_I, 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm()
|
||||
);
|
||||
|
||||
return global_I;
|
||||
}
|
||||
}
|
||||
} // namespace mean_field::physics
|
||||
|
||||
@@ -29,8 +29,10 @@ namespace mean_field::utils {
|
||||
mfem::Array<mfem::IntegrationPoint> origin_ip;
|
||||
fem.mesh->FindPoints(P_origin, origin_elem, origin_ip, false);
|
||||
|
||||
if (origin_elem.Size() > 0 && origin_elem[0] >= 0 && !fem.mapping->IsIdentity()) {
|
||||
mfem::ElementTransformation *T0 = fem.mesh->GetElementTransformation(origin_elem[0]);
|
||||
if (origin_elem.Size() > 0 && origin_elem[0] >= 0 &&
|
||||
fem.mapping->HasDisplacementField()) {
|
||||
mfem::ElementTransformation *T0 =
|
||||
fem.mesh->GetElementTransformation(origin_elem[0]);
|
||||
T0->SetIntPoint(&origin_ip[0]);
|
||||
|
||||
mfem::DenseMatrix J0(dim, dim), J0_inv(dim, dim);
|
||||
@@ -81,7 +83,8 @@ namespace mean_field::utils {
|
||||
|
||||
if (elem_ids.Size() == 0 || elem_ids[0] < 0) {
|
||||
find_failures++;
|
||||
if (find_failures > 10) return false;
|
||||
if (find_failures > 10)
|
||||
return false;
|
||||
|
||||
double norm = x_ref.Norml2();
|
||||
if (norm > 1e-15) {
|
||||
@@ -95,7 +98,8 @@ namespace mean_field::utils {
|
||||
int elemID = elem_ids[0];
|
||||
const mfem::IntegrationPoint &ip = ips[0];
|
||||
|
||||
mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(elemID);
|
||||
mfem::ElementTransformation *T =
|
||||
fem.mesh->GetElementTransformation(elemID);
|
||||
T->SetIntPoint(&ip);
|
||||
|
||||
mfem::Vector current_x_phys(dim);
|
||||
@@ -135,7 +139,8 @@ namespace mean_field::utils {
|
||||
x_ref = x_ref_candidate;
|
||||
} else {
|
||||
find_failures++;
|
||||
if (find_failures > 10) return false;
|
||||
if (find_failures > 10)
|
||||
return false;
|
||||
if (double norm = x_ref.Norml2(); norm > 1e-15) {
|
||||
x_ref *= 0.5 * RADIUS / norm;
|
||||
} else {
|
||||
@@ -155,7 +160,8 @@ namespace mean_field::utils {
|
||||
const mapping::COORDINATE_SPACE rspace
|
||||
) {
|
||||
mfem::Vector x_search;
|
||||
if (vspace == mapping::COORDINATE_SPACE::PHYSICAL && fem.has_mapping()) {
|
||||
if (vspace == mapping::COORDINATE_SPACE::PHYSICAL &&
|
||||
fem.has_mapping()) {
|
||||
GetReferencePoint(fem, x, x_search);
|
||||
} else {
|
||||
x_search = x;
|
||||
@@ -171,18 +177,22 @@ namespace mean_field::utils {
|
||||
double local_val = 0.0;
|
||||
if (elem_ids.Size() > 0 && elem_ids[0] >= 0) {
|
||||
const double val = u.GetValue(elem_ids[0], ips[0]);
|
||||
if (rspace == mapping::COORDINATE_SPACE::PHYSICAL && !fem.has_mapping()) {
|
||||
MFEM_ABORT("Physical evaluation mode requested but no mapping provided. Check domain bounds and mapping setup.");
|
||||
if (rspace == mapping::COORDINATE_SPACE::PHYSICAL &&
|
||||
!fem.has_mapping()) {
|
||||
MFEM_ABORT(
|
||||
"Physical evaluation mode requested but no mapping "
|
||||
"provided. Check "
|
||||
"domain bounds and mapping setup."
|
||||
);
|
||||
}
|
||||
local_val = val;
|
||||
}
|
||||
|
||||
double global_val = 0.0;
|
||||
MPI_Allreduce(&local_val, &global_val, 1, MPI_DOUBLE, MPI_MAX, fem.H1_fes->GetComm());
|
||||
MPI_Allreduce(
|
||||
&local_val, &global_val, 1, MPI_DOUBLE, MPI_MAX, fem.mesh->GetComm()
|
||||
);
|
||||
return global_val;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
} // namespace mean_field::utils
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
#include <expected>
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
import :boundary.contexts;
|
||||
@@ -10,18 +10,22 @@ namespace mean_field::utils {
|
||||
DOMAINS lhs,
|
||||
DOMAINS rhs
|
||||
) {
|
||||
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
|
||||
return static_cast<DOMAINS>(
|
||||
static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs)
|
||||
);
|
||||
}
|
||||
|
||||
DOMAINS operator&(
|
||||
DOMAINS lhs,
|
||||
DOMAINS rhs
|
||||
) {
|
||||
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
|
||||
return static_cast<DOMAINS>(
|
||||
static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs)
|
||||
);
|
||||
}
|
||||
|
||||
void populate_element_mask(
|
||||
const mfem::Mesh* mesh,
|
||||
const mfem::Mesh *mesh,
|
||||
const DOMAINS domain,
|
||||
mfem::Array<int> &mask
|
||||
) {
|
||||
@@ -33,7 +37,8 @@ namespace mean_field::utils {
|
||||
mask[0] = 1;
|
||||
}
|
||||
|
||||
if ((domain & DOMAINS::ENVELOPE) == DOMAINS::ENVELOPE && max_attr >= 2) {
|
||||
if ((domain & DOMAINS::ENVELOPE) == DOMAINS::ENVELOPE &&
|
||||
max_attr >= 2) {
|
||||
mask[1] = 1;
|
||||
}
|
||||
|
||||
@@ -59,7 +64,8 @@ namespace mean_field::utils {
|
||||
|
||||
for (int j = 0; j < dofs.Size(); j++) {
|
||||
int index = dofs[j];
|
||||
if (index < 0) index = -1 - index;
|
||||
if (index < 0)
|
||||
index = -1 - index;
|
||||
vdof_marker[index] = 1;
|
||||
}
|
||||
}
|
||||
@@ -68,8 +74,10 @@ namespace mean_field::utils {
|
||||
fes->MarkerToList(vdof_marker, ess_tdof);
|
||||
}
|
||||
|
||||
|
||||
std::expected<boundary::Bounds, boundary::BoundsError> discover_bounds(
|
||||
std::expected<
|
||||
boundary::Bounds,
|
||||
boundary::BoundsError>
|
||||
discover_bounds(
|
||||
const mfem::Mesh *mesh,
|
||||
const int vacuum_attr
|
||||
) {
|
||||
@@ -82,9 +90,12 @@ namespace mean_field::utils {
|
||||
found_vacuum = true;
|
||||
mfem::Array<int> vertices;
|
||||
mesh->GetElementVertices(i, vertices);
|
||||
for (const int v: vertices) {
|
||||
for (const int v : vertices) {
|
||||
const double *coords = mesh->GetVertex(v);
|
||||
double r = std::sqrt(coords[0] * coords[0] + coords[1] * coords[1] + coords[2] * coords[2]);
|
||||
double r = std::sqrt(
|
||||
coords[0] * coords[0] + coords[1] * coords[1] +
|
||||
coords[2] * coords[2]
|
||||
);
|
||||
local_min_r = std::min(local_min_r, r);
|
||||
local_max_r = std::max(local_max_r, r);
|
||||
}
|
||||
@@ -100,9 +111,15 @@ namespace mean_field::utils {
|
||||
comm = pmesh->GetComm();
|
||||
}
|
||||
|
||||
MPI_Allreduce(&local_min_r, &global_min_r, 1, MPI_DOUBLE, MPI_MIN, comm);
|
||||
MPI_Allreduce(&local_max_r, &global_max_r, 1, MPI_DOUBLE, MPI_MAX, comm);
|
||||
MPI_Allreduce(&l_found, &global_found_vacuum, 1, MPI_INT, MPI_MAX, comm);
|
||||
MPI_Allreduce(
|
||||
&local_min_r, &global_min_r, 1, MPI_DOUBLE, MPI_MIN, comm
|
||||
);
|
||||
MPI_Allreduce(
|
||||
&local_max_r, &global_max_r, 1, MPI_DOUBLE, MPI_MAX, comm
|
||||
);
|
||||
MPI_Allreduce(
|
||||
&l_found, &global_found_vacuum, 1, MPI_INT, MPI_MAX, comm
|
||||
);
|
||||
|
||||
if (global_found_vacuum) {
|
||||
return boundary::Bounds(global_min_r, global_max_r);
|
||||
@@ -110,15 +127,11 @@ namespace mean_field::utils {
|
||||
return std::unexpected(boundary::BoundsError::CANNOT_FIND_VACUUM);
|
||||
}
|
||||
|
||||
int get_mesh_order(
|
||||
const mfem::Mesh &mesh
|
||||
) {
|
||||
int get_mesh_order(const mfem::Mesh &mesh) {
|
||||
if (mesh.GetNodes() != nullptr) {
|
||||
return mesh.GetNodes()->FESpace()->GetMaxElementOrder();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
} // namespace mean_field::utils
|
||||
194
libmeanfield/include/profile.h
Normal file
194
libmeanfield/include/profile.h
Normal file
@@ -0,0 +1,194 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mpi.h>
|
||||
|
||||
namespace mean_field::profiling {
|
||||
struct Statistics {
|
||||
unsigned long long observations{0};
|
||||
unsigned long long warmups{0};
|
||||
unsigned long long samples{0};
|
||||
unsigned long long warmup_target{0};
|
||||
double total_seconds{0.0};
|
||||
double minimum_seconds{std::numeric_limits<double>::infinity()};
|
||||
double maximum_seconds{0.0};
|
||||
};
|
||||
|
||||
class Registry {
|
||||
public:
|
||||
static Registry& Get() {
|
||||
static Registry registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
void Record(const std::string& label, const double seconds, const unsigned long long warmup_count) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
Statistics& statistics = m_statistics[label];
|
||||
|
||||
statistics.warmup_target = std::max(statistics.warmup_target, warmup_count);
|
||||
const bool is_warmup = statistics.observations < statistics.warmup_target;
|
||||
++statistics.observations;
|
||||
|
||||
if (is_warmup) {
|
||||
++statistics.warmups;
|
||||
return;
|
||||
}
|
||||
|
||||
++statistics.samples;
|
||||
statistics.total_seconds += seconds;
|
||||
statistics.minimum_seconds = std::min(statistics.minimum_seconds, seconds);
|
||||
statistics.maximum_seconds = std::max(statistics.maximum_seconds, seconds);
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_statistics.clear();
|
||||
}
|
||||
|
||||
void Print(MPI_Comm communicator) const {
|
||||
const std::map<std::string, Statistics> snapshot = GetSnapshot();
|
||||
|
||||
int mpi_initialized = 0;
|
||||
int mpi_finalized = 0;
|
||||
MPI_Initialized(&mpi_initialized);
|
||||
if (mpi_initialized) MPI_Finalized(&mpi_finalized);
|
||||
|
||||
const bool use_mpi = mpi_initialized && !mpi_finalized;
|
||||
int rank = 0;
|
||||
int communicator_size = 1;
|
||||
|
||||
if (use_mpi) {
|
||||
MPI_Comm_rank(communicator, &rank);
|
||||
MPI_Comm_size(communicator, &communicator_size);
|
||||
}
|
||||
|
||||
if (rank == 0) {
|
||||
std::cout << '\n';
|
||||
std::cout << std::left << std::setw(42) << "Profile Region"
|
||||
<< std::right << std::setw(11) << "Samples"
|
||||
<< std::setw(10) << "Warmups"
|
||||
<< std::setw(14) << "Avg Max ms"
|
||||
<< std::setw(14) << "Min ms"
|
||||
<< std::setw(14) << "Max ms"
|
||||
<< std::setw(14) << "Total Max s" << '\n';
|
||||
std::cout << std::string(119, '-') << '\n';
|
||||
}
|
||||
|
||||
for (const auto& [label, local_statistics] : snapshot) {
|
||||
unsigned long long minimum_samples = local_statistics.samples;
|
||||
unsigned long long maximum_samples = local_statistics.samples;
|
||||
unsigned long long maximum_warmups = local_statistics.warmups;
|
||||
|
||||
double local_average = local_statistics.samples > 0 ? local_statistics.total_seconds / static_cast<double>(local_statistics.samples) : 0.0;
|
||||
double local_minimum = local_statistics.samples > 0 ? local_statistics.minimum_seconds : std::numeric_limits<double>::infinity();
|
||||
double local_maximum = local_statistics.maximum_seconds;
|
||||
double local_total = local_statistics.total_seconds;
|
||||
|
||||
double maximum_rank_average = local_average;
|
||||
double global_minimum = local_minimum;
|
||||
double global_maximum = local_maximum;
|
||||
double maximum_rank_total = local_total;
|
||||
|
||||
if (use_mpi) {
|
||||
MPI_Allreduce(&local_statistics.samples, &minimum_samples, 1, MPI_UNSIGNED_LONG_LONG, MPI_MIN, communicator);
|
||||
MPI_Allreduce(&local_statistics.samples, &maximum_samples, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
|
||||
MPI_Allreduce(&local_statistics.warmups, &maximum_warmups, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
|
||||
MPI_Allreduce(&local_average, &maximum_rank_average, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
||||
MPI_Allreduce(&local_minimum, &global_minimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
|
||||
MPI_Allreduce(&local_maximum, &global_maximum, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
||||
MPI_Allreduce(&local_total, &maximum_rank_total, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
||||
}
|
||||
|
||||
if (!std::isfinite(global_minimum)) global_minimum = 0.0;
|
||||
|
||||
if (rank == 0) {
|
||||
const std::string sample_string = minimum_samples == maximum_samples
|
||||
? std::to_string(minimum_samples)
|
||||
: std::to_string(minimum_samples) + "-" + std::to_string(maximum_samples);
|
||||
|
||||
std::cout << std::left << std::setw(100) << label
|
||||
<< std::right << std::setw(11) << sample_string
|
||||
<< std::setw(10) << maximum_warmups
|
||||
<< std::setw(14) << std::fixed << std::setprecision(3) << 1.0e3 * maximum_rank_average
|
||||
<< std::setw(14) << 1.0e3 * global_minimum
|
||||
<< std::setw(14) << 1.0e3 * global_maximum
|
||||
<< std::setw(14) << std::setprecision(6) << maximum_rank_total << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (rank == 0) {
|
||||
std::cout << std::string(119, '=') << '\n';
|
||||
std::cout << "MPI ranks: " << communicator_size << "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::map<std::string, Statistics> GetSnapshot() const {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex m_mutex;
|
||||
std::map<std::string, Statistics> m_statistics;
|
||||
};
|
||||
|
||||
class ScopedTimer {
|
||||
public:
|
||||
ScopedTimer(std::string label, const unsigned long long warmup_count)
|
||||
: m_label(std::move(label)),
|
||||
m_warmup_count(warmup_count),
|
||||
m_start(std::chrono::steady_clock::now()) {}
|
||||
|
||||
ScopedTimer(const ScopedTimer&) = delete;
|
||||
ScopedTimer& operator=(const ScopedTimer&) = delete;
|
||||
ScopedTimer(ScopedTimer&&) = delete;
|
||||
ScopedTimer& operator=(ScopedTimer&&) = delete;
|
||||
|
||||
~ScopedTimer() {
|
||||
try {
|
||||
const auto stop = std::chrono::steady_clock::now();
|
||||
const double seconds = std::chrono::duration<double>(stop - m_start).count();
|
||||
Registry::Get().Record(m_label, seconds, m_warmup_count);
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_label;
|
||||
unsigned long long m_warmup_count;
|
||||
std::chrono::steady_clock::time_point m_start;
|
||||
};
|
||||
}
|
||||
|
||||
#define MEAN_FIELD_PROFILE_JOIN_IMPL(left, right) left##right
|
||||
#define MEAN_FIELD_PROFILE_JOIN(left, right) MEAN_FIELD_PROFILE_JOIN_IMPL(left, right)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, warmup_count) \
|
||||
::mean_field::profiling::ScopedTimer MEAN_FIELD_PROFILE_JOIN(mean_field_profile_timer_, __COUNTER__)(label, warmup_count)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_SCOPE(label) \
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, 1)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_CALL_WARMUP(label, warmup_count, ...) \
|
||||
do { \
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, warmup_count); \
|
||||
__VA_ARGS__; \
|
||||
} while (false)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_CALL(label, ...) \
|
||||
MEAN_FIELD_PROFILE_CALL_WARMUP(label, 1, __VA_ARGS__)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_RESET() \
|
||||
::mean_field::profiling::Registry::Get().Reset()
|
||||
|
||||
#define MEAN_FIELD_PROFILE_PRINT(communicator) \
|
||||
::mean_field::profiling::Registry::Get().Print(communicator)
|
||||
@@ -12,7 +12,8 @@ export namespace mean_field::analysis {
|
||||
const fem::FEM &fem,
|
||||
const mfem::GridFunction &gf,
|
||||
utils::DOMAINS domain = utils::DOMAINS::ALL,
|
||||
mapping::COORDINATE_SPACE coord_space = mapping::COORDINATE_SPACE::PHYSICAL
|
||||
mapping::COORDINATE_SPACE coord_space =
|
||||
mapping::COORDINATE_SPACE::PHYSICAL
|
||||
);
|
||||
|
||||
mfem::Vector get_com(
|
||||
@@ -32,10 +33,9 @@ export namespace mean_field::analysis {
|
||||
);
|
||||
|
||||
double get_mesh_volume(
|
||||
const fem::FEM& fem,
|
||||
mapping::COORDINATE_SPACE coordinate_space = mapping::COORDINATE_SPACE::PHYSICAL,
|
||||
const fem::FEM &fem,
|
||||
mapping::COORDINATE_SPACE coordinate_space =
|
||||
mapping::COORDINATE_SPACE::PHYSICAL,
|
||||
utils::DOMAINS domain = utils::DOMAINS::STELLAR
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
} // namespace mean_field::analysis
|
||||
|
||||
@@ -9,16 +9,15 @@ export namespace mean_field::boundary {
|
||||
mfem::Array<int> stellar_bounds;
|
||||
};
|
||||
|
||||
enum class Boundaries : uint8_t {
|
||||
STELLAR_SURFACE = 1,
|
||||
INF_SURFACE = 2
|
||||
};
|
||||
enum class Boundaries : uint8_t { STELLAR_SURFACE = 1, INF_SURFACE = 2 };
|
||||
|
||||
int operator-(
|
||||
Boundaries b,
|
||||
const int a
|
||||
) {
|
||||
return static_cast<int>(static_cast<uint8_t>(b) - static_cast<uint8_t>(a));
|
||||
return static_cast<int>(
|
||||
static_cast<uint8_t>(b) - static_cast<uint8_t>(a)
|
||||
);
|
||||
}
|
||||
|
||||
struct Bounds {
|
||||
@@ -26,9 +25,6 @@ export namespace mean_field::boundary {
|
||||
double r_inf_ref;
|
||||
};
|
||||
|
||||
enum BoundsError : uint8_t {
|
||||
CANNOT_FIND_VACUUM
|
||||
};
|
||||
enum BoundsError : uint8_t { CANNOT_FIND_VACUUM };
|
||||
|
||||
|
||||
}
|
||||
} // namespace mean_field::boundary
|
||||
|
||||
@@ -1,95 +1,183 @@
|
||||
module;
|
||||
|
||||
#include <stroid/stroid.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <stroid/stroid.h>
|
||||
|
||||
export module mean_field:fem;
|
||||
|
||||
export import :physics.contexts;
|
||||
export import :boundary.contexts;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :utils.misc;
|
||||
export import :utils.user;
|
||||
export import :quadrature.mfem;
|
||||
export import :field.mfem;
|
||||
|
||||
export namespace mean_field::fem {
|
||||
using GravityField = field::Field<field::Gravity>;
|
||||
using DisplacementField = field::Field<field::Displacement>;
|
||||
using DensityField = field::Field<field::Density>;
|
||||
using EnthalpyField = field::Field<field::Enthalpy>;
|
||||
|
||||
struct FEM {
|
||||
// =====================================================================
|
||||
// Mesh
|
||||
// =====================================================================
|
||||
|
||||
stroid::StroidMesh smesh;
|
||||
std::unique_ptr<mfem::ParMesh> mesh;
|
||||
|
||||
// =====================================
|
||||
// 2. Finite Element Collections
|
||||
// =====================================
|
||||
// H1 (Continuous): For Gravitational Potential (Phi) and Velocity (v)
|
||||
std::unique_ptr<mfem::FiniteElementCollection> H1_fec;
|
||||
// =====================================================================
|
||||
// Compile-time field descriptors
|
||||
// =====================================================================
|
||||
|
||||
// L2 (Discontinuous): For Density (rho) to fix O-grid boundary scalloping
|
||||
std::unique_ptr<mfem::FiniteElementCollection> L2_fec;
|
||||
GravityField gravityField;
|
||||
DisplacementField displacementField;
|
||||
DensityField densityField;
|
||||
EnthalpyField enthalpyField;
|
||||
|
||||
// H(div)/RT space for gravitational field
|
||||
std::unique_ptr<mfem::RT_FECollection> RT_fec;
|
||||
// =====================================================================
|
||||
// Gravity field
|
||||
//
|
||||
// Collection members are declared before their corresponding spaces so
|
||||
// that the spaces are destroyed first.
|
||||
// =====================================================================
|
||||
|
||||
std::unique_ptr<mfem::FiniteElementCollection> gravityPotentialFec;
|
||||
|
||||
// =====================================
|
||||
// 3. Finite Element Spaces
|
||||
// =====================================
|
||||
std::unique_ptr<mfem::ParFiniteElementSpace> H1_fes; // Scalar continuous (Gravity)
|
||||
std::unique_ptr<mfem::ParFiniteElementSpace> Vec_H1_fes; // Vector continuous (Velocity field)
|
||||
std::unique_ptr<mfem::ParFiniteElementSpace> L2_fes; // Scalar discontinuous (Density)
|
||||
std::unique_ptr<mfem::ParFiniteElementSpace> RT_fes; // H(div)/RT space for gravitational field
|
||||
std::unique_ptr<mfem::ParFiniteElementSpace> gravityPotentialFes;
|
||||
|
||||
// Preconditioning for Gravity
|
||||
std::unique_ptr<mfem::ParLORDiscretization> H1_lor_disc;
|
||||
const mfem::ParFiniteElementSpace *H1_lor_fes{nullptr};
|
||||
std::unique_ptr<mfem::FiniteElementCollection> gravityFluxFec;
|
||||
|
||||
std::unique_ptr<mfem::ParFiniteElementSpace> gravityFluxFes;
|
||||
|
||||
// =====================================================================
|
||||
// Displacement field
|
||||
// =====================================================================
|
||||
|
||||
std::unique_ptr<mfem::FiniteElementCollection> displacementFec;
|
||||
|
||||
std::unique_ptr<mfem::ParFiniteElementSpace> displacementFes;
|
||||
|
||||
std::unique_ptr<mfem::ParGridFunction> displacement;
|
||||
|
||||
// =====================================================================
|
||||
// Density field
|
||||
// =====================================================================
|
||||
|
||||
std::unique_ptr<mfem::FiniteElementCollection> densityFec;
|
||||
|
||||
std::unique_ptr<mfem::ParFiniteElementSpace> densityFes;
|
||||
|
||||
// =====================================================================
|
||||
// Specific-enthalpy field
|
||||
// =====================================================================
|
||||
|
||||
std::unique_ptr<mfem::FiniteElementCollection> enthalpyFec;
|
||||
|
||||
std::unique_ptr<mfem::ParFiniteElementSpace> enthalpyFes;
|
||||
|
||||
// =====================================================================
|
||||
// Compactification coordinate
|
||||
// =====================================================================
|
||||
|
||||
std::unique_ptr<mfem::H1_FECollection> compactificationFec;
|
||||
|
||||
std::unique_ptr<mfem::ParFiniteElementSpace> compactificationFes;
|
||||
|
||||
std::unique_ptr<mfem::ParGridFunction> compactificationCoordinate;
|
||||
|
||||
// =====================================================================
|
||||
// Domain mapping
|
||||
//
|
||||
// These are declared after displacement so that they are destroyed
|
||||
// before the displacement grid function to which mapping may refer.
|
||||
// DomainMapper is retained only for legacy integrators. New operators
|
||||
// use DomainMapperStateless exclusively.
|
||||
// =====================================================================
|
||||
|
||||
// =====================================
|
||||
// 4. Domain Mapping
|
||||
// =====================================
|
||||
std::unique_ptr<mapping::DomainMapper> mapping;
|
||||
|
||||
// =====================================
|
||||
// 5. Global System Tracking
|
||||
// =====================================
|
||||
// [ Velocity | Density | Mapping Parameters (Surface) ]
|
||||
mfem::Array<int> block_true_offsets;
|
||||
std::unique_ptr<mapping::DomainMapperStateless> domainMapperStateless;
|
||||
|
||||
mfem::Array<int> gravity_block_true_offsets;
|
||||
// =====================================================================
|
||||
// Block layouts
|
||||
//
|
||||
// These arrays are retained only for legacy code. Canonical operator
|
||||
// layouts are defined by the compile-time forms in :utils.blocks.
|
||||
//
|
||||
// Main system: [Displacement | Density]
|
||||
// Gravity system: [Flux | Potential]
|
||||
// =====================================================================
|
||||
|
||||
// Essential Boundary Conditions for the fluid (e.g., surface stress-free)
|
||||
mfem::Array<int> ess_v_tdofs;
|
||||
mfem::Array<int> blockTrueOffsets;
|
||||
mfem::Array<int> gravityBlockTrueOffsets;
|
||||
|
||||
// Elements entirely in the vacuum domain where fluid equations are not solved
|
||||
mfem::Array<int> vacuum_tdof_rho;
|
||||
mfem::Array<int> vacuum_tdof_v;
|
||||
// =====================================================================
|
||||
// Boundary conditions and domain masks
|
||||
// =====================================================================
|
||||
|
||||
mfem::Array<int> essentialDisplacementTdofs;
|
||||
mfem::Array<int> vacuumDensityTdofs;
|
||||
mfem::Array<int> vacuumEnthalpyTdofs;
|
||||
mfem::Array<int> vacuumDisplacementTdofs;
|
||||
|
||||
// =====================================================================
|
||||
// Global diagnostics
|
||||
// =====================================================================
|
||||
|
||||
// =====================================
|
||||
// 6. Multiphysics State & Integration
|
||||
// =====================================
|
||||
mfem::Vector com;
|
||||
mfem::DenseMatrix Q;
|
||||
|
||||
int int_order{3};
|
||||
std::unique_ptr<mfem::IntegrationRule> int_rule;
|
||||
// =====================================================================
|
||||
// Physics and boundary contexts
|
||||
// =====================================================================
|
||||
|
||||
physics::GravityContext gravity_context;
|
||||
boundary::BoundaryContext boundary_context;
|
||||
physics::GravityContext gravityContext;
|
||||
boundary::BoundaryContext boundaryContext;
|
||||
|
||||
std::unique_ptr<quadrature::RuleFactory> quadrature_factory;
|
||||
std::unique_ptr<quadrature::RuleFactory> quadratureFactory;
|
||||
|
||||
// =====================================================================
|
||||
// Validation
|
||||
// =====================================================================
|
||||
|
||||
// =====================================
|
||||
// 7. Utilities
|
||||
// =====================================
|
||||
[[nodiscard]] bool okay() const {
|
||||
return (mesh != nullptr) &&
|
||||
(H1_fec != nullptr) && (L2_fec != nullptr) && (RT_fec != nullptr) &&
|
||||
(H1_fes != nullptr) && (Vec_H1_fes != nullptr) && (L2_fes != nullptr) && (RT_fes != nullptr);
|
||||
return mesh != nullptr &&
|
||||
|
||||
gravityPotentialFec != nullptr &&
|
||||
gravityPotentialFes != nullptr &&
|
||||
gravityFluxFec != nullptr && gravityFluxFes != nullptr &&
|
||||
|
||||
displacementFec != nullptr && displacementFes != nullptr &&
|
||||
displacement != nullptr &&
|
||||
|
||||
densityFec != nullptr && densityFes != nullptr &&
|
||||
|
||||
enthalpyFec != nullptr && enthalpyFes != nullptr &&
|
||||
|
||||
compactificationFec != nullptr &&
|
||||
compactificationFes != nullptr &&
|
||||
compactificationCoordinate != nullptr &&
|
||||
|
||||
mapping != nullptr && domainMapperStateless != nullptr &&
|
||||
quadratureFactory != nullptr &&
|
||||
|
||||
blockTrueOffsets.Size() == 3 &&
|
||||
gravityBlockTrueOffsets.Size() == 3;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool has_mapping() const { return mapping != nullptr; }
|
||||
[[nodiscard]] bool has_mapping() const {
|
||||
return mapping != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
FEM setup_fem(const std::string &filename, const utils::Args &args, int extra_refine = 0);
|
||||
}
|
||||
|
||||
FEM setup_fem(
|
||||
const std::string &filename,
|
||||
const utils::Args &args,
|
||||
int extraRefine = 0
|
||||
);
|
||||
} // namespace mean_field::fem
|
||||
|
||||
356
libmeanfield/interface/field/field_base.cppm
Normal file
356
libmeanfield/interface/field/field_base.cppm
Normal file
@@ -0,0 +1,356 @@
|
||||
module;
|
||||
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
export module mean_field:field.base;
|
||||
|
||||
export namespace mean_field::field {
|
||||
template <typename... Ts> struct TypeList { };
|
||||
|
||||
template <typename T, typename ListT> struct TypeListContains;
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
struct TypeListContains<T, TypeList<Ts...>>
|
||||
: std::bool_constant<(std::same_as<T, Ts> || ...)> { };
|
||||
|
||||
template <typename T, typename ListT>
|
||||
inline constexpr bool typeListContains = TypeListContains<T, ListT>::value;
|
||||
|
||||
enum class StorageKind { finite_element, global_scalar };
|
||||
|
||||
inline constexpr int dynamicBlockSize = -1;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Function-space tags
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
struct L2 {
|
||||
static constexpr std::string_view name = "L2";
|
||||
};
|
||||
|
||||
struct H1 {
|
||||
static constexpr std::string_view name = "H1";
|
||||
};
|
||||
|
||||
struct RT {
|
||||
static constexpr std::string_view name = "RT";
|
||||
};
|
||||
|
||||
struct ND {
|
||||
static constexpr std::string_view name = "ND";
|
||||
};
|
||||
|
||||
template <typename SpaceT>
|
||||
concept SpaceTag = std::same_as<SpaceT, L2> || std::same_as<SpaceT, H1> ||
|
||||
std::same_as<SpaceT, RT> || std::same_as<SpaceT, ND>;
|
||||
|
||||
template <SpaceTag SpaceT, int RankV>
|
||||
inline constexpr bool spaceSupportsRank =
|
||||
(std::same_as<SpaceT, H1> && (RankV == 0 || RankV == 1)) ||
|
||||
(std::same_as<SpaceT, L2> && (RankV == 0 || RankV == 1)) ||
|
||||
(std::same_as<SpaceT, RT> && RankV == 1) ||
|
||||
(std::same_as<SpaceT, ND> && RankV == 1);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Discretization descriptors
|
||||
//
|
||||
// familyOrder is the order passed to the backend's FE collection
|
||||
// constructor. It is deliberately not called polynomialOrder because those
|
||||
// values differ for some spaces, notably Raviart-Thomas elements in MFEM.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <SpaceTag SpaceT, int FamilyOrderV> struct Disc {
|
||||
using Space = SpaceT;
|
||||
|
||||
static constexpr int familyOrder = FamilyOrderV;
|
||||
|
||||
static_assert(
|
||||
FamilyOrderV >= 0,
|
||||
"Finite-element family order must be non-negative."
|
||||
);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept DiscretizationTag = requires {
|
||||
typename T::Space;
|
||||
{ T::familyOrder } -> std::convertible_to<int>;
|
||||
} && SpaceTag<typename T::Space>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Physical relations between quantities
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
struct FieldRelation {
|
||||
struct Independent { };
|
||||
|
||||
template <typename SourceT> struct Gradient {
|
||||
using Source = SourceT;
|
||||
};
|
||||
|
||||
template <typename SourceT> struct Divergence {
|
||||
using Source = SourceT;
|
||||
};
|
||||
|
||||
template <typename SourceT> struct Curl {
|
||||
using Source = SourceT;
|
||||
};
|
||||
};
|
||||
|
||||
template <typename T> struct IsGradient : std::false_type { };
|
||||
|
||||
template <typename T> struct IsDivergence : std::false_type { };
|
||||
|
||||
template <typename T> struct IsCurl : std::false_type { };
|
||||
|
||||
template <typename SourceT>
|
||||
struct IsGradient<FieldRelation::Gradient<SourceT>> : std::true_type { };
|
||||
|
||||
template <typename SourceT>
|
||||
struct IsDivergence<FieldRelation::Divergence<SourceT>> : std::true_type {
|
||||
};
|
||||
|
||||
template <typename SourceT>
|
||||
struct IsCurl<FieldRelation::Curl<SourceT>> : std::true_type { };
|
||||
|
||||
template <typename RelationT>
|
||||
concept ValidRelation =
|
||||
std::same_as<RelationT, FieldRelation::Independent> ||
|
||||
IsGradient<RelationT>::value || IsDivergence<RelationT>::value ||
|
||||
IsCurl<RelationT>::value;
|
||||
|
||||
template <typename RelationT> struct RelationTarget {
|
||||
using Type = void;
|
||||
};
|
||||
|
||||
template <typename SourceT>
|
||||
struct RelationTarget<FieldRelation::Gradient<SourceT>> {
|
||||
using Type = SourceT;
|
||||
};
|
||||
|
||||
template <typename SourceT>
|
||||
struct RelationTarget<FieldRelation::Divergence<SourceT>> {
|
||||
using Type = SourceT;
|
||||
};
|
||||
|
||||
template <typename SourceT>
|
||||
struct RelationTarget<FieldRelation::Curl<SourceT>> {
|
||||
using Type = SourceT;
|
||||
};
|
||||
|
||||
template <typename QuantityT>
|
||||
using RelationTargetT =
|
||||
typename RelationTarget<typename QuantityT::Relation>::Type;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Field quantities
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <int RankV, ValidRelation RelationT, DiscretizationTag DiscT>
|
||||
struct Quantity {
|
||||
using Relation = RelationT;
|
||||
using Discretization = DiscT;
|
||||
using Space = typename DiscT::Space;
|
||||
|
||||
static constexpr int rankValue = RankV;
|
||||
static constexpr int familyOrder = DiscT::familyOrder;
|
||||
|
||||
static constexpr StorageKind storageKind = StorageKind::finite_element;
|
||||
static constexpr int staticBlockSize = dynamicBlockSize;
|
||||
|
||||
static_assert(
|
||||
RankV >= 0,
|
||||
"A field quantity cannot have a negative tensor rank."
|
||||
);
|
||||
|
||||
static_assert(
|
||||
spaceSupportsRank<
|
||||
Space,
|
||||
RankV>,
|
||||
"This function space cannot represent a quantity of this rank."
|
||||
);
|
||||
};
|
||||
|
||||
template <ValidRelation RelationT, DiscretizationTag DiscT>
|
||||
using ScalarQ = Quantity<0, RelationT, DiscT>;
|
||||
|
||||
template <ValidRelation RelationT, DiscretizationTag DiscT>
|
||||
using VectorQ = Quantity<1, RelationT, DiscT>;
|
||||
|
||||
struct GlobalScalarQ {
|
||||
using Relation = FieldRelation::Independent;
|
||||
|
||||
static constexpr int rankValue = 0;
|
||||
static constexpr StorageKind storageKind = StorageKind::global_scalar;
|
||||
static constexpr int staticBlockSize = 1;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept FieldQuantity =
|
||||
requires {
|
||||
typename T::Relation;
|
||||
typename T::Discretization;
|
||||
typename T::Space;
|
||||
|
||||
{ T::rankValue } -> std::convertible_to<int>;
|
||||
{ T::familyOrder } -> std::convertible_to<int>;
|
||||
{ T::storageKind } -> std::convertible_to<StorageKind>;
|
||||
{ T::staticBlockSize } -> std::convertible_to<int>;
|
||||
} && SpaceTag<typename T::Space> &&
|
||||
T::storageKind == StorageKind::finite_element;
|
||||
|
||||
template <typename T>
|
||||
concept GlobalScalarQuantity =
|
||||
requires {
|
||||
typename T::Relation;
|
||||
|
||||
{ T::rankValue } -> std::convertible_to<int>;
|
||||
{ T::storageKind } -> std::convertible_to<StorageKind>;
|
||||
{ T::staticBlockSize } -> std::convertible_to<int>;
|
||||
} && T::rankValue == 0 &&
|
||||
T::storageKind == StorageKind::global_scalar && T::staticBlockSize == 1;
|
||||
|
||||
template <typename T>
|
||||
concept RegisteredQuantity = FieldQuantity<T> || GlobalScalarQuantity<T>;
|
||||
|
||||
template <typename QuantityT>
|
||||
concept DerivedQuantity = FieldQuantity<QuantityT> &&
|
||||
(!std::same_as<RelationTargetT<QuantityT>, void>);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Compile-time discretization constraints
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <FieldQuantity FluxT, FieldQuantity PotentialT>
|
||||
struct RtL2StablePair {
|
||||
static consteval void validate() {
|
||||
static_assert(
|
||||
std::same_as<typename FluxT::Space, RT>,
|
||||
"The flux in an RT/L2 pair must use Raviart-Thomas elements."
|
||||
);
|
||||
|
||||
static_assert(
|
||||
std::same_as<typename PotentialT::Space, L2>,
|
||||
"The potential in an RT/L2 pair must use L2 elements."
|
||||
);
|
||||
|
||||
static_assert(
|
||||
FluxT::rankValue == 1,
|
||||
"The flux in an RT/L2 pair must be vector-valued."
|
||||
);
|
||||
|
||||
static_assert(
|
||||
PotentialT::rankValue == 0,
|
||||
"The potential in an RT/L2 pair must be scalar-valued."
|
||||
);
|
||||
|
||||
static_assert(
|
||||
FluxT::familyOrder == PotentialT::familyOrder,
|
||||
"The MFEM RT and L2 family orders must match."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... ConstraintTs>
|
||||
consteval bool validate_constraints(TypeList<ConstraintTs...>) {
|
||||
(ConstraintTs::validate(), ...);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Operations applied to quantities inside weak forms
|
||||
//
|
||||
// These describe the mathematics. Backend-specific polynomial-order rules
|
||||
// are provided by field.mfem.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
struct FieldOperation {
|
||||
struct Value { };
|
||||
struct Gradient { };
|
||||
struct Divergence { };
|
||||
struct Curl { };
|
||||
struct NormalTrace { };
|
||||
};
|
||||
|
||||
template <typename OperationT>
|
||||
concept FieldOperationTag =
|
||||
std::same_as<OperationT, FieldOperation::Value> ||
|
||||
std::same_as<OperationT, FieldOperation::Gradient> ||
|
||||
std::same_as<OperationT, FieldOperation::Divergence> ||
|
||||
std::same_as<OperationT, FieldOperation::Curl> ||
|
||||
std::same_as<OperationT, FieldOperation::NormalTrace>;
|
||||
|
||||
template <
|
||||
RegisteredQuantity QuantityT,
|
||||
FieldOperationTag OperationT = FieldOperation::Value>
|
||||
struct Operand {
|
||||
using Quantity = QuantityT;
|
||||
using Operation = OperationT;
|
||||
|
||||
static_assert(
|
||||
FieldQuantity<QuantityT> || std::same_as<
|
||||
OperationT,
|
||||
FieldOperation::Value>,
|
||||
"Global scalar quantities support only the value operation."
|
||||
);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept FieldOperand =
|
||||
requires {
|
||||
typename T::Quantity;
|
||||
typename T::Operation;
|
||||
} && RegisteredQuantity<typename T::Quantity> &&
|
||||
FieldOperationTag<typename T::Operation>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Weak-form descriptions
|
||||
//
|
||||
// PolicyKeyV associates the form with a runtime quadrature-policy key.
|
||||
//
|
||||
// DynamicOrderCountV is the number of polynomial-order contributions that
|
||||
// cannot yet be derived from registered quantities. For example, a source
|
||||
// coefficient supplied at runtime contributes one dynamic order.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <
|
||||
auto PolicyKeyV,
|
||||
std::size_t DynamicOrderCountV,
|
||||
FieldOperand... OperandTs>
|
||||
struct FormSpec {
|
||||
static constexpr auto policyKey = PolicyKeyV;
|
||||
static constexpr std::size_t dynamicOrderCount = DynamicOrderCountV;
|
||||
|
||||
using Operands = TypeList<OperandTs...>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept FieldForm = requires {
|
||||
typename T::Operands;
|
||||
|
||||
T::policyKey;
|
||||
|
||||
{ T::dynamicOrderCount } -> std::convertible_to<std::size_t>;
|
||||
};
|
||||
|
||||
template <typename ListT>
|
||||
struct IsRegisteredQuantityList : std::false_type { };
|
||||
|
||||
template <RegisteredQuantity... QuantityTs>
|
||||
struct IsRegisteredQuantityList<TypeList<QuantityTs...>> : std::true_type {
|
||||
};
|
||||
|
||||
template <typename ListT>
|
||||
inline constexpr bool isRegisteredQuantityList =
|
||||
IsRegisteredQuantityList<ListT>::value;
|
||||
|
||||
template <typename ListT> struct IsFieldFormList : std::false_type { };
|
||||
|
||||
template <FieldForm... FormTs>
|
||||
struct IsFieldFormList<TypeList<FormTs...>> : std::true_type { };
|
||||
|
||||
template <typename ListT>
|
||||
inline constexpr bool isFieldFormList = IsFieldFormList<ListT>::value;
|
||||
} // namespace mean_field::field
|
||||
380
libmeanfield/interface/field/field_mfem.cppm
Normal file
380
libmeanfield/interface/field/field_mfem.cppm
Normal file
@@ -0,0 +1,380 @@
|
||||
module;
|
||||
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:field.mfem;
|
||||
|
||||
export import :field.registry;
|
||||
|
||||
namespace mean_field::field::detail {
|
||||
template <typename T> inline constexpr bool alwaysFalse = false;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MFEM finite-element collection construction
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <typename SpaceT> struct FecFor;
|
||||
|
||||
template <> struct FecFor<L2> {
|
||||
static std::unique_ptr<mfem::FiniteElementCollection> make(
|
||||
int familyOrder,
|
||||
int dimension
|
||||
) {
|
||||
return std::make_unique<mfem::L2_FECollection>(
|
||||
familyOrder, dimension
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct FecFor<H1> {
|
||||
static std::unique_ptr<mfem::FiniteElementCollection> make(
|
||||
int familyOrder,
|
||||
int dimension
|
||||
) {
|
||||
return std::make_unique<mfem::H1_FECollection>(
|
||||
familyOrder, dimension
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct FecFor<RT> {
|
||||
static std::unique_ptr<mfem::FiniteElementCollection> make(
|
||||
int familyOrder,
|
||||
int dimension
|
||||
) {
|
||||
return std::make_unique<mfem::RT_FECollection>(
|
||||
familyOrder, dimension
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct FecFor<ND> {
|
||||
static std::unique_ptr<mfem::FiniteElementCollection> make(
|
||||
int familyOrder,
|
||||
int dimension
|
||||
) {
|
||||
return std::make_unique<mfem::ND_FECollection>(
|
||||
familyOrder, dimension
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MFEM polynomial-order interpretation
|
||||
//
|
||||
// familyOrder is the collection constructor argument.
|
||||
//
|
||||
// For RT_p:
|
||||
// value order = p + 1
|
||||
// divergence order = p
|
||||
// normal-trace order = p
|
||||
//
|
||||
// This distinction is what allows Disc<RT, p> and Disc<L2, p> to form a
|
||||
// compatible pair while still giving different value-shape orders.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <typename OperandT> struct MfemOperandOrder;
|
||||
|
||||
template <RegisteredQuantity QuantityT, FieldOperationTag OperationT>
|
||||
struct MfemOperandOrder<Operand<QuantityT, OperationT>> {
|
||||
static constexpr int orderValue = []() consteval {
|
||||
if constexpr (GlobalScalarQuantity<QuantityT>) {
|
||||
static_assert(
|
||||
std::same_as<OperationT, FieldOperation::Value>,
|
||||
"Global scalars support only the value operation."
|
||||
);
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
using Space = typename QuantityT::Space;
|
||||
constexpr int familyOrder = QuantityT::familyOrder;
|
||||
|
||||
if constexpr (std::same_as<OperationT, FieldOperation::Value>) {
|
||||
if constexpr (std::same_as<Space, RT>) {
|
||||
return familyOrder + 1;
|
||||
} else {
|
||||
return familyOrder;
|
||||
}
|
||||
} else if constexpr (
|
||||
std::same_as<OperationT, FieldOperation::Divergence>
|
||||
) {
|
||||
static_assert(
|
||||
std::same_as<Space, RT>,
|
||||
"Only RT quantities currently support the divergence "
|
||||
"polynomial-order rule."
|
||||
);
|
||||
|
||||
return familyOrder;
|
||||
} else if constexpr (
|
||||
std::same_as<OperationT, FieldOperation::Gradient>
|
||||
) {
|
||||
static_assert(
|
||||
std::same_as<Space, H1>,
|
||||
"Only H1 quantities currently support the gradient "
|
||||
"polynomial-order rule."
|
||||
);
|
||||
|
||||
return familyOrder > 0 ? familyOrder - 1 : 0;
|
||||
} else if constexpr (
|
||||
std::same_as<OperationT, FieldOperation::Curl>
|
||||
) {
|
||||
static_assert(
|
||||
std::same_as<Space, ND>,
|
||||
"Only ND quantities currently support the curl "
|
||||
"polynomial-order rule."
|
||||
);
|
||||
|
||||
return familyOrder > 0 ? familyOrder - 1 : 0;
|
||||
} else if constexpr (
|
||||
std::same_as<OperationT, FieldOperation::NormalTrace>
|
||||
) {
|
||||
static_assert(
|
||||
std::same_as<Space, RT>,
|
||||
"Only RT quantities currently support the normal-trace "
|
||||
"polynomial-order rule."
|
||||
);
|
||||
|
||||
return familyOrder;
|
||||
} else {
|
||||
static_assert(
|
||||
alwaysFalse<OperationT>,
|
||||
"Unsupported MFEM field operation."
|
||||
);
|
||||
}
|
||||
}
|
||||
}();
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Static polynomial-order contribution of an entire form
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <typename FormT> struct MfemFormOrder;
|
||||
|
||||
template <
|
||||
auto PolicyKeyV,
|
||||
std::size_t DynamicOrderCountV,
|
||||
FieldOperand... OperandTs>
|
||||
struct MfemFormOrder<
|
||||
FormSpec<PolicyKeyV, DynamicOrderCountV, OperandTs...>> {
|
||||
static constexpr int staticOrder =
|
||||
(MfemOperandOrder<OperandTs>::orderValue + ... + 0);
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MFEM vector-dimension and ordering rules
|
||||
//
|
||||
// Vector H1/L2 fields are represented using multiple copies of a scalar
|
||||
// finite-element space. RT and ND elements are intrinsically vector-valued
|
||||
// and therefore use vdim = 1.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <FieldQuantity QuantityT> int get_vdim(int spaceDimension) {
|
||||
if (spaceDimension <= 0) {
|
||||
throw std::invalid_argument("Space dimension must be positive.");
|
||||
}
|
||||
|
||||
if constexpr (QuantityT::rankValue == 0) {
|
||||
return 1;
|
||||
} else if constexpr (
|
||||
std::same_as<typename QuantityT::Space, H1> ||
|
||||
std::same_as<typename QuantityT::Space, L2>
|
||||
) {
|
||||
return spaceDimension;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <FieldQuantity QuantityT>
|
||||
constexpr mfem::Ordering::Type get_ordering() {
|
||||
if constexpr (
|
||||
QuantityT::rankValue == 1 &&
|
||||
(std::same_as<typename QuantityT::Space, H1> ||
|
||||
std::same_as<typename QuantityT::Space, L2>)
|
||||
) {
|
||||
return mfem::Ordering::byVDIM;
|
||||
} else {
|
||||
return mfem::Ordering::byNODES;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Quantity-specific MFEM realization
|
||||
//
|
||||
// Backend choices that are part of a field definition live here rather
|
||||
// than leaking into FEM setup or call sites.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <FieldQuantity QuantityT> struct MfemQuantityTraits {
|
||||
static std::unique_ptr<mfem::FiniteElementCollection>
|
||||
make_fec(int dimension) {
|
||||
return FecFor<typename QuantityT::Space>::make(
|
||||
QuantityT::familyOrder, dimension
|
||||
);
|
||||
}
|
||||
|
||||
static constexpr mfem::Ordering::Type ordering =
|
||||
get_ordering<QuantityT>();
|
||||
};
|
||||
|
||||
template <> struct MfemQuantityTraits<Gravity::Flux> {
|
||||
static std::unique_ptr<mfem::FiniteElementCollection>
|
||||
make_fec(int dimension) {
|
||||
return std::make_unique<mfem::RT_FECollection>(
|
||||
Gravity::Flux::familyOrder, dimension,
|
||||
mfem::BasisType::GaussLobatto, mfem::BasisType::IntegratedGLL
|
||||
);
|
||||
}
|
||||
|
||||
static constexpr mfem::Ordering::Type ordering =
|
||||
mfem::Ordering::byNODES;
|
||||
};
|
||||
|
||||
template <> struct MfemQuantityTraits<Displacement::Vector> {
|
||||
static std::unique_ptr<mfem::FiniteElementCollection>
|
||||
make_fec(int dimension) {
|
||||
return FecFor<H1>::make(
|
||||
Displacement::Vector::familyOrder, dimension
|
||||
);
|
||||
}
|
||||
|
||||
static constexpr mfem::Ordering::Type ordering =
|
||||
mfem::Ordering::byNODES;
|
||||
};
|
||||
} // namespace mean_field::field::detail
|
||||
|
||||
export namespace mean_field::field {
|
||||
// -------------------------------------------------------------------------
|
||||
// User-facing field type
|
||||
//
|
||||
// The object itself is currently a zero-cost compile-time descriptor:
|
||||
//
|
||||
// Field<Gravity> gravityField;
|
||||
//
|
||||
// MFEM construction and typed quadrature-query generation are provided as
|
||||
// static operations. Runtime ownership can later be added without changing
|
||||
// Gravity, Displacement, or their form definitions.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <FieldTag TagT> class Field {
|
||||
public:
|
||||
using Tag = TagT;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// MFEM finite-element collection construction
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
template <FieldQuantity QuantityT>
|
||||
requires typeListContains<
|
||||
QuantityT,
|
||||
typename TagT::Quantities>
|
||||
static std::unique_ptr<mfem::FiniteElementCollection>
|
||||
make_fec(int dimension) {
|
||||
if (dimension <= 0) {
|
||||
throw std::invalid_argument("Mesh dimension must be positive.");
|
||||
}
|
||||
|
||||
return detail::MfemQuantityTraits<QuantityT>::make_fec(dimension);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// MFEM parallel finite-element space construction
|
||||
//
|
||||
// The finite-element collection must outlive the returned space.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
template <FieldQuantity QuantityT>
|
||||
requires typeListContains<
|
||||
QuantityT,
|
||||
typename TagT::Quantities>
|
||||
static std::unique_ptr<mfem::ParFiniteElementSpace> make_fespace(
|
||||
mfem::ParMesh &mesh,
|
||||
mfem::FiniteElementCollection &finiteElementCollection
|
||||
) {
|
||||
return std::make_unique<mfem::ParFiniteElementSpace>(
|
||||
&mesh, &finiteElementCollection,
|
||||
detail::get_vdim<QuantityT>(mesh.SpaceDimension()),
|
||||
detail::MfemQuantityTraits<QuantityT>::ordering
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Typed quadrature-query construction
|
||||
//
|
||||
// geometryWeightOrder is supplied at runtime because it depends on the
|
||||
// actual element transformation.
|
||||
//
|
||||
// dynamicOrders contains the form-specific polynomial orders that are
|
||||
// not represented by registered compile-time quantities.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// Density::Form::CenterOfMass:
|
||||
// { positionOrder }
|
||||
//
|
||||
// Gravity source forms need no dynamic orders because density and
|
||||
// potential are both registered quantities.
|
||||
//
|
||||
// The completed base order is stored in Query::base_order, so Policy
|
||||
// does not need to understand divergence, RT conventions, or individual
|
||||
// field layouts.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
template <FieldForm FormT>
|
||||
requires typeListContains<
|
||||
FormT,
|
||||
typename TagT::FormList>
|
||||
static constexpr quadrature::Query make_query(
|
||||
quadrature::QuadratureRole role,
|
||||
int geometryWeightOrder,
|
||||
std::array<
|
||||
int,
|
||||
FormT::dynamicOrderCount> dynamicOrders = {},
|
||||
utils::DOMAINS domain = utils::DOMAINS::ALL,
|
||||
quadrature::MappingKind mapping = quadrature::MappingKind::none
|
||||
) {
|
||||
if (geometryWeightOrder < 0) {
|
||||
throw std::invalid_argument(
|
||||
"Geometry weight order cannot be negative."
|
||||
);
|
||||
}
|
||||
|
||||
int baseOrder =
|
||||
detail::MfemFormOrder<FormT>::staticOrder + geometryWeightOrder;
|
||||
|
||||
for (const int dynamicOrder : dynamicOrders) {
|
||||
if (dynamicOrder < 0) {
|
||||
throw std::invalid_argument(
|
||||
"Dynamic polynomial orders cannot be negative."
|
||||
);
|
||||
}
|
||||
|
||||
baseOrder += dynamicOrder;
|
||||
}
|
||||
|
||||
return {
|
||||
.term = FormT::policyKey,
|
||||
.role = role,
|
||||
.domain = domain,
|
||||
.mapping = mapping,
|
||||
.trial_order = 0,
|
||||
.test_order = 0,
|
||||
.coefficient_order = 0,
|
||||
.geometry_weight_order = geometryWeightOrder,
|
||||
.base_order = baseOrder
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(FieldTag<Gravity>);
|
||||
static_assert(FieldTag<Displacement>);
|
||||
static_assert(FieldTag<Density>);
|
||||
static_assert(FieldTag<BarotropicConstant>);
|
||||
} // namespace mean_field::field
|
||||
379
libmeanfield/interface/field/field_registry.cppm
Normal file
379
libmeanfield/interface/field/field_registry.cppm
Normal file
@@ -0,0 +1,379 @@
|
||||
module;
|
||||
|
||||
#include <concepts>
|
||||
#include <string_view>
|
||||
|
||||
export module mean_field:field.registry;
|
||||
|
||||
export import :field.base;
|
||||
export import :quadrature.policy;
|
||||
|
||||
export namespace mean_field::field {
|
||||
// =========================================================================
|
||||
// Density
|
||||
// =========================================================================
|
||||
|
||||
struct Density {
|
||||
static constexpr std::string_view name = "density";
|
||||
static constexpr int scalarOrder = 2;
|
||||
|
||||
struct Scalar final
|
||||
: ScalarQ<FieldRelation::Independent, Disc<L2, scalarOrder>> {
|
||||
static constexpr std::string_view symbol = "ρ";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Scalar>;
|
||||
using Constraints = TypeList<>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
|
||||
struct Form {
|
||||
// Density-space mass matrix: (rho, q).
|
||||
using ProjectionMass = FormSpec<
|
||||
quadrature::Term::density_projection,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Projection RHS with one runtime coefficient order.
|
||||
using ProjectionSource = FormSpec<
|
||||
quadrature::Term::density_projection,
|
||||
1,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Density-space contribution to the barotropic EOS closure:
|
||||
// (rho, q_rho).
|
||||
using EosClosureMass = FormSpec<
|
||||
quadrature::Term::eos_closure,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Integral of density over the physical volume.
|
||||
using MassConservation = FormSpec<
|
||||
quadrature::Term::mass_conservation,
|
||||
0,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// The same physical integral used as a nonlinear normalization
|
||||
// constraint. It has a distinct policy key so solver assembly and
|
||||
// diagnostics can be overintegrated independently.
|
||||
using MassNormalization = FormSpec<
|
||||
quadrature::Term::mass_normalization,
|
||||
0,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Integral of rho * x. The combined position-coefficient order is
|
||||
// supplied as one dynamic order.
|
||||
using CenterOfMass =
|
||||
FormSpec<quadrature::Term::center_of_mass, 1, Operand<Scalar>>;
|
||||
|
||||
// Integral of rho times the quadratic position tensor. The
|
||||
// combined tensor-coefficient order is supplied dynamically.
|
||||
using Quadrupole =
|
||||
FormSpec<quadrature::Term::quadrupole, 1, Operand<Scalar>>;
|
||||
|
||||
using ErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
};
|
||||
|
||||
using FormList = TypeList<
|
||||
Form::ProjectionMass,
|
||||
Form::ProjectionSource,
|
||||
Form::EosClosureMass,
|
||||
Form::MassConservation,
|
||||
Form::MassNormalization,
|
||||
Form::CenterOfMass,
|
||||
Form::Quadrupole,
|
||||
Form::ErrorNorm>;
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Gravity
|
||||
// =========================================================================
|
||||
|
||||
struct Gravity {
|
||||
static constexpr std::string_view name = "gravity";
|
||||
|
||||
static constexpr int potentialOrder = 2;
|
||||
static constexpr int fluxOrder = 2;
|
||||
|
||||
struct Potential final
|
||||
: ScalarQ<FieldRelation::Independent, Disc<L2, potentialOrder>> {
|
||||
static constexpr std::string_view symbol = "φ";
|
||||
};
|
||||
|
||||
struct Flux final
|
||||
: VectorQ<FieldRelation::Gradient<Potential>, Disc<RT, fluxOrder>> {
|
||||
static constexpr std::string_view symbol = "∇φ";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Potential, Flux>;
|
||||
|
||||
using Constraints = TypeList<RtL2StablePair<Flux, Potential>>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
|
||||
struct Form {
|
||||
using HDivMass = FormSpec<
|
||||
quadrature::Term::gravity_hdiv_mass,
|
||||
0,
|
||||
Operand<Flux>,
|
||||
Operand<Flux>>;
|
||||
|
||||
using DivergenceCoupling = FormSpec<
|
||||
quadrature::Term::gravity_divergence,
|
||||
0,
|
||||
Operand<Flux, FieldOperation::Divergence>,
|
||||
Operand<Potential>>;
|
||||
|
||||
using Boundary = FormSpec<
|
||||
quadrature::Term::gravity_boundary,
|
||||
0,
|
||||
Operand<Flux, FieldOperation::NormalTrace>,
|
||||
Operand<Flux, FieldOperation::NormalTrace>>;
|
||||
|
||||
// Density is a registered coefficient field and potential is the
|
||||
// test field, so the full polynomial order is compile-time data.
|
||||
using SourceLinear = FormSpec<
|
||||
quadrature::Term::gravity_source,
|
||||
0,
|
||||
Operand<Density::Scalar>,
|
||||
Operand<Potential>>;
|
||||
|
||||
// Mixed density-to-potential projection. Both trial and test
|
||||
// orders are registered quantities.
|
||||
using SourceProjection = FormSpec<
|
||||
quadrature::Term::gravity_source,
|
||||
0,
|
||||
Operand<Density::Scalar>,
|
||||
Operand<Potential>>;
|
||||
|
||||
using PotentialErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
0,
|
||||
Operand<Potential>,
|
||||
Operand<Potential>>;
|
||||
|
||||
using FluxErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
0,
|
||||
Operand<Flux>,
|
||||
Operand<Flux>>;
|
||||
};
|
||||
|
||||
using FormList = TypeList<
|
||||
Form::HDivMass,
|
||||
Form::DivergenceCoupling,
|
||||
Form::Boundary,
|
||||
Form::SourceLinear,
|
||||
Form::SourceProjection,
|
||||
Form::PotentialErrorNorm,
|
||||
Form::FluxErrorNorm>;
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Displacement
|
||||
// =========================================================================
|
||||
|
||||
struct Displacement {
|
||||
static constexpr std::string_view name = "displacement";
|
||||
static constexpr int vectorOrder = 3;
|
||||
|
||||
struct Vector final
|
||||
: VectorQ<FieldRelation::Independent, Disc<H1, vectorOrder>> {
|
||||
static constexpr std::string_view symbol = "d";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Vector>;
|
||||
using Constraints = TypeList<>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
|
||||
struct Form {
|
||||
// Harmonic or pseudoelastic interior mesh extension. For the
|
||||
// initial Laplacian model this is (grad d, grad w).
|
||||
using MeshExtension = FormSpec<
|
||||
quadrature::Term::mesh_extension,
|
||||
0,
|
||||
Operand<Vector, FieldOperation::Gradient>,
|
||||
Operand<Vector, FieldOperation::Gradient>>;
|
||||
|
||||
using ErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
0,
|
||||
Operand<Vector>,
|
||||
Operand<Vector>>;
|
||||
};
|
||||
|
||||
using FormList = TypeList<Form::MeshExtension, Form::ErrorNorm>;
|
||||
};
|
||||
|
||||
struct BarotropicConstant {
|
||||
static constexpr std::string_view name = "barotropic_constant";
|
||||
|
||||
struct Scalar final : GlobalScalarQ {
|
||||
static constexpr std::string_view symbol = "C";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Scalar>;
|
||||
using Constraints = TypeList<>;
|
||||
using FormList = TypeList<>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Specific enthalpy
|
||||
//
|
||||
// Pressure is deliberately not registered as an independent field. For a
|
||||
// barotrope it is derived from h through the EOS, while h supplies the
|
||||
// continuous H1 trace used to define the isobaric stellar surface.
|
||||
// =========================================================================
|
||||
|
||||
struct Enthalpy {
|
||||
static constexpr std::string_view name = "specific_enthalpy";
|
||||
static constexpr int scalarOrder = 3;
|
||||
|
||||
struct Scalar final
|
||||
: ScalarQ<FieldRelation::Independent, Disc<H1, scalarOrder>> {
|
||||
static constexpr std::string_view symbol = "h";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Scalar>;
|
||||
using Constraints = TypeList<>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
|
||||
struct Form {
|
||||
// EOS source contribution (rho(h), q_rho). The dynamic order is
|
||||
// the extra polynomial order introduced by the nonlinear EOS
|
||||
// beyond the registered order of h. For an n=3 polytrope this is
|
||||
// 2 * hOrder, making rho(h) cubic in h.
|
||||
using EosClosureSource = FormSpec<
|
||||
quadrature::Term::eos_closure,
|
||||
1,
|
||||
Operand<Scalar>,
|
||||
Operand<Density::Scalar>>;
|
||||
|
||||
// (h, q_h) contribution to
|
||||
// h + phi - Psi_rotation - C = 0.
|
||||
using EquilibriumEnthalpy = FormSpec<
|
||||
quadrature::Term::hydrostatic_equilibrium,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// (phi, q_h) contribution to hydrostatic equilibrium.
|
||||
using EquilibriumGravity = FormSpec<
|
||||
quadrature::Term::hydrostatic_equilibrium,
|
||||
0,
|
||||
Operand<Gravity::Potential>,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// (Psi_rotation, q_h). The rotation-potential order is supplied
|
||||
// dynamically because it belongs to runtime rotation data.
|
||||
using EquilibriumRotation = FormSpec<
|
||||
quadrature::Term::hydrostatic_equilibrium,
|
||||
1,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// (C, q_h), where C is spatially constant.
|
||||
using EquilibriumConstant = FormSpec<
|
||||
quadrature::Term::hydrostatic_equilibrium,
|
||||
0,
|
||||
Operand<BarotropicConstant::Scalar>,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Boundary trace form available for weak enforcement, testing, or
|
||||
// a future multiplier formulation of h|Gamma_star = 0.
|
||||
using IsobaricSurface = FormSpec<
|
||||
quadrature::Term::isobaric_surface,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Integral of P(h). The dynamic order is the extra EOS order
|
||||
// beyond the registered order of h.
|
||||
using PressureIntegral = FormSpec<
|
||||
quadrature::Term::pressure_integral,
|
||||
1,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Weak pressure force in the displacement test space:
|
||||
//
|
||||
// -int P(h) I : grad(w) dV
|
||||
//
|
||||
// which is equivalent to -int P(h) div(w) dV. The dynamic order
|
||||
// is the extra EOS order beyond the registered order of h. For an
|
||||
// n=3 polytrope this is 3 * hOrder, making P(h) quartic in h.
|
||||
using PressureForce = FormSpec<
|
||||
quadrature::Term::pressure_force,
|
||||
1,
|
||||
Operand<Scalar>,
|
||||
Operand<Displacement::Vector, FieldOperation::Gradient>>;
|
||||
|
||||
using ErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
};
|
||||
|
||||
using FormList = TypeList<
|
||||
Form::EosClosureSource,
|
||||
Form::EquilibriumEnthalpy,
|
||||
Form::EquilibriumGravity,
|
||||
Form::EquilibriumRotation,
|
||||
Form::EquilibriumConstant,
|
||||
Form::IsobaricSurface,
|
||||
Form::PressureIntegral,
|
||||
Form::PressureForce,
|
||||
Form::ErrorNorm>;
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Field definition concept
|
||||
// =========================================================================
|
||||
|
||||
template <typename T>
|
||||
concept FieldTag =
|
||||
requires {
|
||||
typename T::Quantities;
|
||||
typename T::Constraints;
|
||||
typename T::FormList;
|
||||
|
||||
{ T::name } -> std::convertible_to<std::string_view>;
|
||||
} && isRegisteredQuantityList<typename T::Quantities> &&
|
||||
isFieldFormList<typename T::FormList>;
|
||||
|
||||
static_assert(FieldTag<Gravity>);
|
||||
static_assert(FieldTag<Displacement>);
|
||||
static_assert(FieldTag<Density>);
|
||||
static_assert(FieldTag<Enthalpy>);
|
||||
static_assert(FieldTag<BarotropicConstant>);
|
||||
|
||||
static_assert(DerivedQuantity<Gravity::Flux>);
|
||||
|
||||
static_assert(std::same_as<
|
||||
RelationTargetT<Gravity::Flux>,
|
||||
Gravity::Potential>);
|
||||
} // namespace mean_field::field
|
||||
@@ -25,4 +25,4 @@ export namespace mean_field::integrators {
|
||||
private:
|
||||
const mapping::DomainMapper &m_map;
|
||||
};
|
||||
}
|
||||
} // namespace mean_field::integrators
|
||||
@@ -1,14 +1,19 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
export module mean_field:integrators.centrifugal;
|
||||
import :mapping.domain_mapper;
|
||||
export import :mapping.domain_mapper;
|
||||
|
||||
export namespace mean_field::integrators {
|
||||
class CentrifugalForceIntegrator : public mfem::BlockNonlinearFormIntegrator {
|
||||
class CentrifugalForceIntegrator
|
||||
: public mfem::BlockNonlinearFormIntegrator {
|
||||
public:
|
||||
CentrifugalForceIntegrator(const mapping::DomainMapper& map, const mfem::Vector& omega);
|
||||
CentrifugalForceIntegrator(
|
||||
const mapping::DomainMapper &map,
|
||||
const mfem::Vector &omega
|
||||
);
|
||||
|
||||
void SetOmega(const mfem::Vector& omega);
|
||||
void SetOmega(const mfem::Vector &omega);
|
||||
void SetIntegrationRule(const mfem::IntegrationRule &ir);
|
||||
|
||||
void AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
@@ -25,8 +30,9 @@ export namespace mean_field::integrators {
|
||||
) override;
|
||||
|
||||
private:
|
||||
const mapping::DomainMapper& m_map;
|
||||
const mapping::DomainMapper &m_map;
|
||||
mfem::Vector m_omega;
|
||||
const mfem::IntegrationRule *m_ir = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace mean_field::integrators
|
||||
@@ -3,26 +3,32 @@ module;
|
||||
export module mean_field:integrators.coriolis;
|
||||
import :mapping.domain_mapper;
|
||||
|
||||
|
||||
export namespace mean_field::integrators {
|
||||
class CoriolisIntegrator : public mfem::BlockNonlinearFormIntegrator {
|
||||
public:
|
||||
CoriolisIntegrator(const mapping::DomainMapper& map, const mfem::Vector& omega);
|
||||
CoriolisIntegrator(
|
||||
const mapping::DomainMapper &map,
|
||||
const mfem::Vector &omega
|
||||
);
|
||||
|
||||
void AssembleElementVector(const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
void AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array<mfem::Vector *> &elvec) override;
|
||||
const mfem::Array<mfem::Vector *> &elvec
|
||||
) override;
|
||||
|
||||
void AssembleElementGrad(const mfem::Array<const mfem::FiniteElement*> &el,
|
||||
void AssembleElementGrad(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats) override;
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) override;
|
||||
|
||||
private:
|
||||
const mapping::DomainMapper& m_map;
|
||||
const mapping::DomainMapper &m_map;
|
||||
mfem::Vector m_omega;
|
||||
mfem::DenseMatrix m_omega_mat;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace mean_field::integrators
|
||||
@@ -1,14 +1,29 @@
|
||||
module;
|
||||
#include <cstdint>
|
||||
#include <mfem.hpp>
|
||||
export module mean_field:integrators.gravity;
|
||||
import :mapping.domain_mapper;
|
||||
|
||||
export namespace mean_field::integrators {
|
||||
class GravityForceIntegrator : public mfem::BlockNonlinearFormIntegrator {
|
||||
public:
|
||||
GravityForceIntegrator(const mapping::DomainMapper& map, const mfem::GridFunction& phi);
|
||||
enum class GravityForceJacobianMode : std::uint8_t {
|
||||
minimal,
|
||||
field_coupled,
|
||||
exact
|
||||
};
|
||||
|
||||
void SetPotential(const mfem::GridFunction& phi);
|
||||
class GravityMomentumIntegrator
|
||||
: public mfem::BlockNonlinearFormIntegrator {
|
||||
public:
|
||||
explicit GravityMomentumIntegrator(
|
||||
const mapping::DomainMapper &map,
|
||||
GravityForceJacobianMode jacobian_mode =
|
||||
GravityForceJacobianMode::field_coupled
|
||||
);
|
||||
|
||||
void SetJacobianMode(GravityForceJacobianMode jacobian_mode);
|
||||
void SetIntegrationRule(const mfem::IntegrationRule &integration_rule);
|
||||
|
||||
[[nodiscard]] GravityForceJacobianMode GetJacobianMode() const;
|
||||
|
||||
void AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
@@ -16,7 +31,6 @@ export namespace mean_field::integrators {
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array<mfem::Vector *> &elvec
|
||||
) override;
|
||||
|
||||
void AssembleElementGrad(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
@@ -24,9 +38,9 @@ export namespace mean_field::integrators {
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) override;
|
||||
|
||||
|
||||
private:
|
||||
const mapping::DomainMapper& m_map;
|
||||
const mfem::GridFunction* m_phi;
|
||||
const mapping::DomainMapper &m_map;
|
||||
GravityForceJacobianMode m_jacobian_mode;
|
||||
const mfem::IntegrationRule *m_integration_rule{nullptr};
|
||||
};
|
||||
}
|
||||
} // namespace mean_field::integrators
|
||||
@@ -4,9 +4,10 @@ export module mean_field:integrators.mass_continuity;
|
||||
import :mapping.domain_mapper;
|
||||
|
||||
export namespace mean_field::integrators {
|
||||
class ContinuityVolumeIntegrator : public mfem::BlockNonlinearFormIntegrator {
|
||||
class ContinuityVolumeIntegrator
|
||||
: public mfem::BlockNonlinearFormIntegrator {
|
||||
public:
|
||||
explicit ContinuityVolumeIntegrator(const mapping::DomainMapper& map);
|
||||
explicit ContinuityVolumeIntegrator(const mapping::DomainMapper &map);
|
||||
|
||||
void AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
@@ -21,13 +22,14 @@ export namespace mean_field::integrators {
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) override;
|
||||
|
||||
private:
|
||||
const mapping::DomainMapper& m_map;
|
||||
const mapping::DomainMapper &m_map;
|
||||
};
|
||||
|
||||
class ContinuityFaceIntegrator : public mfem::BlockNonlinearFormIntegrator {
|
||||
public:
|
||||
explicit ContinuityFaceIntegrator(const mapping::DomainMapper& map);
|
||||
explicit ContinuityFaceIntegrator(const mapping::DomainMapper &map);
|
||||
|
||||
void AssembleFaceVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el1,
|
||||
@@ -44,19 +46,20 @@ export namespace mean_field::integrators {
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) override;
|
||||
|
||||
private:
|
||||
static bool skip_face(const mfem::FaceElementTransformations& Tr);
|
||||
static bool skip_face(const mfem::FaceElementTransformations &Tr);
|
||||
|
||||
static double compute_u_n(
|
||||
const mfem::Vector& v_dofs,
|
||||
const mfem::Vector& shape_v_minus,
|
||||
const mfem::Vector& n_unit,
|
||||
const mfem::Vector &v_dofs,
|
||||
const mfem::Vector &shape_v_minus,
|
||||
const mfem::Vector &n_unit,
|
||||
int dof_v_minus,
|
||||
int dim
|
||||
);
|
||||
|
||||
private:
|
||||
const mapping::DomainMapper& m_map;
|
||||
const mapping::DomainMapper &m_map;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace mean_field::integrators
|
||||
|
||||
@@ -1,36 +1,47 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
#include "xad_promote_polyfill.h"
|
||||
#include <XAD/XAD.hpp>
|
||||
#include <mfem.hpp>
|
||||
export module mean_field:integrators.pressure_gradient;
|
||||
import :mapping.domain_mapper;
|
||||
import :utils.misc;
|
||||
|
||||
export namespace mean_field::integrators {
|
||||
template <utils::is_xad EOS_T>
|
||||
class PressureGradientIntegrator : public mfem::BlockNonlinearFormIntegrator {
|
||||
class PressureGradientIntegrator
|
||||
: public mfem::BlockNonlinearFormIntegrator {
|
||||
public:
|
||||
PressureGradientIntegrator(const mapping::DomainMapper& map, utils::EOS_P<EOS_T> eos);
|
||||
PressureGradientIntegrator(
|
||||
const mapping::DomainMapper &map,
|
||||
utils::EOS_P<EOS_T> eos
|
||||
);
|
||||
|
||||
|
||||
void AssembleElementVector(const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
void AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array<mfem::Vector *> &elvec) override;
|
||||
void AssembleElementGrad(const mfem::Array<const mfem::FiniteElement*> &el,
|
||||
const mfem::Array<mfem::Vector *> &elvec
|
||||
) override;
|
||||
void AssembleElementGrad(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats) override;
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) override;
|
||||
|
||||
private:
|
||||
const mapping::DomainMapper& m_map;
|
||||
const mapping::DomainMapper &m_map;
|
||||
utils::EOS_P<EOS_T> m_eos;
|
||||
};
|
||||
|
||||
template <utils::is_xad EOS_T>
|
||||
PressureGradientIntegrator<EOS_T>::PressureGradientIntegrator(
|
||||
const mapping::DomainMapper& map,
|
||||
const mapping::DomainMapper &map,
|
||||
utils::EOS_P<EOS_T> eos
|
||||
) : m_map(map), m_eos(std::move(eos)) {}
|
||||
)
|
||||
: m_map(map),
|
||||
m_eos(std::move(eos)) {
|
||||
}
|
||||
|
||||
template <utils::is_xad EOS_T>
|
||||
void PressureGradientIntegrator<EOS_T>::AssembleElementVector(
|
||||
@@ -43,16 +54,16 @@ export namespace mean_field::integrators {
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
const mfem::FiniteElement *fe_v = el[0];
|
||||
const mfem::FiniteElement *fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& rho_dofs = *elfun[1];
|
||||
const mfem::Vector &rho_dofs = *elfun[1];
|
||||
|
||||
mfem::Vector& r_v = *elvec[0];
|
||||
mfem::Vector &r_v = *elvec[0];
|
||||
r_v.SetSize(dof_v * dim);
|
||||
r_v = 0.0;
|
||||
if (elvec[1]) {
|
||||
@@ -63,10 +74,11 @@ export namespace mean_field::integrators {
|
||||
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
|
||||
mfem::Vector shape_rho(dof_rho);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
const mfem::IntegrationRule *ir =
|
||||
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
@@ -76,10 +88,12 @@ export namespace mean_field::integrators {
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) rho_val += rho_dofs(i) * shape_rho(i);
|
||||
for (int i = 0; i < dof_rho; ++i)
|
||||
rho_val += rho_dofs(i) * shape_rho(i);
|
||||
|
||||
// Guard against negative density from Newton solver overshoots
|
||||
if (rho_val < 1e-15) rho_val = 1e-15;
|
||||
if (rho_val < 1e-15)
|
||||
rho_val = 1e-15;
|
||||
|
||||
// Evaluate the exact Equation of State Pressure
|
||||
EOS_T x_rho = rho_val;
|
||||
@@ -95,36 +109,40 @@ export namespace mean_field::integrators {
|
||||
|
||||
template <utils::is_xad EOS_T>
|
||||
void PressureGradientIntegrator<EOS_T>::AssembleElementGrad(
|
||||
const mfem::Array<const mfem::FiniteElement*> &el,
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
const mfem::FiniteElement *fe_v = el[0];
|
||||
const mfem::FiniteElement *fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& rho_dofs = *elfun[1];
|
||||
const mfem::Vector &rho_dofs = *elfun[1];
|
||||
|
||||
mfem::DenseMatrix* dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix* dv_drho = elmats(0, 1);
|
||||
mfem::DenseMatrix *dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix *dv_drho = elmats(0, 1);
|
||||
|
||||
if (dv_dv) *dv_dv = 0.0;
|
||||
if (dv_drho) *dv_drho = 0.0;
|
||||
if (!dv_drho) return;
|
||||
if (dv_dv)
|
||||
*dv_dv = 0.0;
|
||||
if (dv_drho)
|
||||
*dv_drho = 0.0;
|
||||
if (!dv_drho)
|
||||
return;
|
||||
|
||||
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
|
||||
mfem::Vector shape_rho(dof_rho);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
const mfem::IntegrationRule *ir =
|
||||
&mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
using Scalar = EOS_T::value_type;
|
||||
xad::Tape<Scalar> tape;
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
@@ -140,7 +158,8 @@ export namespace mean_field::integrators {
|
||||
for (int i = 0; i < dof_rho; ++i) {
|
||||
x_rho += rho_dofs(i) * shape_rho(i);
|
||||
}
|
||||
if (x_rho < 1e-15) x_rho = EOS_T(1e-15);
|
||||
if (x_rho < 1e-15)
|
||||
x_rho = EOS_T(1e-15);
|
||||
EOS_T x_P = m_eos(x_rho, EOS_T(0.0));
|
||||
tape.registerOutput(x_P);
|
||||
x_P.setAdjoint(1.0);
|
||||
@@ -149,7 +168,8 @@ export namespace mean_field::integrators {
|
||||
|
||||
double debug_K = 1.5;
|
||||
double debug_n = 3.0;
|
||||
double analytic_dp = debug_K * (1.0 + 1.0 / debug_n) * std::pow(xad::value(x_rho), 1.0 / debug_n);
|
||||
double analytic_dp = debug_K * (1.0 + 1.0 / debug_n) *
|
||||
std::pow(xad::value(x_rho), 1.0 / debug_n);
|
||||
|
||||
double ad_err = std::abs(dP_drho - analytic_dp);
|
||||
|
||||
@@ -158,11 +178,12 @@ export namespace mean_field::integrators {
|
||||
int row = i + c * dof_v;
|
||||
for (int j = 0; j < dof_rho; ++j) {
|
||||
int col = j;
|
||||
double term = dshape_v_phys(i, c) * dP_drho * shape_rho(j);
|
||||
double term =
|
||||
dshape_v_phys(i, c) * dP_drho * shape_rho(j);
|
||||
(*dv_drho)(row, col) -= term * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace mean_field::integrators
|
||||
|
||||
@@ -6,7 +6,11 @@ import :mapping.domain_mapper;
|
||||
export namespace mean_field::integrators {
|
||||
class ViscosityIntegrator : public mfem::BlockNonlinearFormIntegrator {
|
||||
public:
|
||||
ViscosityIntegrator(const mapping::DomainMapper& map, double mu, int quad_boost);
|
||||
ViscosityIntegrator(
|
||||
const mapping::DomainMapper &map,
|
||||
double mu,
|
||||
int quad_boost
|
||||
);
|
||||
|
||||
void SetMu(const double mu);
|
||||
|
||||
@@ -23,10 +27,11 @@ export namespace mean_field::integrators {
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) override;
|
||||
|
||||
private:
|
||||
const mapping::DomainMapper& m_map;
|
||||
const mapping::DomainMapper &m_map;
|
||||
double m_mu;
|
||||
int m_quad_boost;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace mean_field::integrators
|
||||
|
||||
@@ -10,7 +10,7 @@ export namespace mean_field::mapping {
|
||||
public:
|
||||
MappedScalarCoefficient(
|
||||
const DomainMapper &map,
|
||||
mfem::Coefficient &coeff,
|
||||
Coefficient &coeff,
|
||||
COORDINATE_SPACE coord_space = COORDINATE_SPACE::PHYSICAL
|
||||
);
|
||||
|
||||
@@ -21,14 +21,14 @@ export namespace mean_field::mapping {
|
||||
|
||||
private:
|
||||
static double eval_at_point(
|
||||
mfem::Coefficient &c,
|
||||
Coefficient &c,
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
);
|
||||
|
||||
private:
|
||||
const DomainMapper &m_map;
|
||||
mfem::Coefficient &m_coeff;
|
||||
Coefficient &m_coeff;
|
||||
COORDINATE_SPACE m_coord_space;
|
||||
};
|
||||
|
||||
@@ -42,29 +42,37 @@ export namespace mean_field::mapping {
|
||||
|
||||
MappedDiffusionCoefficient(
|
||||
const DomainMapper &map,
|
||||
mfem::MatrixCoefficient &sigma
|
||||
MatrixCoefficient &sigma
|
||||
);
|
||||
|
||||
void Eval(mfem::DenseMatrix &K, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) override;
|
||||
void Eval(
|
||||
mfem::DenseMatrix &K,
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) override;
|
||||
|
||||
private:
|
||||
const DomainMapper &m_map;
|
||||
mfem::Coefficient *m_scalar;
|
||||
mfem::MatrixCoefficient *m_tensor;
|
||||
MatrixCoefficient *m_tensor;
|
||||
};
|
||||
|
||||
class MappedVectorCoefficient : public mfem::VectorCoefficient {
|
||||
public:
|
||||
MappedVectorCoefficient(
|
||||
const DomainMapper &map,
|
||||
mfem::VectorCoefficient &coeff
|
||||
VectorCoefficient &coeff
|
||||
);
|
||||
|
||||
void Eval(mfem::Vector &V, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) override;
|
||||
void Eval(
|
||||
mfem::Vector &V,
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) override;
|
||||
|
||||
private:
|
||||
const DomainMapper &m_map;
|
||||
mfem::VectorCoefficient &m_coeff;
|
||||
VectorCoefficient &m_coeff;
|
||||
};
|
||||
|
||||
class PhysicalPositionFunctionCoefficient : public mfem::Coefficient {
|
||||
@@ -76,7 +84,10 @@ export namespace mean_field::mapping {
|
||||
Func f
|
||||
);
|
||||
|
||||
double Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) override;
|
||||
double Eval(
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) override;
|
||||
|
||||
private:
|
||||
Func m_f;
|
||||
@@ -85,11 +96,18 @@ export namespace mean_field::mapping {
|
||||
|
||||
class MappedHDivMassCoefficient final : public mfem::MatrixCoefficient {
|
||||
public:
|
||||
MappedHDivMassCoefficient(const DomainMapper& map, const int dim);
|
||||
MappedHDivMassCoefficient(
|
||||
const DomainMapper &map,
|
||||
const int dim
|
||||
);
|
||||
|
||||
void Eval(
|
||||
mfem::DenseMatrix &matrix,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point
|
||||
) override;
|
||||
|
||||
void Eval(mfem::DenseMatrix& matrix, mfem::ElementTransformation& transformation, const mfem::IntegrationPoint& integration_point) override;
|
||||
private:
|
||||
const DomainMapper& m_map;
|
||||
|
||||
const DomainMapper &m_map;
|
||||
};
|
||||
}
|
||||
} // namespace mean_field::mapping
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
export module mean_field:mapping.compactification;
|
||||
export import :mapping.types;
|
||||
|
||||
export namespace mean_field::mapping::compactification {
|
||||
struct ExteriorMapInput {
|
||||
const mfem::Vector &reference_position;
|
||||
const mfem::Vector &displaced_position;
|
||||
const mfem::DenseMatrix &displacement_jacobian;
|
||||
double compactification_coordinate;
|
||||
const mfem::Vector &compactification_coordinate_gradient;
|
||||
};
|
||||
|
||||
struct ExteriorMapResult {
|
||||
mfem::Vector physical_position;
|
||||
mfem::DenseMatrix mapping_jacobian;
|
||||
};
|
||||
|
||||
struct ExteriorMapDirection {
|
||||
const mfem::Vector &displaced_position_variation;
|
||||
const mfem::DenseMatrix &displacement_jacobian_variation;
|
||||
};
|
||||
|
||||
struct ExteriorMapVariation {
|
||||
mfem::Vector physical_position_variation;
|
||||
mfem::DenseMatrix mapping_jacobian_variation;
|
||||
};
|
||||
|
||||
class ExteriorDomainMap {
|
||||
public:
|
||||
virtual ~ExteriorDomainMap() = default;
|
||||
|
||||
[[nodiscard]] virtual MappingStatus Evaluate(
|
||||
const ExteriorMapInput &input,
|
||||
ExteriorMapResult &result
|
||||
) const = 0;
|
||||
[[nodiscard]] virtual MappingStatus EvaluateVariation(
|
||||
const ExteriorMapInput &input,
|
||||
const ExteriorMapResult &result,
|
||||
const ExteriorMapDirection &direction,
|
||||
ExteriorMapVariation &variation
|
||||
) const = 0;
|
||||
[[nodiscard]] virtual std::string_view GetName() const noexcept = 0;
|
||||
};
|
||||
} // namespace mean_field::mapping::compactification
|
||||
50
libmeanfield/interface/mapping/compactification/kelvin.cppm
Normal file
50
libmeanfield/interface/mapping/compactification/kelvin.cppm
Normal file
@@ -0,0 +1,50 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:mapping.kelvin;
|
||||
export import :mapping.compactification;
|
||||
export import :mapping.types;
|
||||
export import :mapping.compactification.options;
|
||||
|
||||
export namespace mean_field::mapping::compactification {
|
||||
|
||||
class KelvinCompactification final : public ExteriorDomainMap {
|
||||
public:
|
||||
explicit KelvinCompactification(
|
||||
options::KelvinCompactificationOptions options
|
||||
);
|
||||
|
||||
[[nodiscard]] MappingStatus Evaluate(
|
||||
const ExteriorMapInput &input,
|
||||
ExteriorMapResult &result
|
||||
) const override;
|
||||
|
||||
[[nodiscard]] MappingStatus EvaluateVariation(
|
||||
const ExteriorMapInput &input,
|
||||
const ExteriorMapResult &result,
|
||||
const ExteriorMapDirection &direction,
|
||||
ExteriorMapVariation &variation
|
||||
) const override;
|
||||
|
||||
[[nodiscard]] std::string_view GetName() const noexcept override;
|
||||
|
||||
[[nodiscard]] double GetReferenceStellarRadius() const noexcept;
|
||||
[[nodiscard]] double GetReferenceInfinityRadius() const noexcept;
|
||||
[[nodiscard]] double GetCoordinateTolerance() const noexcept;
|
||||
|
||||
private:
|
||||
struct RadialFactors {
|
||||
double coordinate;
|
||||
double computational_radius;
|
||||
double scale;
|
||||
double scale_derivative;
|
||||
};
|
||||
|
||||
[[nodiscard]] MappingStatus ComputeRadialFactors(
|
||||
double compactification_coordinate,
|
||||
RadialFactors &factors
|
||||
) const;
|
||||
|
||||
options::KelvinCompactificationOptions m_options;
|
||||
};
|
||||
} // namespace mean_field::mapping::compactification
|
||||
@@ -0,0 +1,9 @@
|
||||
export module mean_field:mapping.compactification.options;
|
||||
|
||||
export namespace mean_field::mapping::compactification::options {
|
||||
struct KelvinCompactificationOptions {
|
||||
double r_star_ref{1.0};
|
||||
double r_inf_ref{2.0};
|
||||
double coordinate_tolerance{1.0e-12};
|
||||
};
|
||||
} // namespace mean_field::mapping::compactification::options
|
||||
@@ -3,51 +3,302 @@ module;
|
||||
#include "mean_field.h"
|
||||
|
||||
export module mean_field:mapping.domain_mapper;
|
||||
export import :mapping.types;
|
||||
import :mapping.compactification;
|
||||
import :utils.user;
|
||||
|
||||
export namespace mean_field::mapping {
|
||||
enum class FaceElementSide : uint8_t { element_1, element_2 };
|
||||
|
||||
class ElementDisplacementData {
|
||||
public:
|
||||
ElementDisplacementData(
|
||||
const mfem::FiniteElement &element,
|
||||
const mfem::Vector &displacement_dofs,
|
||||
mfem::Ordering::Type ordering = mfem::Ordering::byNODES
|
||||
);
|
||||
|
||||
[[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept;
|
||||
[[nodiscard]] const mfem::DenseMatrix &GetDofMatrix() const noexcept;
|
||||
[[nodiscard]] int GetDimension() const noexcept;
|
||||
[[nodiscard]] int GetDofCount() const noexcept;
|
||||
[[nodiscard]] mfem::Ordering::Type GetOrdering() const noexcept;
|
||||
|
||||
private:
|
||||
const mfem::FiniteElement *m_element;
|
||||
mfem::DenseMatrix m_dof_matrix;
|
||||
int m_dimension;
|
||||
mfem::Ordering::Type m_ordering;
|
||||
};
|
||||
|
||||
struct CompactificationPointData {
|
||||
double coordinate{0.0};
|
||||
mfem::Vector coordinate_gradient;
|
||||
};
|
||||
|
||||
[[nodiscard]] ElementDisplacementData
|
||||
ElementDisplacementDataFromElementVDofs(
|
||||
const mfem::FiniteElement &element,
|
||||
const mfem::Vector &displacement_dofs
|
||||
);
|
||||
|
||||
class ElementCompactificationData {
|
||||
public:
|
||||
ElementCompactificationData(
|
||||
const mfem::FiniteElement &element,
|
||||
const mfem::Vector &dofs
|
||||
);
|
||||
|
||||
[[nodiscard]] const mfem::FiniteElement &GetElement() const noexcept;
|
||||
[[nodiscard]] const mfem::Vector &GetDofs() const noexcept;
|
||||
[[nodiscard]] int GetDofCount() const noexcept;
|
||||
|
||||
private:
|
||||
const mfem::FiniteElement *m_element;
|
||||
mfem::Vector m_dofs;
|
||||
};
|
||||
|
||||
struct ElementMappingData {
|
||||
const ElementDisplacementData &displacement;
|
||||
const ElementCompactificationData &compactification;
|
||||
};
|
||||
|
||||
class DomainMapperStateless {
|
||||
public:
|
||||
class Workspace {
|
||||
public:
|
||||
explicit Workspace(int dimension = 3);
|
||||
|
||||
void SetDimension(int dimension);
|
||||
|
||||
[[nodiscard]] int GetDimension() const noexcept;
|
||||
|
||||
private:
|
||||
friend class DomainMapperStateless;
|
||||
|
||||
int m_dimension;
|
||||
|
||||
mfem::Vector m_shape;
|
||||
mfem::DenseMatrix m_mesh_dshape;
|
||||
mfem::Vector m_field_value;
|
||||
mfem::DenseMatrix m_field_jacobian;
|
||||
|
||||
mfem::Vector m_compactification_shape;
|
||||
mfem::DenseMatrix m_compactification_dshape;
|
||||
CompactificationPointData m_compactification_point;
|
||||
|
||||
mfem::Vector m_reference_normal;
|
||||
mfem::Vector m_mapped_normal;
|
||||
mfem::DenseMatrix m_full_element_jacobian;
|
||||
|
||||
mfem::Vector m_vector_temp;
|
||||
mfem::DenseMatrix m_matrix_temp_1;
|
||||
mfem::DenseMatrix m_matrix_temp_2;
|
||||
|
||||
compactification::ExteriorMapResult m_exterior_result;
|
||||
compactification::ExteriorMapVariation m_exterior_variation;
|
||||
};
|
||||
|
||||
public:
|
||||
DomainMapperStateless(
|
||||
utils::DomainMapperStatelessOptions options,
|
||||
std::unique_ptr<const compactification::ExteriorDomainMap>
|
||||
exterior_map
|
||||
);
|
||||
|
||||
DomainMapperStateless(const DomainMapperStateless &) = delete;
|
||||
DomainMapperStateless &
|
||||
operator=(const DomainMapperStateless &) = delete;
|
||||
DomainMapperStateless(DomainMapperStateless &&) = default;
|
||||
DomainMapperStateless &operator=(DomainMapperStateless &&) = default;
|
||||
|
||||
[[nodiscard]] MappingStatus EvaluatePoint(
|
||||
const ElementMappingData &element_data,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
Workspace &workspace,
|
||||
MappingPointContext &context
|
||||
) const;
|
||||
|
||||
[[nodiscard]] MappingStatus EvaluateVolume(
|
||||
const ElementMappingData &element_data,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
Workspace &workspace,
|
||||
VolumeMappingContext &context
|
||||
) const;
|
||||
|
||||
[[nodiscard]] MappingStatus EvaluateFace(
|
||||
const ElementMappingData &element_data,
|
||||
mfem::FaceElementTransformations &transformation,
|
||||
FaceElementSide side,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
Workspace &workspace,
|
||||
FaceMappingContext &context
|
||||
) const;
|
||||
|
||||
[[nodiscard]] MappingStatus EvaluatePointVariation(
|
||||
const ElementMappingData &element_data,
|
||||
const ElementDisplacementData &direction,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
const MappingPointContext &base_context,
|
||||
Workspace &workspace,
|
||||
MappingPointVariation &variation
|
||||
) const;
|
||||
|
||||
[[nodiscard]] MappingStatus EvaluateVolumeVariation(
|
||||
const ElementMappingData &element_data,
|
||||
const ElementDisplacementData &direction,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
const VolumeMappingContext &base_context,
|
||||
Workspace &workspace,
|
||||
VolumeMappingVariation &variation
|
||||
) const;
|
||||
|
||||
[[nodiscard]] MappingStatus EvaluateFaceVariation(
|
||||
const ElementMappingData &element_data,
|
||||
const ElementDisplacementData &direction,
|
||||
mfem::FaceElementTransformations &transformation,
|
||||
FaceElementSide side,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
const FaceMappingContext &base_context,
|
||||
Workspace &workspace,
|
||||
FaceMappingVariation &variation
|
||||
) const;
|
||||
|
||||
[[nodiscard]] bool IsCompactifiedElement(
|
||||
const mfem::ElementTransformation &transformation
|
||||
) const noexcept;
|
||||
[[nodiscard]] int GetDimension() const noexcept;
|
||||
[[nodiscard]] int GetVacuumElementAttribute() const noexcept;
|
||||
[[nodiscard]] const compactification::ExteriorDomainMap &
|
||||
GetExteriorMap() const noexcept;
|
||||
|
||||
private:
|
||||
void ValidateElementData(const ElementMappingData &element_data) const;
|
||||
|
||||
void EvaluateField(
|
||||
const ElementDisplacementData &field,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
Workspace &workspace,
|
||||
mfem::Vector &value,
|
||||
mfem::DenseMatrix &jacobian
|
||||
) const;
|
||||
|
||||
[[nodiscard]] MappingStatus EvaluateCompactificationCoordinate(
|
||||
const ElementCompactificationData &compactification,
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
Workspace &workspace,
|
||||
CompactificationPointData &point_data
|
||||
) const;
|
||||
|
||||
[[nodiscard]] static mfem::ElementTransformation &
|
||||
SelectFaceElementTransformation(
|
||||
mfem::FaceElementTransformations &transformation,
|
||||
FaceElementSide side
|
||||
);
|
||||
|
||||
[[nodiscard]] static const mfem::IntegrationPoint &
|
||||
SelectFaceElementIntegrationPoint(
|
||||
mfem::FaceElementTransformations &transformation,
|
||||
FaceElementSide side
|
||||
);
|
||||
|
||||
utils::DomainMapperStatelessOptions m_options;
|
||||
std::unique_ptr<const compactification::ExteriorDomainMap>
|
||||
m_exterior_map;
|
||||
};
|
||||
class DomainMapper {
|
||||
public:
|
||||
struct VolumeQuadratureContext {
|
||||
mfem::DenseMatrix J_inv;
|
||||
double detJ;
|
||||
double weight;
|
||||
};
|
||||
|
||||
struct FaceQuadratureContext {
|
||||
mfem::Vector normal;
|
||||
double ds;
|
||||
double v_dot_n_scale;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit DomainMapper(const double r_star_ref, const double r_inf_ref);
|
||||
explicit DomainMapper(
|
||||
const double r_star_ref,
|
||||
const double r_inf_ref
|
||||
);
|
||||
|
||||
explicit DomainMapper(const mfem::GridFunction &d, const double r_star_ref, const double r_inf_ref);
|
||||
explicit DomainMapper(
|
||||
const mfem::GridFunction &d,
|
||||
const double r_star_ref,
|
||||
const double r_inf_ref
|
||||
);
|
||||
|
||||
[[nodiscard]] bool is_vacuum(const mfem::ElementTransformation &T) const;
|
||||
[[nodiscard]] bool
|
||||
is_vacuum(const mfem::ElementTransformation &T) const;
|
||||
|
||||
void SetDisplacement(const mfem::GridFunction &d);
|
||||
|
||||
[[nodiscard]] bool IsIdentity() const;
|
||||
[[nodiscard]] bool HasCompactification() const noexcept;
|
||||
[[nodiscard]] bool HasDisplacementField() const noexcept;
|
||||
[[nodiscard]] bool CalcIsIdentity() const;
|
||||
|
||||
void ResetDisplacement();
|
||||
|
||||
void ComputeJacobian(mfem::ElementTransformation &T, mfem::DenseMatrix &J) const;
|
||||
void ComputeJacobian(
|
||||
mfem::ElementTransformation &T,
|
||||
mfem::DenseMatrix &J
|
||||
) const;
|
||||
|
||||
double ComputeDetJ(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) const;
|
||||
double ComputeDetJ(
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) const;
|
||||
|
||||
void ComputeMappedDiffusionTensor(mfem::ElementTransformation &T, mfem::DenseMatrix &D) const;
|
||||
void ComputeMappedDiffusionTensor(
|
||||
mfem::ElementTransformation &T,
|
||||
mfem::DenseMatrix &D
|
||||
) const;
|
||||
|
||||
void ComputeInverseJacobian(mfem::ElementTransformation &T, mfem::DenseMatrix &JInv) const;
|
||||
void ComputeInverseJacobian(
|
||||
mfem::ElementTransformation &T,
|
||||
mfem::DenseMatrix &JInv
|
||||
) const;
|
||||
|
||||
VolumeQuadratureContext GetQuadratureContext(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) const;
|
||||
VolumeQuadratureContext GetQuadratureContext(
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) const;
|
||||
|
||||
FaceQuadratureContext GetFaceQuadratureContext(mfem::FaceElementTransformations &T, const mfem::IntegrationPoint &ip) const;
|
||||
FaceQuadratureContext GetFaceQuadratureContext(
|
||||
mfem::FaceElementTransformations &T,
|
||||
const mfem::IntegrationPoint &ip
|
||||
) const;
|
||||
|
||||
void GetPhysicalPoint(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip, mfem::Vector &x_phys) const;
|
||||
void GetPhysicalPoint(
|
||||
mfem::ElementTransformation &T,
|
||||
const mfem::IntegrationPoint &ip,
|
||||
mfem::Vector &x_phys
|
||||
) const;
|
||||
|
||||
void GetVectorValue(const int i, const mfem::IntegrationPoint &ip, mfem::Vector &val) const;
|
||||
void GetVectorValue(
|
||||
const int i,
|
||||
const mfem::IntegrationPoint &ip,
|
||||
mfem::Vector &val
|
||||
) const;
|
||||
|
||||
void MapHDivFluxToPhysical(
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
const mfem::Vector &reference_flux,
|
||||
mfem::Vector &physical_flux
|
||||
) const;
|
||||
|
||||
void MapPhysicalFluxToHDivReference(
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
const mfem::Vector &physical_flux,
|
||||
mfem::Vector &reference_flux
|
||||
) const;
|
||||
|
||||
void MapReferenceGradientToPhysical(
|
||||
mfem::ElementTransformation &transformation,
|
||||
const mfem::IntegrationPoint &integration_point,
|
||||
const mfem::Vector &reference_gradient,
|
||||
mfem::Vector &physical_gradient
|
||||
) const;
|
||||
[[nodiscard]] const mfem::GridFunction *GetDisplacement() const;
|
||||
|
||||
[[nodiscard]] double GetPhysInfRadius() const;
|
||||
@@ -63,9 +314,17 @@ export namespace mean_field::mapping {
|
||||
private:
|
||||
void InitAllScratchSpaces() const;
|
||||
|
||||
void ApplyKelvinMapping(const mfem::Vector &x_ref, mfem::Vector &x_phys) const;
|
||||
void ApplyKelvinMapping(
|
||||
const mfem::Vector &x_ref,
|
||||
mfem::Vector &x_phys
|
||||
) const;
|
||||
|
||||
void ComputeKelvinJacobian(const mfem::Vector &x_ref, const mfem::Vector &x_disp, const mfem::DenseMatrix &J_D, mfem::DenseMatrix &J) const;
|
||||
void ComputeKelvinJacobian(
|
||||
const mfem::Vector &x_ref,
|
||||
const mfem::Vector &x_disp,
|
||||
const mfem::DenseMatrix &J_D,
|
||||
mfem::DenseMatrix &J
|
||||
) const;
|
||||
|
||||
void InvalidateCache() const;
|
||||
|
||||
@@ -98,6 +357,8 @@ export namespace mean_field::mapping {
|
||||
mutable mfem::Vector m_x_ref;
|
||||
mutable mfem::Vector m_x_disp;
|
||||
mutable mfem::Vector m_d_val;
|
||||
|
||||
bool m_displacement_is_identity{true};
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace mean_field::mapping
|
||||
|
||||
87
libmeanfield/interface/mapping/transformations.cppm
Normal file
87
libmeanfield/interface/mapping/transformations.cppm
Normal file
@@ -0,0 +1,87 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
export module mean_field:mapping.transformations;
|
||||
export import :mapping.types;
|
||||
|
||||
export namespace mean_field::mapping {
|
||||
void MapHDivFluxToPhysical(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &reference_flux,
|
||||
mfem::Vector &physical_flux
|
||||
);
|
||||
void MapPhysicalFluxToHDivReference(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &physical_flux,
|
||||
mfem::Vector &reference_flux
|
||||
);
|
||||
|
||||
void MapReferenceGradientToPhysical(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &reference_gradient,
|
||||
mfem::Vector &physical_gradient
|
||||
);
|
||||
void MapPhysicalGradientToReference(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &physical_gradient,
|
||||
mfem::Vector &reference_gradient
|
||||
);
|
||||
|
||||
void MapReferenceVectorGradientToPhysical(
|
||||
const MappingPointContext &context,
|
||||
const mfem::DenseMatrix &reference_gradient,
|
||||
mfem::DenseMatrix &physical_gradient
|
||||
);
|
||||
void MapPhysicalVectorGradientToReference(
|
||||
const MappingPointContext &context,
|
||||
const mfem::DenseMatrix &physical_gradient,
|
||||
mfem::DenseMatrix &reference_gradient
|
||||
);
|
||||
|
||||
[[nodiscard]] double MapHDivDivergenceToPhysical(
|
||||
const MappingPointContext &context,
|
||||
double reference_divergence
|
||||
);
|
||||
|
||||
void ComputeHDivMassTensor(
|
||||
const MappingPointContext &context,
|
||||
mfem::DenseMatrix &mass_tensor
|
||||
);
|
||||
void ComputeScalarDiffusionTensor(
|
||||
const MappingPointContext &context,
|
||||
mfem::DenseMatrix &diffusion_tensor
|
||||
);
|
||||
|
||||
void MapHCurlFieldToPhysical(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &reference_field,
|
||||
mfem::Vector &physical_field
|
||||
);
|
||||
void MapPhysicalFieldToHCurlReference(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &physical_field,
|
||||
mfem::Vector &reference_field
|
||||
);
|
||||
void MapHCurlCurlToPhysical(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &reference_curl,
|
||||
mfem::Vector &physical_curl
|
||||
);
|
||||
void MapPhysicalCurlToHCurlReference(
|
||||
const MappingPointContext &context,
|
||||
const mfem::Vector &physical_curl,
|
||||
mfem::Vector &reference_curl
|
||||
);
|
||||
void ComputeHCurlMassTensor(
|
||||
const MappingPointContext &context,
|
||||
mfem::DenseMatrix &mass_tensor
|
||||
);
|
||||
void ComputeHCurlCurlTensor(
|
||||
const MappingPointContext &context,
|
||||
mfem::DenseMatrix &curl_tensor
|
||||
);
|
||||
void ComputeHDivMassTensorVariation(
|
||||
const MappingPointContext &context,
|
||||
const MappingPointVariation &variation,
|
||||
mfem::DenseMatrix &mass_tensor_variation
|
||||
);
|
||||
} // namespace mean_field::mapping
|
||||
@@ -1,10 +1,77 @@
|
||||
module;
|
||||
#include <cstdint>
|
||||
#include <mfem.hpp>
|
||||
export module mean_field:mapping.types;
|
||||
|
||||
namespace mean_field::mapping {
|
||||
enum class COORDINATE_SPACE : uint8_t {
|
||||
PHYSICAL,
|
||||
REFERENCE
|
||||
export namespace mean_field::mapping {
|
||||
enum class COORDINATE_SPACE : uint8_t { PHYSICAL, REFERENCE };
|
||||
|
||||
enum class MappingStatus : uint8_t {
|
||||
valid,
|
||||
invalid_dimension,
|
||||
non_finite_input,
|
||||
invalid_reference_radius,
|
||||
at_compactified_infinity,
|
||||
outside_reference_domain,
|
||||
non_finite_result,
|
||||
non_positive_determinant
|
||||
};
|
||||
}
|
||||
|
||||
struct VolumeQuadratureContext {
|
||||
mfem::DenseMatrix J_inv;
|
||||
double detJ;
|
||||
double weight;
|
||||
};
|
||||
|
||||
struct FaceQuadratureContext {
|
||||
mfem::Vector normal;
|
||||
double ds;
|
||||
double v_dot_n_scale;
|
||||
};
|
||||
|
||||
struct MappingPointContext {
|
||||
mfem::Vector reference_position;
|
||||
mfem::Vector displaced_position;
|
||||
mfem::Vector physical_position;
|
||||
mfem::DenseMatrix displacement_jacobian;
|
||||
mfem::DenseMatrix mapping_jacobian;
|
||||
mfem::DenseMatrix inverse_mapping_jacobian;
|
||||
double mapping_determinant{0.0};
|
||||
bool compactified{false};
|
||||
};
|
||||
|
||||
struct VolumeMappingContext {
|
||||
MappingPointContext mapping;
|
||||
VolumeQuadratureContext quadrature;
|
||||
};
|
||||
|
||||
struct FaceMappingContext {
|
||||
MappingPointContext mapping;
|
||||
FaceQuadratureContext quadrature;
|
||||
mfem::Vector reference_normal;
|
||||
double reference_surface_weight{0.0};
|
||||
double physical_surface_weight{0.0};
|
||||
};
|
||||
|
||||
struct MappingPointVariation {
|
||||
mfem::Vector displacement_variation;
|
||||
mfem::Vector physical_position_variation;
|
||||
mfem::DenseMatrix displacement_jacobian_variation;
|
||||
mfem::DenseMatrix mapping_jacobian_variation;
|
||||
mfem::DenseMatrix inverse_mapping_jacobian_variation;
|
||||
double mapping_determinant_variation{0.0};
|
||||
};
|
||||
|
||||
struct VolumeMappingVariation {
|
||||
MappingPointVariation mapping;
|
||||
mfem::DenseMatrix inverse_element_jacobian_variation;
|
||||
double weight_variation{0.0};
|
||||
};
|
||||
|
||||
struct FaceMappingVariation {
|
||||
MappingPointVariation mapping;
|
||||
mfem::Vector physical_normal_variation;
|
||||
double physical_surface_weight_variation{0.0};
|
||||
double normal_flux_scale_variation{0.0};
|
||||
};
|
||||
} // namespace mean_field::mapping
|
||||
@@ -5,11 +5,18 @@ export import :utils.misc;
|
||||
export import :utils.user;
|
||||
export import :utils.domain;
|
||||
export import :physics.gravity;
|
||||
export import :physics.solid_body;
|
||||
export import :physics.barotrope;
|
||||
export import :physics.contexts;
|
||||
export import :boundary.contexts;
|
||||
export import :analysis.integral;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :mapping.coefficients;
|
||||
export import :mapping.compactification;
|
||||
export import :mapping.kelvin;
|
||||
export import :mapping.transformations;
|
||||
export import :mapping.types;
|
||||
export import :mapping.compactification.options;
|
||||
export import :integrators.advection;
|
||||
export import :integrators.centrifugal;
|
||||
export import :integrators.gravity;
|
||||
@@ -19,3 +26,22 @@ export import :integrators.pressure_gradient;
|
||||
export import :integrators.viscosity;
|
||||
export import :quadrature.policy;
|
||||
export import :quadrature.mfem;
|
||||
export import :solver.fields;
|
||||
export import :utils.blocks;
|
||||
export import :operators.gravity_field;
|
||||
export import :operators.gravity_field_jacobian;
|
||||
export import :operators.kernels.gravity_field;
|
||||
export import :operators.prepared_gravity_source;
|
||||
export import :operators.prepared_hdiv_mass;
|
||||
export import :operators.context.gravity_field;
|
||||
export import :field.base;
|
||||
export import :field.registry;
|
||||
export import :field.mfem;
|
||||
export import :operators.kernels.barotropic_closure;
|
||||
export import :operators.prepared_barotropic_closure;
|
||||
export import :operators.context.barotropic_closure_linearization;
|
||||
export import :physics.rigid_rotation;
|
||||
export import :operators.kernels.hydrostatic_equilibrium;
|
||||
export import :operators.context.hydrostatic_equilibrium;
|
||||
export import :operators.prepared_hydrostatic_equilibrium;
|
||||
export import :operators.kernels.pressure_force;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
module;
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:operators.context.barotropic_closure_linearization;
|
||||
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :operators.prepared_barotropic_closure;
|
||||
export import :physics.barotrope;
|
||||
|
||||
export namespace mean_field::operators::context::barotropic {
|
||||
struct BarotropicClosureRevisions final {
|
||||
std::uint64_t density = 0;
|
||||
std::uint64_t enthalpy = 0;
|
||||
std::uint64_t displacement = 0;
|
||||
|
||||
[[nodiscard]] bool
|
||||
operator==(const BarotropicClosureRevisions &) const noexcept = default;
|
||||
};
|
||||
|
||||
class BarotropicClosureLinearizationContext final {
|
||||
public:
|
||||
BarotropicClosureLinearizationContext(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope
|
||||
);
|
||||
|
||||
void Prepare(
|
||||
const mfem::Vector &baseDensityTrue,
|
||||
const mfem::Vector &baseEnthalpyTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
const BarotropicClosureRevisions &revisions
|
||||
);
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
|
||||
[[nodiscard]] bool MatchesRevisions(
|
||||
const BarotropicClosureRevisions &revisions
|
||||
) const noexcept;
|
||||
|
||||
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
|
||||
|
||||
[[nodiscard]] const BarotropicClosureRevisions &GetRevisions() const;
|
||||
|
||||
[[nodiscard]] const mfem::Vector &GetBaseDensityTrue() const;
|
||||
|
||||
[[nodiscard]] const mfem::Vector &GetBaseEnthalpyTrue() const;
|
||||
|
||||
[[nodiscard]] const mfem::Vector &GetDisplacementTrue() const;
|
||||
|
||||
[[nodiscard]]
|
||||
const PreparedBarotropicClosureOperator &GetOperator() const noexcept;
|
||||
|
||||
void BuildResidual(mfem::Vector &residual) const;
|
||||
|
||||
private:
|
||||
void VerifyPrepared() const;
|
||||
|
||||
const fem::FEM &m_f;
|
||||
|
||||
PreparedBarotropicClosureOperator m_operator;
|
||||
|
||||
mfem::Vector m_baseDensityTrue;
|
||||
mfem::Vector m_baseEnthalpyTrue;
|
||||
mfem::Vector m_displacementTrue;
|
||||
|
||||
BarotropicClosureRevisions m_revisions;
|
||||
|
||||
std::uint64_t m_preparationCount = 0;
|
||||
bool m_isPrepared = false;
|
||||
};
|
||||
} // namespace mean_field::operators::context::barotropic
|
||||
@@ -0,0 +1,157 @@
|
||||
module;
|
||||
#include <compare>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:operators.context.gravity_field;
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :operators.prepared_gravity_source;
|
||||
export import :operators.prepared_hdiv_mass;
|
||||
|
||||
export namespace mean_field::operators::context::gravity_field {
|
||||
template <typename Tag> struct Revision {
|
||||
std::uint64_t value{0};
|
||||
|
||||
constexpr auto operator<=>(const Revision &) const = default;
|
||||
};
|
||||
|
||||
struct DiscretizationRevisionTag { };
|
||||
struct DisplacementRevisionTag { };
|
||||
struct DensityRevisionTag { };
|
||||
struct GravityGradientRevisionTag { };
|
||||
struct GravityPotentialRevisionTag { };
|
||||
|
||||
using DiscretizationRevision = Revision<DiscretizationRevisionTag>;
|
||||
using DisplacementRevision = Revision<DisplacementRevisionTag>;
|
||||
using DensityRevision = Revision<DensityRevisionTag>;
|
||||
using GravityGradientRevision = Revision<GravityGradientRevisionTag>;
|
||||
using GravityPotentialRevision = Revision<GravityPotentialRevisionTag>;
|
||||
|
||||
struct GravityFieldRevisions {
|
||||
DiscretizationRevision discretization;
|
||||
DisplacementRevision displacement;
|
||||
DensityRevision density;
|
||||
GravityGradientRevision gravity_gradient;
|
||||
GravityPotentialRevision gravity_potential;
|
||||
};
|
||||
|
||||
struct GravityFieldStateView {
|
||||
const mfem::Vector &density;
|
||||
const mfem::Vector &displacement;
|
||||
const mfem::Vector &gravity_gradient;
|
||||
const mfem::Vector &gravity_potential;
|
||||
};
|
||||
|
||||
struct GravityFieldGeometryPreparation {
|
||||
bool reconstructed_operators{false};
|
||||
bool rebuilt_mass_operator{false};
|
||||
bool rebuilt_source_operator{false};
|
||||
bool refreshed_variation_state{false};
|
||||
|
||||
[[nodiscard]] bool DidAnyWork() const noexcept {
|
||||
return reconstructed_operators || rebuilt_mass_operator ||
|
||||
rebuilt_source_operator || refreshed_variation_state;
|
||||
}
|
||||
};
|
||||
|
||||
class GravityFieldGeometryContext {
|
||||
public:
|
||||
GravityFieldGeometryContext(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper
|
||||
);
|
||||
|
||||
GravityFieldGeometryContext(const GravityFieldGeometryContext &) =
|
||||
delete;
|
||||
GravityFieldGeometryContext &
|
||||
operator=(const GravityFieldGeometryContext &) = delete;
|
||||
GravityFieldGeometryContext(GravityFieldGeometryContext &&) = delete;
|
||||
GravityFieldGeometryContext &
|
||||
operator=(GravityFieldGeometryContext &&) = delete;
|
||||
|
||||
GravityFieldGeometryPreparation Prepare(
|
||||
const mfem::Vector &displacement_true,
|
||||
DiscretizationRevision discretization_revision,
|
||||
DisplacementRevision displacement_revision
|
||||
);
|
||||
|
||||
[[nodiscard]] const PreparedMappedHDivMassOperator &
|
||||
GetMassOperator() const;
|
||||
[[nodiscard]] const PreparedMappedGravitySourceOperator &
|
||||
GetSourceOperator() const;
|
||||
[[nodiscard]] const mfem::Vector &GetDisplacement() const;
|
||||
[[nodiscard]] DiscretizationRevision
|
||||
GetDiscretizationRevision() const noexcept;
|
||||
[[nodiscard]] DisplacementRevision
|
||||
GetDisplacementRevision() const noexcept;
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
|
||||
private:
|
||||
const fem::FEM &m_fem;
|
||||
const mapping::DomainMapperStateless &m_domain_mapper;
|
||||
|
||||
std::unique_ptr<PreparedMappedHDivMassOperator> m_mass_operator;
|
||||
std::unique_ptr<PreparedMappedGravitySourceOperator> m_source_operator;
|
||||
|
||||
mfem::Vector m_displacement_true;
|
||||
|
||||
DiscretizationRevision m_discretization_revision;
|
||||
DisplacementRevision m_displacement_revision;
|
||||
|
||||
bool m_is_prepared{false};
|
||||
};
|
||||
|
||||
struct GravityFieldPreparationReport {
|
||||
GravityFieldGeometryPreparation geometry;
|
||||
bool updated_density{false};
|
||||
bool updated_gravity_gradient{false};
|
||||
|
||||
[[nodiscard]] bool DidAnyWork() const noexcept {
|
||||
return geometry.DidAnyWork() || updated_density ||
|
||||
updated_gravity_gradient;
|
||||
}
|
||||
};
|
||||
|
||||
class GravityFieldLinearizationContext {
|
||||
public:
|
||||
GravityFieldLinearizationContext(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper
|
||||
);
|
||||
|
||||
GravityFieldLinearizationContext(
|
||||
const GravityFieldLinearizationContext &
|
||||
) = delete;
|
||||
GravityFieldLinearizationContext &
|
||||
operator=(const GravityFieldLinearizationContext &) = delete;
|
||||
GravityFieldLinearizationContext(GravityFieldLinearizationContext &&) =
|
||||
delete;
|
||||
GravityFieldLinearizationContext &
|
||||
operator=(GravityFieldLinearizationContext &&) = delete;
|
||||
|
||||
GravityFieldPreparationReport Prepare(
|
||||
const GravityFieldStateView &state,
|
||||
const GravityFieldRevisions &revisions
|
||||
);
|
||||
|
||||
[[nodiscard]] const GravityFieldGeometryContext &
|
||||
GetGeometryContext() const;
|
||||
[[nodiscard]] const mfem::Vector &GetDensity() const;
|
||||
[[nodiscard]] const mfem::Vector &GetGravityGradient() const;
|
||||
[[nodiscard]] const GravityFieldRevisions &GetRevisions() const;
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
|
||||
private:
|
||||
const fem::FEM &m_fem;
|
||||
|
||||
GravityFieldGeometryContext m_geometry_context;
|
||||
|
||||
mfem::Vector m_density_true;
|
||||
mfem::Vector m_gravity_gradient_true;
|
||||
|
||||
GravityFieldRevisions m_revisions;
|
||||
bool m_is_prepared{false};
|
||||
};
|
||||
} // namespace mean_field::operators::context::gravity_field
|
||||
@@ -0,0 +1,154 @@
|
||||
module;
|
||||
|
||||
#include <compare>
|
||||
#include <cstdint>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:operators.context.hydrostatic_equilibrium;
|
||||
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
|
||||
export namespace mean_field::operators::context::hydrostatic {
|
||||
template <typename Tag> struct DependencyStamp {
|
||||
std::uint64_t identity{0};
|
||||
std::uint64_t revision{0};
|
||||
|
||||
[[nodiscard]] constexpr bool
|
||||
CanFollow(const DependencyStamp &prepared) const noexcept {
|
||||
return identity != prepared.identity ||
|
||||
revision >= prepared.revision;
|
||||
}
|
||||
|
||||
constexpr auto operator<=>(const DependencyStamp &) const = default;
|
||||
};
|
||||
|
||||
struct DiscretizationDependencyTag { };
|
||||
struct EnthalpyDependencyTag { };
|
||||
struct GravityPotentialDependencyTag { };
|
||||
struct DisplacementDependencyTag { };
|
||||
struct RotationDependencyTag { };
|
||||
struct BernoulliConstantDependencyTag { };
|
||||
|
||||
using DiscretizationDependency =
|
||||
DependencyStamp<DiscretizationDependencyTag>;
|
||||
|
||||
using EnthalpyDependency = DependencyStamp<EnthalpyDependencyTag>;
|
||||
|
||||
using GravityPotentialDependency =
|
||||
DependencyStamp<GravityPotentialDependencyTag>;
|
||||
|
||||
using DisplacementDependency = DependencyStamp<DisplacementDependencyTag>;
|
||||
|
||||
using RotationDependency = DependencyStamp<RotationDependencyTag>;
|
||||
|
||||
using BernoulliConstantDependency =
|
||||
DependencyStamp<BernoulliConstantDependencyTag>;
|
||||
|
||||
struct HydrostaticEquilibriumDependencies {
|
||||
DiscretizationDependency discretization;
|
||||
EnthalpyDependency enthalpy;
|
||||
GravityPotentialDependency gravityPotential;
|
||||
DisplacementDependency displacement;
|
||||
RotationDependency rotation;
|
||||
BernoulliConstantDependency bernoulliConstant;
|
||||
|
||||
constexpr auto
|
||||
operator<=>(const HydrostaticEquilibriumDependencies &) const = default;
|
||||
};
|
||||
|
||||
struct HydrostaticEquilibriumStateView {
|
||||
const mfem::Vector &enthalpy;
|
||||
const mfem::Vector &gravityPotential;
|
||||
const mfem::Vector &displacement;
|
||||
double bernoulliConstant{0.0};
|
||||
};
|
||||
|
||||
struct HydrostaticPreparationReport {
|
||||
bool preparedStaticDependencies{false};
|
||||
bool preparedGeometryState{false};
|
||||
bool preparedRotationDependencies{false};
|
||||
bool preparedBaseState{false};
|
||||
|
||||
bool updatedEnthalpy{false};
|
||||
bool updatedGravityPotential{false};
|
||||
bool updatedDisplacement{false};
|
||||
bool updatedBernoulliConstant{false};
|
||||
|
||||
[[nodiscard]] bool DidAnyWork() const noexcept {
|
||||
return preparedStaticDependencies || preparedGeometryState ||
|
||||
preparedRotationDependencies || preparedBaseState;
|
||||
}
|
||||
};
|
||||
|
||||
struct HydrostaticPreparationStatistics {
|
||||
std::uint64_t staticPreparations{0};
|
||||
std::uint64_t geometryPreparations{0};
|
||||
std::uint64_t rotationPreparations{0};
|
||||
std::uint64_t baseStatePreparations{0};
|
||||
|
||||
constexpr auto
|
||||
operator<=>(const HydrostaticPreparationStatistics &) const = default;
|
||||
};
|
||||
|
||||
class HydrostaticEquilibriumContext {
|
||||
public:
|
||||
HydrostaticEquilibriumContext(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper
|
||||
);
|
||||
|
||||
HydrostaticEquilibriumContext(const HydrostaticEquilibriumContext &) =
|
||||
delete;
|
||||
|
||||
HydrostaticEquilibriumContext &
|
||||
operator=(const HydrostaticEquilibriumContext &) = delete;
|
||||
|
||||
HydrostaticEquilibriumContext(HydrostaticEquilibriumContext &&) =
|
||||
delete;
|
||||
|
||||
HydrostaticEquilibriumContext &
|
||||
operator=(HydrostaticEquilibriumContext &&) = delete;
|
||||
|
||||
HydrostaticPreparationReport Prepare(
|
||||
const HydrostaticEquilibriumStateView &state,
|
||||
const HydrostaticEquilibriumDependencies &dependencies
|
||||
);
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
|
||||
[[nodiscard]] bool MatchesDependencies(
|
||||
const HydrostaticEquilibriumDependencies &dependencies
|
||||
) const noexcept;
|
||||
|
||||
[[nodiscard]] const HydrostaticEquilibriumDependencies &
|
||||
GetDependencies() const;
|
||||
|
||||
[[nodiscard]] const HydrostaticPreparationStatistics &
|
||||
GetPreparationStatistics() const noexcept;
|
||||
|
||||
[[nodiscard]] const mfem::Vector &GetBaseEnthalpyTrue() const;
|
||||
|
||||
[[nodiscard]] const mfem::Vector &GetBaseGravityPotentialTrue() const;
|
||||
|
||||
[[nodiscard]] const mfem::Vector &GetDisplacementTrue() const;
|
||||
|
||||
[[nodiscard]] double GetBernoulliConstant() const;
|
||||
|
||||
private:
|
||||
void VerifyPrepared() const;
|
||||
|
||||
const fem::FEM &m_f;
|
||||
const mapping::DomainMapperStateless &m_domainMapper;
|
||||
|
||||
mfem::Vector m_baseEnthalpyTrue;
|
||||
mfem::Vector m_baseGravityPotentialTrue;
|
||||
mfem::Vector m_displacementTrue;
|
||||
double m_bernoulliConstant{0.0};
|
||||
|
||||
HydrostaticEquilibriumDependencies m_dependencies;
|
||||
HydrostaticPreparationStatistics m_statistics;
|
||||
bool m_isPrepared{false};
|
||||
};
|
||||
} // namespace mean_field::operators::context::hydrostatic
|
||||
147
libmeanfield/interface/operators/gravity_field.cppm
Normal file
147
libmeanfield/interface/operators/gravity_field.cppm
Normal file
@@ -0,0 +1,147 @@
|
||||
module;
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:operators.gravity_field;
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :operators.gravity_field_jacobian;
|
||||
export import :operators.context.gravity_field;
|
||||
|
||||
export namespace mean_field::operators {
|
||||
enum class GravityResidualBlock : std::uint8_t {
|
||||
gradient_equation = 0,
|
||||
poisson_equation = 1,
|
||||
count = 2
|
||||
};
|
||||
|
||||
constexpr int
|
||||
gravity_residual_block_index(const GravityResidualBlock block) noexcept {
|
||||
return static_cast<int>(block);
|
||||
}
|
||||
|
||||
inline constexpr int gravity_residual_block_count =
|
||||
gravity_residual_block_index(GravityResidualBlock::count);
|
||||
|
||||
class GravityFieldOperator final : public mfem::Operator {
|
||||
public:
|
||||
GravityFieldOperator(
|
||||
fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper,
|
||||
context::gravity_field::GravityFieldLinearizationContext
|
||||
&linearization_context,
|
||||
const mfem::Array<int> &state_true_offsets,
|
||||
GravityFieldJacobianOperator &jacobian
|
||||
);
|
||||
|
||||
context::gravity_field::GravityFieldPreparationReport Prepare(
|
||||
const mfem::Vector &state,
|
||||
const context::gravity_field::GravityFieldRevisions &revisions
|
||||
);
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &state,
|
||||
mfem::Vector &residual
|
||||
) const override;
|
||||
|
||||
Operator &GetGradient(const mfem::Vector &state) const override;
|
||||
|
||||
[[nodiscard]] const mfem::Array<int> &
|
||||
GetStateTrueOffsets() const noexcept;
|
||||
|
||||
[[nodiscard]] const mfem::Array<int> &
|
||||
GetResidualTrueOffsets() const noexcept;
|
||||
|
||||
[[nodiscard]] context::gravity_field::GravityFieldLinearizationContext &
|
||||
GetLinearizationContext() noexcept;
|
||||
|
||||
[[nodiscard]] const context::gravity_field::
|
||||
GravityFieldLinearizationContext &
|
||||
GetLinearizationContext() const noexcept;
|
||||
|
||||
void ApplyGravityUnknowns(
|
||||
const mfem::Vector &gravity_gradient,
|
||||
const mfem::Vector &gravity_potential,
|
||||
const context::gravity_field::GravityFieldGeometryContext
|
||||
&geometry_context,
|
||||
mfem::Vector &action
|
||||
) const;
|
||||
|
||||
void ApplyDensitySource(
|
||||
const mfem::Vector &density,
|
||||
const context::gravity_field::GravityFieldGeometryContext
|
||||
&geometry_context,
|
||||
mfem::Vector &action
|
||||
) const;
|
||||
|
||||
private:
|
||||
fem::FEM &m_fem;
|
||||
const mapping::DomainMapperStateless &m_domain_mapper;
|
||||
context::gravity_field::GravityFieldLinearizationContext
|
||||
&m_linearization_context;
|
||||
mfem::Array<int> m_state_true_offsets;
|
||||
mfem::Array<int> m_residual_true_offsets;
|
||||
GravityFieldJacobianOperator &m_jacobian;
|
||||
};
|
||||
|
||||
class ReducedGravityFieldOperator final : public mfem::Operator {
|
||||
public:
|
||||
ReducedGravityFieldOperator(
|
||||
GravityFieldOperator &gravity_field_operator,
|
||||
context::gravity_field::GravityFieldGeometryContext
|
||||
&gravity_field_geometry_context,
|
||||
const mfem::Vector &displacement
|
||||
);
|
||||
|
||||
ReducedGravityFieldOperator(const ReducedGravityFieldOperator &) =
|
||||
delete;
|
||||
ReducedGravityFieldOperator &
|
||||
operator=(const ReducedGravityFieldOperator &) = delete;
|
||||
ReducedGravityFieldOperator(ReducedGravityFieldOperator &&) = delete;
|
||||
ReducedGravityFieldOperator &
|
||||
operator=(ReducedGravityFieldOperator &&) = delete;
|
||||
|
||||
void SetDisplacement(const mfem::Vector &displacement);
|
||||
|
||||
[[nodiscard]] const mfem::Vector &GetDisplacement() const;
|
||||
|
||||
void BuildRightHandSide(
|
||||
const mfem::Vector &density,
|
||||
mfem::Vector &right_hand_side
|
||||
) const;
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &gravity_state,
|
||||
mfem::Vector &action
|
||||
) const override;
|
||||
|
||||
[[nodiscard]] GravityFieldOperator &GetGravityFieldOperator() noexcept;
|
||||
|
||||
[[nodiscard]] const GravityFieldOperator &
|
||||
GetGravityFieldOperator() const noexcept;
|
||||
|
||||
[[nodiscard]] context::gravity_field::GravityFieldGeometryContext &
|
||||
GetGeometryContext() noexcept;
|
||||
|
||||
[[nodiscard]] const context::gravity_field::
|
||||
GravityFieldGeometryContext &
|
||||
GetGeometryContext() const noexcept;
|
||||
|
||||
[[nodiscard]] const mfem::Array<int> &
|
||||
GetGravityTrueOffsets() const noexcept;
|
||||
|
||||
private:
|
||||
void ValidateDisplacement(const mfem::Vector &displacement) const;
|
||||
|
||||
void ValidateDensity(const mfem::Vector &density) const;
|
||||
|
||||
void ValidateGravityState(const mfem::Vector &gravity_state) const;
|
||||
|
||||
private:
|
||||
GravityFieldOperator &m_gravity_field_operator;
|
||||
mfem::Array<int> m_gravity_true_offsets;
|
||||
context::gravity_field::GravityFieldGeometryContext
|
||||
&m_gravity_field_geometry_context;
|
||||
};
|
||||
} // namespace mean_field::operators
|
||||
38
libmeanfield/interface/operators/gravity_field_jacobian.cppm
Normal file
38
libmeanfield/interface/operators/gravity_field_jacobian.cppm
Normal file
@@ -0,0 +1,38 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:operators.gravity_field_jacobian;
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :operators.context.gravity_field;
|
||||
|
||||
export namespace mean_field::operators {
|
||||
class GravityFieldJacobianOperator final : public mfem::Operator {
|
||||
public:
|
||||
GravityFieldJacobianOperator(
|
||||
fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper,
|
||||
const context::gravity_field::GravityFieldLinearizationContext
|
||||
&linearization_context,
|
||||
const mfem::Array<int> &state_true_offsets,
|
||||
const mfem::Array<int> &residual_true_offsets
|
||||
);
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &action
|
||||
) const override;
|
||||
|
||||
[[nodiscard]] const context::gravity_field::
|
||||
GravityFieldLinearizationContext &
|
||||
GetLinearizationContext() const noexcept;
|
||||
|
||||
private:
|
||||
fem::FEM &m_fem;
|
||||
const mapping::DomainMapperStateless &m_domain_mapper;
|
||||
const context::gravity_field::GravityFieldLinearizationContext
|
||||
&m_linearization_context;
|
||||
mfem::Array<int> m_state_true_offsets;
|
||||
mfem::Array<int> m_residual_true_offsets;
|
||||
};
|
||||
} // namespace mean_field::operators
|
||||
@@ -0,0 +1,51 @@
|
||||
module;
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:operators.kernels.barotropic_closure;
|
||||
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :physics.barotrope;
|
||||
|
||||
export namespace mean_field::operators::kernels {
|
||||
void apply_barotropic_closure(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::Vector &densityTrue,
|
||||
const mfem::Vector &enthalpyTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &residual
|
||||
);
|
||||
|
||||
void apply_barotropic_closure_density_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::Vector &densityVariationTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &action
|
||||
);
|
||||
|
||||
void apply_barotropic_closure_enthalpy_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::Vector &baseEnthalpyTrue,
|
||||
const mfem::Vector &enthalpyVariationTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &action
|
||||
);
|
||||
|
||||
void apply_barotropic_closure_displacement_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::Vector &baseDensityTrue,
|
||||
const mfem::Vector &baseEnthalpyTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
const mfem::Vector &displacementVariationTrue,
|
||||
mfem::Vector &action
|
||||
);
|
||||
} // namespace mean_field::operators::kernels
|
||||
@@ -0,0 +1,42 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
export module mean_field:operators.kernels.gravity_field;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :fem;
|
||||
|
||||
export namespace mean_field::operators::kernels {
|
||||
|
||||
void apply_mapped_hdiv_mass(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper,
|
||||
const mfem::Vector &gravity_gradient_true,
|
||||
const mfem::Vector &displacement_true,
|
||||
mfem::Vector &action
|
||||
);
|
||||
|
||||
void apply_mapped_source(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper,
|
||||
const mfem::Vector &density_true,
|
||||
const mfem::Vector &displacement_true,
|
||||
mfem::Vector &action
|
||||
);
|
||||
|
||||
void apply_mapped_hdiv_mass_variation(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper,
|
||||
const mfem::Vector &gravity_gradient_true,
|
||||
const mfem::Vector &displacement_true,
|
||||
const mfem::Vector &displacement_variation_true,
|
||||
mfem::Vector &action
|
||||
);
|
||||
|
||||
void apply_mapped_source_variation(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper,
|
||||
const mfem::Vector &density_true,
|
||||
const mfem::Vector &displacement_true,
|
||||
const mfem::Vector &displacement_variation_true,
|
||||
mfem::Vector &action_variation
|
||||
);
|
||||
} // namespace mean_field::operators::kernels
|
||||
@@ -0,0 +1,73 @@
|
||||
module;
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:operators.kernels.hydrostatic_equilibrium;
|
||||
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :physics.rigid_rotation;
|
||||
|
||||
export namespace mean_field::operators::kernels {
|
||||
void apply_hydrostatic_equilibrium(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::RigidRotation &rotation,
|
||||
const mfem::Vector &enthalpyTrue,
|
||||
const mfem::Vector &potentialTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
double bernoulliConstant,
|
||||
mfem::Vector &residual
|
||||
);
|
||||
|
||||
void apply_hydrostatic_equilibrium_enthalpy_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const mfem::Vector &enthalpyVariationTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &action
|
||||
);
|
||||
|
||||
void apply_hydrostatic_equilibrium_potential_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const mfem::Vector &potentialVariationTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &action
|
||||
);
|
||||
|
||||
void apply_hydrostatic_equilibrium_constant_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
double constantVariation,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &action
|
||||
);
|
||||
|
||||
void apply_hydrostatic_equilibrium_displacement_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::RigidRotation &rotation,
|
||||
const mfem::Vector &baseEnthalpyTrue,
|
||||
const mfem::Vector &basePotentialTrue,
|
||||
const mfem::Vector &baseDisplacementTrue,
|
||||
double baseBernoulliConstant,
|
||||
const mfem::Vector &displacementVariationTrue,
|
||||
mfem::Vector &action
|
||||
);
|
||||
|
||||
void apply_hydrostatic_equilibrium_action(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::RigidRotation &rotation,
|
||||
const mfem::Vector &baseEnthalpyTrue,
|
||||
const mfem::Vector &basePotentialTrue,
|
||||
const mfem::Vector &baseDisplacementTrue,
|
||||
double baseBernoulliConstant,
|
||||
const mfem::Vector &enthalpyVariationTrue,
|
||||
const mfem::Vector &potentialVariationTrue,
|
||||
double constantVariation,
|
||||
const mfem::Vector &displacementVariationTrue,
|
||||
mfem::Vector &action
|
||||
);
|
||||
} // namespace mean_field::operators::kernels
|
||||
@@ -0,0 +1,20 @@
|
||||
module;
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:operators.kernels.pressure_force;
|
||||
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :physics.barotrope;
|
||||
|
||||
export namespace mean_field::operators::kernels {
|
||||
void apply_pressure_force_residual(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope,
|
||||
const mfem::Vector &enthalpyTrue,
|
||||
const mfem::Vector &displacementTrue,
|
||||
mfem::Vector &residualTrue
|
||||
);
|
||||
} // namespace mean_field::operators::kernels
|
||||
@@ -0,0 +1,90 @@
|
||||
module;
|
||||
|
||||
#include <cstdint>
|
||||
#include <mfem.hpp>
|
||||
#include <vector>
|
||||
|
||||
export module mean_field:operators.prepared_barotropic_closure;
|
||||
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :physics.barotrope;
|
||||
|
||||
export namespace mean_field::operators {
|
||||
class PreparedBarotropicClosureOperator final : public mfem::Operator {
|
||||
public:
|
||||
PreparedBarotropicClosureOperator(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper,
|
||||
const physics::PolytropicBarotrope &barotrope
|
||||
);
|
||||
|
||||
void Prepare(
|
||||
const mfem::Vector &baseDensityTrue,
|
||||
const mfem::Vector &baseEnthalpyTrue,
|
||||
const mfem::Vector &displacementTrue
|
||||
);
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &densityVariationTrue,
|
||||
const mfem::Vector &enthalpyVariationTrue,
|
||||
const mfem::Vector &displacementVariationTrue,
|
||||
mfem::Vector &action
|
||||
) const;
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &combinedVariation,
|
||||
mfem::Vector &action
|
||||
) const override;
|
||||
|
||||
void BuildResidual(mfem::Vector &residual) const;
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
|
||||
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
|
||||
|
||||
[[nodiscard]] int GetDensitySize() const noexcept;
|
||||
|
||||
[[nodiscard]] int GetEnthalpySize() const noexcept;
|
||||
|
||||
private:
|
||||
void VerifyPrepared() const;
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &densityVariationTrue,
|
||||
const mfem::Vector &enthalpyVariationTrue,
|
||||
mfem::Vector &action
|
||||
) const;
|
||||
|
||||
struct ElementPAData {
|
||||
mfem::Array<int> densityDofs;
|
||||
mfem::Array<int> enthalpyDofs;
|
||||
|
||||
mfem::DofTransformation *densityDofTransformation{nullptr};
|
||||
mfem::DofTransformation *enthalpyDofTransformation{nullptr};
|
||||
|
||||
mfem::DenseMatrix densityBasis;
|
||||
mfem::DenseMatrix enthalpyBasis;
|
||||
|
||||
mfem::Vector weightedResidual;
|
||||
mfem::Vector quadratureWeights;
|
||||
mfem::Vector weightedEnthalpyDerivative;
|
||||
};
|
||||
|
||||
const fem::FEM &m_fem;
|
||||
const mapping::DomainMapperStateless &m_domainMapper;
|
||||
const physics::PolytropicBarotrope &m_barotrope;
|
||||
|
||||
std::vector<ElementPAData> m_elements;
|
||||
|
||||
mfem::Vector m_baseDensityTrue;
|
||||
mfem::Vector m_baseEnthalpyTrue;
|
||||
mfem::Vector m_baseDisplacementTrue;
|
||||
|
||||
int m_densitySize{0};
|
||||
int m_enthalpySize{0};
|
||||
|
||||
std::uint64_t m_preparationCount{0};
|
||||
bool m_isPrepared{false};
|
||||
};
|
||||
} // namespace mean_field::operators
|
||||
@@ -0,0 +1,61 @@
|
||||
module;
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
#include <vector>
|
||||
|
||||
export module mean_field:operators.prepared_gravity_source;
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
|
||||
export namespace mean_field::operators {
|
||||
class PreparedMappedGravitySourceOperator final : public mfem::Operator {
|
||||
public:
|
||||
PreparedMappedGravitySourceOperator(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper
|
||||
);
|
||||
|
||||
void Prepare(const mfem::Vector &displacement_true);
|
||||
void Mult(
|
||||
const mfem::Vector &density_true,
|
||||
mfem::Vector &action
|
||||
) const override;
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
|
||||
|
||||
void MultTranspose(
|
||||
const mfem::Vector &potential_true,
|
||||
mfem::Vector &action
|
||||
) const override;
|
||||
|
||||
private:
|
||||
struct ElementPAData {
|
||||
int element_id{-1};
|
||||
|
||||
mfem::Array<int> density_dofs;
|
||||
mfem::Array<int> potential_dofs;
|
||||
|
||||
mfem::DofTransformation *density_dof_transformation{nullptr};
|
||||
mfem::DofTransformation *potential_dof_transformation{nullptr};
|
||||
|
||||
// Rows are quadrature points; columns are element DOFs.
|
||||
mfem::DenseMatrix density_basis;
|
||||
mfem::DenseMatrix potential_basis;
|
||||
|
||||
// Contains quadrature weight, mesh Jacobian, mapped Jacobian,
|
||||
// and 4*pi*G.
|
||||
mfem::Vector quadrature_data;
|
||||
};
|
||||
|
||||
const fem::FEM &m_fem;
|
||||
const mapping::DomainMapperStateless &m_domain_mapper;
|
||||
|
||||
mfem::Array<int> m_stellar_marker;
|
||||
std::vector<ElementPAData> m_elements;
|
||||
|
||||
std::uint64_t m_preparation_count{0};
|
||||
bool m_is_prepared{false};
|
||||
};
|
||||
} // namespace mean_field::operators
|
||||
40
libmeanfield/interface/operators/prepared_hdiv_mass.cppm
Normal file
40
libmeanfield/interface/operators/prepared_hdiv_mass.cppm
Normal file
@@ -0,0 +1,40 @@
|
||||
module;
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:operators.prepared_hdiv_mass;
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
|
||||
export namespace mean_field::operators {
|
||||
class PreparedMappedHDivMassOperator final : public mfem::Operator {
|
||||
public:
|
||||
PreparedMappedHDivMassOperator(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domain_mapper
|
||||
);
|
||||
|
||||
void Prepare(const mfem::Vector &displacement_true);
|
||||
void Mult(
|
||||
const mfem::Vector &gravity_gradient_true,
|
||||
mfem::Vector &action
|
||||
) const override;
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
|
||||
|
||||
private:
|
||||
const fem::FEM &m_fem;
|
||||
const mapping::DomainMapperStateless &m_domain_mapper;
|
||||
|
||||
mfem::Array<int> m_stellar_marker;
|
||||
mfem::Array<int> m_vacuum_marker;
|
||||
|
||||
std::unique_ptr<mfem::MatrixCoefficient> m_stellar_mass_coefficient;
|
||||
std::unique_ptr<mfem::MatrixCoefficient> m_vacuum_mass_coefficient;
|
||||
std::unique_ptr<mfem::ParBilinearForm> m_mass_form;
|
||||
std::uint64_t m_preparation_count{0};
|
||||
bool m_is_prepared{false};
|
||||
};
|
||||
} // namespace mean_field::operators
|
||||
@@ -0,0 +1,278 @@
|
||||
module;
|
||||
|
||||
#include <compare>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:operators.prepared_hydrostatic_equilibrium;
|
||||
|
||||
export import :fem;
|
||||
export import :mapping.domain_mapper;
|
||||
export import :operators.context.hydrostatic_equilibrium;
|
||||
export import :physics.rigid_rotation;
|
||||
|
||||
export namespace mean_field::operators {
|
||||
struct PreparedHydrostaticEquilibriumReport {
|
||||
context::hydrostatic::HydrostaticPreparationReport contextReport;
|
||||
|
||||
bool updatedRotation{false};
|
||||
bool preparedAlgebraicJacobianBlocks{false};
|
||||
bool preparedDisplacementJacobianData{false};
|
||||
bool preparedResidual{false};
|
||||
|
||||
[[nodiscard]] bool DidAnyWork() const noexcept {
|
||||
return contextReport.DidAnyWork() || updatedRotation ||
|
||||
preparedAlgebraicJacobianBlocks ||
|
||||
preparedDisplacementJacobianData || preparedResidual;
|
||||
}
|
||||
};
|
||||
|
||||
struct PreparedHydrostaticAlgebraicJacobianStatistics {
|
||||
std::uint64_t preparations{0};
|
||||
std::uint64_t enthalpyApplications{0};
|
||||
std::uint64_t gravityPotentialApplications{0};
|
||||
std::uint64_t bernoulliConstantApplications{0};
|
||||
std::uint64_t combinedApplications{0};
|
||||
|
||||
constexpr auto operator<=>(
|
||||
const PreparedHydrostaticAlgebraicJacobianStatistics &
|
||||
) const = default;
|
||||
};
|
||||
|
||||
struct PreparedHydrostaticDisplacementJacobianStatistics {
|
||||
std::uint64_t preparations{0};
|
||||
std::uint64_t applications{0};
|
||||
|
||||
constexpr auto operator<=>(
|
||||
const PreparedHydrostaticDisplacementJacobianStatistics &
|
||||
) const = default;
|
||||
};
|
||||
|
||||
struct PreparedHydrostaticCompleteJacobianStatistics {
|
||||
std::uint64_t applications{0};
|
||||
|
||||
constexpr auto operator<=>(
|
||||
const PreparedHydrostaticCompleteJacobianStatistics &
|
||||
) const = default;
|
||||
};
|
||||
|
||||
enum class HydrostaticJacobianInputBlock : int {
|
||||
enthalpy = 0,
|
||||
gravityPotential = 1,
|
||||
bernoulliConstant = 2,
|
||||
displacement = 3
|
||||
};
|
||||
|
||||
class HydrostaticJacobianBlockLayout final {
|
||||
public:
|
||||
explicit HydrostaticJacobianBlockLayout(const fem::FEM &f);
|
||||
|
||||
[[nodiscard]] int Offset(HydrostaticJacobianInputBlock block) const;
|
||||
|
||||
[[nodiscard]] int Size(HydrostaticJacobianInputBlock block) const;
|
||||
|
||||
[[nodiscard]] int GetTotalSize() const noexcept;
|
||||
|
||||
[[nodiscard]] int GetResidualSize() const noexcept;
|
||||
|
||||
private:
|
||||
int m_enthalpySize{0};
|
||||
int m_gravityPotentialSize{0};
|
||||
int m_displacementSize{0};
|
||||
int m_totalSize{0};
|
||||
int m_residualSize{0};
|
||||
};
|
||||
|
||||
class PreparedHydrostaticEquilibriumOperator final {
|
||||
public:
|
||||
PreparedHydrostaticEquilibriumOperator(
|
||||
const fem::FEM &f,
|
||||
const mapping::DomainMapperStateless &domainMapper
|
||||
);
|
||||
|
||||
PreparedHydrostaticEquilibriumOperator(
|
||||
const PreparedHydrostaticEquilibriumOperator &
|
||||
) = delete;
|
||||
|
||||
PreparedHydrostaticEquilibriumOperator &
|
||||
operator=(const PreparedHydrostaticEquilibriumOperator &) = delete;
|
||||
|
||||
PreparedHydrostaticEquilibriumOperator(
|
||||
PreparedHydrostaticEquilibriumOperator &&
|
||||
) = delete;
|
||||
|
||||
PreparedHydrostaticEquilibriumOperator &
|
||||
operator=(PreparedHydrostaticEquilibriumOperator &&) = delete;
|
||||
|
||||
PreparedHydrostaticEquilibriumReport Prepare(
|
||||
const context::hydrostatic::HydrostaticEquilibriumStateView &state,
|
||||
const context::hydrostatic::HydrostaticEquilibriumDependencies
|
||||
&dependencies,
|
||||
const physics::RigidRotation &rotation
|
||||
);
|
||||
|
||||
void BuildResidual(mfem::Vector &residual) const;
|
||||
|
||||
void ApplyEnthalpyJacobianAction(
|
||||
const mfem::Vector &enthalpyVariation,
|
||||
mfem::Vector &action
|
||||
) const;
|
||||
|
||||
void ApplyGravityPotentialJacobianAction(
|
||||
const mfem::Vector &gravityPotentialVariation,
|
||||
mfem::Vector &action
|
||||
) const;
|
||||
|
||||
void ApplyBernoulliConstantJacobianAction(
|
||||
double bernoulliConstantVariation,
|
||||
mfem::Vector &action
|
||||
) const;
|
||||
|
||||
void ApplyAlgebraicJacobianAction(
|
||||
const mfem::Vector &enthalpyVariation,
|
||||
const mfem::Vector &gravityPotentialVariation,
|
||||
double bernoulliConstantVariation,
|
||||
mfem::Vector &action
|
||||
) const;
|
||||
|
||||
void ApplyDisplacementJacobianAction(
|
||||
const mfem::Vector &displacementVariation,
|
||||
mfem::Vector &action
|
||||
) const;
|
||||
|
||||
void ApplyCompleteJacobianAction(
|
||||
const mfem::Vector &enthalpyVariation,
|
||||
const mfem::Vector &gravityPotentialVariation,
|
||||
double bernoulliConstantVariation,
|
||||
const mfem::Vector &displacementVariation,
|
||||
mfem::Vector &action
|
||||
) const;
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
|
||||
[[nodiscard]] const context::hydrostatic::
|
||||
HydrostaticPreparationStatistics &
|
||||
GetContextPreparationStatistics() const noexcept;
|
||||
|
||||
[[nodiscard]] std::uint64_t
|
||||
GetResidualPreparationCount() const noexcept;
|
||||
|
||||
[[nodiscard]] std::uint64_t
|
||||
GetResidualApplicationCount() const noexcept;
|
||||
|
||||
[[nodiscard]] const PreparedHydrostaticAlgebraicJacobianStatistics &
|
||||
GetAlgebraicJacobianStatistics() const noexcept;
|
||||
|
||||
[[nodiscard]] const PreparedHydrostaticDisplacementJacobianStatistics &
|
||||
GetDisplacementJacobianStatistics() const noexcept;
|
||||
|
||||
[[nodiscard]] const PreparedHydrostaticCompleteJacobianStatistics &
|
||||
GetCompleteJacobianStatistics() const noexcept;
|
||||
|
||||
[[nodiscard]] std::size_t GetStellarElementCount() const noexcept;
|
||||
|
||||
[[nodiscard]] const fem::FEM &GetFEM() const noexcept;
|
||||
|
||||
private:
|
||||
struct ElementPAData {
|
||||
int elementId{-1};
|
||||
|
||||
mfem::Array<int> enthalpyDofs;
|
||||
mfem::Array<int> gravityPotentialDofs;
|
||||
mfem::Array<int> displacementDofs;
|
||||
|
||||
mfem::DofTransformation *enthalpyDofTransformation{nullptr};
|
||||
|
||||
mfem::DofTransformation *gravityPotentialDofTransformation{nullptr};
|
||||
|
||||
mfem::DofTransformation *displacementDofTransformation{nullptr};
|
||||
|
||||
const mfem::IntegrationRule *integrationRule{nullptr};
|
||||
|
||||
// Rows are quadrature points and columns are element DOFs.
|
||||
mfem::DenseMatrix enthalpyBasis;
|
||||
mfem::DenseMatrix gravityPotentialBasis;
|
||||
|
||||
// Rows are quadrature points and columns are physical components.
|
||||
mfem::DenseMatrix physicalPositions;
|
||||
|
||||
mfem::Vector quadratureWeights;
|
||||
std::vector<mapping::VolumeMappingContext> baseMappingContexts;
|
||||
|
||||
std::optional<mapping::ElementDisplacementData>
|
||||
baseDisplacementData;
|
||||
|
||||
std::optional<mapping::ElementCompactificationData>
|
||||
compactificationData;
|
||||
|
||||
mfem::Vector rotationPotential;
|
||||
mfem::DenseMatrix rotationGradient;
|
||||
mfem::Vector hydrostaticImbalance;
|
||||
mfem::Vector weightedResidual;
|
||||
|
||||
// Geometry-dependent algebraic Jacobian blocks.
|
||||
mfem::DenseMatrix enthalpyJacobian;
|
||||
mfem::DenseMatrix gravityPotentialJacobian;
|
||||
mfem::Vector bernoulliConstantJacobian;
|
||||
};
|
||||
|
||||
void PrepareStaticPlan();
|
||||
void PrepareGeometry();
|
||||
void PrepareAlgebraicJacobianBlocks();
|
||||
void PrepareRotation();
|
||||
void PrepareBaseState();
|
||||
void FinalizeDisplacementJacobianPreparation();
|
||||
void AssembleCachedResidual();
|
||||
void VerifyPrepared() const;
|
||||
|
||||
const fem::FEM &m_fem;
|
||||
const mapping::DomainMapperStateless &m_domainMapper;
|
||||
|
||||
context::hydrostatic::HydrostaticEquilibriumContext m_context;
|
||||
|
||||
std::optional<physics::RigidRotation> m_rotation;
|
||||
std::vector<ElementPAData> m_elements;
|
||||
mfem::Vector m_cachedResidual;
|
||||
|
||||
std::uint64_t m_residualPreparationCount{0};
|
||||
|
||||
mutable std::uint64_t m_residualApplicationCount{0};
|
||||
|
||||
mutable PreparedHydrostaticAlgebraicJacobianStatistics
|
||||
m_algebraicJacobianStatistics;
|
||||
|
||||
mutable PreparedHydrostaticDisplacementJacobianStatistics
|
||||
m_displacementJacobianStatistics;
|
||||
|
||||
mutable PreparedHydrostaticCompleteJacobianStatistics
|
||||
m_completeJacobianStatistics;
|
||||
|
||||
bool m_isPrepared{false};
|
||||
};
|
||||
|
||||
class PreparedHydrostaticEquilibriumJacobianOperator final
|
||||
: public mfem::Operator {
|
||||
public:
|
||||
PreparedHydrostaticEquilibriumJacobianOperator(
|
||||
const fem::FEM &f,
|
||||
const PreparedHydrostaticEquilibriumOperator &preparedOperator
|
||||
);
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &action
|
||||
) const override;
|
||||
|
||||
[[nodiscard]] const HydrostaticJacobianBlockLayout &
|
||||
GetLayout() const noexcept;
|
||||
|
||||
private:
|
||||
HydrostaticJacobianBlockLayout m_layout;
|
||||
|
||||
const PreparedHydrostaticEquilibriumOperator &m_preparedOperator;
|
||||
};
|
||||
} // namespace mean_field::operators
|
||||
173
libmeanfield/interface/physics/barotrope.cppm
Normal file
173
libmeanfield/interface/physics/barotrope.cppm
Normal file
@@ -0,0 +1,173 @@
|
||||
module;
|
||||
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
|
||||
export module mean_field:physics.barotrope;
|
||||
|
||||
export namespace mean_field::physics {
|
||||
class PolytropicBarotrope final {
|
||||
public:
|
||||
PolytropicBarotrope(
|
||||
const double polytropic_index,
|
||||
const double polytropic_constant
|
||||
)
|
||||
: m_polytropic_index(polytropic_index),
|
||||
m_polytropic_constant(polytropic_constant),
|
||||
m_enthalpy_scale((polytropic_index + 1.0) * polytropic_constant) {
|
||||
if (!std::isfinite(polytropic_index) || polytropic_index < 1.0) {
|
||||
throw std::invalid_argument(
|
||||
std::format(
|
||||
"The differentiable polytropic closure requires a "
|
||||
"finite polytropic index greater than or equal to one. "
|
||||
"Instead a value of {} has been provided",
|
||||
polytropic_index
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!std::isfinite(polytropic_constant) ||
|
||||
polytropic_constant <= 0.0) {
|
||||
throw std::invalid_argument(
|
||||
std::format(
|
||||
"The polytropic constant must be finite and positive. "
|
||||
"Instead a value of {} has been provided",
|
||||
polytropic_constant
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] double polytropic_index() const noexcept {
|
||||
return m_polytropic_index;
|
||||
}
|
||||
|
||||
[[nodiscard]] double polytropic_constant() const noexcept {
|
||||
return m_polytropic_constant;
|
||||
}
|
||||
|
||||
[[nodiscard]] double enthalpy_scale() const noexcept {
|
||||
return m_enthalpy_scale;
|
||||
}
|
||||
|
||||
[[nodiscard]] double pressure_from_density(const double density) const {
|
||||
validate_nonnegativity(density, "density");
|
||||
if (density == 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return m_polytropic_constant *
|
||||
std::pow(density, 1.0 + 1.0 / m_polytropic_index);
|
||||
}
|
||||
|
||||
[[nodiscard]] double enthalpy_from_density(const double density) const {
|
||||
validate_nonnegativity(density, "density");
|
||||
if (density == 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return m_enthalpy_scale *
|
||||
std::pow(density, 1.0 / m_polytropic_index);
|
||||
}
|
||||
|
||||
[[nodiscard]] double
|
||||
density_from_enthalpy(const double enthalpy) const {
|
||||
validate_finite(enthalpy, "enthalpy");
|
||||
|
||||
if (enthalpy <= 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return std::pow(enthalpy / m_enthalpy_scale, m_polytropic_index);
|
||||
}
|
||||
|
||||
[[nodiscard]] double
|
||||
pressure_from_enthalpy(const double enthalpy) const {
|
||||
validate_finite(enthalpy, "enthalpy");
|
||||
|
||||
if (enthalpy <= 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return density_from_enthalpy(enthalpy) * enthalpy /
|
||||
(m_polytropic_index + 1.0);
|
||||
}
|
||||
|
||||
[[nodiscard]] double
|
||||
density_derivative_from_enthalpy(const double enthalpy) const {
|
||||
validate_finite(enthalpy, "enthalpy");
|
||||
if (enthalpy < 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if (enthalpy == 0.0) {
|
||||
return m_polytropic_index == 1.0 ? 1.0 / m_enthalpy_scale : 0.0;
|
||||
}
|
||||
|
||||
return m_polytropic_index / m_enthalpy_scale *
|
||||
std::pow(
|
||||
enthalpy / m_enthalpy_scale, m_polytropic_index - 1.0
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] double
|
||||
pressure_derivative_from_enthalpy(const double enthalpy) const {
|
||||
validate_finite(enthalpy, "enthalpy");
|
||||
|
||||
if (enthalpy <= 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return density_from_enthalpy(enthalpy);
|
||||
}
|
||||
|
||||
[[nodiscard]] double
|
||||
pressure_derivative_from_density(const double density) const {
|
||||
validate_nonnegativity(density, "density");
|
||||
if (density == 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return m_polytropic_constant * (1.0 + 1.0 / m_polytropic_index) *
|
||||
std::pow(density, 1.0 / m_polytropic_index);
|
||||
}
|
||||
|
||||
private:
|
||||
static void validate_finite(
|
||||
const double value,
|
||||
const char *quantity
|
||||
) {
|
||||
if (!std::isfinite(value)) {
|
||||
throw std::domain_error(
|
||||
std::format(
|
||||
"The {} must be finite. Instead a value of {} has been "
|
||||
"provided",
|
||||
quantity, value
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void validate_nonnegativity(
|
||||
const double value,
|
||||
const char *quantity
|
||||
) {
|
||||
validate_finite(value, quantity);
|
||||
if (value < 0.0) {
|
||||
throw std::domain_error(
|
||||
std::format(
|
||||
"The {} must be non-negative. Instead a value of {} "
|
||||
"has been "
|
||||
"provided",
|
||||
quantity, value
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
double m_polytropic_index;
|
||||
double m_polytropic_constant;
|
||||
double m_enthalpy_scale;
|
||||
};
|
||||
} // namespace mean_field::physics
|
||||
@@ -1,4 +1,5 @@
|
||||
module;
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:physics.contexts;
|
||||
@@ -23,6 +24,6 @@ export namespace mean_field::physics {
|
||||
std::unique_ptr<mfem::HypreParMatrix> Schur;
|
||||
|
||||
std::unique_ptr<mfem::MatrixCoefficient> mapped_hdiv_mass_coeff;
|
||||
|
||||
std::unique_ptr<mfem::Operator> source_form;
|
||||
};
|
||||
}
|
||||
} // namespace mean_field::physics
|
||||
|
||||
@@ -10,7 +10,10 @@ export namespace mean_field::physics {
|
||||
mfem::ParGridFunction gradPhi;
|
||||
mfem::ParGridFunction phi;
|
||||
|
||||
explicit GravitySolution(fem::FEM& fem): gradPhi(fem.RT_fes.get()), phi(fem.L2_fes.get()) {}
|
||||
explicit GravitySolution(fem::FEM &fem)
|
||||
: gradPhi(fem.gravityFluxFes.get()),
|
||||
phi(fem.gravityPotentialFes.get()) {
|
||||
}
|
||||
};
|
||||
|
||||
GravitySolution grav_potential(
|
||||
@@ -20,6 +23,13 @@ export namespace mean_field::physics {
|
||||
bool phi_warm = false
|
||||
);
|
||||
|
||||
GravitySolution grav_potential_new(
|
||||
fem::FEM &f,
|
||||
const utils::Args &args,
|
||||
const mfem::GridFunction &rho,
|
||||
const mfem::GridFunction &displacement
|
||||
);
|
||||
|
||||
mfem::GridFunction get_potential(
|
||||
fem::FEM &fem,
|
||||
const utils::Args &args,
|
||||
@@ -40,6 +50,4 @@ export namespace mean_field::physics {
|
||||
);
|
||||
|
||||
void update_stiffness_matrix(fem::FEM &fem);
|
||||
}
|
||||
|
||||
|
||||
} // namespace mean_field::physics
|
||||
|
||||
127
libmeanfield/interface/physics/rigid_rotation.cppm
Normal file
127
libmeanfield/interface/physics/rigid_rotation.cppm
Normal file
@@ -0,0 +1,127 @@
|
||||
module;
|
||||
|
||||
#include <cmath>
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:physics.rigid_rotation;
|
||||
|
||||
export namespace mean_field::physics {
|
||||
class RigidRotation final {
|
||||
public:
|
||||
RigidRotation(
|
||||
const mfem::Vector &angularVelocity,
|
||||
const mfem::Vector ¢er
|
||||
)
|
||||
: m_angularVelocity(angularVelocity),
|
||||
m_center(center) {
|
||||
MFEM_VERIFY(
|
||||
m_angularVelocity.Size() == 3,
|
||||
"RigidRotation requires a three-dimensional "
|
||||
"angular-velocity vector."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
m_center.Size() == 3,
|
||||
"RigidRotation requires a three-dimensional center."
|
||||
);
|
||||
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(m_angularVelocity(component)),
|
||||
"RigidRotation received a non-finite "
|
||||
"angular-velocity component."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
std::isfinite(m_center(component)),
|
||||
"RigidRotation received a non-finite center component."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] double
|
||||
potential(const mfem::Vector &physicalPosition) const {
|
||||
MFEM_VERIFY(
|
||||
physicalPosition.Size() == 3,
|
||||
"RigidRotation::potential requires a "
|
||||
"three-dimensional position."
|
||||
);
|
||||
|
||||
const double relativeX = physicalPosition(0) - m_center(0);
|
||||
|
||||
const double relativeY = physicalPosition(1) - m_center(1);
|
||||
|
||||
const double relativeZ = physicalPosition(2) - m_center(2);
|
||||
|
||||
const double crossX = m_angularVelocity(1) * relativeZ -
|
||||
m_angularVelocity(2) * relativeY;
|
||||
|
||||
const double crossY = m_angularVelocity(2) * relativeX -
|
||||
m_angularVelocity(0) * relativeZ;
|
||||
|
||||
const double crossZ = m_angularVelocity(0) * relativeY -
|
||||
m_angularVelocity(1) * relativeX;
|
||||
|
||||
return 0.5 * (crossX * crossX + crossY * crossY + crossZ * crossZ);
|
||||
}
|
||||
|
||||
[[nodiscard]] double potential_directional_derivative(
|
||||
const mfem::Vector &physicalPosition,
|
||||
const mfem::Vector &physicalPositionVariation
|
||||
) const {
|
||||
MFEM_VERIFY(
|
||||
physicalPosition.Size() == 3,
|
||||
"RigidRotation derivative requires a "
|
||||
"three-dimensional position."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
physicalPositionVariation.Size() == 3,
|
||||
"RigidRotation derivative requires a "
|
||||
"three-dimensional direction."
|
||||
);
|
||||
|
||||
double angularVelocitySquared = 0.0;
|
||||
double angularVelocityDotPosition = 0.0;
|
||||
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
const double relativePosition =
|
||||
physicalPosition(component) - m_center(component);
|
||||
|
||||
angularVelocitySquared +=
|
||||
m_angularVelocity(component) * m_angularVelocity(component);
|
||||
|
||||
angularVelocityDotPosition +=
|
||||
m_angularVelocity(component) * relativePosition;
|
||||
}
|
||||
|
||||
double derivative = 0.0;
|
||||
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
const double relativePosition =
|
||||
physicalPosition(component) - m_center(component);
|
||||
|
||||
const double gradientComponent =
|
||||
angularVelocitySquared * relativePosition -
|
||||
angularVelocityDotPosition * m_angularVelocity(component);
|
||||
|
||||
derivative +=
|
||||
gradientComponent * physicalPositionVariation(component);
|
||||
}
|
||||
|
||||
return derivative;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Vector &angular_velocity() const noexcept {
|
||||
return m_angularVelocity;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Vector ¢er() const noexcept {
|
||||
return m_center;
|
||||
}
|
||||
|
||||
private:
|
||||
mfem::Vector m_angularVelocity;
|
||||
mfem::Vector m_center;
|
||||
};
|
||||
} // namespace mean_field::physics
|
||||
@@ -5,5 +5,8 @@ export module mean_field:physics.solid_body;
|
||||
export import :fem;
|
||||
|
||||
export namespace mean_field::physics {
|
||||
double compute_moment_of_inertia(const fem::FEM &fem, const mfem::GridFunction &rho_ref);
|
||||
double compute_moment_of_inertia(
|
||||
const fem::FEM &fem,
|
||||
const mfem::GridFunction &rho_ref
|
||||
);
|
||||
}
|
||||
@@ -4,71 +4,91 @@ module;
|
||||
|
||||
export module mean_field:quadrature.mfem;
|
||||
export import :quadrature.policy;
|
||||
export import :integrators.centrifugal;
|
||||
import :field.mfem;
|
||||
|
||||
export namespace mean_field::quadrature {
|
||||
struct MfemRule {
|
||||
Resolution resolution;
|
||||
const mfem::IntegrationRule* integration_rule;
|
||||
const mfem::IntegrationRule *integration_rule;
|
||||
};
|
||||
|
||||
class RuleFactory {
|
||||
public:
|
||||
explicit RuleFactory(
|
||||
Policy policy
|
||||
);
|
||||
MfemRule get(
|
||||
const Query& query,
|
||||
mfem::Geometry::Type geometry
|
||||
) const;
|
||||
MfemRule get(
|
||||
Term term,
|
||||
explicit RuleFactory(Policy policy);
|
||||
MfemRule
|
||||
get(const Query &query,
|
||||
mfem::Geometry::Type geometry) const;
|
||||
MfemRule
|
||||
get(Term term,
|
||||
QuadratureRole role,
|
||||
mfem::Geometry::Type geometry,
|
||||
int base_order,
|
||||
utils::DOMAINS domain = utils::DOMAINS::ALL,
|
||||
MappingKind mapping = MappingKind::none
|
||||
) const;
|
||||
MappingKind mapping = MappingKind::none) const;
|
||||
|
||||
Resolution configure_gravity_hdiv_mass(
|
||||
mfem::VectorFEMassIntegrator& integrator,
|
||||
mfem::VectorFEMassIntegrator &integrator,
|
||||
QuadratureRole role,
|
||||
const mfem::FiniteElement& element,
|
||||
const mfem::ElementTransformation& transformation,
|
||||
const mfem::FiniteElement &element,
|
||||
const mfem::ElementTransformation &transformation,
|
||||
utils::DOMAINS domain = utils::DOMAINS::ALL,
|
||||
MappingKind mapping = MappingKind::none
|
||||
) const;
|
||||
|
||||
Resolution configure_gravity_divergence(
|
||||
mfem::VectorFEDivergenceIntegrator& integrator,
|
||||
mfem::VectorFEDivergenceIntegrator &integrator,
|
||||
QuadratureRole role,
|
||||
const mfem::FiniteElement& trial_element,
|
||||
const mfem::FiniteElement& test_element,
|
||||
const mfem::ElementTransformation& transformation,
|
||||
const mfem::FiniteElement &trial_element,
|
||||
const mfem::FiniteElement &test_element,
|
||||
const mfem::ElementTransformation &transformation,
|
||||
utils::DOMAINS domain = utils::DOMAINS::ALL,
|
||||
MappingKind mapping = MappingKind::none
|
||||
) const;
|
||||
|
||||
Resolution configure_gravity_boundary(
|
||||
mfem::VectorFEBoundaryFluxLFIntegrator& integrator,
|
||||
mfem::VectorFEBoundaryFluxLFIntegrator &integrator,
|
||||
QuadratureRole role,
|
||||
const mfem::FiniteElement& boundary_element,
|
||||
const mfem::FiniteElement &boundary_element,
|
||||
utils::DOMAINS domain = utils::DOMAINS::VACUUM,
|
||||
MappingKind mapping = MappingKind::none
|
||||
) const;
|
||||
|
||||
Resolution configure_gravity_source(
|
||||
mfem::DomainLFIntegrator& integrator,
|
||||
mfem::DomainLFIntegrator &integrator,
|
||||
QuadratureRole role,
|
||||
const mfem::FiniteElement& test_element,
|
||||
const mfem::ElementTransformation& transformation,
|
||||
const mfem::FiniteElement &test_element,
|
||||
const mfem::ElementTransformation &transformation,
|
||||
int coefficient_order,
|
||||
utils::DOMAINS domain = utils::DOMAINS::STELLAR,
|
||||
MappingKind mapping = MappingKind::none
|
||||
) const;
|
||||
|
||||
template<typename IntegratorType>
|
||||
Resolution configure_gravity_source(
|
||||
mfem::MixedScalarMassIntegrator &integrator,
|
||||
QuadratureRole role,
|
||||
const mfem::FiniteElement &trial_element,
|
||||
const mfem::FiniteElement &test_element,
|
||||
const mfem::ElementTransformation &transformation,
|
||||
int coefficient_order,
|
||||
utils::DOMAINS domain = utils::DOMAINS::STELLAR,
|
||||
MappingKind mapping = MappingKind::none
|
||||
) const;
|
||||
|
||||
Resolution configure_centrifugal(
|
||||
integrators::CentrifugalForceIntegrator &integrator,
|
||||
QuadratureRole role,
|
||||
const mfem::FiniteElement &density_element,
|
||||
const mfem::FiniteElement &velocity_element,
|
||||
const mfem::ElementTransformation &transformation,
|
||||
int position_order,
|
||||
utils::DOMAINS domain = utils::DOMAINS::STELLAR,
|
||||
MappingKind mapping = MappingKind::none
|
||||
) const;
|
||||
|
||||
template <typename IntegratorType>
|
||||
Resolution configure(
|
||||
IntegratorType& integrator,
|
||||
IntegratorType &integrator,
|
||||
Term term,
|
||||
QuadratureRole role,
|
||||
mfem::Geometry::Type geometry,
|
||||
@@ -77,20 +97,23 @@ export namespace mean_field::quadrature {
|
||||
MappingKind mapping = MappingKind::none
|
||||
) const;
|
||||
|
||||
|
||||
private:
|
||||
Policy policy;
|
||||
};
|
||||
|
||||
RuleFactory::RuleFactory(Policy policy) : policy(std::move(policy)) {}
|
||||
RuleFactory::RuleFactory(Policy policy) : policy(std::move(policy)) {
|
||||
}
|
||||
|
||||
MfemRule RuleFactory::get(
|
||||
const Query& query,
|
||||
const Query &query,
|
||||
const mfem::Geometry::Type geometry
|
||||
) const {
|
||||
const Resolution resolution = policy.resolve(query);
|
||||
const mfem::IntegrationRule& integration_rule = mfem::IntRules.Get(geometry, resolution.order);
|
||||
return {.resolution = resolution, .integration_rule = &integration_rule};
|
||||
const mfem::IntegrationRule &integration_rule =
|
||||
mfem::IntRules.Get(geometry, resolution.order);
|
||||
return {
|
||||
.resolution = resolution, .integration_rule = &integration_rule
|
||||
};
|
||||
}
|
||||
|
||||
MfemRule RuleFactory::get(
|
||||
@@ -110,79 +133,189 @@ export namespace mean_field::quadrature {
|
||||
}
|
||||
|
||||
Resolution RuleFactory::configure_gravity_hdiv_mass(
|
||||
mfem::VectorFEMassIntegrator& integrator,
|
||||
mfem::VectorFEMassIntegrator &integrator,
|
||||
const QuadratureRole role,
|
||||
const mfem::FiniteElement& element,
|
||||
const mfem::ElementTransformation& transformation,
|
||||
const mfem::FiniteElement &element,
|
||||
const mfem::ElementTransformation &transformation,
|
||||
const utils::DOMAINS domain,
|
||||
const MappingKind mapping
|
||||
) const {
|
||||
const int base_order = 2 * element.GetOrder() + transformation.OrderW();
|
||||
return configure(integrator, Term::gravity_hdiv_mass, role, element.GetGeomType(), base_order, domain, mapping);
|
||||
using GravityField = field::Field<field::Gravity>;
|
||||
MFEM_VERIFY(
|
||||
element.GetOrder() == field::Gravity::Flux::familyOrder + 1,
|
||||
"The H(div) element order does not match the registered gravity "
|
||||
"flux."
|
||||
);
|
||||
const Query query =
|
||||
GravityField::make_query<field::Gravity::Form::HDivMass>(
|
||||
role, transformation.OrderW(), {}, domain, mapping
|
||||
);
|
||||
const auto [resolution, integration_rule] =
|
||||
get(query, element.GetGeomType());
|
||||
integrator.SetIntegrationRule(*integration_rule);
|
||||
return resolution;
|
||||
}
|
||||
|
||||
Resolution RuleFactory::configure_gravity_divergence(
|
||||
mfem::VectorFEDivergenceIntegrator& integrator,
|
||||
mfem::VectorFEDivergenceIntegrator &integrator,
|
||||
const QuadratureRole role,
|
||||
const mfem::FiniteElement& trial_element,
|
||||
const mfem::FiniteElement& test_element,
|
||||
const mfem::ElementTransformation& transformation,
|
||||
const mfem::FiniteElement &trial_element,
|
||||
const mfem::FiniteElement &test_element,
|
||||
const mfem::ElementTransformation &transformation,
|
||||
const utils::DOMAINS domain,
|
||||
const MappingKind mapping
|
||||
) const {
|
||||
const Query query = {
|
||||
.term = Term::gravity_divergence,
|
||||
.role = role,
|
||||
.domain = domain,
|
||||
.mapping = mapping,
|
||||
.trial_order = trial_element.GetOrder(),
|
||||
.test_order = test_element.GetOrder(),
|
||||
.geometry_weight_order = transformation.OrderW()
|
||||
};
|
||||
using GravityField = field::Field<field::Gravity>;
|
||||
MFEM_VERIFY(
|
||||
trial_element.GetOrder() == field::Gravity::Flux::familyOrder + 1,
|
||||
"The divergence trial element does not match the registered "
|
||||
"gravity flux."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
test_element.GetOrder() == field::Gravity::Potential::familyOrder,
|
||||
"The divergence test element does not match the registered "
|
||||
"gravity potential."
|
||||
);
|
||||
const Query query =
|
||||
GravityField::make_query<field::Gravity::Form::DivergenceCoupling>(
|
||||
role, transformation.OrderW(), {}, domain, mapping
|
||||
);
|
||||
|
||||
const auto [resolution, integration_rule] = get(query, trial_element.GetGeomType());
|
||||
const auto [resolution, integration_rule] =
|
||||
get(query, trial_element.GetGeomType());
|
||||
integrator.SetIntegrationRule(*integration_rule);
|
||||
return resolution;
|
||||
}
|
||||
|
||||
Resolution RuleFactory::configure_gravity_boundary(
|
||||
mfem::VectorFEBoundaryFluxLFIntegrator& integrator,
|
||||
mfem::VectorFEBoundaryFluxLFIntegrator &integrator,
|
||||
const QuadratureRole role,
|
||||
const mfem::FiniteElement& boundary_element,
|
||||
const mfem::FiniteElement &boundary_element,
|
||||
const utils::DOMAINS domain,
|
||||
const MappingKind mapping
|
||||
) const {
|
||||
const int base_order = 2 * boundary_element.GetOrder();
|
||||
return configure(integrator, Term::gravity_boundary, role, boundary_element.GetGeomType(), base_order, domain, mapping);
|
||||
}
|
||||
|
||||
Resolution RuleFactory::configure_gravity_source(
|
||||
mfem::DomainLFIntegrator& integrator,
|
||||
const QuadratureRole role,
|
||||
const mfem::FiniteElement& test_element,
|
||||
const mfem::ElementTransformation& transformation,
|
||||
const int coefficient_order,
|
||||
const utils::DOMAINS domain,
|
||||
const MappingKind mapping
|
||||
) const {
|
||||
const Query query = {
|
||||
.term = Term::gravity_source,
|
||||
.role = role,
|
||||
.domain = domain,
|
||||
.mapping = mapping,
|
||||
.test_order = test_element.GetOrder(),
|
||||
.coefficient_order = coefficient_order,
|
||||
.geometry_weight_order = transformation.OrderW()
|
||||
};
|
||||
|
||||
const auto [resolution, integration_rule] = get(query, test_element.GetGeomType());
|
||||
using GravityField = field::Field<field::Gravity>;
|
||||
MFEM_VERIFY(
|
||||
boundary_element.GetOrder() == field::Gravity::Flux::familyOrder,
|
||||
"The boundary element does not match the registered gravity-flux "
|
||||
"normal trace."
|
||||
);
|
||||
const Query query =
|
||||
GravityField::make_query<field::Gravity::Form::Boundary>(
|
||||
role, 0, {}, domain, mapping
|
||||
);
|
||||
const auto [resolution, integration_rule] =
|
||||
get(query, boundary_element.GetGeomType());
|
||||
integrator.SetIntegrationRule(*integration_rule);
|
||||
return resolution;
|
||||
}
|
||||
|
||||
template<typename IntegratorType>
|
||||
Resolution RuleFactory::configure_gravity_source(
|
||||
mfem::DomainLFIntegrator &integrator,
|
||||
const QuadratureRole role,
|
||||
const mfem::FiniteElement &test_element,
|
||||
const mfem::ElementTransformation &transformation,
|
||||
const int coefficient_order,
|
||||
const utils::DOMAINS domain,
|
||||
const MappingKind mapping
|
||||
) const {
|
||||
using GravityField = field::Field<field::Gravity>;
|
||||
MFEM_VERIFY(
|
||||
test_element.GetOrder() == field::Gravity::Potential::familyOrder,
|
||||
"The gravity-source test element does not match the registered "
|
||||
"gravity potential."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
coefficient_order == field::Density::Scalar::familyOrder,
|
||||
"The gravity-source coefficient order does not match the "
|
||||
"registered density field."
|
||||
);
|
||||
const Query query =
|
||||
GravityField::make_query<field::Gravity::Form::SourceLinear>(
|
||||
role, transformation.OrderW(), {}, domain, mapping
|
||||
);
|
||||
|
||||
const auto [resolution, integration_rule] =
|
||||
get(query, test_element.GetGeomType());
|
||||
integrator.SetIntegrationRule(*integration_rule);
|
||||
return resolution;
|
||||
}
|
||||
Resolution RuleFactory::configure_gravity_source(
|
||||
mfem::MixedScalarMassIntegrator &integrator,
|
||||
QuadratureRole role,
|
||||
const mfem::FiniteElement &trial_element,
|
||||
const mfem::FiniteElement &test_element,
|
||||
const mfem::ElementTransformation &transformation,
|
||||
int coefficient_order,
|
||||
utils::DOMAINS domain,
|
||||
MappingKind mapping
|
||||
) const {
|
||||
MFEM_VERIFY(
|
||||
trial_element.GetGeomType() == test_element.GetGeomType(),
|
||||
"Gravity source trial and test elements must use the same geometry."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
trial_element.GetGeomType() == transformation.GetGeometryType(),
|
||||
"Gravity source element and transformation geometries must agree."
|
||||
);
|
||||
|
||||
using GravityField = field::Field<field::Gravity>;
|
||||
MFEM_VERIFY(
|
||||
trial_element.GetOrder() == field::Density::Scalar::familyOrder,
|
||||
"The gravity-source trial element does not match the registered "
|
||||
"density field."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
test_element.GetOrder() == field::Gravity::Potential::familyOrder,
|
||||
"The gravity-source test element does not match the registered "
|
||||
"gravity potential."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
coefficient_order == 0,
|
||||
"The mapped gravity-source coefficient order must be zero; "
|
||||
"density order is supplied by the registered trial field."
|
||||
);
|
||||
const Query query =
|
||||
GravityField::make_query<field::Gravity::Form::SourceProjection>(
|
||||
role, transformation.OrderW(), {}, domain, mapping
|
||||
);
|
||||
|
||||
const auto [resolution, integration_rule] =
|
||||
get(query, transformation.GetGeometryType());
|
||||
integrator.SetIntRule(integration_rule);
|
||||
return resolution;
|
||||
}
|
||||
|
||||
Resolution RuleFactory::configure_centrifugal(
|
||||
integrators::CentrifugalForceIntegrator &integrator,
|
||||
const QuadratureRole role,
|
||||
const mfem::FiniteElement &density_element,
|
||||
const mfem::FiniteElement &velocity_element,
|
||||
const mfem::ElementTransformation &transformation,
|
||||
const int position_order,
|
||||
const utils::DOMAINS domain,
|
||||
const MappingKind mapping
|
||||
) const {
|
||||
const Query query = {
|
||||
.term = Term::centrifugal,
|
||||
.role = role,
|
||||
.domain = domain,
|
||||
.mapping = mapping,
|
||||
.trial_order = density_element.GetOrder(),
|
||||
.test_order = velocity_element.GetOrder(),
|
||||
.coefficient_order = position_order,
|
||||
.geometry_weight_order = transformation.OrderW()
|
||||
};
|
||||
|
||||
const auto [resolution, integration_rule] =
|
||||
get(query, velocity_element.GetGeomType());
|
||||
integrator.SetIntegrationRule(*integration_rule);
|
||||
return resolution;
|
||||
}
|
||||
|
||||
template <typename IntegratorType>
|
||||
Resolution RuleFactory::configure(
|
||||
IntegratorType& integrator,
|
||||
IntegratorType &integrator,
|
||||
const Term term,
|
||||
const QuadratureRole role,
|
||||
const mfem::Geometry::Type geometry,
|
||||
@@ -190,9 +323,10 @@ export namespace mean_field::quadrature {
|
||||
const utils::DOMAINS domain,
|
||||
const MappingKind mapping
|
||||
) const {
|
||||
const auto [resolution, integration_rule] = get(term, role, geometry, base_order, domain, mapping);
|
||||
const auto [resolution, integration_rule] =
|
||||
get(term, role, geometry, base_order, domain, mapping);
|
||||
integrator.SetIntegrationRule(*integration_rule);
|
||||
return resolution;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace mean_field::quadrature
|
||||
|
||||
@@ -2,7 +2,9 @@ module;
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
export module mean_field:quadrature.policy;
|
||||
export import :utils.misc;
|
||||
@@ -13,11 +15,19 @@ export namespace mean_field::quadrature {
|
||||
gravity_divergence,
|
||||
gravity_source,
|
||||
gravity_boundary,
|
||||
centrifugal,
|
||||
density_projection,
|
||||
eos_closure,
|
||||
hydrostatic_equilibrium,
|
||||
isobaric_surface,
|
||||
mesh_extension,
|
||||
mass_conservation,
|
||||
mass_normalization,
|
||||
center_of_mass,
|
||||
quadrupole,
|
||||
gravitational_energy,
|
||||
pressure_integral,
|
||||
pressure_force,
|
||||
virial,
|
||||
error_norm
|
||||
};
|
||||
@@ -29,19 +39,9 @@ export namespace mean_field::quadrature {
|
||||
projection
|
||||
};
|
||||
|
||||
enum class MappingKind {
|
||||
none,
|
||||
affine,
|
||||
general,
|
||||
kelvin
|
||||
};
|
||||
enum class MappingKind { none, affine, general, kelvin };
|
||||
|
||||
enum class Mode {
|
||||
fast,
|
||||
production,
|
||||
reference,
|
||||
convergence
|
||||
};
|
||||
enum class Mode { fast, production, reference, convergence };
|
||||
|
||||
struct RuleControl {
|
||||
std::optional<int> fixed_order;
|
||||
@@ -55,17 +55,24 @@ export namespace mean_field::quadrature {
|
||||
RuleControl projection;
|
||||
};
|
||||
|
||||
|
||||
struct RuleSet {
|
||||
RuleControl gravity_hdiv_mass;
|
||||
RuleControl gravity_divergence;
|
||||
RuleControl gravity_source;
|
||||
RuleControl gravity_boundary;
|
||||
RuleControl centrifugal;
|
||||
RuleControl density_projection;
|
||||
RuleControl eos_closure;
|
||||
RuleControl hydrostatic_equilibrium;
|
||||
RuleControl isobaric_surface;
|
||||
RuleControl mesh_extension;
|
||||
RuleControl mass_conservation;
|
||||
RuleControl mass_normalization;
|
||||
RuleControl center_of_mass;
|
||||
RuleControl quadrupole;
|
||||
RuleControl gravitational_energy;
|
||||
RuleControl pressure_integral;
|
||||
RuleControl pressure_force;
|
||||
RuleControl virial;
|
||||
RuleControl error_norm;
|
||||
RoleControls roles;
|
||||
@@ -124,11 +131,19 @@ export namespace mean_field::quadrature {
|
||||
QuadratureTermOptions gravity_divergence;
|
||||
QuadratureTermOptions gravity_source;
|
||||
QuadratureTermOptions gravity_boundary;
|
||||
QuadratureTermOptions centrifugal;
|
||||
QuadratureTermOptions density_projection;
|
||||
QuadratureTermOptions eos_closure;
|
||||
QuadratureTermOptions hydrostatic_equilibrium;
|
||||
QuadratureTermOptions isobaric_surface;
|
||||
QuadratureTermOptions mesh_extension;
|
||||
QuadratureTermOptions mass_conservation;
|
||||
QuadratureTermOptions mass_normalization;
|
||||
QuadratureTermOptions center_of_mass;
|
||||
QuadratureTermOptions quadrupole;
|
||||
QuadratureTermOptions gravitational_energy;
|
||||
QuadratureTermOptions pressure_integral;
|
||||
QuadratureTermOptions pressure_force;
|
||||
QuadratureTermOptions virial;
|
||||
QuadratureTermOptions error_norm;
|
||||
|
||||
@@ -139,22 +154,28 @@ export namespace mean_field::quadrature {
|
||||
QuadratureValidationOptions validation;
|
||||
};
|
||||
|
||||
RuleSet make_rule_set(Mode mode, int global_boost = 0);
|
||||
RuleSet make_rule_set(
|
||||
Mode mode,
|
||||
int global_boost = 0
|
||||
);
|
||||
|
||||
class Policy {
|
||||
public:
|
||||
explicit Policy(RuleSet rule_set);
|
||||
Resolution resolve(const Query& query) const;
|
||||
Resolution resolve(const Query &query) const;
|
||||
|
||||
private:
|
||||
const RuleControl& get_control(Term term) const;
|
||||
static int compute_base_order(const Query& query) ;
|
||||
const RuleControl& get_role_control(QuadratureRole role) const;
|
||||
const RuleControl &get_control(Term term) const;
|
||||
static int compute_base_order(const Query &query);
|
||||
const RuleControl &get_role_control(QuadratureRole role) const;
|
||||
|
||||
RuleSet rule_set;
|
||||
};
|
||||
|
||||
RuleSet make_rule_set(const Mode mode, const int global_boost) {
|
||||
RuleSet make_rule_set(
|
||||
const Mode mode,
|
||||
const int global_boost
|
||||
) {
|
||||
RuleSet rule_set;
|
||||
|
||||
switch (mode) {
|
||||
@@ -171,12 +192,13 @@ export namespace mean_field::quadrature {
|
||||
return rule_set;
|
||||
}
|
||||
|
||||
Policy::Policy(RuleSet rule_set) : rule_set(std::move(rule_set)) {}
|
||||
Policy::Policy(RuleSet rule_set) : rule_set(std::move(rule_set)) {
|
||||
}
|
||||
|
||||
Resolution Policy::resolve(const Query& query) const {
|
||||
Resolution Policy::resolve(const Query &query) const {
|
||||
const int base_order = compute_base_order(query);
|
||||
const RuleControl& term_control = get_control(query.term);
|
||||
const RuleControl& role_control = get_role_control(query.role);
|
||||
const RuleControl &term_control = get_control(query.term);
|
||||
const RuleControl &role_control = get_role_control(query.role);
|
||||
std::optional<int> fixed_order;
|
||||
|
||||
if (term_control.fixed_order.has_value()) {
|
||||
@@ -189,49 +211,96 @@ export namespace mean_field::quadrature {
|
||||
|
||||
if (fixed_order.has_value()) {
|
||||
if (*fixed_order < 0) {
|
||||
throw std::invalid_argument("Quadrature fixed order cannot be negative.");
|
||||
throw std::invalid_argument(
|
||||
"Quadrature fixed order cannot be negative."
|
||||
);
|
||||
}
|
||||
|
||||
return {.base_order = base_order, .boost = 0, .order = *fixed_order, .used_fixed_order = true};
|
||||
return {
|
||||
.base_order = base_order,
|
||||
.boost = 0,
|
||||
.order = *fixed_order,
|
||||
.used_fixed_order = true
|
||||
};
|
||||
}
|
||||
|
||||
const int boost = rule_set.fallback.boost + role_control.boost + term_control.boost;
|
||||
const int boost =
|
||||
rule_set.fallback.boost + role_control.boost + term_control.boost;
|
||||
const int order = base_order + boost;
|
||||
|
||||
if (order < 0) {
|
||||
throw std::invalid_argument("Resolved quadrature order cannot be negative.");
|
||||
throw std::invalid_argument(
|
||||
"Resolved quadrature order cannot be negative."
|
||||
);
|
||||
}
|
||||
|
||||
return {.base_order = base_order, .boost = boost, .order = order, .used_fixed_order = false};
|
||||
return {
|
||||
.base_order = base_order,
|
||||
.boost = boost,
|
||||
.order = order,
|
||||
.used_fixed_order = false
|
||||
};
|
||||
}
|
||||
const RuleControl& Policy::get_control(const Term term) const {
|
||||
const RuleControl &Policy::get_control(const Term term) const {
|
||||
switch (term) {
|
||||
case Term::gravity_hdiv_mass: return rule_set.gravity_hdiv_mass;
|
||||
case Term::gravity_divergence: return rule_set.gravity_divergence;
|
||||
case Term::gravity_source: return rule_set.gravity_source;
|
||||
case Term::gravity_boundary: return rule_set.gravity_boundary;
|
||||
case Term::density_projection: return rule_set.density_projection;
|
||||
case Term::mass_conservation: return rule_set.mass_conservation;
|
||||
case Term::center_of_mass: return rule_set.center_of_mass;
|
||||
case Term::quadrupole: return rule_set.quadrupole;
|
||||
case Term::gravitational_energy: return rule_set.gravitational_energy;
|
||||
case Term::virial: return rule_set.virial;
|
||||
case Term::error_norm: return rule_set.error_norm;
|
||||
case Term::gravity_hdiv_mass:
|
||||
return rule_set.gravity_hdiv_mass;
|
||||
case Term::gravity_divergence:
|
||||
return rule_set.gravity_divergence;
|
||||
case Term::gravity_source:
|
||||
return rule_set.gravity_source;
|
||||
case Term::gravity_boundary:
|
||||
return rule_set.gravity_boundary;
|
||||
case Term::centrifugal:
|
||||
return rule_set.centrifugal;
|
||||
case Term::density_projection:
|
||||
return rule_set.density_projection;
|
||||
case Term::eos_closure:
|
||||
return rule_set.eos_closure;
|
||||
case Term::hydrostatic_equilibrium:
|
||||
return rule_set.hydrostatic_equilibrium;
|
||||
case Term::isobaric_surface:
|
||||
return rule_set.isobaric_surface;
|
||||
case Term::mesh_extension:
|
||||
return rule_set.mesh_extension;
|
||||
case Term::mass_conservation:
|
||||
return rule_set.mass_conservation;
|
||||
case Term::mass_normalization:
|
||||
return rule_set.mass_normalization;
|
||||
case Term::center_of_mass:
|
||||
return rule_set.center_of_mass;
|
||||
case Term::quadrupole:
|
||||
return rule_set.quadrupole;
|
||||
case Term::gravitational_energy:
|
||||
return rule_set.gravitational_energy;
|
||||
case Term::pressure_integral:
|
||||
return rule_set.pressure_integral;
|
||||
case Term::pressure_force:
|
||||
return rule_set.pressure_force;
|
||||
case Term::virial:
|
||||
return rule_set.virial;
|
||||
case Term::error_norm:
|
||||
return rule_set.error_norm;
|
||||
}
|
||||
|
||||
throw std::logic_error("Unknown quadrature term.");
|
||||
}
|
||||
|
||||
int Policy::compute_base_order(const Query& query) {
|
||||
int Policy::compute_base_order(const Query &query) {
|
||||
if (query.base_order.has_value()) {
|
||||
if (*query.base_order < 0) {
|
||||
throw std::invalid_argument("Quadrature base order cannot be negative.");
|
||||
throw std::invalid_argument(
|
||||
"Quadrature base order cannot be negative."
|
||||
);
|
||||
}
|
||||
return *query.base_order;
|
||||
}
|
||||
|
||||
if (query.trial_order < 0 || query.test_order < 0 || query.coefficient_order < 0 || query.geometry_weight_order < 0) {
|
||||
throw std::invalid_argument("Quadrature query orders cannot be negative.");
|
||||
if (query.trial_order < 0 || query.test_order < 0 ||
|
||||
query.coefficient_order < 0 || query.geometry_weight_order < 0) {
|
||||
throw std::invalid_argument(
|
||||
"Quadrature query orders cannot be negative."
|
||||
);
|
||||
}
|
||||
|
||||
int trial_order = query.trial_order;
|
||||
@@ -239,18 +308,24 @@ export namespace mean_field::quadrature {
|
||||
trial_order = std::max(0, trial_order - 1);
|
||||
}
|
||||
|
||||
return trial_order + query.test_order + query.coefficient_order + query.geometry_weight_order;
|
||||
return trial_order + query.test_order + query.coefficient_order +
|
||||
query.geometry_weight_order;
|
||||
}
|
||||
|
||||
const RuleControl& Policy::get_role_control(const QuadratureRole role) const {
|
||||
const RuleControl &
|
||||
Policy::get_role_control(const QuadratureRole role) const {
|
||||
switch (role) {
|
||||
case QuadratureRole::discretization: return rule_set.roles.discretization;
|
||||
case QuadratureRole::preconditioner: return rule_set.roles.preconditioner;
|
||||
case QuadratureRole::diagnostic: return rule_set.roles.diagnostic;
|
||||
case QuadratureRole::projection: return rule_set.roles.projection;
|
||||
case QuadratureRole::discretization:
|
||||
return rule_set.roles.discretization;
|
||||
case QuadratureRole::preconditioner:
|
||||
return rule_set.roles.preconditioner;
|
||||
case QuadratureRole::diagnostic:
|
||||
return rule_set.roles.diagnostic;
|
||||
case QuadratureRole::projection:
|
||||
return rule_set.roles.projection;
|
||||
}
|
||||
|
||||
throw std::logic_error("Unknown quadrature role.");
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace mean_field::quadrature
|
||||
|
||||
29
libmeanfield/interface/solver/fields.cppm
Normal file
29
libmeanfield/interface/solver/fields.cppm
Normal file
@@ -0,0 +1,29 @@
|
||||
module;
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
export module mean_field:solver.fields;
|
||||
|
||||
export namespace mean_field::solver {
|
||||
enum class FieldBlock : std::uint8_t {
|
||||
velocity = 0,
|
||||
density = 1,
|
||||
gravity_gradient = 2,
|
||||
gravity_potential = 3,
|
||||
displacement = 4,
|
||||
count = 5
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr int block_index(const FieldBlock field) noexcept {
|
||||
return static_cast<int>(field);
|
||||
}
|
||||
|
||||
inline constexpr int field_block_count = block_index(FieldBlock::count);
|
||||
|
||||
static_assert(block_index(FieldBlock::velocity) == 0);
|
||||
static_assert(block_index(FieldBlock::density) == 1);
|
||||
static_assert(block_index(FieldBlock::gravity_gradient) == 2);
|
||||
static_assert(block_index(FieldBlock::gravity_potential) == 3);
|
||||
static_assert(block_index(FieldBlock::displacement) == 4);
|
||||
static_assert(field_block_count == 5);
|
||||
} // namespace mean_field::solver
|
||||
493
libmeanfield/interface/utils/blocks.cppm
Normal file
493
libmeanfield/interface/utils/blocks.cppm
Normal file
@@ -0,0 +1,493 @@
|
||||
module;
|
||||
#include <array>
|
||||
#include <mfem.hpp>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
export module mean_field:utils.blocks;
|
||||
|
||||
export namespace mean_field::utils::blocks {
|
||||
inline constexpr int dynamic_block_size = -1;
|
||||
|
||||
struct block { };
|
||||
|
||||
struct residual_block_base : block {
|
||||
static constexpr int static_block_size = dynamic_block_size;
|
||||
};
|
||||
|
||||
struct value_block_base : block {
|
||||
static constexpr int static_block_size = dynamic_block_size;
|
||||
};
|
||||
|
||||
struct term { };
|
||||
struct field { };
|
||||
|
||||
template <typename Residual, typename... Values> struct block_row { };
|
||||
template <int index_value>
|
||||
struct residual_block final : residual_block_base {
|
||||
static constexpr int index = index_value;
|
||||
|
||||
// ReSharper disable once CppNonExplicitConversionOperator
|
||||
constexpr operator int() const noexcept {
|
||||
return index;
|
||||
}
|
||||
};
|
||||
|
||||
template <int index_value> struct value_block final : value_block_base {
|
||||
static constexpr int index = index_value;
|
||||
|
||||
// ReSharper disable once CppNonExplicitConversionOperator
|
||||
constexpr operator int() const noexcept {
|
||||
return index;
|
||||
}
|
||||
};
|
||||
|
||||
struct density final : field {
|
||||
struct mass final : term {
|
||||
struct value final : value_block_base { };
|
||||
struct residual final : residual_block_base { };
|
||||
};
|
||||
|
||||
static inline constexpr mass mass_term{};
|
||||
};
|
||||
|
||||
struct displacement final : field {
|
||||
struct geometry final : term {
|
||||
struct value final : value_block_base { };
|
||||
struct residual final : residual_block_base { };
|
||||
};
|
||||
|
||||
static inline constexpr geometry geometry_term{};
|
||||
};
|
||||
|
||||
struct gravity final : field {
|
||||
struct gradient final : term {
|
||||
struct value final : value_block_base { };
|
||||
struct residual final : residual_block_base { };
|
||||
};
|
||||
|
||||
struct poisson final : term {
|
||||
struct value final : value_block_base { };
|
||||
struct residual final : residual_block_base { };
|
||||
};
|
||||
|
||||
static inline constexpr gradient gradient_term{};
|
||||
static inline constexpr poisson poisson_term{};
|
||||
};
|
||||
|
||||
struct enthalpy final : field {
|
||||
struct specific final : term {
|
||||
struct value final : value_block_base { };
|
||||
struct residual final : residual_block_base { };
|
||||
};
|
||||
|
||||
static inline constexpr specific specific_term{};
|
||||
};
|
||||
|
||||
struct barotropic_constant final : field {
|
||||
struct mass_normalization final : term {
|
||||
struct value final : value_block_base {
|
||||
static constexpr int static_block_size = 1;
|
||||
};
|
||||
|
||||
struct residual final : residual_block_base {
|
||||
static constexpr int static_block_size = 1;
|
||||
};
|
||||
};
|
||||
|
||||
static inline constexpr mass_normalization mass_normalization_term{};
|
||||
};
|
||||
|
||||
inline constexpr density density_field{};
|
||||
inline constexpr displacement displacement_field{};
|
||||
inline constexpr gravity gravity_field{};
|
||||
inline constexpr enthalpy enthalpy_field{};
|
||||
inline constexpr barotropic_constant barotropic_constant_field{};
|
||||
|
||||
template <typename... Types> struct type_list {
|
||||
static constexpr int size = sizeof...(Types);
|
||||
};
|
||||
|
||||
template <typename Query, typename List> struct contains_type;
|
||||
|
||||
template <typename Query>
|
||||
struct contains_type<Query, type_list<>> : std::false_type { };
|
||||
|
||||
template <typename Query, typename Head, typename... Tail>
|
||||
struct contains_type<Query, type_list<Head, Tail...>>
|
||||
: std::conditional_t<
|
||||
std::is_same_v<Query, Head>,
|
||||
std::true_type,
|
||||
contains_type<Query, type_list<Tail...>>> { };
|
||||
|
||||
template <typename Query, typename List>
|
||||
inline constexpr bool contains_type_v = contains_type<Query, List>::value;
|
||||
|
||||
template <typename Query, typename List> struct type_count;
|
||||
|
||||
template <typename Query>
|
||||
struct type_count<Query, type_list<>> : std::integral_constant<int, 0> { };
|
||||
|
||||
template <typename Query, typename Head, typename... Tail>
|
||||
struct type_count<Query, type_list<Head, Tail...>>
|
||||
: std::integral_constant<
|
||||
int,
|
||||
(std::is_same_v<Query, Head> ? 1 : 0) +
|
||||
type_count<Query, type_list<Tail...>>::value> { };
|
||||
|
||||
template <typename Query, typename List>
|
||||
inline constexpr int type_count_v = type_count<Query, List>::value;
|
||||
|
||||
template <typename List> struct types_are_unique;
|
||||
|
||||
template <typename... Types>
|
||||
struct types_are_unique<type_list<Types...>>
|
||||
: std::bool_constant<
|
||||
((type_count_v<Types, type_list<Types...>> == 1) && ...)> { };
|
||||
|
||||
template <typename List>
|
||||
inline constexpr bool types_are_unique_v = types_are_unique<List>::value;
|
||||
|
||||
template <typename Row> struct block_row_traits {
|
||||
using residual = void;
|
||||
using values = type_list<>;
|
||||
|
||||
static constexpr int value_count = 0;
|
||||
static constexpr bool is_block_row = false;
|
||||
};
|
||||
|
||||
template <typename Residual, typename... Values>
|
||||
struct block_row_traits<block_row<Residual, Values...>> {
|
||||
using residual = Residual;
|
||||
using values = type_list<Values...>;
|
||||
|
||||
static constexpr int value_count = sizeof...(Values);
|
||||
static constexpr bool is_block_row = true;
|
||||
};
|
||||
|
||||
template <typename Query, typename List> struct type_index;
|
||||
|
||||
template <typename Query, typename... Tail>
|
||||
struct type_index<Query, type_list<Query, Tail...>> {
|
||||
static constexpr int value = 0;
|
||||
};
|
||||
|
||||
template <typename Query, typename Head, typename... Tail>
|
||||
struct type_index<Query, type_list<Head, Tail...>> {
|
||||
static constexpr int value =
|
||||
1 + type_index<Query, type_list<Tail...>>::value;
|
||||
};
|
||||
|
||||
template <typename Query, typename List>
|
||||
inline constexpr int type_index_v = type_index<Query, List>::value;
|
||||
|
||||
template <typename ValueBlocks, typename ResidualBlocks> struct block_form {
|
||||
using value_blocks = ValueBlocks;
|
||||
using residual_blocks = ResidualBlocks;
|
||||
|
||||
static constexpr int value_block_count = ValueBlocks::size;
|
||||
static constexpr int residual_block_count = ResidualBlocks::size;
|
||||
};
|
||||
|
||||
template <typename Form> struct block_form_is_valid : std::false_type { };
|
||||
|
||||
template <typename... Values, typename... Residuals>
|
||||
struct block_form_is_valid<
|
||||
block_form<type_list<Values...>, type_list<Residuals...>>>
|
||||
: std::bool_constant<
|
||||
(std::is_base_of_v<value_block_base, Values> && ...) &&
|
||||
(std::is_base_of_v<residual_block_base, Residuals> && ...) &&
|
||||
types_are_unique_v<type_list<Values...>> &&
|
||||
types_are_unique_v<type_list<Residuals...>>> { };
|
||||
|
||||
template <typename Form>
|
||||
inline constexpr bool block_form_is_valid_v =
|
||||
block_form_is_valid<Form>::value;
|
||||
|
||||
template <typename Row, typename ValueBlocks, typename ResidualBlocks>
|
||||
struct block_row_is_valid : std::false_type { };
|
||||
|
||||
template <
|
||||
typename Residual,
|
||||
typename... Values,
|
||||
typename ValueBlocks,
|
||||
typename ResidualBlocks>
|
||||
struct block_row_is_valid<
|
||||
block_row<Residual, Values...>,
|
||||
ValueBlocks,
|
||||
ResidualBlocks>
|
||||
: std::bool_constant<
|
||||
std::is_base_of_v<residual_block_base, Residual> &&
|
||||
contains_type_v<Residual, ResidualBlocks> &&
|
||||
((std::is_base_of_v<value_block_base, Values> &&
|
||||
contains_type_v<Values, ValueBlocks>) &&
|
||||
...) &&
|
||||
types_are_unique_v<type_list<Values...>>> { };
|
||||
|
||||
template <typename Rows> struct row_residual_list;
|
||||
|
||||
template <typename... Rows> struct row_residual_list<type_list<Rows...>> {
|
||||
using type = type_list<typename block_row_traits<Rows>::residual...>;
|
||||
};
|
||||
|
||||
template <typename Rows>
|
||||
using row_residual_list_t = typename row_residual_list<Rows>::type;
|
||||
|
||||
template <typename Form, typename JacobianForm>
|
||||
struct jacobian_form_is_valid : std::false_type { };
|
||||
|
||||
template <typename... Values, typename... Residuals, typename... Rows>
|
||||
struct jacobian_form_is_valid<
|
||||
block_form<type_list<Values...>, type_list<Residuals...>>,
|
||||
type_list<Rows...>> {
|
||||
using form_type =
|
||||
block_form<type_list<Values...>, type_list<Residuals...>>;
|
||||
|
||||
using value_blocks = type_list<Values...>;
|
||||
using residual_blocks = type_list<Residuals...>;
|
||||
using rows = type_list<Rows...>;
|
||||
|
||||
static constexpr bool value =
|
||||
block_form_is_valid_v<form_type> &&
|
||||
(block_row_is_valid<Rows, value_blocks, residual_blocks>::value &&
|
||||
...) &&
|
||||
std::is_same_v<row_residual_list_t<rows>, residual_blocks>;
|
||||
};
|
||||
|
||||
template <typename Form, typename JacobianForm>
|
||||
inline constexpr bool jacobian_form_is_valid_v =
|
||||
jacobian_form_is_valid<Form, JacobianForm>::value;
|
||||
|
||||
template <typename Form, typename JacobianForm>
|
||||
concept valid_jacobian_form = jacobian_form_is_valid_v<Form, JacobianForm>;
|
||||
|
||||
template <typename Residual, typename Value, typename JacobianForm>
|
||||
struct has_jacobian_coupling;
|
||||
|
||||
template <typename Residual, typename Value>
|
||||
struct has_jacobian_coupling<Residual, Value, type_list<>>
|
||||
: std::false_type { };
|
||||
|
||||
template <
|
||||
typename Residual,
|
||||
typename Value,
|
||||
typename RowResidual,
|
||||
typename... RowValues,
|
||||
typename... RemainingRows>
|
||||
struct has_jacobian_coupling<
|
||||
Residual,
|
||||
Value,
|
||||
type_list<block_row<RowResidual, RowValues...>, RemainingRows...>>
|
||||
: std::conditional_t<
|
||||
std::is_same_v<Residual, RowResidual>,
|
||||
std::bool_constant<(std::is_same_v<Value, RowValues> || ...)>,
|
||||
has_jacobian_coupling<
|
||||
Residual,
|
||||
Value,
|
||||
type_list<RemainingRows...>>> { };
|
||||
|
||||
template <typename Residual, typename Value, typename JacobianForm>
|
||||
inline constexpr bool has_jacobian_coupling_v =
|
||||
has_jacobian_coupling<Residual, Value, JacobianForm>::value;
|
||||
|
||||
template <
|
||||
typename Form,
|
||||
typename Term>
|
||||
consteval auto get_value_block(const Term &) {
|
||||
using value_type = typename Term::value;
|
||||
constexpr int index =
|
||||
type_index_v<value_type, typename Form::value_blocks>;
|
||||
return value_block<index>{};
|
||||
}
|
||||
|
||||
template <
|
||||
typename Form,
|
||||
typename Term>
|
||||
consteval auto get_residual_block(const Term &) {
|
||||
using residual_type = typename Term::residual;
|
||||
constexpr int index =
|
||||
type_index_v<residual_type, typename Form::residual_blocks>;
|
||||
return residual_block<index>{};
|
||||
}
|
||||
|
||||
template <typename Form> class form_layout {
|
||||
public:
|
||||
form_layout(
|
||||
const std::array<
|
||||
int,
|
||||
Form::value_block_count> &value_sizes,
|
||||
const std::array<
|
||||
int,
|
||||
Form::residual_block_count> &residual_sizes
|
||||
) {
|
||||
build_offsets(
|
||||
m_value_offsets, value_sizes, typename Form::value_blocks{}
|
||||
);
|
||||
|
||||
build_offsets(
|
||||
m_residual_offsets, residual_sizes,
|
||||
typename Form::residual_blocks{}
|
||||
);
|
||||
}
|
||||
|
||||
template <int index> [[nodiscard]] int size(value_block<index>) const {
|
||||
return m_value_offsets[index + 1] - m_value_offsets[index];
|
||||
}
|
||||
|
||||
template <int index>
|
||||
[[nodiscard]] int size(residual_block<index>) const {
|
||||
return m_residual_offsets[index + 1] - m_residual_offsets[index];
|
||||
}
|
||||
|
||||
template <int index>
|
||||
[[nodiscard]] int offset(value_block<index>) const {
|
||||
return m_value_offsets[index];
|
||||
}
|
||||
|
||||
template <int index>
|
||||
[[nodiscard]] int offset(residual_block<index>) const {
|
||||
return m_residual_offsets[index];
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const mfem::Array<int> &value_offsets() const noexcept {
|
||||
return m_value_offsets;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const mfem::Array<int> &residual_offsets() const noexcept {
|
||||
return m_residual_offsets;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename BlockType>
|
||||
[[nodiscard]]
|
||||
static int resolve_block_size(const int requested_size) {
|
||||
if constexpr (BlockType::static_block_size == dynamic_block_size) {
|
||||
return requested_size;
|
||||
} else {
|
||||
if (requested_size != BlockType::static_block_size) {
|
||||
throw std::invalid_argument(
|
||||
"A statically sized block was given an "
|
||||
"incompatible runtime size."
|
||||
);
|
||||
}
|
||||
|
||||
return BlockType::static_block_size;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... BlockTypes>
|
||||
static void build_offsets(
|
||||
mfem::Array<int> &offsets,
|
||||
const std::array<
|
||||
int,
|
||||
sizeof...(BlockTypes)> &requested_sizes,
|
||||
type_list<BlockTypes...>
|
||||
) {
|
||||
offsets.SetSize(sizeof...(BlockTypes) + 1);
|
||||
offsets[0] = 0;
|
||||
|
||||
int block_index = 0;
|
||||
|
||||
((offsets[block_index + 1] =
|
||||
offsets[block_index] +
|
||||
resolve_block_size<BlockTypes>(requested_sizes[block_index]),
|
||||
++block_index),
|
||||
...);
|
||||
}
|
||||
|
||||
mfem::Array<int> m_value_offsets;
|
||||
mfem::Array<int> m_residual_offsets;
|
||||
};
|
||||
using gravity_field_form = block_form<
|
||||
type_list<
|
||||
density::mass::value,
|
||||
displacement::geometry::value,
|
||||
gravity::gradient::value,
|
||||
gravity::poisson::value>,
|
||||
type_list<gravity::gradient::residual, gravity::poisson::residual>>;
|
||||
|
||||
using gravity_jacobian_form = type_list<
|
||||
block_row<
|
||||
gravity::gradient::residual,
|
||||
gravity::gradient::value,
|
||||
gravity::poisson::value,
|
||||
displacement::geometry::value>,
|
||||
block_row<
|
||||
gravity::poisson::residual,
|
||||
gravity::gradient::value,
|
||||
density::mass::value,
|
||||
displacement::geometry::value>>;
|
||||
|
||||
// Columns:
|
||||
// [rho, d, g, Phi, h, C]
|
||||
//
|
||||
// Rows:
|
||||
// [R_g, R_Phi, R_rho, R_d, R_h, R_M]
|
||||
using barotropic_equilibrium_form = block_form<
|
||||
type_list<
|
||||
density::mass::value,
|
||||
displacement::geometry::value,
|
||||
gravity::gradient::value,
|
||||
gravity::poisson::value,
|
||||
enthalpy::specific::value,
|
||||
barotropic_constant::mass_normalization::value>,
|
||||
type_list<
|
||||
gravity::gradient::residual,
|
||||
gravity::poisson::residual,
|
||||
density::mass::residual,
|
||||
displacement::geometry::residual,
|
||||
enthalpy::specific::residual,
|
||||
barotropic_constant::mass_normalization::residual>>;
|
||||
|
||||
using barotropic_equilibrium_jacobian_form = type_list<
|
||||
// R_g(g, Phi, d)
|
||||
block_row<
|
||||
gravity::gradient::residual,
|
||||
gravity::gradient::value,
|
||||
gravity::poisson::value,
|
||||
displacement::geometry::value>,
|
||||
|
||||
// R_Phi(g, rho, d)
|
||||
block_row<
|
||||
gravity::poisson::residual,
|
||||
gravity::gradient::value,
|
||||
density::mass::value,
|
||||
displacement::geometry::value>,
|
||||
|
||||
// R_rho(rho, h, d)
|
||||
block_row<
|
||||
density::mass::residual,
|
||||
density::mass::value,
|
||||
enthalpy::specific::value,
|
||||
displacement::geometry::value>,
|
||||
|
||||
// R_d(d, h)
|
||||
block_row<
|
||||
displacement::geometry::residual,
|
||||
displacement::geometry::value,
|
||||
enthalpy::specific::value>,
|
||||
|
||||
// R_h(h, Phi, d, C)
|
||||
block_row<
|
||||
enthalpy::specific::residual,
|
||||
enthalpy::specific::value,
|
||||
gravity::poisson::value,
|
||||
displacement::geometry::value,
|
||||
barotropic_constant::mass_normalization::value>,
|
||||
|
||||
// R_M(rho, d)
|
||||
block_row<
|
||||
barotropic_constant::mass_normalization::residual,
|
||||
density::mass::value,
|
||||
displacement::geometry::value>>;
|
||||
|
||||
static_assert(valid_jacobian_form<
|
||||
gravity_field_form,
|
||||
gravity_jacobian_form>);
|
||||
|
||||
static_assert(valid_jacobian_form<
|
||||
barotropic_equilibrium_form,
|
||||
barotropic_equilibrium_jacobian_form>);
|
||||
} // namespace mean_field::utils::blocks
|
||||
@@ -21,4 +21,4 @@ export namespace mean_field::utils {
|
||||
mapping::COORDINATE_SPACE vspace = mapping::COORDINATE_SPACE::REFERENCE,
|
||||
mapping::COORDINATE_SPACE rspace = mapping::COORDINATE_SPACE::PHYSICAL
|
||||
);
|
||||
}
|
||||
} // namespace mean_field::utils
|
||||
@@ -1,7 +1,7 @@
|
||||
module;
|
||||
#include <string_view>
|
||||
#include <functional>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
@@ -14,8 +14,10 @@ import :boundary.contexts;
|
||||
export namespace mean_field::utils {
|
||||
constexpr double APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING = 1e-4;
|
||||
|
||||
|
||||
bool is_vacuum(const mfem::ElementTransformation &Tr, mfem::Array<mfem::Vector*> elvec) {
|
||||
bool is_vacuum(
|
||||
const mfem::ElementTransformation &Tr,
|
||||
mfem::Array<mfem::Vector *> elvec
|
||||
) {
|
||||
if (Tr.Attribute == 3) {
|
||||
const int size_elvec = elvec.Size();
|
||||
for (int i = 0; i < size_elvec; i++) {
|
||||
@@ -28,6 +30,25 @@ export namespace mean_field::utils {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_vacuum(
|
||||
const mfem::ElementTransformation &Tr,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
if (Tr.Attribute == 3) {
|
||||
const int cols = elmats.NumCols();
|
||||
const int rows = elmats.NumRows();
|
||||
for (int rowID = 0; rowID < rows; rowID++) {
|
||||
for (int colID = 0; colID < cols; colID++) {
|
||||
if (elmats(rowID, colID)) {
|
||||
*elmats(rowID, colID) = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr std::string_view ANSI_GREEN = "\033[32m";
|
||||
constexpr std::string_view ANSI_RED = "\033[31m";
|
||||
constexpr std::string_view ANSI_YELLOW = "\033[33m";
|
||||
@@ -37,7 +58,6 @@ export namespace mean_field::utils {
|
||||
constexpr std::string_view ANSI_RESET = "\033[0m";
|
||||
constexpr std::string_view ANSI_BCYAN = "\033[1;36m";
|
||||
|
||||
|
||||
constexpr double G = 1.0;
|
||||
constexpr double MASS = 1.0;
|
||||
constexpr double RADIUS = 1.0;
|
||||
@@ -45,18 +65,16 @@ export namespace mean_field::utils {
|
||||
[[maybe_unused]] constexpr char HOST[10] = "localhost";
|
||||
[[maybe_unused]] constexpr int PORT = 19916;
|
||||
|
||||
template <typename T>
|
||||
concept is_xad = std::is_same_v<T, xad::AReal<long double>> ||
|
||||
std::is_same_v<T, xad::AReal<double>> ||
|
||||
std::is_same_v<T, xad::AReal<float>>;
|
||||
|
||||
template<typename T>
|
||||
concept is_xad =
|
||||
std::is_same_v<T, xad::AReal<long double> >
|
||||
|| std::is_same_v<T, xad::AReal<double> >
|
||||
|| std::is_same_v<T, xad::AReal<float> >;
|
||||
|
||||
template<typename T>
|
||||
template <typename T>
|
||||
concept is_real = std::is_floating_point_v<T> || is_xad<T>;
|
||||
|
||||
template<is_real T>
|
||||
using EOS_P = std::function<T(const T& rho, const T& temp)>;
|
||||
template <is_real T>
|
||||
using EOS_P = std::function<T(const T &rho, const T &temp)>;
|
||||
|
||||
enum class DOMAINS : uint8_t {
|
||||
CORE = 1 << 0,
|
||||
@@ -66,7 +84,6 @@ export namespace mean_field::utils {
|
||||
ALL = CORE | ENVELOPE | VACUUM
|
||||
};
|
||||
|
||||
|
||||
DOMAINS operator|(
|
||||
DOMAINS lhs,
|
||||
DOMAINS rhs
|
||||
@@ -78,7 +95,7 @@ export namespace mean_field::utils {
|
||||
);
|
||||
|
||||
void populate_element_mask(
|
||||
const mfem::Mesh* mesh,
|
||||
const mfem::Mesh *mesh,
|
||||
DOMAINS domain,
|
||||
mfem::Array<int> &mask
|
||||
);
|
||||
@@ -89,13 +106,14 @@ export namespace mean_field::utils {
|
||||
mfem::Array<int> &ess_tdof
|
||||
);
|
||||
|
||||
std::expected<boundary::Bounds, boundary::BoundsError> discover_bounds(
|
||||
std::expected<
|
||||
boundary::Bounds,
|
||||
boundary::BoundsError>
|
||||
discover_bounds(
|
||||
const mfem::Mesh *mesh,
|
||||
int vacuum_attr
|
||||
);
|
||||
|
||||
int get_mesh_order(
|
||||
const mfem::Mesh &mesh
|
||||
);
|
||||
int get_mesh_order(const mfem::Mesh &mesh);
|
||||
|
||||
}
|
||||
} // namespace mean_field::utils
|
||||
|
||||
@@ -3,6 +3,8 @@ module;
|
||||
export module mean_field:utils.user;
|
||||
export import :quadrature.policy;
|
||||
|
||||
export import :mapping.compactification.options;
|
||||
|
||||
export namespace mean_field::utils {
|
||||
struct potential {
|
||||
double rtol;
|
||||
@@ -16,6 +18,11 @@ export namespace mean_field::utils {
|
||||
double L;
|
||||
};
|
||||
|
||||
struct DomainMapperStatelessOptions {
|
||||
int dimension{3};
|
||||
int vacuum_element_attribute{3};
|
||||
};
|
||||
|
||||
struct Args {
|
||||
std::string mesh_file;
|
||||
potential p{};
|
||||
@@ -24,13 +31,13 @@ export namespace mean_field::utils {
|
||||
double index{};
|
||||
double mass{};
|
||||
double c{};
|
||||
|
||||
int quad_boost{0};
|
||||
|
||||
DomainMapperStatelessOptions domain_mapper_options{};
|
||||
mapping::compactification::options::KelvinCompactificationOptions
|
||||
kelvin_options{};
|
||||
int max_iters{};
|
||||
double tol{};
|
||||
|
||||
quadrature::QuadratureOptions quadrature{};
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace mean_field::utils
|
||||
|
||||
377501
sandbox.smesh
377501
sandbox.smesh
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,22 @@
|
||||
#include <algorithm>
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
using namespace mean_field;
|
||||
|
||||
|
||||
TEST_CASE("STROID Volume vs sphere", tags::geometry & tags::volume) {
|
||||
TEST_CASE(
|
||||
"STROID Volume vs sphere",
|
||||
tags::geometry &tags::volume
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
const double analytic_vol = (4.0 / 3.0) * M_PI * std::pow(utils::RADIUS, 3);
|
||||
|
||||
1394
tests/integrators/centrifugal.cpp
Normal file
1394
tests/integrators/centrifugal.cpp
Normal file
File diff suppressed because it is too large
Load Diff
931
tests/integrators/gravity.cpp
Normal file
931
tests/integrators/gravity.cpp
Normal file
@@ -0,0 +1,931 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
using namespace mean_field;
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Force Integrator Jacobian Matches Residual Linearization",
|
||||
tags::unit &tags::solver &tags::integrator &tags::gravity
|
||||
) {
|
||||
constexpr int dim = 3;
|
||||
constexpr double finite_difference_step = 1.0e-3;
|
||||
constexpr double jacobian_tolerance = 1.0e-8;
|
||||
constexpr double zero_tolerance = 1.0e-14;
|
||||
|
||||
constexpr int velocity_block =
|
||||
solver::block_index(solver::FieldBlock::velocity);
|
||||
constexpr int density_block =
|
||||
solver::block_index(solver::FieldBlock::density);
|
||||
constexpr int gravity_gradient_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_gradient);
|
||||
constexpr int gravity_potential_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_potential);
|
||||
constexpr int displacement_block =
|
||||
solver::block_index(solver::FieldBlock::displacement);
|
||||
constexpr int block_count = solver::field_block_count;
|
||||
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(
|
||||
1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0
|
||||
);
|
||||
|
||||
mfem::H1_FECollection velocity_fec(2, dim);
|
||||
mfem::L2_FECollection density_fec(1, dim);
|
||||
mfem::RT_FECollection gravity_gradient_fec(1, dim);
|
||||
mfem::L2_FECollection gravity_potential_fec(1, dim);
|
||||
mfem::H1_FECollection displacement_fec(2, dim);
|
||||
|
||||
mfem::FiniteElementSpace velocity_fes(
|
||||
&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM
|
||||
);
|
||||
mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
|
||||
mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec);
|
||||
mfem::FiniteElementSpace gravity_potential_fes(
|
||||
&mesh, &gravity_potential_fec
|
||||
);
|
||||
mfem::FiniteElementSpace displacement_fes(
|
||||
&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM
|
||||
);
|
||||
|
||||
mfem::GridFunction displacement(&displacement_fes);
|
||||
displacement = 0.0;
|
||||
|
||||
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0);
|
||||
INFO(
|
||||
std::format(
|
||||
"Domain mapping is has displacement field: {}",
|
||||
domain_mapper.HasDisplacementField()
|
||||
)
|
||||
);
|
||||
INFO(
|
||||
std::format(
|
||||
"Domain mapping is identity: {}", domain_mapper.CalcIsIdentity()
|
||||
)
|
||||
);
|
||||
|
||||
REQUIRE(domain_mapper.CalcIsIdentity());
|
||||
|
||||
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
|
||||
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
|
||||
const mfem::FiniteElement *gravity_gradient_element =
|
||||
gravity_gradient_fes.GetFE(0);
|
||||
const mfem::FiniteElement *gravity_potential_element =
|
||||
gravity_potential_fes.GetFE(0);
|
||||
const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0);
|
||||
mfem::ElementTransformation *transformation =
|
||||
mesh.GetElementTransformation(0);
|
||||
|
||||
const int velocity_dofs_count = velocity_element->GetDof();
|
||||
const int density_dofs_count = density_element->GetDof();
|
||||
const int gravity_gradient_dofs_count = gravity_gradient_element->GetDof();
|
||||
const int gravity_potential_dofs_count =
|
||||
gravity_potential_element->GetDof();
|
||||
const int displacement_dofs_count = displacement_element->GetDof();
|
||||
const int velocity_size = dim * velocity_dofs_count;
|
||||
const int displacement_size = dim * displacement_dofs_count;
|
||||
|
||||
mfem::Vector velocity_dofs(velocity_size);
|
||||
mfem::Vector density_dofs(density_dofs_count);
|
||||
mfem::Vector gravity_gradient_dofs(gravity_gradient_dofs_count);
|
||||
mfem::Vector gravity_potential_dofs(gravity_potential_dofs_count);
|
||||
mfem::Vector displacement_dofs(displacement_size);
|
||||
velocity_dofs = 0.0;
|
||||
gravity_potential_dofs = 0.0;
|
||||
displacement_dofs = 0.0;
|
||||
|
||||
for (int i = 0; i < density_dofs_count; ++i) {
|
||||
density_dofs(i) = 0.8 + 0.07 * static_cast<double>(i + 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < gravity_gradient_dofs_count; ++i) {
|
||||
const double sign = i % 2 == 0 ? 1.0 : -1.0;
|
||||
gravity_gradient_dofs(i) = sign * 0.04 * static_cast<double>(i + 1);
|
||||
}
|
||||
|
||||
mfem::Vector density_direction(density_dofs_count);
|
||||
mfem::Vector gravity_gradient_direction(gravity_gradient_dofs_count);
|
||||
mfem::Vector velocity_direction(velocity_size);
|
||||
mfem::Vector displacement_direction(displacement_size);
|
||||
|
||||
for (int i = 0; i < density_dofs_count; ++i) {
|
||||
density_direction(i) = 0.13 - 0.02 * static_cast<double>(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < gravity_gradient_dofs_count; ++i) {
|
||||
const double sign = i % 3 == 0 ? -1.0 : 1.0;
|
||||
gravity_gradient_direction(i) =
|
||||
sign * (0.03 + 0.005 * static_cast<double>(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < velocity_size; ++i) {
|
||||
velocity_direction(i) = 0.01 * static_cast<double>(i + 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < displacement_size; ++i) {
|
||||
displacement_direction(i) = -0.008 * static_cast<double>(i + 1);
|
||||
}
|
||||
|
||||
mfem::Array<const mfem::FiniteElement *> elements(block_count);
|
||||
elements[velocity_block] = velocity_element;
|
||||
elements[density_block] = density_element;
|
||||
elements[gravity_gradient_block] = gravity_gradient_element;
|
||||
elements[gravity_potential_block] = gravity_potential_element;
|
||||
elements[displacement_block] = displacement_element;
|
||||
|
||||
mfem::Array<const mfem::Vector *> element_state(block_count);
|
||||
element_state[velocity_block] = &velocity_dofs;
|
||||
element_state[density_block] = &density_dofs;
|
||||
element_state[gravity_gradient_block] = &gravity_gradient_dofs;
|
||||
element_state[gravity_potential_block] = &gravity_potential_dofs;
|
||||
element_state[displacement_block] = &displacement_dofs;
|
||||
|
||||
mfem::Vector velocity_residual(velocity_size);
|
||||
mfem::Vector density_residual(density_dofs_count);
|
||||
mfem::Vector gravity_gradient_residual(gravity_gradient_dofs_count);
|
||||
mfem::Vector gravity_potential_residual(gravity_potential_dofs_count);
|
||||
mfem::Vector displacement_residual(displacement_size);
|
||||
|
||||
mfem::Array<mfem::Vector *> element_residual(block_count);
|
||||
element_residual[velocity_block] = &velocity_residual;
|
||||
element_residual[density_block] = &density_residual;
|
||||
element_residual[gravity_gradient_block] = &gravity_gradient_residual;
|
||||
element_residual[gravity_potential_block] = &gravity_potential_residual;
|
||||
element_residual[displacement_block] = &displacement_residual;
|
||||
|
||||
integrators::GravityMomentumIntegrator integrator(
|
||||
domain_mapper, integrators::GravityForceJacobianMode::field_coupled
|
||||
);
|
||||
|
||||
const int maximum_order = std::max(
|
||||
velocity_element->GetOrder(),
|
||||
std::max(
|
||||
density_element->GetOrder(), gravity_gradient_element->GetOrder()
|
||||
)
|
||||
);
|
||||
const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(
|
||||
velocity_element->GetGeomType(), 2 * maximum_order + 8
|
||||
);
|
||||
integrator.SetIntegrationRule(integration_rule);
|
||||
|
||||
mfem::DenseMatrix dv_dv(velocity_size, velocity_size);
|
||||
mfem::DenseMatrix dv_drho(velocity_size, density_dofs_count);
|
||||
mfem::DenseMatrix dv_dgrad_phi(velocity_size, gravity_gradient_dofs_count);
|
||||
mfem::DenseMatrix dv_ddisplacement(velocity_size, displacement_size);
|
||||
dv_dv = 1.0;
|
||||
dv_drho = 1.0;
|
||||
dv_dgrad_phi = 1.0;
|
||||
dv_ddisplacement = 1.0;
|
||||
|
||||
mfem::Array2D<mfem::DenseMatrix *> element_matrices(
|
||||
block_count, block_count
|
||||
);
|
||||
|
||||
for (int row = 0; row < block_count; ++row) {
|
||||
for (int column = 0; column < block_count; ++column) {
|
||||
element_matrices(row, column) = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
element_matrices(velocity_block, velocity_block) = &dv_dv;
|
||||
element_matrices(velocity_block, density_block) = &dv_drho;
|
||||
element_matrices(velocity_block, gravity_gradient_block) = &dv_dgrad_phi;
|
||||
element_matrices(velocity_block, displacement_block) = &dv_ddisplacement;
|
||||
|
||||
integrator.AssembleElementGrad(
|
||||
elements, *transformation, element_state, element_matrices
|
||||
);
|
||||
|
||||
auto assemble_velocity_residual =
|
||||
[&](const mfem::Vector &density_state,
|
||||
const mfem::Vector &gravity_gradient_state) {
|
||||
element_state[density_block] = &density_state;
|
||||
element_state[gravity_gradient_block] = &gravity_gradient_state;
|
||||
integrator.AssembleElementVector(
|
||||
elements, *transformation, element_state, element_residual
|
||||
);
|
||||
return mfem::Vector(velocity_residual);
|
||||
};
|
||||
|
||||
auto relative_error = [](mfem::Vector computed,
|
||||
const mfem::Vector &reference) {
|
||||
computed -= reference;
|
||||
return computed.Norml2() / std::max(reference.Norml2(), 1.0e-30);
|
||||
};
|
||||
|
||||
mfem::Vector density_plus(density_dofs);
|
||||
mfem::Vector density_minus(density_dofs);
|
||||
density_plus.Add(finite_difference_step, density_direction);
|
||||
density_minus.Add(-finite_difference_step, density_direction);
|
||||
|
||||
mfem::Vector density_residual_plus =
|
||||
assemble_velocity_residual(density_plus, gravity_gradient_dofs);
|
||||
mfem::Vector density_residual_minus =
|
||||
assemble_velocity_residual(density_minus, gravity_gradient_dofs);
|
||||
mfem::Vector density_finite_difference(density_residual_plus);
|
||||
density_finite_difference -= density_residual_minus;
|
||||
density_finite_difference *= 0.5 / finite_difference_step;
|
||||
|
||||
mfem::Vector density_jacobian_action(velocity_size);
|
||||
dv_drho.Mult(density_direction, density_jacobian_action);
|
||||
|
||||
mfem::Vector gravity_gradient_plus(gravity_gradient_dofs);
|
||||
mfem::Vector gravity_gradient_minus(gravity_gradient_dofs);
|
||||
gravity_gradient_plus.Add(
|
||||
finite_difference_step, gravity_gradient_direction
|
||||
);
|
||||
gravity_gradient_minus.Add(
|
||||
-finite_difference_step, gravity_gradient_direction
|
||||
);
|
||||
|
||||
mfem::Vector gravity_residual_plus =
|
||||
assemble_velocity_residual(density_dofs, gravity_gradient_plus);
|
||||
mfem::Vector gravity_residual_minus =
|
||||
assemble_velocity_residual(density_dofs, gravity_gradient_minus);
|
||||
mfem::Vector gravity_finite_difference(gravity_residual_plus);
|
||||
gravity_finite_difference -= gravity_residual_minus;
|
||||
gravity_finite_difference *= 0.5 / finite_difference_step;
|
||||
|
||||
mfem::Vector gravity_jacobian_action(velocity_size);
|
||||
dv_dgrad_phi.Mult(gravity_gradient_direction, gravity_jacobian_action);
|
||||
|
||||
mfem::Vector combined_density_plus(density_dofs);
|
||||
mfem::Vector combined_density_minus(density_dofs);
|
||||
mfem::Vector combined_gravity_plus(gravity_gradient_dofs);
|
||||
mfem::Vector combined_gravity_minus(gravity_gradient_dofs);
|
||||
combined_density_plus.Add(finite_difference_step, density_direction);
|
||||
combined_density_minus.Add(-finite_difference_step, density_direction);
|
||||
combined_gravity_plus.Add(
|
||||
finite_difference_step, gravity_gradient_direction
|
||||
);
|
||||
combined_gravity_minus.Add(
|
||||
-finite_difference_step, gravity_gradient_direction
|
||||
);
|
||||
|
||||
mfem::Vector combined_residual_plus = assemble_velocity_residual(
|
||||
combined_density_plus, combined_gravity_plus
|
||||
);
|
||||
mfem::Vector combined_residual_minus = assemble_velocity_residual(
|
||||
combined_density_minus, combined_gravity_minus
|
||||
);
|
||||
mfem::Vector combined_finite_difference(combined_residual_plus);
|
||||
combined_finite_difference -= combined_residual_minus;
|
||||
combined_finite_difference *= 0.5 / finite_difference_step;
|
||||
|
||||
mfem::Vector combined_jacobian_action(density_jacobian_action);
|
||||
combined_jacobian_action += gravity_jacobian_action;
|
||||
|
||||
mfem::Vector inactive_velocity_action(velocity_size);
|
||||
mfem::Vector inactive_displacement_action(velocity_size);
|
||||
dv_dv.Mult(velocity_direction, inactive_velocity_action);
|
||||
dv_ddisplacement.Mult(displacement_direction, inactive_displacement_action);
|
||||
|
||||
const double density_relative_error =
|
||||
relative_error(density_finite_difference, density_jacobian_action);
|
||||
const double gravity_relative_error =
|
||||
relative_error(gravity_finite_difference, gravity_jacobian_action);
|
||||
const double combined_relative_error =
|
||||
relative_error(combined_finite_difference, combined_jacobian_action);
|
||||
|
||||
INFO("Density Jacobian relative error = " << density_relative_error);
|
||||
INFO(
|
||||
"Gravity-gradient Jacobian relative error = " << gravity_relative_error
|
||||
);
|
||||
INFO("Combined Jacobian relative error = " << combined_relative_error);
|
||||
|
||||
CHECK_THAT(
|
||||
density_relative_error,
|
||||
Catch::Matchers::WithinAbs(0.0, jacobian_tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
gravity_relative_error,
|
||||
Catch::Matchers::WithinAbs(0.0, jacobian_tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
combined_relative_error,
|
||||
Catch::Matchers::WithinAbs(0.0, jacobian_tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
inactive_velocity_action.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, zero_tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
inactive_displacement_action.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, zero_tolerance)
|
||||
);
|
||||
|
||||
mfem::Vector field_coupled_density_action(density_jacobian_action);
|
||||
|
||||
integrator.SetJacobianMode(integrators::GravityForceJacobianMode::minimal);
|
||||
dv_dv = 1.0;
|
||||
dv_drho = 1.0;
|
||||
dv_dgrad_phi = 1.0;
|
||||
dv_ddisplacement = 1.0;
|
||||
element_state[density_block] = &density_dofs;
|
||||
element_state[gravity_gradient_block] = &gravity_gradient_dofs;
|
||||
integrator.AssembleElementGrad(
|
||||
elements, *transformation, element_state, element_matrices
|
||||
);
|
||||
|
||||
mfem::Vector minimal_density_action(velocity_size);
|
||||
mfem::Vector minimal_gravity_action(velocity_size);
|
||||
dv_drho.Mult(density_direction, minimal_density_action);
|
||||
dv_dgrad_phi.Mult(gravity_gradient_direction, minimal_gravity_action);
|
||||
|
||||
const double minimal_density_difference =
|
||||
relative_error(minimal_density_action, field_coupled_density_action);
|
||||
|
||||
INFO(
|
||||
"Minimal-mode density-block difference = " << minimal_density_difference
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
minimal_density_difference,
|
||||
Catch::Matchers::WithinAbs(0.0, zero_tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
minimal_gravity_action.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, zero_tolerance)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Force Integrator Matches Manufactured Cartesian Load",
|
||||
tags::unit &tags::solver &tags::integrator &tags::gravity
|
||||
) {
|
||||
constexpr int dim = 3;
|
||||
constexpr double tolerance = 1.0e-12;
|
||||
|
||||
constexpr int velocity_block =
|
||||
solver::block_index(solver::FieldBlock::velocity);
|
||||
constexpr int density_block =
|
||||
solver::block_index(solver::FieldBlock::density);
|
||||
constexpr int gravity_gradient_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_gradient);
|
||||
constexpr int gravity_potential_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_potential);
|
||||
constexpr int displacement_block =
|
||||
solver::block_index(solver::FieldBlock::displacement);
|
||||
constexpr int block_count = solver::field_block_count;
|
||||
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(
|
||||
1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0
|
||||
);
|
||||
|
||||
mfem::H1_FECollection velocity_fec(1, dim);
|
||||
mfem::L2_FECollection density_fec(1, dim);
|
||||
mfem::RT_FECollection gravity_gradient_fec(0, dim);
|
||||
mfem::H1_FECollection displacement_fec(1, dim);
|
||||
|
||||
mfem::FiniteElementSpace velocity_fes(
|
||||
&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM
|
||||
);
|
||||
mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
|
||||
mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec);
|
||||
mfem::FiniteElementSpace displacement_fes(
|
||||
&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM
|
||||
);
|
||||
|
||||
mfem::GridFunction displacement(&displacement_fes);
|
||||
displacement = 0.0;
|
||||
|
||||
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0);
|
||||
|
||||
REQUIRE(domain_mapper.CalcIsIdentity());
|
||||
|
||||
auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); };
|
||||
|
||||
auto reference_gravity_gradient = [](const mfem::Vector &x,
|
||||
mfem::Vector &gradient) {
|
||||
gradient.SetSize(3);
|
||||
gradient(0) = 2.0 * x(0);
|
||||
gradient(1) = 3.0 * x(1);
|
||||
gradient(2) = 4.0 * x(2);
|
||||
};
|
||||
|
||||
mfem::FunctionCoefficient density_coefficient(reference_density);
|
||||
mfem::VectorFunctionCoefficient gravity_gradient_coefficient(
|
||||
dim, reference_gravity_gradient
|
||||
);
|
||||
|
||||
mfem::GridFunction density(&density_fes);
|
||||
mfem::GridFunction gravity_gradient(&gravity_gradient_fes);
|
||||
density.ProjectCoefficient(density_coefficient);
|
||||
gravity_gradient.ProjectCoefficient(gravity_gradient_coefficient);
|
||||
|
||||
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
|
||||
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
|
||||
const mfem::FiniteElement *gravity_gradient_element =
|
||||
gravity_gradient_fes.GetFE(0);
|
||||
const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0);
|
||||
mfem::ElementTransformation *transformation =
|
||||
mesh.GetElementTransformation(0);
|
||||
|
||||
const int velocity_dofs_count = velocity_element->GetDof();
|
||||
const int density_dofs_count = density_element->GetDof();
|
||||
const int gravity_gradient_dofs_count = gravity_gradient_element->GetDof();
|
||||
const int displacement_dofs_count = displacement_element->GetDof();
|
||||
const int velocity_size = dim * velocity_dofs_count;
|
||||
const int displacement_size = dim * displacement_dofs_count;
|
||||
|
||||
mfem::Array<int> density_dof_indices;
|
||||
mfem::Array<int> gravity_gradient_dof_indices;
|
||||
mfem::Vector density_dofs;
|
||||
mfem::Vector gravity_gradient_dofs;
|
||||
density_fes.GetElementDofs(0, density_dof_indices);
|
||||
gravity_gradient_fes.GetElementVDofs(0, gravity_gradient_dof_indices);
|
||||
density.GetSubVector(density_dof_indices, density_dofs);
|
||||
gravity_gradient.GetSubVector(
|
||||
gravity_gradient_dof_indices, gravity_gradient_dofs
|
||||
);
|
||||
|
||||
REQUIRE(density_dofs.Size() == density_dofs_count);
|
||||
REQUIRE(gravity_gradient_dofs.Size() == gravity_gradient_dofs_count);
|
||||
|
||||
mfem::Vector velocity_dofs(velocity_size);
|
||||
mfem::Vector gravity_potential_dofs(density_dofs_count);
|
||||
mfem::Vector displacement_dofs(displacement_size);
|
||||
velocity_dofs = 0.0;
|
||||
gravity_potential_dofs = 0.0;
|
||||
displacement_dofs = 0.0;
|
||||
|
||||
mfem::Array<const mfem::FiniteElement *> elements(block_count);
|
||||
elements[velocity_block] = velocity_element;
|
||||
elements[density_block] = density_element;
|
||||
elements[gravity_gradient_block] = gravity_gradient_element;
|
||||
elements[gravity_potential_block] = density_element;
|
||||
elements[displacement_block] = displacement_element;
|
||||
|
||||
mfem::Array<const mfem::Vector *> element_state(block_count);
|
||||
element_state[velocity_block] = &velocity_dofs;
|
||||
element_state[density_block] = &density_dofs;
|
||||
element_state[gravity_gradient_block] = &gravity_gradient_dofs;
|
||||
element_state[gravity_potential_block] = &gravity_potential_dofs;
|
||||
element_state[displacement_block] = &displacement_dofs;
|
||||
|
||||
mfem::Vector velocity_residual(velocity_size);
|
||||
mfem::Vector density_residual(density_dofs_count);
|
||||
mfem::Vector gravity_gradient_residual(gravity_gradient_dofs_count);
|
||||
mfem::Vector gravity_potential_residual(density_dofs_count);
|
||||
mfem::Vector displacement_residual(displacement_size);
|
||||
|
||||
mfem::Array<mfem::Vector *> element_residual(block_count);
|
||||
element_residual[velocity_block] = &velocity_residual;
|
||||
element_residual[density_block] = &density_residual;
|
||||
element_residual[gravity_gradient_block] = &gravity_gradient_residual;
|
||||
element_residual[gravity_potential_block] = &gravity_potential_residual;
|
||||
element_residual[displacement_block] = &displacement_residual;
|
||||
|
||||
integrators::GravityMomentumIntegrator integrator(
|
||||
domain_mapper, integrators::GravityForceJacobianMode::field_coupled
|
||||
);
|
||||
|
||||
const mfem::IntegrationRule &integration_rule =
|
||||
mfem::IntRules.Get(velocity_element->GetGeomType(), 8);
|
||||
integrator.SetIntegrationRule(integration_rule);
|
||||
integrator.AssembleElementVector(
|
||||
elements, *transformation, element_state, element_residual
|
||||
);
|
||||
|
||||
mfem::Vector reference_velocity_residual(velocity_residual);
|
||||
|
||||
auto residual_action = [&](const int component,
|
||||
const int coordinate_weight) {
|
||||
mfem::Vector test_dofs(velocity_size);
|
||||
mfem::Vector x_physical(dim);
|
||||
test_dofs = 0.0;
|
||||
|
||||
const mfem::IntegrationRule &velocity_nodes =
|
||||
velocity_element->GetNodes();
|
||||
|
||||
for (int i = 0; i < velocity_dofs_count; ++i) {
|
||||
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
|
||||
transformation->SetIntPoint(&node);
|
||||
domain_mapper.GetPhysicalPoint(*transformation, node, x_physical);
|
||||
test_dofs(i + component * velocity_dofs_count) =
|
||||
coordinate_weight < 0 ? 1.0 : x_physical(coordinate_weight);
|
||||
}
|
||||
|
||||
return test_dofs * velocity_residual;
|
||||
};
|
||||
|
||||
CHECK_THAT(
|
||||
residual_action(0, -1), Catch::Matchers::WithinAbs(5.0 / 3.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
residual_action(1, -1), Catch::Matchers::WithinAbs(9.0 / 4.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
residual_action(2, -1), Catch::Matchers::WithinAbs(3.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
residual_action(0, 0), Catch::Matchers::WithinAbs(7.0 / 6.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
residual_action(1, 1), Catch::Matchers::WithinAbs(3.0 / 2.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
residual_action(2, 2), Catch::Matchers::WithinAbs(2.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
residual_action(1, 0), Catch::Matchers::WithinAbs(5.0 / 4.0, tolerance)
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
density_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
gravity_gradient_residual.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
gravity_potential_residual.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
displacement_residual.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
|
||||
integrator.SetJacobianMode(integrators::GravityForceJacobianMode::minimal);
|
||||
integrator.AssembleElementVector(
|
||||
elements, *transformation, element_state, element_residual
|
||||
);
|
||||
|
||||
mfem::Vector minimal_difference(velocity_residual);
|
||||
minimal_difference -= reference_velocity_residual;
|
||||
|
||||
integrator.SetJacobianMode(integrators::GravityForceJacobianMode::exact);
|
||||
integrator.AssembleElementVector(
|
||||
elements, *transformation, element_state, element_residual
|
||||
);
|
||||
|
||||
mfem::Vector exact_difference(velocity_residual);
|
||||
exact_difference -= reference_velocity_residual;
|
||||
|
||||
CHECK_THAT(
|
||||
minimal_difference.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
exact_difference.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
}
|
||||
TEST_CASE(
|
||||
"Gravity Force Integrator Preserves Gravity Identities",
|
||||
tags::unit &tags::solver &tags::integrator &tags::gravity
|
||||
) {
|
||||
constexpr int dim = 3;
|
||||
constexpr double density_value = 1.7;
|
||||
constexpr double gravity_scale = 2.4;
|
||||
constexpr double density_scale = 0.6;
|
||||
constexpr double tolerance = 1.0e-12;
|
||||
|
||||
constexpr int velocity_block =
|
||||
solver::block_index(solver::FieldBlock::velocity);
|
||||
constexpr int density_block =
|
||||
solver::block_index(solver::FieldBlock::density);
|
||||
constexpr int gravity_gradient_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_gradient);
|
||||
constexpr int gravity_potential_block =
|
||||
solver::block_index(solver::FieldBlock::gravity_potential);
|
||||
constexpr int displacement_block =
|
||||
solver::block_index(solver::FieldBlock::displacement);
|
||||
constexpr int block_count = solver::field_block_count;
|
||||
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(
|
||||
1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0
|
||||
);
|
||||
|
||||
mfem::H1_FECollection velocity_fec(1, dim);
|
||||
mfem::L2_FECollection density_fec(0, dim);
|
||||
mfem::RT_FECollection gravity_gradient_fec(0, dim);
|
||||
mfem::H1_FECollection displacement_fec(1, dim);
|
||||
|
||||
mfem::FiniteElementSpace velocity_fes(
|
||||
&mesh, &velocity_fec, dim, mfem::Ordering::byVDIM
|
||||
);
|
||||
mfem::FiniteElementSpace density_fes(&mesh, &density_fec);
|
||||
mfem::FiniteElementSpace gravity_gradient_fes(&mesh, &gravity_gradient_fec);
|
||||
mfem::FiniteElementSpace displacement_fes(
|
||||
&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM
|
||||
);
|
||||
|
||||
mfem::GridFunction displacement(&displacement_fes);
|
||||
displacement = 0.0;
|
||||
|
||||
mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0);
|
||||
|
||||
REQUIRE(domain_mapper.HasDisplacementField());
|
||||
|
||||
auto radial_gravity = [](const mfem::Vector &x, mfem::Vector &gravity) {
|
||||
gravity.SetSize(3);
|
||||
gravity(0) = x(0) - 0.5;
|
||||
gravity(1) = x(1) - 0.5;
|
||||
gravity(2) = x(2) - 0.5;
|
||||
};
|
||||
|
||||
mfem::ConstantCoefficient density_coefficient(density_value);
|
||||
mfem::VectorFunctionCoefficient gravity_coefficient(dim, radial_gravity);
|
||||
|
||||
mfem::GridFunction density(&density_fes);
|
||||
mfem::GridFunction gravity_gradient(&gravity_gradient_fes);
|
||||
density.ProjectCoefficient(density_coefficient);
|
||||
gravity_gradient.ProjectCoefficient(gravity_coefficient);
|
||||
|
||||
const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0);
|
||||
const mfem::FiniteElement *density_element = density_fes.GetFE(0);
|
||||
const mfem::FiniteElement *gravity_gradient_element =
|
||||
gravity_gradient_fes.GetFE(0);
|
||||
const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0);
|
||||
mfem::ElementTransformation *transformation =
|
||||
mesh.GetElementTransformation(0);
|
||||
|
||||
const int velocity_dofs_count = velocity_element->GetDof();
|
||||
const int density_dofs_count = density_element->GetDof();
|
||||
const int gravity_gradient_dofs_count = gravity_gradient_element->GetDof();
|
||||
const int displacement_dofs_count = displacement_element->GetDof();
|
||||
const int velocity_size = dim * velocity_dofs_count;
|
||||
const int displacement_size = dim * displacement_dofs_count;
|
||||
|
||||
mfem::Array<int> density_dof_indices;
|
||||
mfem::Array<int> gravity_gradient_dof_indices;
|
||||
mfem::Vector density_dofs;
|
||||
mfem::Vector gravity_gradient_dofs;
|
||||
density_fes.GetElementDofs(0, density_dof_indices);
|
||||
gravity_gradient_fes.GetElementVDofs(0, gravity_gradient_dof_indices);
|
||||
density.GetSubVector(density_dof_indices, density_dofs);
|
||||
gravity_gradient.GetSubVector(
|
||||
gravity_gradient_dof_indices, gravity_gradient_dofs
|
||||
);
|
||||
|
||||
mfem::Vector zero_density(density_dofs_count);
|
||||
mfem::Vector zero_gravity(gravity_gradient_dofs_count);
|
||||
mfem::Vector velocity_dofs(velocity_size);
|
||||
mfem::Vector gravity_potential_dofs(density_dofs_count);
|
||||
mfem::Vector displacement_dofs(displacement_size);
|
||||
zero_density = 0.0;
|
||||
zero_gravity = 0.0;
|
||||
velocity_dofs = 0.0;
|
||||
gravity_potential_dofs = 0.0;
|
||||
displacement_dofs = 0.0;
|
||||
|
||||
mfem::Array<const mfem::FiniteElement *> elements(block_count);
|
||||
elements[velocity_block] = velocity_element;
|
||||
elements[density_block] = density_element;
|
||||
elements[gravity_gradient_block] = gravity_gradient_element;
|
||||
elements[gravity_potential_block] = density_element;
|
||||
elements[displacement_block] = displacement_element;
|
||||
|
||||
mfem::Array<const mfem::Vector *> element_state(block_count);
|
||||
element_state[velocity_block] = &velocity_dofs;
|
||||
element_state[density_block] = &density_dofs;
|
||||
element_state[gravity_gradient_block] = &gravity_gradient_dofs;
|
||||
element_state[gravity_potential_block] = &gravity_potential_dofs;
|
||||
element_state[displacement_block] = &displacement_dofs;
|
||||
|
||||
mfem::Vector velocity_residual(velocity_size);
|
||||
mfem::Vector density_residual(density_dofs_count);
|
||||
mfem::Vector gravity_gradient_residual(gravity_gradient_dofs_count);
|
||||
mfem::Vector gravity_potential_residual(density_dofs_count);
|
||||
mfem::Vector displacement_residual(displacement_size);
|
||||
|
||||
mfem::Array<mfem::Vector *> element_residual(block_count);
|
||||
element_residual[velocity_block] = &velocity_residual;
|
||||
element_residual[density_block] = &density_residual;
|
||||
element_residual[gravity_gradient_block] = &gravity_gradient_residual;
|
||||
element_residual[gravity_potential_block] = &gravity_potential_residual;
|
||||
element_residual[displacement_block] = &displacement_residual;
|
||||
|
||||
integrators::GravityMomentumIntegrator integrator(
|
||||
domain_mapper, integrators::GravityForceJacobianMode::field_coupled
|
||||
);
|
||||
|
||||
const mfem::IntegrationRule &integration_rule =
|
||||
mfem::IntRules.Get(velocity_element->GetGeomType(), 8);
|
||||
integrator.SetIntegrationRule(integration_rule);
|
||||
|
||||
auto assemble_velocity_residual = [&](const mfem::Vector &density_state,
|
||||
const mfem::Vector &gravity_state) {
|
||||
element_state[density_block] = &density_state;
|
||||
element_state[gravity_gradient_block] = &gravity_state;
|
||||
integrator.AssembleElementVector(
|
||||
elements, *transformation, element_state, element_residual
|
||||
);
|
||||
return mfem::Vector(velocity_residual);
|
||||
};
|
||||
|
||||
auto scaled_difference_norm = [](mfem::Vector computed,
|
||||
const mfem::Vector &reference,
|
||||
const double scale) {
|
||||
computed.Add(-scale, reference);
|
||||
return computed.Norml2();
|
||||
};
|
||||
|
||||
const mfem::Vector base_residual =
|
||||
assemble_velocity_residual(density_dofs, gravity_gradient_dofs);
|
||||
|
||||
REQUIRE(base_residual.Norml2() > tolerance);
|
||||
|
||||
const mfem::Vector zero_field_residual =
|
||||
assemble_velocity_residual(density_dofs, zero_gravity);
|
||||
|
||||
mfem::Vector reversed_gravity(gravity_gradient_dofs);
|
||||
reversed_gravity *= -1.0;
|
||||
const mfem::Vector reversed_residual =
|
||||
assemble_velocity_residual(density_dofs, reversed_gravity);
|
||||
|
||||
mfem::Vector scaled_gravity(gravity_gradient_dofs);
|
||||
scaled_gravity *= gravity_scale;
|
||||
const mfem::Vector gravity_scaled_residual =
|
||||
assemble_velocity_residual(density_dofs, scaled_gravity);
|
||||
|
||||
mfem::Vector scaled_density(density_dofs);
|
||||
scaled_density *= density_scale;
|
||||
const mfem::Vector density_scaled_residual =
|
||||
assemble_velocity_residual(scaled_density, gravity_gradient_dofs);
|
||||
const mfem::Vector jointly_scaled_residual =
|
||||
assemble_velocity_residual(scaled_density, scaled_gravity);
|
||||
|
||||
CHECK_THAT(
|
||||
zero_field_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
scaled_difference_norm(reversed_residual, base_residual, -1.0),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
scaled_difference_norm(
|
||||
gravity_scaled_residual, base_residual, gravity_scale
|
||||
),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
scaled_difference_norm(
|
||||
density_scaled_residual, base_residual, density_scale
|
||||
),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
scaled_difference_norm(
|
||||
jointly_scaled_residual, base_residual,
|
||||
density_scale * gravity_scale
|
||||
),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
|
||||
auto make_test_dofs = [&](auto &&test_function) {
|
||||
mfem::Vector test_dofs(velocity_size);
|
||||
mfem::Vector x_physical(dim);
|
||||
mfem::Vector centered_position(dim);
|
||||
mfem::Vector test_value(dim);
|
||||
test_dofs = 0.0;
|
||||
|
||||
const mfem::IntegrationRule &velocity_nodes =
|
||||
velocity_element->GetNodes();
|
||||
|
||||
for (int i = 0; i < velocity_dofs_count; ++i) {
|
||||
const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i);
|
||||
transformation->SetIntPoint(&node);
|
||||
domain_mapper.GetPhysicalPoint(*transformation, node, x_physical);
|
||||
|
||||
for (int component = 0; component < dim; ++component) {
|
||||
centered_position(component) = x_physical(component) - 0.5;
|
||||
}
|
||||
|
||||
test_function(centered_position, test_value);
|
||||
|
||||
for (int component = 0; component < dim; ++component) {
|
||||
test_dofs(i + component * velocity_dofs_count) =
|
||||
test_value(component);
|
||||
}
|
||||
}
|
||||
|
||||
return test_dofs;
|
||||
};
|
||||
|
||||
const mfem::Vector force_x_test =
|
||||
make_test_dofs([](const mfem::Vector &, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value = 0.0;
|
||||
value(0) = 1.0;
|
||||
});
|
||||
|
||||
const mfem::Vector force_y_test =
|
||||
make_test_dofs([](const mfem::Vector &, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value = 0.0;
|
||||
value(1) = 1.0;
|
||||
});
|
||||
|
||||
const mfem::Vector force_z_test =
|
||||
make_test_dofs([](const mfem::Vector &, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value = 0.0;
|
||||
value(2) = 1.0;
|
||||
});
|
||||
|
||||
const mfem::Vector torque_x_test =
|
||||
make_test_dofs([](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value(0) = 0.0;
|
||||
value(1) = -position(2);
|
||||
value(2) = position(1);
|
||||
});
|
||||
|
||||
const mfem::Vector torque_y_test =
|
||||
make_test_dofs([](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value(0) = position(2);
|
||||
value(1) = 0.0;
|
||||
value(2) = -position(0);
|
||||
});
|
||||
|
||||
const mfem::Vector torque_z_test =
|
||||
make_test_dofs([](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value(0) = -position(1);
|
||||
value(1) = position(0);
|
||||
value(2) = 0.0;
|
||||
});
|
||||
|
||||
CHECK_THAT(
|
||||
force_x_test * base_residual, Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
force_y_test * base_residual, Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
force_z_test * base_residual, Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
torque_x_test * base_residual,
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
torque_y_test * base_residual,
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
torque_z_test * base_residual,
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
|
||||
mfem::DenseMatrix dv_drho(velocity_size, density_dofs_count);
|
||||
mfem::DenseMatrix dv_dgrad_phi(velocity_size, gravity_gradient_dofs_count);
|
||||
|
||||
mfem::Array2D<mfem::DenseMatrix *> element_matrices(
|
||||
block_count, block_count
|
||||
);
|
||||
|
||||
for (int row = 0; row < block_count; ++row) {
|
||||
for (int column = 0; column < block_count; ++column) {
|
||||
element_matrices(row, column) = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
element_matrices(velocity_block, density_block) = &dv_drho;
|
||||
element_matrices(velocity_block, gravity_gradient_block) = &dv_dgrad_phi;
|
||||
|
||||
element_state[density_block] = &density_dofs;
|
||||
element_state[gravity_gradient_block] = &zero_gravity;
|
||||
integrator.AssembleElementGrad(
|
||||
elements, *transformation, element_state, element_matrices
|
||||
);
|
||||
|
||||
mfem::Vector zero_gravity_density_action(velocity_size);
|
||||
mfem::Vector zero_gravity_field_action(velocity_size);
|
||||
dv_drho.Mult(density_dofs, zero_gravity_density_action);
|
||||
dv_dgrad_phi.Mult(gravity_gradient_dofs, zero_gravity_field_action);
|
||||
|
||||
CHECK_THAT(
|
||||
zero_gravity_density_action.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
scaled_difference_norm(zero_gravity_field_action, base_residual, 1.0),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
|
||||
element_state[density_block] = &zero_density;
|
||||
element_state[gravity_gradient_block] = &gravity_gradient_dofs;
|
||||
integrator.AssembleElementGrad(
|
||||
elements, *transformation, element_state, element_matrices
|
||||
);
|
||||
|
||||
mfem::Vector zero_density_density_action(velocity_size);
|
||||
mfem::Vector zero_density_field_action(velocity_size);
|
||||
dv_drho.Mult(density_dofs, zero_density_density_action);
|
||||
dv_dgrad_phi.Mult(gravity_gradient_dofs, zero_density_field_action);
|
||||
|
||||
CHECK_THAT(
|
||||
scaled_difference_norm(zero_density_density_action, base_residual, 1.0),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
CHECK_THAT(
|
||||
zero_density_field_action.Norml2(),
|
||||
Catch::Matchers::WithinAbs(0.0, tolerance)
|
||||
);
|
||||
}
|
||||
922
tests/mapping/compactification/kelvin.cpp
Normal file
922
tests/mapping/compactification/kelvin.cpp
Normal file
@@ -0,0 +1,922 @@
|
||||
#include <array>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <mfem.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
using namespace mean_field;
|
||||
using Catch::Matchers::WithinAbs;
|
||||
|
||||
namespace {
|
||||
constexpr int dimension = 3;
|
||||
|
||||
mfem::Vector make_vector(
|
||||
const double x,
|
||||
const double y,
|
||||
const double z
|
||||
) {
|
||||
mfem::Vector vector(dimension);
|
||||
vector(0) = x;
|
||||
vector(1) = y;
|
||||
vector(2) = z;
|
||||
return vector;
|
||||
}
|
||||
|
||||
mfem::DenseMatrix make_identity() {
|
||||
mfem::DenseMatrix matrix(dimension);
|
||||
matrix = 0.0;
|
||||
for (int i = 0; i < dimension; ++i)
|
||||
matrix(i, i) = 1.0;
|
||||
return matrix;
|
||||
}
|
||||
|
||||
void check_vector(
|
||||
const mfem::Vector &actual,
|
||||
const mfem::Vector &expected,
|
||||
const double tolerance
|
||||
) {
|
||||
REQUIRE(actual.Size() == expected.Size());
|
||||
for (int i = 0; i < actual.Size(); ++i)
|
||||
CHECK_THAT(actual(i), WithinAbs(expected(i), tolerance));
|
||||
}
|
||||
|
||||
void check_matrix(
|
||||
const mfem::DenseMatrix &actual,
|
||||
const mfem::DenseMatrix &expected,
|
||||
const double tolerance
|
||||
) {
|
||||
REQUIRE(actual.Height() == expected.Height());
|
||||
REQUIRE(actual.Width() == expected.Width());
|
||||
|
||||
for (int i = 0; i < actual.Height(); ++i) {
|
||||
for (int j = 0; j < actual.Width(); ++j)
|
||||
CHECK_THAT(actual(i, j), WithinAbs(expected(i, j), tolerance));
|
||||
}
|
||||
}
|
||||
|
||||
struct AnalyticFactors {
|
||||
double computational_radius;
|
||||
double scale;
|
||||
double scale_derivative;
|
||||
};
|
||||
|
||||
AnalyticFactors compute_analytic_factors(
|
||||
const double r_star,
|
||||
const double r_inf,
|
||||
const double coordinate
|
||||
) {
|
||||
const double radial_extent = r_inf - r_star;
|
||||
const double computational_radius = r_star + coordinate * radial_extent;
|
||||
const double scale =
|
||||
r_star / (computational_radius * (1.0 - coordinate));
|
||||
const double scale_derivative =
|
||||
scale *
|
||||
(1.0 / (1.0 - coordinate) - radial_extent / computational_radius);
|
||||
return {
|
||||
.computational_radius = computational_radius,
|
||||
.scale = scale,
|
||||
.scale_derivative = scale_derivative
|
||||
};
|
||||
}
|
||||
|
||||
mapping::MappingStatus evaluate_affine_map(
|
||||
const mapping::compactification::ExteriorDomainMap &exterior_map,
|
||||
const mfem::Vector &reference_position,
|
||||
const mfem::DenseMatrix &affine_jacobian,
|
||||
const mfem::Vector &offset,
|
||||
const double compactification_coordinate,
|
||||
const mfem::Vector &compactification_coordinate_gradient,
|
||||
mapping::compactification::ExteriorMapResult &result
|
||||
) {
|
||||
mfem::Vector displaced_position(dimension);
|
||||
affine_jacobian.Mult(reference_position, displaced_position);
|
||||
displaced_position += offset;
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
.reference_position = reference_position,
|
||||
.displaced_position = displaced_position,
|
||||
.displacement_jacobian = affine_jacobian,
|
||||
.compactification_coordinate = compactification_coordinate,
|
||||
.compactification_coordinate_gradient =
|
||||
compactification_coordinate_gradient
|
||||
};
|
||||
|
||||
return exterior_map.Evaluate(input, result);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Validates Its Configuration",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
CHECK_NOTHROW(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
)
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 0.0, .r_inf_ref = 4.0}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = -1.0, .r_inf_ref = 4.0}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 2.0, .r_inf_ref = 2.0}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 3.0, .r_inf_ref = 2.0}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 1.0,
|
||||
.r_inf_ref = std::numeric_limits<double>::infinity()}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 1.0,
|
||||
.r_inf_ref = 4.0,
|
||||
.coordinate_tolerance = -1.0e-12}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0, .coordinate_tolerance = 1.0}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mapping::compactification::KelvinCompactification(
|
||||
{.r_star_ref = 1.0,
|
||||
.r_inf_ref = 4.0,
|
||||
.coordinate_tolerance = std::numeric_limits<double>::quiet_NaN()}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Reports Its Configuration",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double coordinate_tolerance = 3.0e-11;
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.25,
|
||||
.r_inf_ref = 5.5,
|
||||
.coordinate_tolerance = coordinate_tolerance}
|
||||
);
|
||||
|
||||
CHECK(compactification.GetName() == "KelvinCompactification");
|
||||
CHECK_THAT(
|
||||
compactification.GetReferenceStellarRadius(), WithinAbs(1.25, 0.0)
|
||||
);
|
||||
CHECK_THAT(
|
||||
compactification.GetReferenceInfinityRadius(), WithinAbs(5.5, 0.0)
|
||||
);
|
||||
CHECK_THAT(
|
||||
compactification.GetCoordinateTolerance(),
|
||||
WithinAbs(coordinate_tolerance, 0.0)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Uses The Exterior Coordinate Rather Than "
|
||||
"Euclidean Radius",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double tolerance = 0.0;
|
||||
constexpr double coordinate = 0.37;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
const mfem::DenseMatrix displacement_jacobian = make_identity();
|
||||
const mfem::Vector displaced_position = make_vector(1.4, -0.2, 0.3);
|
||||
const mfem::Vector coordinate_gradient = make_vector(0.2, -0.1, 0.05);
|
||||
const mfem::Vector reference_a = make_vector(0.2, 0.1, -0.1);
|
||||
const mfem::Vector reference_b = make_vector(12.0, -7.0, 4.0);
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input_a{
|
||||
reference_a, displaced_position, displacement_jacobian, coordinate,
|
||||
coordinate_gradient
|
||||
};
|
||||
const mapping::compactification::ExteriorMapInput input_b{
|
||||
reference_b, displaced_position, displacement_jacobian, coordinate,
|
||||
coordinate_gradient
|
||||
};
|
||||
|
||||
mapping::compactification::ExteriorMapResult result_a;
|
||||
mapping::compactification::ExteriorMapResult result_b;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_a, result_a) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_b, result_b) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
check_vector(
|
||||
result_a.physical_position, result_b.physical_position, tolerance
|
||||
);
|
||||
check_matrix(
|
||||
result_a.mapping_jacobian, result_b.mapping_jacobian, tolerance
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Matches Its Analytic Radial Map",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double r_star = 1.0;
|
||||
constexpr double r_inf = 4.0;
|
||||
constexpr double radial_extent = r_inf - r_star;
|
||||
constexpr double tolerance = 2.0e-12;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = r_star, .r_inf_ref = r_inf}
|
||||
);
|
||||
const mfem::DenseMatrix identity = make_identity();
|
||||
const mfem::Vector coordinate_gradient =
|
||||
make_vector(1.0 / radial_extent, 0.0, 0.0);
|
||||
|
||||
for (const double computational_radius :
|
||||
std::array{1.0, 1.25, 2.0, 3.0, 3.75}) {
|
||||
CAPTURE(computational_radius);
|
||||
|
||||
const double coordinate =
|
||||
(computational_radius - r_star) / radial_extent;
|
||||
const mfem::Vector reference_position =
|
||||
make_vector(computational_radius, 0.0, 0.0);
|
||||
const mfem::Vector displaced_position(reference_position);
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, displaced_position, identity, coordinate,
|
||||
coordinate_gradient
|
||||
};
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
const double expected_radius =
|
||||
r_star * radial_extent / (r_inf - computational_radius);
|
||||
const double expected_radial_derivative =
|
||||
r_star * radial_extent /
|
||||
std::pow(r_inf - computational_radius, 2.0);
|
||||
const double expected_tangential_scale =
|
||||
expected_radius / computational_radius;
|
||||
|
||||
const mfem::Vector expected_position =
|
||||
make_vector(expected_radius, 0.0, 0.0);
|
||||
mfem::DenseMatrix expected_jacobian(dimension);
|
||||
expected_jacobian = 0.0;
|
||||
expected_jacobian(0, 0) = expected_radial_derivative;
|
||||
expected_jacobian(1, 1) = expected_tangential_scale;
|
||||
expected_jacobian(2, 2) = expected_tangential_scale;
|
||||
|
||||
check_vector(result.physical_position, expected_position, tolerance);
|
||||
check_matrix(result.mapping_jacobian, expected_jacobian, tolerance);
|
||||
CHECK(result.mapping_jacobian.Det() > 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Matches Its Full Cartesian Formula",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double r_star = 1.0;
|
||||
constexpr double r_inf = 4.0;
|
||||
constexpr double coordinate = 0.42;
|
||||
constexpr double tolerance = 1.0e-12;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = r_star, .r_inf_ref = r_inf}
|
||||
);
|
||||
const mfem::Vector reference_position = make_vector(0.8, 0.4, -0.2);
|
||||
const mfem::Vector displaced_position = make_vector(1.1, 0.5, -0.1);
|
||||
const mfem::Vector coordinate_gradient = make_vector(0.20, -0.10, 0.05);
|
||||
|
||||
mfem::DenseMatrix displacement_jacobian(dimension);
|
||||
displacement_jacobian(0, 0) = 1.10;
|
||||
displacement_jacobian(0, 1) = 0.05;
|
||||
displacement_jacobian(0, 2) = 0.00;
|
||||
displacement_jacobian(1, 0) = -0.02;
|
||||
displacement_jacobian(1, 1) = 0.95;
|
||||
displacement_jacobian(1, 2) = 0.03;
|
||||
displacement_jacobian(2, 0) = 0.01;
|
||||
displacement_jacobian(2, 1) = -0.04;
|
||||
displacement_jacobian(2, 2) = 1.05;
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, displaced_position, displacement_jacobian,
|
||||
coordinate, coordinate_gradient
|
||||
};
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
const AnalyticFactors factors =
|
||||
compute_analytic_factors(r_star, r_inf, coordinate);
|
||||
mfem::Vector expected_position(displaced_position);
|
||||
expected_position *= factors.scale;
|
||||
|
||||
mfem::DenseMatrix expected_jacobian(displacement_jacobian);
|
||||
expected_jacobian *= factors.scale;
|
||||
|
||||
for (int i = 0; i < dimension; ++i) {
|
||||
for (int j = 0; j < dimension; ++j)
|
||||
expected_jacobian(i, j) += displaced_position(i) *
|
||||
factors.scale_derivative *
|
||||
coordinate_gradient(j);
|
||||
}
|
||||
|
||||
check_vector(result.physical_position, expected_position, tolerance);
|
||||
check_matrix(result.mapping_jacobian, expected_jacobian, tolerance);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Jacobian Matches Coordinate Finite Differences",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double base_coordinate = 0.38;
|
||||
constexpr double difference_step = 1.0e-6;
|
||||
constexpr double tolerance = 3.0e-9;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
|
||||
mfem::DenseMatrix affine_jacobian(dimension);
|
||||
affine_jacobian(0, 0) = 1.05;
|
||||
affine_jacobian(0, 1) = 0.02;
|
||||
affine_jacobian(0, 2) = 0.00;
|
||||
affine_jacobian(1, 0) = -0.01;
|
||||
affine_jacobian(1, 1) = 0.98;
|
||||
affine_jacobian(1, 2) = 0.03;
|
||||
affine_jacobian(2, 0) = 0.02;
|
||||
affine_jacobian(2, 1) = 0.00;
|
||||
affine_jacobian(2, 2) = 1.02;
|
||||
|
||||
const mfem::Vector offset = make_vector(0.04, -0.03, 0.02);
|
||||
const mfem::Vector reference_position = make_vector(1.4, 0.3, -0.2);
|
||||
const mfem::Vector coordinate_gradient = make_vector(0.11, -0.07, 0.05);
|
||||
|
||||
mapping::compactification::ExteriorMapResult base_result;
|
||||
REQUIRE(
|
||||
evaluate_affine_map(
|
||||
compactification, reference_position, affine_jacobian, offset,
|
||||
base_coordinate, coordinate_gradient, base_result
|
||||
) == mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
for (int coordinate = 0; coordinate < dimension; ++coordinate) {
|
||||
mfem::Vector reference_plus(reference_position);
|
||||
mfem::Vector reference_minus(reference_position);
|
||||
reference_plus(coordinate) += difference_step;
|
||||
reference_minus(coordinate) -= difference_step;
|
||||
|
||||
const double compactification_plus =
|
||||
base_coordinate + difference_step * coordinate_gradient(coordinate);
|
||||
const double compactification_minus =
|
||||
base_coordinate - difference_step * coordinate_gradient(coordinate);
|
||||
|
||||
mapping::compactification::ExteriorMapResult result_plus;
|
||||
mapping::compactification::ExteriorMapResult result_minus;
|
||||
|
||||
REQUIRE(
|
||||
evaluate_affine_map(
|
||||
compactification, reference_plus, affine_jacobian, offset,
|
||||
compactification_plus, coordinate_gradient, result_plus
|
||||
) == mapping::MappingStatus::valid
|
||||
);
|
||||
REQUIRE(
|
||||
evaluate_affine_map(
|
||||
compactification, reference_minus, affine_jacobian, offset,
|
||||
compactification_minus, coordinate_gradient, result_minus
|
||||
) == mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
const double finite_difference =
|
||||
(result_plus.physical_position(component) -
|
||||
result_minus.physical_position(component)) /
|
||||
(2.0 * difference_step);
|
||||
CHECK_THAT(
|
||||
finite_difference,
|
||||
WithinAbs(
|
||||
base_result.mapping_jacobian(component, coordinate),
|
||||
tolerance
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Variation Matches State Finite Differences",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double coordinate = 0.46;
|
||||
constexpr double difference_step = 1.0e-6;
|
||||
constexpr double tolerance = 2.0e-10;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
const mfem::Vector reference_position = make_vector(1.3, -0.2, 0.4);
|
||||
const mfem::Vector displaced_position = make_vector(1.4, -0.1, 0.35);
|
||||
const mfem::Vector coordinate_gradient = make_vector(0.12, -0.04, 0.08);
|
||||
const mfem::Vector position_direction = make_vector(0.03, -0.05, 0.02);
|
||||
|
||||
mfem::DenseMatrix displacement_jacobian = make_identity();
|
||||
displacement_jacobian(0, 1) = 0.04;
|
||||
displacement_jacobian(1, 2) = -0.03;
|
||||
displacement_jacobian(2, 0) = 0.02;
|
||||
|
||||
mfem::DenseMatrix jacobian_direction(dimension);
|
||||
jacobian_direction(0, 0) = 0.02;
|
||||
jacobian_direction(0, 1) = -0.01;
|
||||
jacobian_direction(0, 2) = 0.03;
|
||||
jacobian_direction(1, 0) = 0.01;
|
||||
jacobian_direction(1, 1) = -0.02;
|
||||
jacobian_direction(1, 2) = 0.00;
|
||||
jacobian_direction(2, 0) = -0.01;
|
||||
jacobian_direction(2, 1) = 0.02;
|
||||
jacobian_direction(2, 2) = 0.01;
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, displaced_position, displacement_jacobian,
|
||||
coordinate, coordinate_gradient
|
||||
};
|
||||
const mapping::compactification::ExteriorMapDirection direction{
|
||||
position_direction, jacobian_direction
|
||||
};
|
||||
|
||||
mapping::compactification::ExteriorMapResult base_result;
|
||||
mapping::compactification::ExteriorMapVariation variation;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, base_result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
REQUIRE(
|
||||
compactification.EvaluateVariation(
|
||||
input, base_result, direction, variation
|
||||
) == mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
mfem::Vector displaced_plus(displaced_position);
|
||||
mfem::Vector displaced_minus(displaced_position);
|
||||
displaced_plus.Add(difference_step, position_direction);
|
||||
displaced_minus.Add(-difference_step, position_direction);
|
||||
|
||||
mfem::DenseMatrix jacobian_plus(displacement_jacobian);
|
||||
mfem::DenseMatrix jacobian_minus(displacement_jacobian);
|
||||
jacobian_plus.Add(difference_step, jacobian_direction);
|
||||
jacobian_minus.Add(-difference_step, jacobian_direction);
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input_plus{
|
||||
reference_position, displaced_plus, jacobian_plus, coordinate,
|
||||
coordinate_gradient
|
||||
};
|
||||
const mapping::compactification::ExteriorMapInput input_minus{
|
||||
reference_position, displaced_minus, jacobian_minus, coordinate,
|
||||
coordinate_gradient
|
||||
};
|
||||
|
||||
mapping::compactification::ExteriorMapResult result_plus;
|
||||
mapping::compactification::ExteriorMapResult result_minus;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_plus, result_plus) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_minus, result_minus) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
for (int i = 0; i < dimension; ++i) {
|
||||
const double position_finite_difference =
|
||||
(result_plus.physical_position(i) -
|
||||
result_minus.physical_position(i)) /
|
||||
(2.0 * difference_step);
|
||||
CHECK_THAT(
|
||||
position_finite_difference,
|
||||
WithinAbs(variation.physical_position_variation(i), tolerance)
|
||||
);
|
||||
|
||||
for (int j = 0; j < dimension; ++j) {
|
||||
const double jacobian_finite_difference =
|
||||
(result_plus.mapping_jacobian(i, j) -
|
||||
result_minus.mapping_jacobian(i, j)) /
|
||||
(2.0 * difference_step);
|
||||
CHECK_THAT(
|
||||
jacobian_finite_difference,
|
||||
WithinAbs(variation.mapping_jacobian_variation(i, j), tolerance)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Preserves Rotational Covariance",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double coordinate = 0.31;
|
||||
constexpr double tolerance = 1.0e-12;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
const mfem::Vector reference_position = make_vector(1.3, 0.4, -0.2);
|
||||
const mfem::Vector displaced_position = make_vector(1.4, 0.2, -0.1);
|
||||
const mfem::Vector coordinate_gradient = make_vector(0.16, -0.08, 0.03);
|
||||
|
||||
mfem::DenseMatrix displacement_jacobian = make_identity();
|
||||
displacement_jacobian(0, 1) = 0.05;
|
||||
displacement_jacobian(1, 0) = -0.02;
|
||||
displacement_jacobian(2, 1) = 0.03;
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, displaced_position, displacement_jacobian,
|
||||
coordinate, coordinate_gradient
|
||||
};
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
mfem::DenseMatrix rotation(dimension);
|
||||
rotation = 0.0;
|
||||
rotation(0, 1) = -1.0;
|
||||
rotation(1, 0) = 1.0;
|
||||
rotation(2, 2) = 1.0;
|
||||
|
||||
mfem::Vector rotated_reference(dimension);
|
||||
mfem::Vector rotated_displaced(dimension);
|
||||
mfem::Vector rotated_coordinate_gradient(dimension);
|
||||
rotation.Mult(reference_position, rotated_reference);
|
||||
rotation.Mult(displaced_position, rotated_displaced);
|
||||
rotation.Mult(coordinate_gradient, rotated_coordinate_gradient);
|
||||
|
||||
mfem::DenseMatrix temporary(dimension);
|
||||
mfem::DenseMatrix rotated_displacement_jacobian(dimension);
|
||||
mfem::Mult(rotation, displacement_jacobian, temporary);
|
||||
mfem::MultABt(temporary, rotation, rotated_displacement_jacobian);
|
||||
|
||||
const mapping::compactification::ExteriorMapInput rotated_input{
|
||||
rotated_reference, rotated_displaced, rotated_displacement_jacobian,
|
||||
coordinate, rotated_coordinate_gradient
|
||||
};
|
||||
mapping::compactification::ExteriorMapResult rotated_result;
|
||||
REQUIRE(
|
||||
compactification.Evaluate(rotated_input, rotated_result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
mfem::Vector expected_position(dimension);
|
||||
rotation.Mult(result.physical_position, expected_position);
|
||||
|
||||
mfem::DenseMatrix expected_jacobian(dimension);
|
||||
mfem::Mult(rotation, result.mapping_jacobian, temporary);
|
||||
mfem::MultABt(temporary, rotation, expected_jacobian);
|
||||
|
||||
check_vector(
|
||||
rotated_result.physical_position, expected_position, tolerance
|
||||
);
|
||||
check_matrix(rotated_result.mapping_jacobian, expected_jacobian, tolerance);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Is Continuous At The Mesh Defined Stellar Surface",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double tolerance = 1.0e-14;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
const mfem::Vector reference_position = make_vector(
|
||||
-0.5260553366425769, 0.5260553366425769, -0.6553163792879153
|
||||
);
|
||||
const mfem::Vector displaced_position = make_vector(-0.55, 0.51, -0.63);
|
||||
const mfem::Vector coordinate_gradient = make_vector(-0.18, 0.18, -0.22);
|
||||
const mfem::DenseMatrix displacement_jacobian = make_identity();
|
||||
|
||||
REQUIRE(reference_position.Norml2() < 1.0);
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, displaced_position, displacement_jacobian, 0.0,
|
||||
coordinate_gradient
|
||||
};
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
check_vector(result.physical_position, displaced_position, tolerance);
|
||||
CHECK(result.mapping_jacobian.Det() > 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Has Correct Infinity And Coordinate Bound "
|
||||
"Behavior",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double r_star = 1.0;
|
||||
constexpr double r_inf = 4.0;
|
||||
constexpr double radial_extent = r_inf - r_star;
|
||||
constexpr double coordinate_tolerance = 1.0e-12;
|
||||
constexpr double tolerance = 2.0e-11;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = r_star,
|
||||
.r_inf_ref = r_inf,
|
||||
.coordinate_tolerance = coordinate_tolerance}
|
||||
);
|
||||
const mfem::DenseMatrix identity = make_identity();
|
||||
const mfem::Vector coordinate_gradient =
|
||||
make_vector(1.0 / radial_extent, 0.0, 0.0);
|
||||
|
||||
for (const double coordinate :
|
||||
std::array{0.0, 0.25, 0.75, 0.95, 0.99, 0.999}) {
|
||||
CAPTURE(coordinate);
|
||||
|
||||
const double computational_radius = r_star + coordinate * radial_extent;
|
||||
const mfem::Vector reference_position =
|
||||
make_vector(computational_radius, 0.0, 0.0);
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, reference_position, identity, coordinate,
|
||||
coordinate_gradient
|
||||
};
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
CHECK_THAT(
|
||||
result.physical_position.Norml2() * (1.0 - coordinate),
|
||||
WithinAbs(r_star, tolerance)
|
||||
);
|
||||
}
|
||||
|
||||
const mfem::Vector reference_position = make_vector(r_inf, 0.0, 0.0);
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, reference_position, identity, 1.0,
|
||||
coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::at_compactified_infinity
|
||||
);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, reference_position, identity,
|
||||
1.0 - 0.5 * coordinate_tolerance, coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::at_compactified_infinity
|
||||
);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, reference_position, identity,
|
||||
1.0 + 0.5 * coordinate_tolerance, coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::at_compactified_infinity
|
||||
);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, reference_position, identity,
|
||||
1.0 + 2.0 * coordinate_tolerance, coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::outside_reference_domain
|
||||
);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, reference_position, identity,
|
||||
-2.0 * coordinate_tolerance, coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::outside_reference_domain
|
||||
);
|
||||
|
||||
const mfem::Vector surface_position = make_vector(0.97, 0.0, 0.0);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{surface_position, surface_position, identity,
|
||||
-0.5 * coordinate_tolerance, coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::valid
|
||||
);
|
||||
check_vector(result.physical_position, surface_position, tolerance);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Rejects Invalid Inputs And Inverted Maps",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
|
||||
const mfem::Vector reference_position = make_vector(2.0, 0.0, 0.0);
|
||||
const mfem::Vector displaced_position(reference_position);
|
||||
const mfem::Vector coordinate_gradient = make_vector(1.0 / 3.0, 0.0, 0.0);
|
||||
const mfem::Vector zero_gradient = make_vector(0.0, 0.0, 0.0);
|
||||
const mfem::DenseMatrix identity = make_identity();
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
|
||||
mfem::Vector wrong_dimension(2);
|
||||
wrong_dimension = 1.0;
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{wrong_dimension, displaced_position, identity, 1.0 / 3.0,
|
||||
coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::invalid_dimension
|
||||
);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, displaced_position, identity, 1.0 / 3.0,
|
||||
wrong_dimension},
|
||||
result
|
||||
) == mapping::MappingStatus::invalid_dimension
|
||||
);
|
||||
|
||||
mfem::Vector non_finite_position(reference_position);
|
||||
non_finite_position(1) = std::numeric_limits<double>::quiet_NaN();
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{non_finite_position, displaced_position, identity, 1.0 / 3.0,
|
||||
coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::non_finite_input
|
||||
);
|
||||
|
||||
mfem::Vector non_finite_gradient(coordinate_gradient);
|
||||
non_finite_gradient(2) = std::numeric_limits<double>::infinity();
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, displaced_position, identity, 1.0 / 3.0,
|
||||
non_finite_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::non_finite_input
|
||||
);
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, displaced_position, identity,
|
||||
std::numeric_limits<double>::quiet_NaN(), coordinate_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::non_finite_input
|
||||
);
|
||||
|
||||
mfem::DenseMatrix singular_displacement_jacobian(dimension);
|
||||
singular_displacement_jacobian = 0.0;
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, displaced_position,
|
||||
singular_displacement_jacobian, 1.0 / 3.0, zero_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::non_positive_determinant
|
||||
);
|
||||
|
||||
mfem::DenseMatrix inverted_displacement_jacobian = make_identity();
|
||||
inverted_displacement_jacobian(0, 0) = -1.0;
|
||||
CHECK(
|
||||
compactification.Evaluate(
|
||||
{reference_position, displaced_position,
|
||||
inverted_displacement_jacobian, 1.0 / 3.0, zero_gradient},
|
||||
result
|
||||
) == mapping::MappingStatus::non_positive_determinant
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Variation Rejects Invalid Inputs",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
const mfem::Vector reference_position = make_vector(2.0, 0.0, 0.0);
|
||||
const mfem::Vector displaced_position(reference_position);
|
||||
const mfem::Vector coordinate_gradient = make_vector(1.0 / 3.0, 0.0, 0.0);
|
||||
const mfem::DenseMatrix identity = make_identity();
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
reference_position, displaced_position, identity, 1.0 / 3.0,
|
||||
coordinate_gradient
|
||||
};
|
||||
|
||||
mapping::compactification::ExteriorMapResult result;
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input, result) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
const mfem::Vector valid_position_direction =
|
||||
make_vector(0.01, -0.02, 0.03);
|
||||
const mfem::DenseMatrix valid_jacobian_direction = make_identity();
|
||||
mapping::compactification::ExteriorMapVariation variation;
|
||||
|
||||
mfem::Vector wrong_dimension(2);
|
||||
wrong_dimension = 0.0;
|
||||
CHECK(
|
||||
compactification.EvaluateVariation(
|
||||
input, result, {wrong_dimension, valid_jacobian_direction},
|
||||
variation
|
||||
) == mapping::MappingStatus::invalid_dimension
|
||||
);
|
||||
|
||||
mfem::DenseMatrix wrong_jacobian_dimension(2);
|
||||
wrong_jacobian_dimension = 0.0;
|
||||
CHECK(
|
||||
compactification.EvaluateVariation(
|
||||
input, result, {valid_position_direction, wrong_jacobian_dimension},
|
||||
variation
|
||||
) == mapping::MappingStatus::invalid_dimension
|
||||
);
|
||||
|
||||
mfem::Vector non_finite_direction(valid_position_direction);
|
||||
non_finite_direction(0) = std::numeric_limits<double>::quiet_NaN();
|
||||
CHECK(
|
||||
compactification.EvaluateVariation(
|
||||
input, result, {non_finite_direction, valid_jacobian_direction},
|
||||
variation
|
||||
) == mapping::MappingStatus::non_finite_input
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Evaluations Are Independent",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
) {
|
||||
constexpr double tolerance = 0.0;
|
||||
|
||||
mapping::compactification::KelvinCompactification compactification(
|
||||
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
);
|
||||
const mfem::DenseMatrix identity = make_identity();
|
||||
const mfem::Vector gradient_a = make_vector(0.12, 0.03, -0.02);
|
||||
const mfem::Vector gradient_b = make_vector(-0.04, 0.15, 0.01);
|
||||
const mfem::Vector reference_a = make_vector(1.5, 0.2, 0.1);
|
||||
const mfem::Vector reference_b = make_vector(2.5, -0.3, 0.4);
|
||||
|
||||
const mapping::compactification::ExteriorMapInput input_a{
|
||||
reference_a, reference_a, identity, 0.25, gradient_a
|
||||
};
|
||||
const mapping::compactification::ExteriorMapInput input_b{
|
||||
reference_b, reference_b, identity, 0.70, gradient_b
|
||||
};
|
||||
|
||||
mapping::compactification::ExteriorMapResult first_a;
|
||||
mapping::compactification::ExteriorMapResult result_b;
|
||||
mapping::compactification::ExteriorMapResult second_a;
|
||||
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_a, first_a) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_b, result_b) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
REQUIRE(
|
||||
compactification.Evaluate(input_a, second_a) ==
|
||||
mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
check_vector(
|
||||
first_a.physical_position, second_a.physical_position, tolerance
|
||||
);
|
||||
check_matrix(
|
||||
first_a.mapping_jacobian, second_a.mapping_jacobian, tolerance
|
||||
);
|
||||
}
|
||||
3486
tests/mapping/domain_mapper.cpp
Normal file
3486
tests/mapping/domain_mapper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
454
tests/mapping/hdiv_mass_tensor.cpp
Normal file
454
tests/mapping/hdiv_mass_tensor.cpp
Normal file
@@ -0,0 +1,454 @@
|
||||
#include <array>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <mfem.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
using namespace mean_field;
|
||||
using Catch::Matchers::WithinAbs;
|
||||
|
||||
namespace {
|
||||
constexpr int dimension = 3;
|
||||
|
||||
mfem::DenseMatrix make_matrix(
|
||||
const std::array<
|
||||
double,
|
||||
9> &values
|
||||
) {
|
||||
mfem::DenseMatrix matrix(dimension);
|
||||
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column)
|
||||
matrix(row, column) = values[row * dimension + column];
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
mfem::DenseMatrix make_identity_matrix() {
|
||||
mfem::DenseMatrix identity(dimension);
|
||||
identity = 0.0;
|
||||
for (int i = 0; i < dimension; ++i)
|
||||
identity(i, i) = 1.0;
|
||||
return identity;
|
||||
}
|
||||
|
||||
mapping::MappingPointContext
|
||||
make_context(const mfem::DenseMatrix &jacobian) {
|
||||
mapping::MappingPointContext context;
|
||||
context.mapping_jacobian = jacobian;
|
||||
context.mapping_determinant = jacobian.Det();
|
||||
context.inverse_mapping_jacobian.SetSize(dimension);
|
||||
mfem::CalcInverse(jacobian, context.inverse_mapping_jacobian);
|
||||
context.physical_position.SetSize(dimension);
|
||||
context.physical_position = 0.0;
|
||||
return context;
|
||||
}
|
||||
|
||||
double determinant_variation(
|
||||
const mfem::DenseMatrix &jacobian,
|
||||
const mfem::DenseMatrix &jacobian_variation
|
||||
) {
|
||||
mfem::DenseMatrix inverse_jacobian(dimension);
|
||||
mfem::DenseMatrix product(dimension);
|
||||
mfem::CalcInverse(jacobian, inverse_jacobian);
|
||||
mfem::Mult(inverse_jacobian, jacobian_variation, product);
|
||||
|
||||
double trace = 0.0;
|
||||
for (int i = 0; i < dimension; ++i)
|
||||
trace += product(i, i);
|
||||
return jacobian.Det() * trace;
|
||||
}
|
||||
|
||||
mapping::MappingPointVariation make_variation(
|
||||
const mfem::DenseMatrix &jacobian,
|
||||
const mfem::DenseMatrix &jacobian_variation
|
||||
) {
|
||||
mapping::MappingPointVariation variation;
|
||||
variation.mapping_jacobian_variation = jacobian_variation;
|
||||
variation.mapping_determinant_variation =
|
||||
determinant_variation(jacobian, jacobian_variation);
|
||||
variation.physical_position_variation.SetSize(dimension);
|
||||
variation.physical_position_variation = 0.0;
|
||||
return variation;
|
||||
}
|
||||
|
||||
double matrix_norm(const mfem::DenseMatrix &matrix) {
|
||||
double norm_squared = 0.0;
|
||||
|
||||
for (int row = 0; row < matrix.Height(); ++row) {
|
||||
for (int column = 0; column < matrix.Width(); ++column)
|
||||
norm_squared += matrix(row, column) * matrix(row, column);
|
||||
}
|
||||
|
||||
return std::sqrt(norm_squared);
|
||||
}
|
||||
|
||||
double relative_matrix_error(
|
||||
const mfem::DenseMatrix &computed,
|
||||
const mfem::DenseMatrix &reference
|
||||
) {
|
||||
REQUIRE(computed.Height() == reference.Height());
|
||||
REQUIRE(computed.Width() == reference.Width());
|
||||
|
||||
mfem::DenseMatrix difference(computed);
|
||||
difference -= reference;
|
||||
|
||||
return matrix_norm(difference) /
|
||||
std::max(
|
||||
matrix_norm(reference),
|
||||
std::numeric_limits<double>::epsilon()
|
||||
);
|
||||
}
|
||||
|
||||
double matrix_asymmetry(const mfem::DenseMatrix &matrix) {
|
||||
double asymmetry_squared = 0.0;
|
||||
|
||||
for (int row = 0; row < matrix.Height(); ++row) {
|
||||
for (int column = 0; column < matrix.Width(); ++column) {
|
||||
const double difference =
|
||||
matrix(row, column) - matrix(column, row);
|
||||
asymmetry_squared += difference * difference;
|
||||
}
|
||||
}
|
||||
|
||||
return std::sqrt(asymmetry_squared);
|
||||
}
|
||||
|
||||
mfem::DenseMatrix centered_mass_tensor_difference(
|
||||
const mfem::DenseMatrix &jacobian,
|
||||
const mfem::DenseMatrix &jacobian_variation,
|
||||
const double step
|
||||
) {
|
||||
mfem::DenseMatrix plus_jacobian(jacobian);
|
||||
mfem::DenseMatrix minus_jacobian(jacobian);
|
||||
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column) {
|
||||
plus_jacobian(row, column) +=
|
||||
step * jacobian_variation(row, column);
|
||||
minus_jacobian(row, column) -=
|
||||
step * jacobian_variation(row, column);
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(plus_jacobian.Det() > 0.0);
|
||||
REQUIRE(minus_jacobian.Det() > 0.0);
|
||||
|
||||
const mapping::MappingPointContext plus_context =
|
||||
make_context(plus_jacobian);
|
||||
const mapping::MappingPointContext minus_context =
|
||||
make_context(minus_jacobian);
|
||||
|
||||
mfem::DenseMatrix plus_tensor;
|
||||
mfem::DenseMatrix minus_tensor;
|
||||
mapping::ComputeHDivMassTensor(plus_context, plus_tensor);
|
||||
mapping::ComputeHDivMassTensor(minus_context, minus_tensor);
|
||||
|
||||
plus_tensor -= minus_tensor;
|
||||
plus_tensor *= 1 / (2.0 * step);
|
||||
return plus_tensor;
|
||||
}
|
||||
|
||||
void check_zero_matrix(
|
||||
const mfem::DenseMatrix &matrix,
|
||||
const double tolerance
|
||||
) {
|
||||
for (int row = 0; row < matrix.Height(); ++row) {
|
||||
for (int column = 0; column < matrix.Width(); ++column)
|
||||
CHECK_THAT(matrix(row, column), WithinAbs(0.0, tolerance));
|
||||
}
|
||||
}
|
||||
|
||||
struct TensorVariationCase {
|
||||
std::string name;
|
||||
mfem::DenseMatrix jacobian;
|
||||
mfem::DenseMatrix jacobian_variation;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Hdiv Mass Tensor Variation Matches Centered Differences",
|
||||
tags::unit &tags::transformations
|
||||
) {
|
||||
std::vector<TensorVariationCase> cases;
|
||||
|
||||
cases.push_back(
|
||||
{"identity with general variation", make_identity_matrix(),
|
||||
make_matrix({0.12, -0.07, 0.03, 0.05, -0.09, 0.04, -0.02, 0.08, 0.06})}
|
||||
);
|
||||
|
||||
cases.push_back(
|
||||
{"anisotropic stretch",
|
||||
make_matrix({1.20, 0.00, 0.00, 0.00, 0.85, 0.00, 0.00, 0.00, 1.10}),
|
||||
make_matrix({0.08, 0.01, -0.03, 0.02, -0.05, 0.04, 0.01, -0.02, 0.07})}
|
||||
);
|
||||
|
||||
cases.push_back(
|
||||
{"sheared mapping",
|
||||
make_matrix({1.10, 0.20, -0.05, 0.04, 0.90, 0.12, -0.03, 0.08, 1.15}),
|
||||
make_matrix(
|
||||
{0.06, -0.04, 0.02, 0.03, 0.05, -0.07, -0.01, 0.04, -0.02}
|
||||
)}
|
||||
);
|
||||
|
||||
cases.push_back(
|
||||
{"strong general mapping",
|
||||
make_matrix({1.35, 0.31, -0.18, -0.12, 0.78, 0.22, 0.09, -0.16, 1.27}),
|
||||
make_matrix({-0.11, 0.08, 0.05, 0.07, 0.09, -0.04, -0.06, 0.03, 0.12})}
|
||||
);
|
||||
|
||||
for (const TensorVariationCase &test_case : cases) {
|
||||
DYNAMIC_SECTION(test_case.name) {
|
||||
REQUIRE(test_case.jacobian.Det() > 0.0);
|
||||
|
||||
const mapping::MappingPointContext context =
|
||||
make_context(test_case.jacobian);
|
||||
const mapping::MappingPointVariation variation = make_variation(
|
||||
test_case.jacobian, test_case.jacobian_variation
|
||||
);
|
||||
|
||||
mfem::DenseMatrix analytic_variation;
|
||||
mapping::ComputeHDivMassTensorVariation(
|
||||
context, variation, analytic_variation
|
||||
);
|
||||
|
||||
const mfem::DenseMatrix finite_difference =
|
||||
centered_mass_tensor_difference(
|
||||
test_case.jacobian, test_case.jacobian_variation, 1.0e-6
|
||||
);
|
||||
const double relative_error =
|
||||
relative_matrix_error(analytic_variation, finite_difference);
|
||||
const double asymmetry = matrix_asymmetry(analytic_variation);
|
||||
|
||||
INFO("Mapping determinant = " << context.mapping_determinant);
|
||||
INFO(
|
||||
"Determinant variation = "
|
||||
<< variation.mapping_determinant_variation
|
||||
);
|
||||
INFO(
|
||||
"Analytic variation norm = " << matrix_norm(analytic_variation)
|
||||
);
|
||||
INFO(
|
||||
"Finite-difference variation norm = "
|
||||
<< matrix_norm(finite_difference)
|
||||
);
|
||||
INFO("Relative tensor-variation error = " << relative_error);
|
||||
INFO("Tensor-variation asymmetry = " << asymmetry);
|
||||
|
||||
CHECK(relative_error < 2.0e-9);
|
||||
CHECK(asymmetry < 2.0e-14);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hdiv Mass Tensor Variation Has Second Order Centered Difference "
|
||||
"Convergence",
|
||||
tags::unit &tags::transformations &tags::convergence
|
||||
) {
|
||||
const mfem::DenseMatrix jacobian =
|
||||
make_matrix({1.18, 0.17, -0.09, -0.04, 0.92, 0.14, 0.07, -0.11, 1.23});
|
||||
|
||||
const mfem::DenseMatrix jacobian_variation =
|
||||
make_matrix({0.09, -0.06, 0.04, 0.03, 0.07, -0.05, -0.02, 0.08, -0.03});
|
||||
|
||||
const mapping::MappingPointContext context = make_context(jacobian);
|
||||
const mapping::MappingPointVariation variation =
|
||||
make_variation(jacobian, jacobian_variation);
|
||||
|
||||
mfem::DenseMatrix analytic_variation;
|
||||
mapping::ComputeHDivMassTensorVariation(
|
||||
context, variation, analytic_variation
|
||||
);
|
||||
|
||||
const std::array<double, 3> steps{4.0e-2, 2.0e-2, 1.0e-2};
|
||||
std::array<double, 3> errors{};
|
||||
|
||||
for (int i = 0; i < static_cast<int>(steps.size()); ++i) {
|
||||
const mfem::DenseMatrix finite_difference =
|
||||
centered_mass_tensor_difference(
|
||||
jacobian, jacobian_variation, steps[i]
|
||||
);
|
||||
errors[i] =
|
||||
relative_matrix_error(finite_difference, analytic_variation);
|
||||
INFO("Step = " << steps[i] << ", relative error = " << errors[i]);
|
||||
}
|
||||
|
||||
const double first_reduction = errors[1] / errors[0];
|
||||
const double second_reduction = errors[2] / errors[1];
|
||||
|
||||
INFO("First error-reduction ratio = " << first_reduction);
|
||||
INFO("Second error-reduction ratio = " << second_reduction);
|
||||
|
||||
CHECK(first_reduction < 0.30);
|
||||
CHECK(second_reduction < 0.30);
|
||||
CHECK(errors[2] < 1.0e-5);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hdiv Mass Tensor Variation Vanishes For Translation",
|
||||
tags::unit &tags::transformations
|
||||
) {
|
||||
const mfem::DenseMatrix jacobian =
|
||||
make_matrix({1.12, 0.08, -0.03, 0.02, 0.94, 0.07, -0.01, 0.05, 1.09});
|
||||
|
||||
mfem::DenseMatrix zero_jacobian_variation(dimension);
|
||||
zero_jacobian_variation = 0.0;
|
||||
|
||||
mapping::MappingPointContext context = make_context(jacobian);
|
||||
mapping::MappingPointVariation variation =
|
||||
make_variation(jacobian, zero_jacobian_variation);
|
||||
variation.physical_position_variation.SetSize(dimension);
|
||||
variation.physical_position_variation(0) = 0.7;
|
||||
variation.physical_position_variation(1) = -0.4;
|
||||
variation.physical_position_variation(2) = 0.9;
|
||||
|
||||
mfem::DenseMatrix tensor_variation;
|
||||
mapping::ComputeHDivMassTensorVariation(
|
||||
context, variation, tensor_variation
|
||||
);
|
||||
|
||||
CHECK_THAT(variation.mapping_determinant_variation, WithinAbs(0.0, 0.0));
|
||||
check_zero_matrix(tensor_variation, 1.0e-14);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hdiv Mass Tensor Variation Vanishes For Infinitesimal Rotation At "
|
||||
"Identity",
|
||||
tags::unit &tags::transformations
|
||||
) {
|
||||
const mfem::DenseMatrix identity = make_identity_matrix();
|
||||
|
||||
const mfem::DenseMatrix rotation_variation =
|
||||
make_matrix({0.0, -0.30, 0.20, 0.30, 0.0, -0.15, -0.20, 0.15, 0.0});
|
||||
|
||||
const mapping::MappingPointContext context = make_context(identity);
|
||||
const mapping::MappingPointVariation variation =
|
||||
make_variation(identity, rotation_variation);
|
||||
|
||||
mfem::DenseMatrix tensor_variation;
|
||||
mapping::ComputeHDivMassTensorVariation(
|
||||
context, variation, tensor_variation
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
variation.mapping_determinant_variation, WithinAbs(0.0, 1.0e-15)
|
||||
);
|
||||
check_zero_matrix(tensor_variation, 1.0e-14);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hdiv Mass Tensor Variation Matches Isotropic Scaling At Identity",
|
||||
tags::unit &tags::transformations
|
||||
) {
|
||||
constexpr double scaling_variation = 0.17;
|
||||
|
||||
const mfem::DenseMatrix identity = make_identity_matrix();
|
||||
mfem::DenseMatrix jacobian_variation(dimension);
|
||||
jacobian_variation = 0.0;
|
||||
for (int i = 0; i < dimension; ++i)
|
||||
jacobian_variation(i, i) = scaling_variation;
|
||||
|
||||
const mapping::MappingPointContext context = make_context(identity);
|
||||
const mapping::MappingPointVariation variation =
|
||||
make_variation(identity, jacobian_variation);
|
||||
|
||||
mfem::DenseMatrix tensor_variation;
|
||||
mapping::ComputeHDivMassTensorVariation(
|
||||
context, variation, tensor_variation
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
variation.mapping_determinant_variation,
|
||||
WithinAbs(3.0 * scaling_variation, 1.0e-14)
|
||||
);
|
||||
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column) {
|
||||
const double expected = row == column ? -scaling_variation : 0.0;
|
||||
CHECK_THAT(
|
||||
tensor_variation(row, column), WithinAbs(expected, 1.0e-14)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hdiv Mass Tensor Variation Symmetrizes Simple Shear At Identity",
|
||||
tags::unit &tags::transformations
|
||||
) {
|
||||
constexpr double shear_variation = 0.23;
|
||||
|
||||
const mfem::DenseMatrix identity = make_identity_matrix();
|
||||
mfem::DenseMatrix jacobian_variation(dimension);
|
||||
jacobian_variation = 0.0;
|
||||
jacobian_variation(0, 1) = shear_variation;
|
||||
|
||||
const mapping::MappingPointContext context = make_context(identity);
|
||||
const mapping::MappingPointVariation variation =
|
||||
make_variation(identity, jacobian_variation);
|
||||
|
||||
mfem::DenseMatrix tensor_variation;
|
||||
mapping::ComputeHDivMassTensorVariation(
|
||||
context, variation, tensor_variation
|
||||
);
|
||||
|
||||
CHECK_THAT(
|
||||
variation.mapping_determinant_variation, WithinAbs(0.0, 1.0e-15)
|
||||
);
|
||||
CHECK_THAT(tensor_variation(0, 1), WithinAbs(shear_variation, 1.0e-14));
|
||||
CHECK_THAT(tensor_variation(1, 0), WithinAbs(shear_variation, 1.0e-14));
|
||||
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column) {
|
||||
if ((row == 0 && column == 1) || (row == 1 && column == 0))
|
||||
continue;
|
||||
CHECK_THAT(tensor_variation(row, column), WithinAbs(0.0, 1.0e-14));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Mapping Determinant Variation Matches Jacobi Formula",
|
||||
tags::unit &tags::transformations
|
||||
) {
|
||||
const mfem::DenseMatrix jacobian =
|
||||
make_matrix({1.24, 0.19, -0.07, -0.06, 0.88, 0.16, 0.04, -0.12, 1.19});
|
||||
|
||||
const mfem::DenseMatrix jacobian_variation =
|
||||
make_matrix({0.08, -0.03, 0.05, 0.02, 0.06, -0.04, -0.01, 0.07, -0.02});
|
||||
|
||||
const mapping::MappingPointContext context = make_context(jacobian);
|
||||
const mapping::MappingPointVariation variation =
|
||||
make_variation(jacobian, jacobian_variation);
|
||||
constexpr double difference_step = 1.0e-3;
|
||||
|
||||
mfem::DenseMatrix plus_one(context.mapping_jacobian);
|
||||
mfem::DenseMatrix plus_two(context.mapping_jacobian);
|
||||
mfem::DenseMatrix minus_one(context.mapping_jacobian);
|
||||
mfem::DenseMatrix minus_two(context.mapping_jacobian);
|
||||
|
||||
plus_one.Add(difference_step, variation.mapping_jacobian_variation);
|
||||
plus_two.Add(2.0 * difference_step, variation.mapping_jacobian_variation);
|
||||
minus_one.Add(-difference_step, variation.mapping_jacobian_variation);
|
||||
minus_two.Add(-2.0 * difference_step, variation.mapping_jacobian_variation);
|
||||
|
||||
const double finite_difference = (minus_two.Det() - 8.0 * minus_one.Det() +
|
||||
8.0 * plus_one.Det() - plus_two.Det()) /
|
||||
(12.0 * difference_step);
|
||||
const double analytic = variation.mapping_determinant_variation;
|
||||
const double relative_error = std::abs(finite_difference - analytic) /
|
||||
std::max(std::abs(analytic), 1.0e-14);
|
||||
|
||||
INFO("Analytic determinant variation = " << analytic);
|
||||
INFO("Finite-difference determinant variation = " << finite_difference);
|
||||
INFO("Relative determinant-variation error = " << relative_error);
|
||||
|
||||
CHECK(relative_error < 2.0e-11);
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace barotropic_closure_context_test_utils {
|
||||
mfem::Vector project_field(
|
||||
mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
mfem::Coefficient &coefficient
|
||||
) {
|
||||
mfem::ParGridFunction field(&finiteElementSpace);
|
||||
|
||||
field.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector trueVector;
|
||||
field.GetTrueDofs(trueVector);
|
||||
|
||||
return trueVector;
|
||||
}
|
||||
|
||||
mfem::Vector make_density(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
|
||||
return 0.55 + 0.025 * position(0) - 0.010 * position(1);
|
||||
});
|
||||
|
||||
return project_field(*f.densityFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_enthalpy(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
|
||||
return 0.90 + 0.020 * position(0) - 0.010 * position(1) +
|
||||
0.005 * position(2);
|
||||
});
|
||||
|
||||
return project_field(*f.enthalpyFes, coefficient);
|
||||
}
|
||||
} // namespace barotropic_closure_context_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Context Tracks Independent Revisions",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
mean_field::operators::context::barotropic::
|
||||
BarotropicClosureLinearizationContext context(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
mfem::Vector density =
|
||||
barotropic_closure_context_test_utils::make_density(f);
|
||||
|
||||
mfem::Vector enthalpy =
|
||||
barotropic_closure_context_test_utils::make_enthalpy(f);
|
||||
|
||||
mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.0);
|
||||
|
||||
mean_field::operators::context::barotropic::BarotropicClosureRevisions
|
||||
revisions{.density = 3, .enthalpy = 5, .displacement = 7};
|
||||
|
||||
CHECK_FALSE(context.IsPrepared());
|
||||
CHECK(context.GetPreparationCount() == 0);
|
||||
|
||||
context.Prepare(density, enthalpy, displacement, revisions);
|
||||
|
||||
REQUIRE(context.IsPrepared());
|
||||
|
||||
CHECK(context.MatchesRevisions(revisions));
|
||||
CHECK(context.GetRevisions() == revisions);
|
||||
CHECK(context.GetPreparationCount() == 1);
|
||||
|
||||
CHECK(context.GetOperator().GetPreparationCount() == 1);
|
||||
|
||||
context.Prepare(density, enthalpy, displacement, revisions);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 1);
|
||||
|
||||
const double frozenDensityValue = context.GetBaseDensityTrue()(0);
|
||||
|
||||
density(0) += 0.125;
|
||||
|
||||
CHECK(context.GetBaseDensityTrue()(0) == frozenDensityValue);
|
||||
|
||||
context.Prepare(density, enthalpy, displacement, revisions);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 1);
|
||||
CHECK(context.GetBaseDensityTrue()(0) == frozenDensityValue);
|
||||
|
||||
++revisions.density;
|
||||
|
||||
context.Prepare(density, enthalpy, displacement, revisions);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 2);
|
||||
CHECK(context.GetBaseDensityTrue()(0) == density(0));
|
||||
|
||||
enthalpy(0) += 0.050;
|
||||
++revisions.enthalpy;
|
||||
|
||||
context.Prepare(density, enthalpy, displacement, revisions);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 3);
|
||||
CHECK(context.GetBaseEnthalpyTrue()(0) == enthalpy(0));
|
||||
|
||||
displacement = gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
++revisions.displacement;
|
||||
|
||||
context.Prepare(density, enthalpy, displacement, revisions);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 4);
|
||||
CHECK(context.GetRevisions() == revisions);
|
||||
CHECK(context.MatchesRevisions(revisions));
|
||||
|
||||
CHECK(context.GetOperator().GetPreparationCount() == 4);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Context Reprepares A Consistent Frozen State",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
mean_field::operators::context::barotropic::
|
||||
BarotropicClosureLinearizationContext context(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
const mfem::Vector density =
|
||||
barotropic_closure_context_test_utils::make_density(f);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
barotropic_closure_context_test_utils::make_enthalpy(f);
|
||||
|
||||
const mfem::Vector identityDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.0);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.37
|
||||
);
|
||||
|
||||
const mfem::Vector enthalpyVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), 0.71
|
||||
);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.displacementFes->GetTrueVSize(), 0.37
|
||||
);
|
||||
|
||||
mean_field::operators::context::barotropic::BarotropicClosureRevisions
|
||||
revisions{.density = 11, .enthalpy = 13, .displacement = 17};
|
||||
|
||||
context.Prepare(density, enthalpy, identityDisplacement, revisions);
|
||||
|
||||
mfem::Vector initialResidual;
|
||||
mfem::Vector initialAction;
|
||||
|
||||
context.BuildResidual(initialResidual);
|
||||
|
||||
context.GetOperator().Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
initialAction
|
||||
);
|
||||
|
||||
mfem::Vector changedDensity(density);
|
||||
changedDensity.Add(0.025, densityVariation);
|
||||
|
||||
mfem::Vector changedEnthalpy(enthalpy);
|
||||
changedEnthalpy.Add(0.015, enthalpyVariation);
|
||||
|
||||
const mfem::Vector deformedDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
context.Prepare(
|
||||
changedDensity, changedEnthalpy, deformedDisplacement, revisions
|
||||
);
|
||||
|
||||
mfem::Vector unchangedResidual;
|
||||
mfem::Vector unchangedAction;
|
||||
|
||||
context.BuildResidual(unchangedResidual);
|
||||
|
||||
context.GetOperator().Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
unchangedAction
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
CHECK(context.GetPreparationCount() == 1);
|
||||
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
unchangedResidual, initialResidual, communicator
|
||||
) < 5.0e-15
|
||||
);
|
||||
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
unchangedAction, initialAction, communicator
|
||||
) < 5.0e-15
|
||||
);
|
||||
|
||||
++revisions.density;
|
||||
++revisions.enthalpy;
|
||||
++revisions.displacement;
|
||||
|
||||
context.Prepare(
|
||||
changedDensity, changedEnthalpy, deformedDisplacement, revisions
|
||||
);
|
||||
|
||||
mfem::Vector preparedResidual;
|
||||
mfem::Vector preparedAction;
|
||||
|
||||
context.BuildResidual(preparedResidual);
|
||||
|
||||
context.GetOperator().Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
preparedAction
|
||||
);
|
||||
|
||||
mfem::Vector referenceResidual;
|
||||
mfem::Vector referenceDensityAction;
|
||||
mfem::Vector referenceEnthalpyAction;
|
||||
mfem::Vector referenceDisplacementAction;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, changedDensity, changedEnthalpy,
|
||||
deformedDisplacement, referenceResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, densityVariation,
|
||||
deformedDisplacement, referenceDensityAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action(
|
||||
f, *f.domainMapperStateless, barotrope, changedEnthalpy,
|
||||
enthalpyVariation, deformedDisplacement, referenceEnthalpyAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, changedDensity,
|
||||
changedEnthalpy, deformedDisplacement, displacementVariation,
|
||||
referenceDisplacementAction
|
||||
);
|
||||
|
||||
mfem::Vector referenceAction(referenceDensityAction);
|
||||
referenceAction += referenceEnthalpyAction;
|
||||
referenceAction += referenceDisplacementAction;
|
||||
const double residualError = gravity_prepared_test_utils::relative_error(
|
||||
preparedResidual, referenceResidual, communicator
|
||||
);
|
||||
|
||||
const double actionError = gravity_prepared_test_utils::relative_error(
|
||||
preparedAction, referenceAction, communicator
|
||||
);
|
||||
|
||||
const double residualChange = gravity_prepared_test_utils::relative_error(
|
||||
preparedResidual, initialResidual, communicator
|
||||
);
|
||||
|
||||
const double actionChange = gravity_prepared_test_utils::relative_error(
|
||||
preparedAction, initialAction, communicator
|
||||
);
|
||||
|
||||
INFO("Prepared-context residual error = " << residualError);
|
||||
|
||||
INFO("Prepared-context Jacobian error = " << actionError);
|
||||
|
||||
INFO("Residual change after valid revision = " << residualChange);
|
||||
|
||||
INFO("Jacobian change after valid revision = " << actionChange);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 2);
|
||||
CHECK(residualError < 5.0e-12);
|
||||
CHECK(actionError < 5.0e-12);
|
||||
CHECK(residualChange > 1.0e-6);
|
||||
CHECK(actionChange > 1.0e-6);
|
||||
}
|
||||
306
tests/operators/contexts/gravity_field_context.cpp
Normal file
306
tests/operators/contexts/gravity_field_context.cpp
Normal file
@@ -0,0 +1,306 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
using namespace mean_field;
|
||||
namespace prepared_test = gravity_prepared_test_utils;
|
||||
namespace gravity_context = operators::context::gravity_field;
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Field Linearization Context Applies Selective Invalidation",
|
||||
tags::integration &tags::gravity &tags::contexts
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
gravity_context::GravityFieldLinearizationContext context(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
|
||||
mfem::Vector density = prepared_test::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.11
|
||||
);
|
||||
mfem::Vector displacement = prepared_test::make_displacement(f, 0.0);
|
||||
mfem::Vector gravity_gradient = prepared_test::make_deterministic_vector(
|
||||
f.gravityFluxFes->GetTrueVSize(), 0.37
|
||||
);
|
||||
mfem::Vector gravity_potential = prepared_test::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), 0.63
|
||||
);
|
||||
|
||||
gravity_context::GravityFieldRevisions revisions;
|
||||
|
||||
auto make_state = [&]() {
|
||||
return gravity_context::GravityFieldStateView{
|
||||
.density = density,
|
||||
.displacement = displacement,
|
||||
.gravity_gradient = gravity_gradient,
|
||||
.gravity_potential = gravity_potential
|
||||
};
|
||||
};
|
||||
|
||||
REQUIRE_FALSE(context.IsPrepared());
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport initial_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
REQUIRE(context.IsPrepared());
|
||||
CHECK(initial_report.geometry.reconstructed_operators);
|
||||
CHECK(initial_report.geometry.rebuilt_mass_operator);
|
||||
CHECK(initial_report.geometry.rebuilt_source_operator);
|
||||
CHECK(initial_report.geometry.refreshed_variation_state);
|
||||
CHECK(initial_report.updated_density);
|
||||
CHECK(initial_report.updated_gravity_gradient);
|
||||
CHECK(initial_report.DidAnyWork());
|
||||
|
||||
const auto initial_mass_preparations =
|
||||
context.GetGeometryContext().GetMassOperator().GetPreparationCount();
|
||||
const auto initial_source_preparations =
|
||||
context.GetGeometryContext().GetSourceOperator().GetPreparationCount();
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport repeated_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
CHECK_FALSE(repeated_report.DidAnyWork());
|
||||
CHECK(
|
||||
context.GetGeometryContext().GetMassOperator().GetPreparationCount() ==
|
||||
initial_mass_preparations
|
||||
);
|
||||
CHECK(
|
||||
context.GetGeometryContext()
|
||||
.GetSourceOperator()
|
||||
.GetPreparationCount() == initial_source_preparations
|
||||
);
|
||||
|
||||
gravity_potential(0) += 0.25;
|
||||
++revisions.gravity_potential.value;
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport potential_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
CHECK_FALSE(potential_report.DidAnyWork());
|
||||
CHECK(
|
||||
context.GetRevisions().gravity_potential == revisions.gravity_potential
|
||||
);
|
||||
|
||||
density(0) += 0.5;
|
||||
++revisions.density.value;
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport density_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
CHECK(density_report.updated_density);
|
||||
CHECK_FALSE(density_report.updated_gravity_gradient);
|
||||
CHECK_FALSE(density_report.geometry.DidAnyWork());
|
||||
CHECK(context.GetDensity()(0) == density(0));
|
||||
|
||||
gravity_gradient(0) -= 0.4;
|
||||
++revisions.gravity_gradient.value;
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport gradient_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
CHECK_FALSE(gradient_report.updated_density);
|
||||
CHECK(gradient_report.updated_gravity_gradient);
|
||||
CHECK_FALSE(gradient_report.geometry.DidAnyWork());
|
||||
CHECK(context.GetGravityGradient()(0) == gravity_gradient(0));
|
||||
|
||||
displacement = prepared_test::make_displacement(f, 1.0);
|
||||
++revisions.displacement.value;
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport displacement_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
CHECK_FALSE(displacement_report.geometry.reconstructed_operators);
|
||||
CHECK(displacement_report.geometry.rebuilt_mass_operator);
|
||||
CHECK(displacement_report.geometry.rebuilt_source_operator);
|
||||
CHECK(displacement_report.geometry.refreshed_variation_state);
|
||||
CHECK_FALSE(displacement_report.updated_density);
|
||||
CHECK_FALSE(displacement_report.updated_gravity_gradient);
|
||||
CHECK(
|
||||
context.GetGeometryContext().GetMassOperator().GetPreparationCount() ==
|
||||
initial_mass_preparations + 1
|
||||
);
|
||||
CHECK(
|
||||
context.GetGeometryContext()
|
||||
.GetSourceOperator()
|
||||
.GetPreparationCount() == initial_source_preparations + 1
|
||||
);
|
||||
|
||||
++revisions.discretization.value;
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport discretization_report =
|
||||
context.Prepare(make_state(), revisions);
|
||||
|
||||
CHECK(discretization_report.geometry.reconstructed_operators);
|
||||
CHECK(discretization_report.geometry.rebuilt_mass_operator);
|
||||
CHECK(discretization_report.geometry.rebuilt_source_operator);
|
||||
CHECK(discretization_report.updated_density);
|
||||
CHECK(discretization_report.updated_gravity_gradient);
|
||||
CHECK(
|
||||
context.GetGeometryContext().GetMassOperator().GetPreparationCount() ==
|
||||
1
|
||||
);
|
||||
CHECK(
|
||||
context.GetGeometryContext()
|
||||
.GetSourceOperator()
|
||||
.GetPreparationCount() == 1
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Field Linearization Context Owns Frozen Base Fields",
|
||||
tags::integration &tags::gravity &tags::contexts
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
gravity_context::GravityFieldLinearizationContext context(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
|
||||
mfem::Vector density = prepared_test::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.13
|
||||
);
|
||||
mfem::Vector displacement = prepared_test::make_displacement(f, 0.4);
|
||||
mfem::Vector gravity_gradient = prepared_test::make_deterministic_vector(
|
||||
f.gravityFluxFes->GetTrueVSize(), 0.47
|
||||
);
|
||||
mfem::Vector gravity_potential = prepared_test::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), 0.71
|
||||
);
|
||||
|
||||
gravity_context::GravityFieldRevisions revisions;
|
||||
|
||||
context.Prepare(
|
||||
{.density = density,
|
||||
.displacement = displacement,
|
||||
.gravity_gradient = gravity_gradient,
|
||||
.gravity_potential = gravity_potential},
|
||||
revisions
|
||||
);
|
||||
|
||||
const mfem::Vector frozen_density = context.GetDensity();
|
||||
const mfem::Vector frozen_displacement =
|
||||
context.GetGeometryContext().GetDisplacement();
|
||||
const mfem::Vector frozen_gravity_gradient = context.GetGravityGradient();
|
||||
|
||||
density = 0.0;
|
||||
displacement = 0.0;
|
||||
gravity_gradient = 0.0;
|
||||
gravity_potential = 0.0;
|
||||
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
context.GetDensity(), frozen_density, f.mesh->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
context.GetGeometryContext().GetDisplacement(), frozen_displacement,
|
||||
f.displacementFes->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
context.GetGravityGradient(), frozen_gravity_gradient,
|
||||
f.gravityFluxFes->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
|
||||
const gravity_context::GravityFieldPreparationReport
|
||||
unchanged_revision_report = context.Prepare(
|
||||
{.density = density,
|
||||
.displacement = displacement,
|
||||
.gravity_gradient = gravity_gradient,
|
||||
.gravity_potential = gravity_potential},
|
||||
revisions
|
||||
);
|
||||
|
||||
CHECK_FALSE(unchanged_revision_report.DidAnyWork());
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
context.GetDensity(), frozen_density, f.mesh->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
context.GetGeometryContext().GetDisplacement(), frozen_displacement,
|
||||
f.displacementFes->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
CHECK(
|
||||
prepared_test::relative_error(
|
||||
context.GetGravityGradient(), frozen_gravity_gradient,
|
||||
f.gravityFluxFes->GetComm()
|
||||
) == 0.0
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity Field Geometry Contexts Have Independent Prepared State",
|
||||
tags::integration &tags::gravity &tags::contexts
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
gravity_context::GravityFieldGeometryContext first_context(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
gravity_context::GravityFieldGeometryContext second_context(
|
||||
f, *f.domainMapperStateless
|
||||
);
|
||||
|
||||
const mfem::Vector first_displacement =
|
||||
prepared_test::make_displacement(f, 0.0);
|
||||
const mfem::Vector second_displacement =
|
||||
prepared_test::make_displacement(f, 1.0);
|
||||
const mfem::Vector gravity_gradient =
|
||||
prepared_test::make_deterministic_vector(
|
||||
f.gravityFluxFes->GetTrueVSize(), 0.35
|
||||
);
|
||||
|
||||
first_context.Prepare(first_displacement, {.value = 0}, {.value = 0});
|
||||
second_context.Prepare(second_displacement, {.value = 0}, {.value = 0});
|
||||
|
||||
mfem::Vector first_action;
|
||||
mfem::Vector second_action_before;
|
||||
mfem::Vector second_action_after;
|
||||
|
||||
first_context.GetMassOperator().Mult(gravity_gradient, first_action);
|
||||
second_context.GetMassOperator().Mult(
|
||||
gravity_gradient, second_action_before
|
||||
);
|
||||
|
||||
const mfem::Vector updated_first_displacement =
|
||||
prepared_test::make_displacement(f, 0.6);
|
||||
first_context.Prepare(
|
||||
updated_first_displacement, {.value = 0}, {.value = 1}
|
||||
);
|
||||
|
||||
second_context.GetMassOperator().Mult(
|
||||
gravity_gradient, second_action_after
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
||||
const double independent_context_error = prepared_test::relative_error(
|
||||
second_action_after, second_action_before, communicator
|
||||
);
|
||||
const double distinct_geometry_difference = prepared_test::relative_error(
|
||||
first_action, second_action_before, communicator
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Second-context change after preparing first context = "
|
||||
<< independent_context_error
|
||||
);
|
||||
INFO(
|
||||
"Difference between independently prepared geometries = "
|
||||
<< distinct_geometry_difference
|
||||
);
|
||||
|
||||
CHECK(independent_context_error < 2.0e-14);
|
||||
CHECK(distinct_geometry_difference > 1.0e-5);
|
||||
}
|
||||
382
tests/operators/contexts/hydrostatic_equilibrium_context.cpp
Normal file
382
tests/operators/contexts/hydrostatic_equilibrium_context.cpp
Normal file
@@ -0,0 +1,382 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace hydrostatic_context_test_utils {
|
||||
mean_field::operators::context::hydrostatic::
|
||||
HydrostaticEquilibriumDependencies
|
||||
make_dependencies() {
|
||||
return {
|
||||
.discretization = {.identity = 101, .revision = 2},
|
||||
.enthalpy = {.identity = 103, .revision = 3},
|
||||
.gravityPotential = {.identity = 107, .revision = 5},
|
||||
.displacement = {.identity = 109, .revision = 7},
|
||||
.rotation = {.identity = 113, .revision = 11},
|
||||
.bernoulliConstant = {.identity = 127, .revision = 13}
|
||||
};
|
||||
}
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView
|
||||
make_state(
|
||||
const mfem::Vector &enthalpy,
|
||||
const mfem::Vector &gravityPotential,
|
||||
const mfem::Vector &displacement,
|
||||
const double bernoulliConstant
|
||||
) {
|
||||
return {
|
||||
.enthalpy = enthalpy,
|
||||
.gravityPotential = gravityPotential,
|
||||
.displacement = displacement,
|
||||
.bernoulliConstant = bernoulliConstant
|
||||
};
|
||||
}
|
||||
|
||||
void check_base_only(
|
||||
const mean_field::operators::context::hydrostatic::
|
||||
HydrostaticPreparationReport &report
|
||||
) {
|
||||
CHECK_FALSE(report.preparedStaticDependencies);
|
||||
CHECK_FALSE(report.preparedGeometryState);
|
||||
CHECK_FALSE(report.preparedRotationDependencies);
|
||||
CHECK(report.preparedBaseState);
|
||||
}
|
||||
} // namespace hydrostatic_context_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Hydrostatic Context Applies Selective Invalidation",
|
||||
tags::barotrope &tags::contexts &tags::hydro &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumContext
|
||||
context(f, *f.domainMapperStateless);
|
||||
|
||||
mfem::Vector enthalpy =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), 0.17
|
||||
);
|
||||
|
||||
mfem::Vector gravityPotential =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), 0.31
|
||||
);
|
||||
|
||||
mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.35);
|
||||
|
||||
double bernoulliConstant = 0.73;
|
||||
|
||||
mean_field::operators::context::hydrostatic::
|
||||
HydrostaticEquilibriumDependencies dependencies =
|
||||
hydrostatic_context_test_utils::make_dependencies();
|
||||
|
||||
CHECK_FALSE(context.IsPrepared());
|
||||
CHECK_FALSE(context.MatchesDependencies(dependencies));
|
||||
|
||||
const auto initialStatistics = context.GetPreparationStatistics();
|
||||
|
||||
CHECK(initialStatistics.staticPreparations == 0);
|
||||
CHECK(initialStatistics.geometryPreparations == 0);
|
||||
CHECK(initialStatistics.rotationPreparations == 0);
|
||||
CHECK(initialStatistics.baseStatePreparations == 0);
|
||||
|
||||
const auto initialReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
REQUIRE(context.IsPrepared());
|
||||
CHECK(context.MatchesDependencies(dependencies));
|
||||
CHECK(context.GetDependencies() == dependencies);
|
||||
|
||||
CHECK(initialReport.preparedStaticDependencies);
|
||||
CHECK(initialReport.preparedGeometryState);
|
||||
CHECK(initialReport.preparedRotationDependencies);
|
||||
CHECK(initialReport.preparedBaseState);
|
||||
CHECK(initialReport.updatedEnthalpy);
|
||||
CHECK(initialReport.updatedGravityPotential);
|
||||
CHECK(initialReport.updatedDisplacement);
|
||||
CHECK(initialReport.updatedBernoulliConstant);
|
||||
CHECK(initialReport.DidAnyWork());
|
||||
|
||||
const mfem::Vector frozenEnthalpy = context.GetBaseEnthalpyTrue();
|
||||
|
||||
const mfem::Vector frozenGravityPotential =
|
||||
context.GetBaseGravityPotentialTrue();
|
||||
|
||||
const mfem::Vector frozenDisplacement = context.GetDisplacementTrue();
|
||||
|
||||
const double frozenBernoulliConstant = context.GetBernoulliConstant();
|
||||
|
||||
enthalpy(0) += 0.125;
|
||||
gravityPotential(0) -= 0.075;
|
||||
displacement(0) += 0.050;
|
||||
bernoulliConstant += 0.20;
|
||||
|
||||
const auto repeatedReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
CHECK_FALSE(repeatedReport.DidAnyWork());
|
||||
CHECK_FALSE(repeatedReport.updatedEnthalpy);
|
||||
CHECK_FALSE(repeatedReport.updatedGravityPotential);
|
||||
CHECK_FALSE(repeatedReport.updatedDisplacement);
|
||||
CHECK_FALSE(repeatedReport.updatedBernoulliConstant);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
context.GetBaseEnthalpyTrue(), frozenEnthalpy, communicator
|
||||
) == 0.0
|
||||
);
|
||||
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
context.GetBaseGravityPotentialTrue(), frozenGravityPotential,
|
||||
communicator
|
||||
) == 0.0
|
||||
);
|
||||
|
||||
CHECK(
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
context.GetDisplacementTrue(), frozenDisplacement, communicator
|
||||
) == 0.0
|
||||
);
|
||||
|
||||
CHECK(context.GetBernoulliConstant() == frozenBernoulliConstant);
|
||||
|
||||
++dependencies.enthalpy.revision;
|
||||
|
||||
const auto enthalpyReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
hydrostatic_context_test_utils::check_base_only(enthalpyReport);
|
||||
|
||||
CHECK(enthalpyReport.updatedEnthalpy);
|
||||
CHECK_FALSE(enthalpyReport.updatedGravityPotential);
|
||||
CHECK_FALSE(enthalpyReport.updatedDisplacement);
|
||||
CHECK_FALSE(enthalpyReport.updatedBernoulliConstant);
|
||||
CHECK(context.GetBaseEnthalpyTrue()(0) == enthalpy(0));
|
||||
|
||||
++dependencies.gravityPotential.revision;
|
||||
|
||||
const auto gravityPotentialReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
hydrostatic_context_test_utils::check_base_only(gravityPotentialReport);
|
||||
|
||||
CHECK_FALSE(gravityPotentialReport.updatedEnthalpy);
|
||||
CHECK(gravityPotentialReport.updatedGravityPotential);
|
||||
CHECK_FALSE(gravityPotentialReport.updatedDisplacement);
|
||||
CHECK_FALSE(gravityPotentialReport.updatedBernoulliConstant);
|
||||
|
||||
CHECK(context.GetBaseGravityPotentialTrue()(0) == gravityPotential(0));
|
||||
|
||||
++dependencies.bernoulliConstant.revision;
|
||||
|
||||
const auto bernoulliReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
hydrostatic_context_test_utils::check_base_only(bernoulliReport);
|
||||
|
||||
CHECK_FALSE(bernoulliReport.updatedEnthalpy);
|
||||
CHECK_FALSE(bernoulliReport.updatedGravityPotential);
|
||||
CHECK_FALSE(bernoulliReport.updatedDisplacement);
|
||||
CHECK(bernoulliReport.updatedBernoulliConstant);
|
||||
|
||||
CHECK(context.GetBernoulliConstant() == bernoulliConstant);
|
||||
|
||||
++dependencies.rotation.revision;
|
||||
|
||||
const auto rotationReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
CHECK_FALSE(rotationReport.preparedStaticDependencies);
|
||||
CHECK_FALSE(rotationReport.preparedGeometryState);
|
||||
CHECK(rotationReport.preparedRotationDependencies);
|
||||
CHECK(rotationReport.preparedBaseState);
|
||||
CHECK_FALSE(rotationReport.updatedEnthalpy);
|
||||
CHECK_FALSE(rotationReport.updatedGravityPotential);
|
||||
CHECK_FALSE(rotationReport.updatedDisplacement);
|
||||
CHECK_FALSE(rotationReport.updatedBernoulliConstant);
|
||||
|
||||
++dependencies.displacement.revision;
|
||||
|
||||
const auto displacementReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
CHECK_FALSE(displacementReport.preparedStaticDependencies);
|
||||
|
||||
CHECK(displacementReport.preparedGeometryState);
|
||||
CHECK(displacementReport.preparedRotationDependencies);
|
||||
CHECK(displacementReport.preparedBaseState);
|
||||
CHECK_FALSE(displacementReport.updatedEnthalpy);
|
||||
CHECK_FALSE(displacementReport.updatedGravityPotential);
|
||||
CHECK(displacementReport.updatedDisplacement);
|
||||
CHECK_FALSE(displacementReport.updatedBernoulliConstant);
|
||||
|
||||
CHECK(context.GetDisplacementTrue()(0) == displacement(0));
|
||||
|
||||
++dependencies.discretization.revision;
|
||||
|
||||
const auto discretizationReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
CHECK(discretizationReport.preparedStaticDependencies);
|
||||
CHECK(discretizationReport.preparedGeometryState);
|
||||
CHECK(discretizationReport.preparedRotationDependencies);
|
||||
CHECK(discretizationReport.preparedBaseState);
|
||||
CHECK(discretizationReport.updatedEnthalpy);
|
||||
CHECK(discretizationReport.updatedGravityPotential);
|
||||
CHECK(discretizationReport.updatedDisplacement);
|
||||
CHECK(discretizationReport.updatedBernoulliConstant);
|
||||
|
||||
const auto finalStatistics = context.GetPreparationStatistics();
|
||||
|
||||
CHECK(finalStatistics.staticPreparations == 2);
|
||||
CHECK(finalStatistics.geometryPreparations == 3);
|
||||
CHECK(finalStatistics.rotationPreparations == 4);
|
||||
CHECK(finalStatistics.baseStatePreparations == 7);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hydrostatic Context Uses Identity In Every Dependency",
|
||||
tags::barotrope &tags::contexts &tags::hydro &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
mean_field::operators::context::hydrostatic::HydrostaticEquilibriumContext
|
||||
context(f, *f.domainMapperStateless);
|
||||
|
||||
mfem::Vector enthalpy =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(), 0.23
|
||||
);
|
||||
|
||||
const mfem::Vector gravityPotential =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.gravityPotentialFes->GetTrueVSize(), 0.41
|
||||
);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.60);
|
||||
|
||||
constexpr double bernoulliConstant = 0.81;
|
||||
|
||||
mean_field::operators::context::hydrostatic::
|
||||
HydrostaticEquilibriumDependencies dependencies =
|
||||
hydrostatic_context_test_utils::make_dependencies();
|
||||
|
||||
const auto preparedEnthalpyDependency = dependencies.enthalpy;
|
||||
|
||||
auto olderSameIdentity = preparedEnthalpyDependency;
|
||||
--olderSameIdentity.revision;
|
||||
|
||||
auto resetNewIdentity = preparedEnthalpyDependency;
|
||||
++resetNewIdentity.identity;
|
||||
resetNewIdentity.revision = 0;
|
||||
|
||||
CHECK_FALSE(olderSameIdentity.CanFollow(preparedEnthalpyDependency));
|
||||
|
||||
CHECK(resetNewIdentity.CanFollow(preparedEnthalpyDependency));
|
||||
|
||||
context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
const mfem::Vector firstFrozenEnthalpy = context.GetBaseEnthalpyTrue();
|
||||
|
||||
enthalpy(0) += 0.33;
|
||||
|
||||
const auto sameStampReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
CHECK_FALSE(sameStampReport.DidAnyWork());
|
||||
CHECK(context.GetBaseEnthalpyTrue()(0) == firstFrozenEnthalpy(0));
|
||||
|
||||
++dependencies.enthalpy.identity;
|
||||
dependencies.enthalpy.revision = 0;
|
||||
|
||||
const auto newIdentityReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
hydrostatic_context_test_utils::check_base_only(newIdentityReport);
|
||||
|
||||
CHECK(newIdentityReport.updatedEnthalpy);
|
||||
CHECK(context.GetBaseEnthalpyTrue()(0) == enthalpy(0));
|
||||
CHECK(context.MatchesDependencies(dependencies));
|
||||
|
||||
++dependencies.rotation.identity;
|
||||
dependencies.rotation.revision = 0;
|
||||
|
||||
const auto newRotationIdentityReport = context.Prepare(
|
||||
hydrostatic_context_test_utils::make_state(
|
||||
enthalpy, gravityPotential, displacement, bernoulliConstant
|
||||
),
|
||||
dependencies
|
||||
);
|
||||
|
||||
CHECK_FALSE(newRotationIdentityReport.preparedStaticDependencies);
|
||||
|
||||
CHECK_FALSE(newRotationIdentityReport.preparedGeometryState);
|
||||
|
||||
CHECK(newRotationIdentityReport.preparedRotationDependencies);
|
||||
|
||||
CHECK(newRotationIdentityReport.preparedBaseState);
|
||||
|
||||
const auto statistics = context.GetPreparationStatistics();
|
||||
|
||||
CHECK(statistics.staticPreparations == 1);
|
||||
CHECK(statistics.geometryPreparations == 1);
|
||||
CHECK(statistics.rotationPreparations == 2);
|
||||
CHECK(statistics.baseStatePreparations == 3);
|
||||
}
|
||||
4483
tests/operators/gravity_field.cpp
Normal file
4483
tests/operators/gravity_field.cpp
Normal file
File diff suppressed because it is too large
Load Diff
628
tests/operators/kernels/barotropic_closure_kernels.cpp
Normal file
628
tests/operators/kernels/barotropic_closure_kernels.cpp
Normal file
@@ -0,0 +1,628 @@
|
||||
#include <memory>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
mfem::Vector make_zero_displacement(const mean_field::fem::FEM &f) {
|
||||
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
|
||||
displacement = 0.0;
|
||||
return displacement;
|
||||
}
|
||||
|
||||
mfem::Vector project_constant(
|
||||
mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const double value
|
||||
) {
|
||||
mfem::ConstantCoefficient coefficient(value);
|
||||
mfem::ParGridFunction field(&finiteElementSpace);
|
||||
|
||||
field.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector trueVector;
|
||||
field.GetTrueDofs(trueVector);
|
||||
return trueVector;
|
||||
}
|
||||
|
||||
namespace barotropic_closure_geometry_test_utils {
|
||||
mfem::Vector project_scalar_field(
|
||||
mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
mfem::Coefficient &coefficient
|
||||
) {
|
||||
mfem::ParGridFunction field(&finiteElementSpace);
|
||||
|
||||
field.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector trueVector;
|
||||
field.GetTrueDofs(trueVector);
|
||||
|
||||
return trueVector;
|
||||
}
|
||||
|
||||
mfem::Vector make_base_density(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient(
|
||||
[](const mfem::Vector &position) {
|
||||
return 0.55 + 0.025 * position(0) - 0.010 * position(1) +
|
||||
0.006 * position(2);
|
||||
}
|
||||
);
|
||||
|
||||
return project_scalar_field(*f.densityFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_base_enthalpy(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient(
|
||||
[](const mfem::Vector &position) {
|
||||
return 0.90 + 0.020 * position(0) - 0.010 * position(1) +
|
||||
0.005 * position(2);
|
||||
}
|
||||
);
|
||||
|
||||
return project_scalar_field(*f.enthalpyFes, coefficient);
|
||||
}
|
||||
} // namespace barotropic_closure_geometry_test_utils
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Vanishes For A Representable Constant State",
|
||||
tags::hydro &tags::residuals &tags::unit &tags::closure &tags::kernels
|
||||
&tags::barotrope
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
constexpr double enthalpyValue = 0.8;
|
||||
|
||||
const double densityValue = barotrope.density_from_enthalpy(enthalpyValue);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
project_constant(*f.enthalpyFes, enthalpyValue);
|
||||
|
||||
const mfem::Vector density = project_constant(*f.densityFes, densityValue);
|
||||
|
||||
const mfem::Vector displacement = make_zero_displacement(f);
|
||||
|
||||
mfem::Vector residual;
|
||||
mfem::Vector scale;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, density, enthalpy, displacement,
|
||||
residual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, density, displacement, scale
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double relativeResidual =
|
||||
gravity_prepared_test_utils::global_norm(residual, communicator) /
|
||||
gravity_prepared_test_utils::global_norm(scale, communicator);
|
||||
|
||||
INFO("Relative constant-state closure residual = " << relativeResidual);
|
||||
|
||||
CHECK(relativeResidual < 5.0e-12);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Density Action Matches The Stellar Mass Matrix",
|
||||
tags::hydro &tags::jacobian &tags::unit &tags::closure &tags::kernels
|
||||
&tags::barotrope
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const mfem::Vector displacement = make_zero_displacement(f);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.37
|
||||
);
|
||||
|
||||
mfem::Vector kernelAction;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, densityVariation, displacement,
|
||||
kernelAction
|
||||
);
|
||||
|
||||
mfem::Array<int> stellarMarker(f.mesh->attributes.Max());
|
||||
stellarMarker = 0;
|
||||
|
||||
const int vacuumAttribute =
|
||||
f.domainMapperStateless->GetVacuumElementAttribute();
|
||||
|
||||
for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size();
|
||||
++attributeIndex) {
|
||||
const int attribute = f.mesh->attributes[attributeIndex];
|
||||
|
||||
if (attribute != vacuumAttribute) {
|
||||
stellarMarker[attribute - 1] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
mfem::ParBilinearForm massForm(f.densityFes.get());
|
||||
|
||||
massForm.AddDomainIntegrator(new mfem::MassIntegrator(), stellarMarker);
|
||||
|
||||
massForm.Assemble();
|
||||
massForm.Finalize();
|
||||
|
||||
std::unique_ptr<mfem::HypreParMatrix> massMatrix(
|
||||
massForm.ParallelAssemble()
|
||||
);
|
||||
|
||||
REQUIRE(massMatrix != nullptr);
|
||||
REQUIRE(massMatrix->Width() == densityVariation.Size());
|
||||
|
||||
mfem::Vector referenceAction(massMatrix->Height());
|
||||
referenceAction = 0.0;
|
||||
|
||||
massMatrix->Mult(densityVariation, referenceAction);
|
||||
const double relativeError = gravity_prepared_test_utils::relative_error(
|
||||
kernelAction, referenceAction, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Density-action mass-matrix error = " << relativeError);
|
||||
|
||||
CHECK(relativeError < 5.0e-12);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Jacobian Matches A Combined Centered Difference",
|
||||
tags::hydro &tags::jacobian &tags::unit &tags::closure &tags::kernels
|
||||
&tags::barotrope
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
mfem::FunctionCoefficient densityCoefficient(
|
||||
[](const mfem::Vector &position) {
|
||||
return 0.4 + 0.03 * position(0) - 0.01 * position(1);
|
||||
}
|
||||
);
|
||||
|
||||
mfem::FunctionCoefficient enthalpyCoefficient(
|
||||
[](const mfem::Vector &position) {
|
||||
return 0.9 + 0.02 * position(0) - 0.01 * position(1);
|
||||
}
|
||||
);
|
||||
|
||||
mfem::FunctionCoefficient enthalpyVariationCoefficient(
|
||||
[](const mfem::Vector &position) {
|
||||
return 0.07 + 0.015 * position(0) + 0.008 * position(2);
|
||||
}
|
||||
);
|
||||
|
||||
mfem::ParGridFunction densityField(f.densityFes.get());
|
||||
mfem::ParGridFunction enthalpyField(f.enthalpyFes.get());
|
||||
mfem::ParGridFunction enthalpyVariationField(f.enthalpyFes.get());
|
||||
|
||||
densityField.ProjectCoefficient(densityCoefficient);
|
||||
enthalpyField.ProjectCoefficient(enthalpyCoefficient);
|
||||
enthalpyVariationField.ProjectCoefficient(enthalpyVariationCoefficient);
|
||||
|
||||
mfem::Vector density;
|
||||
mfem::Vector enthalpy;
|
||||
mfem::Vector enthalpyVariation;
|
||||
|
||||
densityField.GetTrueDofs(density);
|
||||
enthalpyField.GetTrueDofs(enthalpy);
|
||||
enthalpyVariationField.GetTrueDofs(enthalpyVariation);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.63
|
||||
);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
constexpr double differenceStep = 1.0e-6;
|
||||
|
||||
const mfem::Vector plusDensity =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
density, 1.0, densityVariation, differenceStep
|
||||
);
|
||||
|
||||
const mfem::Vector minusDensity =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
density, 1.0, densityVariation, -differenceStep
|
||||
);
|
||||
|
||||
const mfem::Vector plusEnthalpy =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
enthalpy, 1.0, enthalpyVariation, differenceStep
|
||||
);
|
||||
|
||||
const mfem::Vector minusEnthalpy =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
enthalpy, 1.0, enthalpyVariation, -differenceStep
|
||||
);
|
||||
|
||||
mfem::Vector plusResidual;
|
||||
mfem::Vector minusResidual;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, plusDensity, plusEnthalpy,
|
||||
displacement, plusResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, minusDensity, minusEnthalpy,
|
||||
displacement, minusResidual
|
||||
);
|
||||
|
||||
mfem::Vector finiteDifference(plusResidual);
|
||||
finiteDifference -= minusResidual;
|
||||
finiteDifference *= 1.0 / (2.0 * differenceStep);
|
||||
|
||||
mfem::Vector densityAction;
|
||||
mfem::Vector enthalpyAction;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, densityVariation, displacement,
|
||||
densityAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action(
|
||||
f, *f.domainMapperStateless, barotrope, enthalpy, enthalpyVariation,
|
||||
displacement, enthalpyAction
|
||||
);
|
||||
|
||||
mfem::Vector analyticAction(densityAction);
|
||||
analyticAction += enthalpyAction;
|
||||
|
||||
const double relativeError = gravity_prepared_test_utils::relative_error(
|
||||
analyticAction, finiteDifference, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Combined EOS Jacobian error = " << relativeError);
|
||||
|
||||
CHECK(relativeError < 2.0e-8);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Density Action Excludes Vacuum And Uses Mapped Volume",
|
||||
tags::hydro &tags::mapping &tags::unit &tags::closure &tags::barotrope
|
||||
&tags::kernels
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const mfem::Vector stellarDensity =
|
||||
gravity_prepared_test_utils::make_domain_supported_density(f, true);
|
||||
|
||||
const mfem::Vector vacuumDensity =
|
||||
gravity_prepared_test_utils::make_domain_supported_density(f, false);
|
||||
|
||||
const mfem::Vector identityDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.0);
|
||||
|
||||
const mfem::Vector deformedDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
mfem::Vector stellarAction;
|
||||
mfem::Vector vacuumAction;
|
||||
mfem::Vector deformedAction;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, stellarDensity,
|
||||
identityDisplacement, stellarAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, vacuumDensity,
|
||||
identityDisplacement, vacuumAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, stellarDensity,
|
||||
deformedDisplacement, deformedAction
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double stellarNorm =
|
||||
gravity_prepared_test_utils::global_norm(stellarAction, communicator);
|
||||
|
||||
const double vacuumNorm =
|
||||
gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
|
||||
|
||||
const double geometryChange = gravity_prepared_test_utils::relative_error(
|
||||
deformedAction, stellarAction, communicator
|
||||
);
|
||||
|
||||
INFO("Stellar action norm = " << stellarNorm);
|
||||
INFO("Vacuum action norm = " << vacuumNorm);
|
||||
INFO("Relative mapped-volume change = " << geometryChange);
|
||||
|
||||
CHECK(stellarNorm > 0.0);
|
||||
CHECK(vacuumNorm <= 1.0e-13 * stellarNorm);
|
||||
CHECK(geometryChange > 1.0e-5);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Displacement Action Matches Centered Differences",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::integration
|
||||
&tags::jacobian &tags::mapping &tags::physics
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const mfem::Vector baseDensity =
|
||||
barotropic_closure_geometry_test_utils::make_base_density(f);
|
||||
|
||||
const mfem::Vector baseEnthalpy =
|
||||
barotropic_closure_geometry_test_utils::make_base_enthalpy(f);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.65);
|
||||
|
||||
constexpr double differenceStep = 1.0e-5;
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
for (const double deformationScale : {0.0, 1.0}) {
|
||||
DYNAMIC_SECTION("Base deformation scale = " << deformationScale) {
|
||||
const mfem::Vector baseDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(
|
||||
f, deformationScale
|
||||
);
|
||||
|
||||
mfem::Vector plusDisplacement(baseDisplacement);
|
||||
|
||||
mfem::Vector minusDisplacement(baseDisplacement);
|
||||
|
||||
plusDisplacement.Add(differenceStep, displacementVariation);
|
||||
|
||||
minusDisplacement.Add(-differenceStep, displacementVariation);
|
||||
|
||||
mfem::Vector plusResidual;
|
||||
mfem::Vector minusResidual;
|
||||
mfem::Vector analyticAction;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity,
|
||||
baseEnthalpy, plusDisplacement, plusResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity,
|
||||
baseEnthalpy, minusDisplacement, minusResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity,
|
||||
baseEnthalpy, baseDisplacement, displacementVariation,
|
||||
analyticAction
|
||||
);
|
||||
|
||||
mfem::Vector finiteDifference(plusResidual);
|
||||
|
||||
finiteDifference -= minusResidual;
|
||||
finiteDifference *= 1.0 / (2.0 * differenceStep);
|
||||
|
||||
const double analyticNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
analyticAction, communicator
|
||||
);
|
||||
|
||||
const double finiteDifferenceNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
finiteDifference, communicator
|
||||
);
|
||||
|
||||
const double relativeError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
analyticAction, finiteDifference, communicator
|
||||
);
|
||||
|
||||
INFO("Base deformation scale = " << deformationScale);
|
||||
|
||||
INFO("Analytic geometry-action norm = " << analyticNorm);
|
||||
|
||||
INFO(
|
||||
"Finite-difference geometry-action norm = "
|
||||
<< finiteDifferenceNorm
|
||||
);
|
||||
|
||||
INFO("Geometry-action relative error = " << relativeError);
|
||||
|
||||
REQUIRE(analyticNorm > 1.0e-12);
|
||||
REQUIRE(finiteDifferenceNorm > 1.0e-12);
|
||||
|
||||
CHECK(relativeError < 5.0e-8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Displacement Action Is Linear In Its Direction",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::jacobian &tags::mapping
|
||||
&tags::physics &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const mfem::Vector baseDensity =
|
||||
barotropic_closure_geometry_test_utils::make_base_density(f);
|
||||
|
||||
const mfem::Vector baseEnthalpy =
|
||||
barotropic_closure_geometry_test_utils::make_base_enthalpy(f);
|
||||
|
||||
const mfem::Vector baseDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.8);
|
||||
|
||||
const mfem::Vector firstDirection =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.4);
|
||||
|
||||
mfem::Vector secondDirection =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.displacementFes->GetTrueVSize(), 0.91
|
||||
);
|
||||
|
||||
secondDirection *= 0.01;
|
||||
|
||||
constexpr double firstScale = 1.7;
|
||||
constexpr double secondScale = -0.43;
|
||||
|
||||
mfem::Vector combinedDirection(firstDirection);
|
||||
|
||||
combinedDirection *= firstScale;
|
||||
|
||||
combinedDirection.Add(secondScale, secondDirection);
|
||||
|
||||
mfem::Vector zeroDirection(f.displacementFes->GetTrueVSize());
|
||||
zeroDirection = 0.0;
|
||||
|
||||
mfem::Vector firstAction;
|
||||
mfem::Vector secondAction;
|
||||
mfem::Vector combinedAction;
|
||||
mfem::Vector zeroAction;
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
baseDisplacement, firstDirection, firstAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
baseDisplacement, secondDirection, secondAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
baseDisplacement, combinedDirection, combinedAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
baseDisplacement, zeroDirection, zeroAction
|
||||
);
|
||||
|
||||
mfem::Vector expectedAction(firstAction);
|
||||
|
||||
expectedAction *= firstScale;
|
||||
|
||||
expectedAction.Add(secondScale, secondAction);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double expectedNorm =
|
||||
gravity_prepared_test_utils::global_norm(expectedAction, communicator);
|
||||
|
||||
const double linearityError = gravity_prepared_test_utils::relative_error(
|
||||
combinedAction, expectedAction, communicator
|
||||
);
|
||||
|
||||
const double zeroActionNorm =
|
||||
gravity_prepared_test_utils::global_norm(zeroAction, communicator);
|
||||
|
||||
INFO("Expected combined-action norm = " << expectedNorm);
|
||||
|
||||
INFO("Directional-linearity error = " << linearityError);
|
||||
|
||||
INFO("Zero-direction action norm = " << zeroActionNorm);
|
||||
|
||||
REQUIRE(expectedNorm > 1.0e-12);
|
||||
|
||||
CHECK(linearityError < 5.0e-12);
|
||||
|
||||
CHECK(zeroActionNorm <= 5.0e-14 * expectedNorm);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Barotropic Closure Displacement Action Excludes Vacuum",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::mapping &tags::physics
|
||||
&tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const mfem::Vector stellarDensity =
|
||||
gravity_prepared_test_utils::make_domain_supported_density(f, true);
|
||||
|
||||
const mfem::Vector vacuumDensity =
|
||||
gravity_prepared_test_utils::make_domain_supported_density(f, false);
|
||||
|
||||
mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize());
|
||||
zeroEnthalpy = 0.0;
|
||||
|
||||
const mfem::Vector baseDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.7);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.5);
|
||||
|
||||
mfem::Vector stellarAction;
|
||||
mfem::Vector vacuumAction;
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, stellarDensity,
|
||||
zeroEnthalpy, baseDisplacement, displacementVariation, stellarAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, vacuumDensity, zeroEnthalpy,
|
||||
baseDisplacement, displacementVariation, vacuumAction
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double stellarNorm =
|
||||
gravity_prepared_test_utils::global_norm(stellarAction, communicator);
|
||||
|
||||
const double vacuumNorm =
|
||||
gravity_prepared_test_utils::global_norm(vacuumAction, communicator);
|
||||
|
||||
INFO("Stellar geometry-action norm = " << stellarNorm);
|
||||
|
||||
INFO("Vacuum geometry-action norm = " << vacuumNorm);
|
||||
|
||||
REQUIRE(stellarNorm > 1.0e-12);
|
||||
|
||||
CHECK(vacuumNorm <= 1.0e-13 * stellarNorm);
|
||||
}
|
||||
0
tests/operators/kernels/gravity_kernels.cpp
Normal file
0
tests/operators/kernels/gravity_kernels.cpp
Normal file
1054
tests/operators/kernels/hydrostatic_equilibrium_kernels.cpp
Normal file
1054
tests/operators/kernels/hydrostatic_equilibrium_kernels.cpp
Normal file
File diff suppressed because it is too large
Load Diff
424
tests/operators/kernels/pressure_force_kernels.cpp
Normal file
424
tests/operators/kernels/pressure_force_kernels.cpp
Normal file
@@ -0,0 +1,424 @@
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <array>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace pressure_force_kernel_test_utils {
|
||||
[[nodiscard]] mfem::Vector make_deterministic_vector(
|
||||
const int size,
|
||||
const double phase
|
||||
) {
|
||||
mfem::Vector vector(size);
|
||||
|
||||
for (int index = 0; index < size; ++index) {
|
||||
const double position = static_cast<double>(index + 1);
|
||||
|
||||
vector(index) = 0.71 + 0.19 * std::sin(0.31 * position + phase) +
|
||||
0.08 * std::cos(0.17 * position - 0.5 * phase);
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector
|
||||
make_zero_displacement(const mean_field::fem::FEM &f) {
|
||||
mfem::Vector displacementTrue(f.displacementFes->GetTrueVSize());
|
||||
displacementTrue = 0.0;
|
||||
return displacementTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector
|
||||
make_vacuum_only_enthalpy(const mean_field::fem::FEM &f) {
|
||||
mfem::Vector enthalpyTrue =
|
||||
make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.43);
|
||||
|
||||
mfem::Array<int> stellarElementMask;
|
||||
mean_field::utils::populate_element_mask(
|
||||
f.mesh.get(), mean_field::utils::DOMAINS::STELLAR,
|
||||
stellarElementMask
|
||||
);
|
||||
|
||||
mfem::Array<int> stellarEnthalpyTrueDofs;
|
||||
mean_field::utils::populate_domain_tdofs(
|
||||
f.enthalpyFes.get(), stellarElementMask, stellarEnthalpyTrueDofs
|
||||
);
|
||||
|
||||
for (int listIndex = 0; listIndex < stellarEnthalpyTrueDofs.Size();
|
||||
++listIndex) {
|
||||
const int trueDof = stellarEnthalpyTrueDofs[listIndex];
|
||||
|
||||
MFEM_VERIFY(
|
||||
trueDof >= 0 && trueDof < enthalpyTrue.Size(),
|
||||
"The stellar enthalpy true-DOF mask contains an "
|
||||
"invalid index."
|
||||
);
|
||||
|
||||
enthalpyTrue(trueDof) = 0.0;
|
||||
}
|
||||
|
||||
return enthalpyTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector
|
||||
make_positive_asymmetric_enthalpy(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
|
||||
return 1.10 + 0.07 * position(0) - 0.04 * position(1) +
|
||||
0.03 * position(2);
|
||||
});
|
||||
|
||||
mfem::ParGridFunction enthalpyField(f.enthalpyFes.get());
|
||||
|
||||
enthalpyField.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector enthalpyTrue;
|
||||
enthalpyField.GetTrueDofs(enthalpyTrue);
|
||||
|
||||
return enthalpyTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector make_component_test_field(
|
||||
const mean_field::fem::FEM &f,
|
||||
const int component,
|
||||
const int coordinate
|
||||
) {
|
||||
const int dimension = f.mesh->Dimension();
|
||||
|
||||
MFEM_VERIFY(
|
||||
component >= 0 && component < dimension,
|
||||
"The requested vector component is invalid."
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
coordinate >= -1 && coordinate < dimension,
|
||||
"The requested coordinate is invalid."
|
||||
);
|
||||
|
||||
/*
|
||||
* coordinate == -1 gives the rigid translation e_component.
|
||||
*
|
||||
* Otherwise this gives
|
||||
*
|
||||
* w = x_coordinate e_component.
|
||||
*/
|
||||
mfem::VectorFunctionCoefficient coefficient(
|
||||
dimension,
|
||||
[component, coordinate,
|
||||
dimension](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(dimension);
|
||||
value = 0.0;
|
||||
|
||||
value(component) = coordinate < 0 ? 1.0 : position(coordinate);
|
||||
}
|
||||
);
|
||||
|
||||
mfem::ParGridFunction field(f.displacementFes.get());
|
||||
|
||||
field.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector fieldTrue;
|
||||
field.GetTrueDofs(fieldTrue);
|
||||
|
||||
return fieldTrue;
|
||||
}
|
||||
|
||||
[[nodiscard]] double global_dot(
|
||||
const mfem::Vector &left,
|
||||
const mfem::Vector &right,
|
||||
MPI_Comm communicator
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
left.Size() == right.Size(),
|
||||
"The global dot-product vectors have different sizes."
|
||||
);
|
||||
|
||||
const double localDot = left * right;
|
||||
double globalDot = 0.0;
|
||||
|
||||
MPI_Allreduce(
|
||||
&localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator
|
||||
);
|
||||
|
||||
return globalDot;
|
||||
}
|
||||
} // namespace pressure_force_kernel_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Pressure Force Residual Vanishes For Zero Enthalpy",
|
||||
tags::barotrope &tags::pressure &tags::kernels &tags::integration
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.okay());
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
|
||||
|
||||
mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize());
|
||||
enthalpyTrue = 0.0;
|
||||
|
||||
const mfem::Vector displacementTrue =
|
||||
pressure_force_kernel_test_utils::make_zero_displacement(f);
|
||||
|
||||
mfem::Vector residualTrue;
|
||||
|
||||
mean_field::operators::kernels::apply_pressure_force_residual(
|
||||
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
|
||||
residualTrue
|
||||
);
|
||||
|
||||
REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize());
|
||||
|
||||
const double residualNorm = gravity_prepared_test_utils::global_norm(
|
||||
residualTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
CHECK(residualNorm == 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Pressure Force Residual Excludes Vacuum Enthalpy Exactly",
|
||||
tags::barotrope &tags::pressure &tags::kernels &tags::integration
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.okay());
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
|
||||
|
||||
const mfem::Vector enthalpyTrue =
|
||||
pressure_force_kernel_test_utils::make_vacuum_only_enthalpy(f);
|
||||
|
||||
const double enthalpyNorm = gravity_prepared_test_utils::global_norm(
|
||||
enthalpyTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
/*
|
||||
* Ensure this is a real exclusion test rather than another
|
||||
* all-zero-input test.
|
||||
*/
|
||||
REQUIRE(enthalpyNorm > 0.0);
|
||||
|
||||
const mfem::Vector displacementTrue =
|
||||
pressure_force_kernel_test_utils::make_zero_displacement(f);
|
||||
|
||||
mfem::Vector residualTrue;
|
||||
|
||||
mean_field::operators::kernels::apply_pressure_force_residual(
|
||||
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
|
||||
residualTrue
|
||||
);
|
||||
|
||||
REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize());
|
||||
|
||||
const double residualNorm = gravity_prepared_test_utils::global_norm(
|
||||
residualTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
CHECK(residualNorm == 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Pressure Force Residual Is Nonzero For Positive Stellar Pressure",
|
||||
tags::barotrope &tags::pressure &tags::kernels &tags::integration
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.okay());
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
|
||||
|
||||
/*
|
||||
* With n = 3 and K = 1/4:
|
||||
*
|
||||
* P(1) = 1/4.
|
||||
*/
|
||||
mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize());
|
||||
enthalpyTrue = 1.0;
|
||||
|
||||
const mfem::Vector displacementTrue =
|
||||
pressure_force_kernel_test_utils::make_zero_displacement(f);
|
||||
|
||||
mfem::Vector residualTrue;
|
||||
|
||||
mean_field::operators::kernels::apply_pressure_force_residual(
|
||||
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
|
||||
residualTrue
|
||||
);
|
||||
|
||||
const double residualNorm = gravity_prepared_test_utils::global_norm(
|
||||
residualTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Positive-pressure residual norm = " << residualNorm);
|
||||
|
||||
CHECK(std::isfinite(residualNorm));
|
||||
|
||||
CHECK(residualNorm > 100.0 * std::numeric_limits<double>::epsilon());
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Pressure Force Residual Does No Work Against Rigid Translations",
|
||||
tags::barotrope &tags::pressure &tags::kernels &tags::integration
|
||||
&tags::accuracy
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.okay());
|
||||
|
||||
REQUIRE(f.displacementFes->GetOrdering() == mfem::Ordering::byNODES);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
|
||||
|
||||
const mfem::Vector enthalpyTrue =
|
||||
pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f);
|
||||
|
||||
const mfem::Vector displacementTrue =
|
||||
pressure_force_kernel_test_utils::make_zero_displacement(f);
|
||||
|
||||
mfem::Vector residualTrue;
|
||||
|
||||
mean_field::operators::kernels::apply_pressure_force_residual(
|
||||
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
|
||||
residualTrue
|
||||
);
|
||||
|
||||
const double residualNorm = gravity_prepared_test_utils::global_norm(
|
||||
residualTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
REQUIRE(residualNorm > 0.0);
|
||||
|
||||
const int dimension = f.mesh->Dimension();
|
||||
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
const mfem::Vector translationTrue =
|
||||
pressure_force_kernel_test_utils::make_component_test_field(
|
||||
f, component, -1
|
||||
);
|
||||
|
||||
const double translationNorm = gravity_prepared_test_utils::global_norm(
|
||||
translationTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double translationWork =
|
||||
pressure_force_kernel_test_utils::global_dot(
|
||||
translationTrue, residualTrue, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double dotProductScale =
|
||||
std::fmax(residualNorm * translationNorm, 1.0);
|
||||
|
||||
CAPTURE(component, translationWork, dotProductScale);
|
||||
|
||||
CHECK(std::abs(translationWork) <= 5.0e-12 * dotProductScale);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Pressure Force Residual Respects byNODES Component Layout",
|
||||
tags::barotrope &tags::pressure &tags::kernels &tags::integration
|
||||
&tags::accuracy
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.okay());
|
||||
|
||||
REQUIRE(f.displacementFes->GetOrdering() == mfem::Ordering::byNODES);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25);
|
||||
|
||||
const mfem::Vector enthalpyTrue =
|
||||
pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f);
|
||||
|
||||
const mfem::Vector displacementTrue =
|
||||
pressure_force_kernel_test_utils::make_zero_displacement(f);
|
||||
|
||||
mfem::Vector residualTrue;
|
||||
|
||||
mean_field::operators::kernels::apply_pressure_force_residual(
|
||||
f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue,
|
||||
residualTrue
|
||||
);
|
||||
|
||||
const int dimension = f.mesh->Dimension();
|
||||
|
||||
REQUIRE(dimension == 3);
|
||||
|
||||
mfem::DenseMatrix virtualWork(dimension, dimension);
|
||||
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
for (int coordinate = 0; coordinate < dimension; ++coordinate) {
|
||||
const mfem::Vector affineTestTrue =
|
||||
pressure_force_kernel_test_utils::make_component_test_field(
|
||||
f, component, coordinate
|
||||
);
|
||||
|
||||
virtualWork(component, coordinate) =
|
||||
pressure_force_kernel_test_utils::global_dot(
|
||||
affineTestTrue, residualTrue, f.mesh->GetComm()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
double meanDiagonalWork = 0.0;
|
||||
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
meanDiagonalWork += virtualWork(component, component);
|
||||
}
|
||||
|
||||
meanDiagonalWork /= static_cast<double>(dimension);
|
||||
|
||||
// INFO(
|
||||
// "Affine pressure virtual-work tensor:\n"
|
||||
// << virtualWork
|
||||
// );
|
||||
|
||||
INFO("Mean diagonal virtual work = " << meanDiagonalWork);
|
||||
|
||||
REQUIRE(
|
||||
std::abs(meanDiagonalWork) >
|
||||
100.0 * std::numeric_limits<double>::epsilon()
|
||||
);
|
||||
|
||||
const double comparisonTolerance = 1.0e-8 * std::abs(meanDiagonalWork);
|
||||
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
for (int coordinate = 0; coordinate < dimension; ++coordinate) {
|
||||
const double computedWork = virtualWork(component, coordinate);
|
||||
|
||||
CAPTURE(
|
||||
component, coordinate, computedWork, meanDiagonalWork,
|
||||
comparisonTolerance
|
||||
);
|
||||
|
||||
if (component == coordinate) {
|
||||
CHECK(
|
||||
std::abs(computedWork - meanDiagonalWork) <=
|
||||
comparisonTolerance
|
||||
);
|
||||
} else {
|
||||
CHECK(std::abs(computedWork) <= comparisonTolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
995
tests/operators/prepared_barotropic_closure.cpp
Normal file
995
tests/operators/prepared_barotropic_closure.cpp
Normal file
@@ -0,0 +1,995 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
mfem::Vector project_scalar(
|
||||
mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
mfem::Coefficient &coefficient
|
||||
) {
|
||||
mfem::ParGridFunction field(&finiteElementSpace);
|
||||
|
||||
field.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector trueVector;
|
||||
field.GetTrueDofs(trueVector);
|
||||
return trueVector;
|
||||
}
|
||||
|
||||
mfem::Vector make_base_density(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
|
||||
return 0.42 + 0.025 * position(0) - 0.012 * position(1) +
|
||||
0.007 * position(2);
|
||||
});
|
||||
|
||||
return project_scalar(*f.densityFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_base_enthalpy(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
|
||||
return 0.92 + 0.018 * position(0) - 0.011 * position(1) +
|
||||
0.006 * position(2);
|
||||
});
|
||||
|
||||
return project_scalar(*f.enthalpyFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_enthalpy_variation(const mean_field::fem::FEM &f) {
|
||||
mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) {
|
||||
return 0.065 + 0.014 * position(0) + 0.009 * position(2);
|
||||
});
|
||||
|
||||
return project_scalar(*f.enthalpyFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_combined_variation(
|
||||
const mfem::Vector &densityVariation,
|
||||
const mfem::Vector &enthalpyVariation
|
||||
) {
|
||||
mfem::Vector combinedVariation(
|
||||
densityVariation.Size() + enthalpyVariation.Size()
|
||||
);
|
||||
|
||||
for (int densityDof = 0; densityDof < densityVariation.Size();
|
||||
++densityDof) {
|
||||
combinedVariation(densityDof) = densityVariation(densityDof);
|
||||
}
|
||||
|
||||
for (int enthalpyDof = 0; enthalpyDof < enthalpyVariation.Size();
|
||||
++enthalpyDof) {
|
||||
combinedVariation(densityVariation.Size() + enthalpyDof) =
|
||||
enthalpyVariation(enthalpyDof);
|
||||
}
|
||||
|
||||
return combinedVariation;
|
||||
}
|
||||
struct ClosureCondition {
|
||||
const char *name;
|
||||
|
||||
double polytropicIndex;
|
||||
double polytropicConstant;
|
||||
|
||||
double enthalpyOffset;
|
||||
double enthalpyGradient;
|
||||
|
||||
double densityFactor;
|
||||
double densityOffset;
|
||||
double densityGradient;
|
||||
|
||||
double deformationScale;
|
||||
double directionPhase;
|
||||
};
|
||||
|
||||
inline constexpr std::array<ClosureCondition, 3> conditions{
|
||||
{{.name = "Linear barotrope on identity geometry",
|
||||
.polytropicIndex = 1.0,
|
||||
.polytropicConstant = 0.8,
|
||||
.enthalpyOffset = 0.65,
|
||||
.enthalpyGradient = 0.06,
|
||||
.densityFactor = 0.80,
|
||||
.densityOffset = 0.015,
|
||||
.densityGradient = 0.004,
|
||||
.deformationScale = 0.0,
|
||||
.directionPhase = 0.31},
|
||||
{.name = "Fractional barotrope on moderate deformation",
|
||||
.polytropicIndex = 1.5,
|
||||
.polytropicConstant = 1.2,
|
||||
.enthalpyOffset = 0.90,
|
||||
.enthalpyGradient = 0.09,
|
||||
.densityFactor = 1.15,
|
||||
.densityOffset = -0.003,
|
||||
.densityGradient = 0.003,
|
||||
.deformationScale = 0.45,
|
||||
.directionPhase = 0.53},
|
||||
{.name = "Target n=3 barotrope on strong deformation",
|
||||
.polytropicIndex = 3.0,
|
||||
.polytropicConstant = 1.5,
|
||||
.enthalpyOffset = 1.20,
|
||||
.enthalpyGradient = 0.12,
|
||||
.densityFactor = 1.40,
|
||||
.densityOffset = 0.006,
|
||||
.densityGradient = 0.002,
|
||||
.deformationScale = 1.0,
|
||||
.directionPhase = 0.79}}
|
||||
};
|
||||
|
||||
double evaluate_enthalpy(
|
||||
const mfem::Vector &position,
|
||||
const ClosureCondition &condition
|
||||
) {
|
||||
return condition.enthalpyOffset +
|
||||
condition.enthalpyGradient *
|
||||
(0.50 * position(0) - 0.30 * position(1) +
|
||||
0.20 * position(2));
|
||||
}
|
||||
|
||||
mfem::Vector project_scalar_field(
|
||||
mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
mfem::Coefficient &coefficient
|
||||
) {
|
||||
mfem::ParGridFunction field(&finiteElementSpace);
|
||||
|
||||
field.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector trueVector;
|
||||
field.GetTrueDofs(trueVector);
|
||||
|
||||
return trueVector;
|
||||
}
|
||||
|
||||
mfem::Vector make_enthalpy(
|
||||
const mean_field::fem::FEM &f,
|
||||
const ClosureCondition &condition
|
||||
) {
|
||||
mfem::FunctionCoefficient coefficient(
|
||||
[condition](const mfem::Vector &position) {
|
||||
return evaluate_enthalpy(position, condition);
|
||||
}
|
||||
);
|
||||
|
||||
return project_scalar_field(*f.enthalpyFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_density(
|
||||
const mean_field::fem::FEM &f,
|
||||
const mean_field::physics::PolytropicBarotrope &barotrope,
|
||||
const ClosureCondition &condition
|
||||
) {
|
||||
mfem::FunctionCoefficient coefficient(
|
||||
[&barotrope, condition](const mfem::Vector &position) {
|
||||
const double enthalpy = evaluate_enthalpy(position, condition);
|
||||
|
||||
return condition.densityFactor *
|
||||
barotrope.density_from_enthalpy(enthalpy) +
|
||||
condition.densityOffset +
|
||||
condition.densityGradient *
|
||||
(0.40 * position(0) + 0.25 * position(1) -
|
||||
0.15 * position(2));
|
||||
}
|
||||
);
|
||||
|
||||
return project_scalar_field(*f.densityFes, coefficient);
|
||||
}
|
||||
|
||||
mfem::Vector make_constant_field(
|
||||
mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const double value
|
||||
) {
|
||||
mfem::ConstantCoefficient coefficient(value);
|
||||
|
||||
return project_scalar_field(finiteElementSpace, coefficient);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Barotropic Closure Matches Stateless Kernels",
|
||||
tags::hydro &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
REQUIRE_FALSE(preparedOperator.IsPrepared());
|
||||
REQUIRE(preparedOperator.Height() == f.densityFes->GetTrueVSize());
|
||||
REQUIRE(
|
||||
preparedOperator.Width() ==
|
||||
f.densityFes->GetTrueVSize() + f.enthalpyFes->GetTrueVSize()
|
||||
);
|
||||
REQUIRE(preparedOperator.GetDensitySize() == f.densityFes->GetTrueVSize());
|
||||
REQUIRE(
|
||||
preparedOperator.GetEnthalpySize() == f.enthalpyFes->GetTrueVSize()
|
||||
);
|
||||
|
||||
const mfem::Vector baseDensity = make_base_density(f);
|
||||
|
||||
const mfem::Vector baseEnthalpy = make_base_enthalpy(f);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.43
|
||||
);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.63);
|
||||
|
||||
const mfem::Vector enthalpyVariation = make_enthalpy_variation(f);
|
||||
|
||||
mfem::Vector zeroDensity(f.densityFes->GetTrueVSize());
|
||||
zeroDensity = 0.0;
|
||||
|
||||
mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize());
|
||||
zeroEnthalpy = 0.0;
|
||||
|
||||
preparedOperator.Prepare(baseDensity, baseEnthalpy, displacement);
|
||||
|
||||
mfem::Vector preparedResidual;
|
||||
mfem::Vector preparedDensityAction;
|
||||
mfem::Vector preparedEnthalpyAction;
|
||||
mfem::Vector preparedSplitAction;
|
||||
mfem::Vector preparedCombinedAction;
|
||||
|
||||
preparedOperator.BuildResidual(preparedResidual);
|
||||
|
||||
preparedOperator.Mult(
|
||||
densityVariation, zeroEnthalpy, displacementVariation,
|
||||
preparedDensityAction
|
||||
);
|
||||
|
||||
preparedOperator.Mult(
|
||||
zeroDensity, enthalpyVariation, displacementVariation,
|
||||
preparedEnthalpyAction
|
||||
);
|
||||
|
||||
preparedOperator.Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
preparedSplitAction
|
||||
);
|
||||
|
||||
const mfem::Vector combinedVariation =
|
||||
make_combined_variation(densityVariation, enthalpyVariation);
|
||||
|
||||
preparedOperator.Mult(combinedVariation, preparedCombinedAction);
|
||||
|
||||
mfem::Vector referenceResidual;
|
||||
mfem::Vector referenceDensityAction;
|
||||
mfem::Vector referenceEnthalpyAction;
|
||||
mfem::Vector referenceDisplacementAction;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
displacement, referenceResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, densityVariation, displacement,
|
||||
referenceDensityAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseEnthalpy, enthalpyVariation,
|
||||
displacement, referenceEnthalpyAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
displacement, displacementVariation, referenceDisplacementAction
|
||||
);
|
||||
|
||||
mfem::Vector referenceCombinedAction(referenceDensityAction);
|
||||
referenceCombinedAction += referenceEnthalpyAction;
|
||||
referenceCombinedAction += referenceDisplacementAction;
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double residualError = gravity_prepared_test_utils::relative_error(
|
||||
preparedResidual, referenceResidual, communicator
|
||||
);
|
||||
|
||||
const double densityError = gravity_prepared_test_utils::relative_error(
|
||||
preparedDensityAction, referenceDensityAction, communicator
|
||||
);
|
||||
|
||||
const double enthalpyError = gravity_prepared_test_utils::relative_error(
|
||||
preparedEnthalpyAction, referenceEnthalpyAction, communicator
|
||||
);
|
||||
|
||||
const double splitError = gravity_prepared_test_utils::relative_error(
|
||||
preparedSplitAction, referenceCombinedAction, communicator
|
||||
);
|
||||
|
||||
const double combinedError = gravity_prepared_test_utils::relative_error(
|
||||
preparedCombinedAction, referenceCombinedAction, communicator
|
||||
);
|
||||
|
||||
INFO("Prepared residual error = " << residualError);
|
||||
INFO("Prepared density-action error = " << densityError);
|
||||
INFO("Prepared enthalpy-action error = " << enthalpyError);
|
||||
INFO("Prepared split-action error = " << splitError);
|
||||
INFO("Prepared combined-action error = " << combinedError);
|
||||
|
||||
CHECK(preparedOperator.IsPrepared());
|
||||
CHECK(preparedOperator.GetPreparationCount() == 1);
|
||||
CHECK(residualError < 2.0e-12);
|
||||
CHECK(densityError < 2.0e-12);
|
||||
CHECK(enthalpyError < 2.0e-12);
|
||||
CHECK(splitError < 2.0e-12);
|
||||
CHECK(combinedError < 2.0e-12);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Barotropic Closure Jacobian Matches Centered Difference",
|
||||
tags::hydro &tags::jacobian &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
mfem::Vector baseDensity = make_base_density(f);
|
||||
|
||||
mfem::Vector baseEnthalpy = make_base_enthalpy(f);
|
||||
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.71
|
||||
);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.63);
|
||||
|
||||
const mfem::Vector enthalpyVariation = make_enthalpy_variation(f);
|
||||
|
||||
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
preparedOperator.Prepare(baseDensity, baseEnthalpy, displacement);
|
||||
|
||||
mfem::Vector analyticAction;
|
||||
|
||||
preparedOperator.Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
analyticAction
|
||||
);
|
||||
|
||||
constexpr double differenceStep = 1.0e-6;
|
||||
|
||||
const mfem::Vector plusDensity =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
baseDensity, 1.0, densityVariation, differenceStep
|
||||
);
|
||||
|
||||
const mfem::Vector minusDensity =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
baseDensity, 1.0, densityVariation, -differenceStep
|
||||
);
|
||||
|
||||
const mfem::Vector plusEnthalpy =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
baseEnthalpy, 1.0, enthalpyVariation, differenceStep
|
||||
);
|
||||
|
||||
const mfem::Vector minusEnthalpy =
|
||||
gravity_prepared_test_utils::linear_combination(
|
||||
baseEnthalpy, 1.0, enthalpyVariation, -differenceStep
|
||||
);
|
||||
|
||||
mfem::Vector plusResidual;
|
||||
mfem::Vector minusResidual;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, plusDensity, plusEnthalpy,
|
||||
displacement, plusResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, minusDensity, minusEnthalpy,
|
||||
displacement, minusResidual
|
||||
);
|
||||
|
||||
mfem::Vector finiteDifference(plusResidual);
|
||||
finiteDifference -= minusResidual;
|
||||
finiteDifference *= 1.0 / (2.0 * differenceStep);
|
||||
|
||||
const double relativeError = gravity_prepared_test_utils::relative_error(
|
||||
analyticAction, finiteDifference, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Prepared EOS centered-difference error = " << relativeError);
|
||||
|
||||
CHECK(relativeError < 2.0e-8);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Barotropic Closure Reuses Frozen Data",
|
||||
tags::hydro &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
CHECK_FALSE(preparedOperator.IsPrepared());
|
||||
CHECK(preparedOperator.GetPreparationCount() == 0);
|
||||
|
||||
mfem::Vector baseDensity = make_base_density(f);
|
||||
|
||||
mfem::Vector baseEnthalpy = make_base_enthalpy(f);
|
||||
|
||||
mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, 1.0);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.63);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.31
|
||||
);
|
||||
|
||||
const mfem::Vector enthalpyVariation = make_enthalpy_variation(f);
|
||||
|
||||
preparedOperator.Prepare(baseDensity, baseEnthalpy, displacement);
|
||||
|
||||
REQUIRE(preparedOperator.IsPrepared());
|
||||
REQUIRE(preparedOperator.GetPreparationCount() == 1);
|
||||
|
||||
mfem::Vector firstResidual;
|
||||
mfem::Vector firstAction;
|
||||
|
||||
preparedOperator.BuildResidual(firstResidual);
|
||||
|
||||
preparedOperator.Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation, firstAction
|
||||
);
|
||||
|
||||
baseDensity = 7.0;
|
||||
baseEnthalpy = 3.0;
|
||||
displacement *= -4.0;
|
||||
|
||||
const std::uint64_t preparationCount =
|
||||
preparedOperator.GetPreparationCount();
|
||||
|
||||
mfem::Vector repeatedResidual;
|
||||
mfem::Vector repeatedAction;
|
||||
|
||||
preparedOperator.BuildResidual(repeatedResidual);
|
||||
|
||||
preparedOperator.Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
repeatedAction
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double residualReuseError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
repeatedResidual, firstResidual, communicator
|
||||
);
|
||||
|
||||
const double actionReuseError = gravity_prepared_test_utils::relative_error(
|
||||
repeatedAction, firstAction, communicator
|
||||
);
|
||||
|
||||
INFO("Frozen residual reuse error = " << residualReuseError);
|
||||
INFO("Frozen action reuse error = " << actionReuseError);
|
||||
|
||||
CHECK(residualReuseError < 2.0e-14);
|
||||
CHECK(actionReuseError < 2.0e-14);
|
||||
CHECK(preparedOperator.GetPreparationCount() == preparationCount);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Barotropic Closure Reprepares For New Geometry",
|
||||
tags::hydro &tags::mapping &tags::prepared &tags::unit
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
const mfem::Vector baseDensity = make_base_density(f);
|
||||
|
||||
const mfem::Vector baseEnthalpy = make_base_enthalpy(f);
|
||||
|
||||
const mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), 0.59
|
||||
);
|
||||
|
||||
mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize());
|
||||
zeroEnthalpy = 0.0;
|
||||
|
||||
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
mfem::Vector identityAction;
|
||||
mfem::Vector deformedAction;
|
||||
|
||||
for (const double deformationScale : {0.0, 1.0}) {
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(f, deformationScale);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.63);
|
||||
|
||||
preparedOperator.Prepare(baseDensity, baseEnthalpy, displacement);
|
||||
|
||||
mfem::Vector preparedResidual;
|
||||
mfem::Vector preparedAction;
|
||||
mfem::Vector referenceResidual;
|
||||
mfem::Vector referenceAction;
|
||||
|
||||
preparedOperator.BuildResidual(preparedResidual);
|
||||
|
||||
preparedOperator.Mult(
|
||||
densityVariation, zeroEnthalpy, displacementVariation,
|
||||
preparedAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy,
|
||||
displacement, referenceResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, densityVariation,
|
||||
displacement, referenceAction
|
||||
);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
const double residualError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
preparedResidual, referenceResidual, communicator
|
||||
);
|
||||
|
||||
const double actionError = gravity_prepared_test_utils::relative_error(
|
||||
preparedAction, referenceAction, communicator
|
||||
);
|
||||
|
||||
INFO("Deformation scale = " << deformationScale);
|
||||
INFO("Reprepared residual error = " << residualError);
|
||||
INFO("Reprepared action error = " << actionError);
|
||||
|
||||
CHECK(residualError < 2.0e-12);
|
||||
CHECK(actionError < 2.0e-12);
|
||||
|
||||
if (deformationScale == 0.0) {
|
||||
identityAction = preparedAction;
|
||||
} else {
|
||||
deformedAction = preparedAction;
|
||||
}
|
||||
}
|
||||
|
||||
const double geometryChange = gravity_prepared_test_utils::relative_error(
|
||||
deformedAction, identityAction, f.mesh->GetComm()
|
||||
);
|
||||
|
||||
INFO("Prepared closure geometry change = " << geometryChange);
|
||||
|
||||
CHECK(preparedOperator.GetPreparationCount() == 2);
|
||||
CHECK(geometryChange > 1.0e-5);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Complete Barotropic Closure Matches Blocks And Centered Differences "
|
||||
"Across Conditions",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::integration
|
||||
&tags::jacobian &tags::mapping &tags::physics &tags::prepared
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
constexpr double differenceStep = 1.0e-5;
|
||||
|
||||
for (std::size_t conditionIndex = 0; conditionIndex <
|
||||
|
||||
conditions.size();
|
||||
++conditionIndex) {
|
||||
const auto &condition =
|
||||
|
||||
conditions[conditionIndex];
|
||||
|
||||
DYNAMIC_SECTION(condition.name) {
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(
|
||||
condition.polytropicIndex, condition.polytropicConstant
|
||||
);
|
||||
|
||||
const mfem::Vector baseDensity =
|
||||
|
||||
make_density(f, barotrope, condition);
|
||||
|
||||
const mfem::Vector baseEnthalpy =
|
||||
|
||||
make_enthalpy(f, condition);
|
||||
|
||||
const mfem::Vector baseDisplacement =
|
||||
gravity_prepared_test_utils::make_displacement(
|
||||
f, condition.deformationScale
|
||||
);
|
||||
|
||||
mfem::Vector densityVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.densityFes->GetTrueVSize(), condition.directionPhase
|
||||
);
|
||||
|
||||
mfem::Vector enthalpyVariation =
|
||||
gravity_prepared_test_utils::make_deterministic_vector(
|
||||
f.enthalpyFes->GetTrueVSize(),
|
||||
condition.directionPhase + 0.27
|
||||
);
|
||||
|
||||
mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(
|
||||
f, condition.directionPhase
|
||||
);
|
||||
|
||||
mfem::Vector densityAction;
|
||||
mfem::Vector enthalpyAction;
|
||||
mfem::Vector displacementAction;
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_density_action(
|
||||
f, *f.domainMapperStateless, barotrope, densityVariation,
|
||||
baseDisplacement, densityAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_enthalpy_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseEnthalpy,
|
||||
enthalpyVariation, baseDisplacement, enthalpyAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity,
|
||||
baseEnthalpy, baseDisplacement, displacementVariation,
|
||||
displacementAction
|
||||
);
|
||||
|
||||
double densityActionNorm = gravity_prepared_test_utils::global_norm(
|
||||
densityAction, communicator
|
||||
);
|
||||
|
||||
double enthalpyActionNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
enthalpyAction, communicator
|
||||
);
|
||||
|
||||
double displacementActionNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
displacementAction, communicator
|
||||
);
|
||||
|
||||
REQUIRE(densityActionNorm > 1.0e-12);
|
||||
REQUIRE(enthalpyActionNorm > 1.0e-12);
|
||||
REQUIRE(displacementActionNorm > 1.0e-12);
|
||||
|
||||
const double targetActionNorm = std::min(
|
||||
{densityActionNorm, enthalpyActionNorm, displacementActionNorm}
|
||||
);
|
||||
|
||||
const double densityScale = targetActionNorm / densityActionNorm;
|
||||
|
||||
const double enthalpyScale = targetActionNorm / enthalpyActionNorm;
|
||||
|
||||
const double displacementScale =
|
||||
targetActionNorm / displacementActionNorm;
|
||||
|
||||
densityVariation *= densityScale;
|
||||
densityAction *= densityScale;
|
||||
|
||||
enthalpyVariation *= enthalpyScale;
|
||||
enthalpyAction *= enthalpyScale;
|
||||
|
||||
displacementVariation *= displacementScale;
|
||||
displacementAction *= displacementScale;
|
||||
|
||||
densityActionNorm = gravity_prepared_test_utils::global_norm(
|
||||
densityAction, communicator
|
||||
);
|
||||
|
||||
enthalpyActionNorm = gravity_prepared_test_utils::global_norm(
|
||||
enthalpyAction, communicator
|
||||
);
|
||||
|
||||
displacementActionNorm = gravity_prepared_test_utils::global_norm(
|
||||
displacementAction, communicator
|
||||
);
|
||||
|
||||
mean_field::operators::context::barotropic::
|
||||
BarotropicClosureLinearizationContext context(
|
||||
f, *f.domainMapperStateless, barotrope
|
||||
);
|
||||
|
||||
const std::uint64_t revisionBase =
|
||||
100 + static_cast<std::uint64_t>(10 * conditionIndex);
|
||||
|
||||
const mean_field::operators::context::barotropic::
|
||||
BarotropicClosureRevisions revisions{
|
||||
.density = revisionBase + 1,
|
||||
.enthalpy = revisionBase + 2,
|
||||
.displacement = revisionBase + 3
|
||||
};
|
||||
|
||||
context.Prepare(
|
||||
baseDensity, baseEnthalpy, baseDisplacement, revisions
|
||||
);
|
||||
|
||||
mfem::Vector preparedResidual;
|
||||
mfem::Vector referenceResidual;
|
||||
|
||||
context.BuildResidual(preparedResidual);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, baseDensity,
|
||||
baseEnthalpy, baseDisplacement, referenceResidual
|
||||
);
|
||||
|
||||
const double residualEvaluationError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
preparedResidual, referenceResidual, communicator
|
||||
);
|
||||
|
||||
mfem::Vector preparedAction;
|
||||
|
||||
context.GetOperator().Mult(
|
||||
densityVariation, enthalpyVariation, displacementVariation,
|
||||
preparedAction
|
||||
);
|
||||
|
||||
mfem::Vector blockSum(densityAction);
|
||||
|
||||
blockSum += enthalpyAction;
|
||||
blockSum += displacementAction;
|
||||
|
||||
const double blockAssemblyError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
preparedAction, blockSum, communicator
|
||||
);
|
||||
|
||||
mfem::Vector plusDensity(baseDensity);
|
||||
|
||||
mfem::Vector minusDensity(baseDensity);
|
||||
|
||||
mfem::Vector plusEnthalpy(baseEnthalpy);
|
||||
|
||||
mfem::Vector minusEnthalpy(baseEnthalpy);
|
||||
|
||||
mfem::Vector plusDisplacement(baseDisplacement);
|
||||
|
||||
mfem::Vector minusDisplacement(baseDisplacement);
|
||||
|
||||
plusDensity.Add(differenceStep, densityVariation);
|
||||
|
||||
minusDensity.Add(-differenceStep, densityVariation);
|
||||
|
||||
plusEnthalpy.Add(differenceStep, enthalpyVariation);
|
||||
|
||||
minusEnthalpy.Add(-differenceStep, enthalpyVariation);
|
||||
|
||||
plusDisplacement.Add(differenceStep, displacementVariation);
|
||||
|
||||
minusDisplacement.Add(-differenceStep, displacementVariation);
|
||||
|
||||
mfem::Vector plusResidual;
|
||||
mfem::Vector minusResidual;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, plusDensity,
|
||||
plusEnthalpy, plusDisplacement, plusResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, minusDensity,
|
||||
minusEnthalpy, minusDisplacement, minusResidual
|
||||
);
|
||||
|
||||
mfem::Vector finiteDifference(plusResidual);
|
||||
|
||||
finiteDifference -= minusResidual;
|
||||
|
||||
finiteDifference *= 1.0 / (2.0 * differenceStep);
|
||||
|
||||
mfem::Vector finiteDifferenceError(preparedAction);
|
||||
|
||||
finiteDifferenceError -= finiteDifference;
|
||||
|
||||
const double finiteDifferenceErrorNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
finiteDifferenceError, communicator
|
||||
);
|
||||
|
||||
const double blockNormSum =
|
||||
densityActionNorm + enthalpyActionNorm + displacementActionNorm;
|
||||
|
||||
const double blockScaledDifferenceError =
|
||||
finiteDifferenceErrorNorm / blockNormSum;
|
||||
|
||||
const double completeRelativeError =
|
||||
gravity_prepared_test_utils::relative_error(
|
||||
preparedAction, finiteDifference, communicator
|
||||
);
|
||||
|
||||
INFO("Condition = " << condition.name);
|
||||
|
||||
INFO("Polytropic index = " << condition.polytropicIndex);
|
||||
|
||||
INFO("Deformation scale = " << condition.deformationScale);
|
||||
|
||||
INFO("Prepared residual error = " << residualEvaluationError);
|
||||
|
||||
INFO("Complete block-assembly error = " << blockAssemblyError);
|
||||
|
||||
INFO("Density-action norm = " << densityActionNorm);
|
||||
|
||||
INFO("Enthalpy-action norm = " << enthalpyActionNorm);
|
||||
|
||||
INFO("Displacement-action norm = " << displacementActionNorm);
|
||||
|
||||
INFO(
|
||||
"Complete centered-difference relative error = "
|
||||
<< completeRelativeError
|
||||
);
|
||||
|
||||
INFO(
|
||||
"Block-scaled centered-difference error = "
|
||||
<< blockScaledDifferenceError
|
||||
);
|
||||
|
||||
CHECK(context.GetPreparationCount() == 1);
|
||||
CHECK(residualEvaluationError < 5.0e-12);
|
||||
CHECK(blockAssemblyError < 5.0e-12);
|
||||
CHECK(blockScaledDifferenceError < 2.0e-7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Exact Constant Barotropic Closure Remains Zero Under Deformation",
|
||||
tags::barotrope &tags::closure &tags::hydro &tags::integration
|
||||
&tags::jacobian &tags::mapping &tags::physics
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
|
||||
mean_field::fem::FEM f =
|
||||
mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
|
||||
REQUIRE(f.domainMapperStateless != nullptr);
|
||||
|
||||
const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5);
|
||||
|
||||
constexpr double enthalpyValue = 1.20;
|
||||
|
||||
const double equilibriumDensityValue =
|
||||
barotrope.density_from_enthalpy(enthalpyValue);
|
||||
|
||||
const mfem::Vector enthalpy =
|
||||
make_constant_field(*f.enthalpyFes, enthalpyValue);
|
||||
|
||||
const mfem::Vector equilibriumDensity =
|
||||
make_constant_field(*f.densityFes, equilibriumDensityValue);
|
||||
|
||||
const mfem::Vector referenceDensity =
|
||||
make_constant_field(*f.densityFes, equilibriumDensityValue + 1.0);
|
||||
|
||||
const mfem::Vector displacementVariation =
|
||||
gravity_prepared_test_utils::make_displacement(f, 0.67);
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
for (const double deformationScale : {0.0, 0.5, 1.0}) {
|
||||
DYNAMIC_SECTION("Deformation scale = " << deformationScale) {
|
||||
const mfem::Vector displacement =
|
||||
gravity_prepared_test_utils::make_displacement(
|
||||
f, deformationScale
|
||||
);
|
||||
|
||||
mfem::Vector exactResidual;
|
||||
mfem::Vector referenceResidual;
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, equilibriumDensity,
|
||||
enthalpy, displacement, exactResidual
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
f, *f.domainMapperStateless, barotrope, referenceDensity,
|
||||
enthalpy, displacement, referenceResidual
|
||||
);
|
||||
|
||||
mfem::Vector exactGeometryAction;
|
||||
mfem::Vector referenceGeometryAction;
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, equilibriumDensity,
|
||||
enthalpy, displacement, displacementVariation,
|
||||
exactGeometryAction
|
||||
);
|
||||
|
||||
mean_field::operators::kernels::
|
||||
apply_barotropic_closure_displacement_action(
|
||||
f, *f.domainMapperStateless, barotrope, referenceDensity,
|
||||
enthalpy, displacement, displacementVariation,
|
||||
referenceGeometryAction
|
||||
);
|
||||
|
||||
const double exactResidualNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
exactResidual, communicator
|
||||
);
|
||||
|
||||
const double referenceResidualNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
referenceResidual, communicator
|
||||
);
|
||||
|
||||
const double exactGeometryNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
exactGeometryAction, communicator
|
||||
);
|
||||
|
||||
const double referenceGeometryNorm =
|
||||
gravity_prepared_test_utils::global_norm(
|
||||
referenceGeometryAction, communicator
|
||||
);
|
||||
|
||||
INFO("Deformation scale = " << deformationScale);
|
||||
|
||||
INFO("Exact-closure residual norm = " << exactResidualNorm);
|
||||
|
||||
INFO("Reference residual norm = " << referenceResidualNorm);
|
||||
|
||||
INFO("Exact-closure geometry-action norm = " << exactGeometryNorm);
|
||||
|
||||
INFO("Reference geometry-action norm = " << referenceGeometryNorm);
|
||||
|
||||
REQUIRE(referenceResidualNorm > 1.0e-12);
|
||||
REQUIRE(referenceGeometryNorm > 1.0e-14);
|
||||
|
||||
CHECK(exactResidualNorm <= 5.0e-12 * referenceResidualNorm);
|
||||
|
||||
CHECK(exactGeometryNorm <= 5.0e-12 * referenceGeometryNorm);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user