feat(surface): surface deformation prescriptions
restricted the unknown state vector to surface deformation and implemented one prescription, NodalRadialSurface, while the full volumetric displacment field is reconstructed analytically from that. This reduced the number of degrees of freedom in the system by a factor of 80 while also removing many null vectors from the system.
This commit is contained in:
608
tests/deformation/contracts.cpp
Normal file
608
tests/deformation/contracts.cpp
Normal file
@@ -0,0 +1,608 @@
|
||||
#include <concepts>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
struct DeformationCompilationContext final { };
|
||||
|
||||
constexpr mean_field::deformation::SurfaceDeformationDescriptor surfaceDescriptor{
|
||||
.name = "TestRadialSurface",
|
||||
.spatialDimension = 3,
|
||||
.motionKind = mean_field::deformation::SurfaceMotionKind::Radial,
|
||||
.linearOnReferenceGeometry = true,
|
||||
.requiresStarShapedReferenceSurface = true,
|
||||
.hasExactDerivativeTranspose = true,
|
||||
.hasExactPullbackDerivative = true,
|
||||
.translationTreatment = mean_field::deformation::GeometricGaugeTreatment::ExcludedByParameterization,
|
||||
.orientationTreatment = mean_field::deformation::GeometricGaugeTreatment::ExcludedByParameterization
|
||||
};
|
||||
|
||||
constexpr mean_field::deformation::InteriorDeformationExtensionDescriptor interiorDescriptor{
|
||||
.name = "TestRadialInteriorExtension",
|
||||
.spatialDimension = 3,
|
||||
.linearOnReferenceGeometry = true,
|
||||
.requiresRadialFoliation = true,
|
||||
.requiresAuxiliarySolve = false,
|
||||
.hasExactDerivativeTranspose = true,
|
||||
.hasExactPullbackDerivative = true,
|
||||
.centerBehavior = mean_field::deformation::InteriorCenterBehavior::FixedAtReferenceCenter
|
||||
};
|
||||
|
||||
constexpr mean_field::deformation::VacuumDeformationExtensionDescriptor vacuumDescriptor{
|
||||
.name = "TestFixedInfinityExtension",
|
||||
.spatialDimension = 3,
|
||||
.linearOnReferenceGeometry = true,
|
||||
.requiresRadialFoliation = true,
|
||||
.requiresAuxiliarySolve = false,
|
||||
.hasExactDerivativeTranspose = true,
|
||||
.hasExactPullbackDerivative = true,
|
||||
.outerBoundaryBehavior = mean_field::deformation::VacuumOuterBoundaryBehavior::FixedAtReferenceInfinity
|
||||
};
|
||||
|
||||
template <
|
||||
bool HasBuild = true,
|
||||
bool HasJacobian = true,
|
||||
bool HasJacobianTranspose = true,
|
||||
bool HasPullbackDerivative = true>
|
||||
class PreparedSurfaceFixture final {
|
||||
public:
|
||||
[[nodiscard]] mean_field::deformation::SurfaceDeformationDescriptor descriptor() const noexcept {
|
||||
return surfaceDescriptor;
|
||||
}
|
||||
|
||||
[[nodiscard]] int parameterCount() const noexcept {
|
||||
return 4;
|
||||
}
|
||||
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
|
||||
return 12;
|
||||
}
|
||||
|
||||
void buildSurfaceDisplacement(
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasBuild
|
||||
{
|
||||
}
|
||||
|
||||
void applyJacobian(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasJacobian
|
||||
{
|
||||
}
|
||||
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasJacobianTranspose
|
||||
{
|
||||
}
|
||||
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasPullbackDerivative
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Prepared, bool HasDescriptor = true, bool HasValidation = true, bool HasCompilation = true>
|
||||
class SurfacePrescriptionFixture final {
|
||||
public:
|
||||
using PreparedType = Prepared;
|
||||
|
||||
[[nodiscard]] mean_field::deformation::SurfaceDeformationDescriptor descriptor() const noexcept
|
||||
requires HasDescriptor
|
||||
{
|
||||
return surfaceDescriptor;
|
||||
}
|
||||
|
||||
void validate() const
|
||||
requires HasValidation
|
||||
{
|
||||
}
|
||||
|
||||
static constexpr bool hasCompilation = HasCompilation;
|
||||
};
|
||||
|
||||
template <
|
||||
typename Prepared,
|
||||
bool HasDescriptor,
|
||||
bool HasValidation,
|
||||
bool HasCompilation>
|
||||
requires HasCompilation
|
||||
Prepared compileSurfaceDeformationPrescription(
|
||||
const SurfacePrescriptionFixture<
|
||||
Prepared,
|
||||
HasDescriptor,
|
||||
HasValidation,
|
||||
HasCompilation> &,
|
||||
const DeformationCompilationContext &
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <
|
||||
bool HasBuild = true,
|
||||
bool HasJacobian = true,
|
||||
bool HasJacobianTranspose = true,
|
||||
bool HasPullbackDerivative = true,
|
||||
bool HasScalarDofCount = true,
|
||||
bool HasSupportQuery = true>
|
||||
class PreparedInteriorExtensionFixture final {
|
||||
public:
|
||||
[[nodiscard]] mean_field::deformation::InteriorDeformationExtensionDescriptor descriptor() const noexcept {
|
||||
return interiorDescriptor;
|
||||
}
|
||||
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
|
||||
return 12;
|
||||
}
|
||||
|
||||
[[nodiscard]] int interiorDisplacementSize() const noexcept {
|
||||
return 24;
|
||||
}
|
||||
|
||||
[[nodiscard]] int scalarTrueDofCount() const noexcept
|
||||
requires HasScalarDofCount
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasStellarSupport(const int) const
|
||||
requires HasSupportQuery
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void buildInteriorDisplacement(
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasBuild
|
||||
{
|
||||
}
|
||||
|
||||
void applyJacobian(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasJacobian
|
||||
{
|
||||
}
|
||||
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasJacobianTranspose
|
||||
{
|
||||
}
|
||||
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasPullbackDerivative
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Prepared, bool HasDescriptor = true, bool HasValidation = true, bool HasCompilation = true>
|
||||
class InteriorExtensionFixture final {
|
||||
public:
|
||||
using PreparedType = Prepared;
|
||||
|
||||
[[nodiscard]] mean_field::deformation::InteriorDeformationExtensionDescriptor descriptor() const noexcept
|
||||
requires HasDescriptor
|
||||
{
|
||||
return interiorDescriptor;
|
||||
}
|
||||
|
||||
void validate() const
|
||||
requires HasValidation
|
||||
{
|
||||
}
|
||||
|
||||
static constexpr bool hasCompilation = HasCompilation;
|
||||
};
|
||||
|
||||
template <
|
||||
typename Prepared,
|
||||
bool HasDescriptor,
|
||||
bool HasValidation,
|
||||
bool HasCompilation>
|
||||
requires HasCompilation
|
||||
Prepared compileInteriorDeformationExtension(
|
||||
const InteriorExtensionFixture<
|
||||
Prepared,
|
||||
HasDescriptor,
|
||||
HasValidation,
|
||||
HasCompilation> &,
|
||||
const DeformationCompilationContext &
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <
|
||||
bool HasBuild = true,
|
||||
bool HasJacobian = true,
|
||||
bool HasJacobianTranspose = true,
|
||||
bool HasPullbackDerivative = true,
|
||||
bool HasScalarDofCount = true,
|
||||
bool HasSupportQuery = true>
|
||||
class PreparedVacuumExtensionFixture final {
|
||||
public:
|
||||
[[nodiscard]] mean_field::deformation::VacuumDeformationExtensionDescriptor descriptor() const noexcept {
|
||||
return vacuumDescriptor;
|
||||
}
|
||||
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
|
||||
return 12;
|
||||
}
|
||||
|
||||
[[nodiscard]] int vacuumDisplacementSize() const noexcept {
|
||||
return 30;
|
||||
}
|
||||
|
||||
[[nodiscard]] int scalarTrueDofCount() const noexcept
|
||||
requires HasScalarDofCount
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasVacuumSupport(const int) const
|
||||
requires HasSupportQuery
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void buildVacuumDisplacement(
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasBuild
|
||||
{
|
||||
}
|
||||
|
||||
void applyJacobian(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasJacobian
|
||||
{
|
||||
}
|
||||
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasJacobianTranspose
|
||||
{
|
||||
}
|
||||
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasPullbackDerivative
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Prepared, bool HasDescriptor = true, bool HasValidation = true, bool HasCompilation = true>
|
||||
class VacuumExtensionFixture final {
|
||||
public:
|
||||
using PreparedType = Prepared;
|
||||
|
||||
[[nodiscard]] mean_field::deformation::VacuumDeformationExtensionDescriptor descriptor() const noexcept
|
||||
requires HasDescriptor
|
||||
{
|
||||
return vacuumDescriptor;
|
||||
}
|
||||
|
||||
void validate() const
|
||||
requires HasValidation
|
||||
{
|
||||
}
|
||||
|
||||
static constexpr bool hasCompilation = HasCompilation;
|
||||
};
|
||||
|
||||
template <
|
||||
typename Prepared,
|
||||
bool HasDescriptor,
|
||||
bool HasValidation,
|
||||
bool HasCompilation>
|
||||
requires HasCompilation
|
||||
Prepared compileVacuumDeformationExtension(
|
||||
const VacuumExtensionFixture<
|
||||
Prepared,
|
||||
HasDescriptor,
|
||||
HasValidation,
|
||||
HasCompilation> &,
|
||||
const DeformationCompilationContext &
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
|
||||
using CompletePreparedSurface = PreparedSurfaceFixture<>;
|
||||
using CompleteSurfacePrescription = SurfacePrescriptionFixture<CompletePreparedSurface>;
|
||||
|
||||
using CompletePreparedInteriorExtension = PreparedInteriorExtensionFixture<>;
|
||||
using CompleteInteriorExtension = InteriorExtensionFixture<CompletePreparedInteriorExtension>;
|
||||
|
||||
using CompletePreparedVacuumExtension = PreparedVacuumExtensionFixture<>;
|
||||
using CompleteVacuumExtension = VacuumExtensionFixture<CompletePreparedVacuumExtension>;
|
||||
|
||||
constexpr mean_field::deformation::DomainDeformationDescriptor domainDescriptor{
|
||||
.surfaceDeformation = surfaceDescriptor,
|
||||
.stellarInteriorExtension = interiorDescriptor,
|
||||
.vacuumExtension = vacuumDescriptor,
|
||||
.linearOnReferenceGeometry = true,
|
||||
.requiresAuxiliarySolve = false,
|
||||
.hasExactDerivativeTranspose = true,
|
||||
.hasExactPullbackDerivative = true
|
||||
};
|
||||
|
||||
template <
|
||||
bool HasBuild = true,
|
||||
bool HasJacobian = true,
|
||||
bool HasJacobianTranspose = true,
|
||||
bool HasPullbackDerivative = true>
|
||||
class PreparedDomainDeformationFixture final {
|
||||
public:
|
||||
[[nodiscard]] mean_field::deformation::DomainDeformationDescriptor descriptor() const noexcept {
|
||||
return domainDescriptor;
|
||||
}
|
||||
|
||||
[[nodiscard]] int parameterCount() const noexcept {
|
||||
return 4;
|
||||
}
|
||||
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
|
||||
return 12;
|
||||
}
|
||||
|
||||
[[nodiscard]] int volumeDisplacementSize() const noexcept {
|
||||
return 24;
|
||||
}
|
||||
|
||||
void buildVolumeDisplacement(
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasBuild
|
||||
{
|
||||
}
|
||||
|
||||
void applyJacobian(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasJacobian
|
||||
{
|
||||
}
|
||||
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasJacobianTranspose
|
||||
{
|
||||
}
|
||||
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const
|
||||
requires HasPullbackDerivative
|
||||
{
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Surface Deformation Contracts Require Complete Forward And Pullback Operations",
|
||||
tags::surface_deformation_type_contract
|
||||
) {
|
||||
STATIC_CHECK(mean_field::deformation::PreparedSurfaceDeformationPrescription<CompletePreparedSurface>);
|
||||
STATIC_CHECK(mean_field::deformation::SurfaceDeformationPrescription<CompleteSurfacePrescription>);
|
||||
STATIC_CHECK(
|
||||
mean_field::deformation::SurfaceDeformationCompilable<
|
||||
CompleteSurfacePrescription, DeformationCompilationContext>
|
||||
);
|
||||
|
||||
STATIC_CHECK_FALSE(mean_field::deformation::PreparedSurfaceDeformationPrescription<PreparedSurfaceFixture<false>>);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedSurfaceDeformationPrescription<PreparedSurfaceFixture<true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedSurfaceDeformationPrescription<PreparedSurfaceFixture<true, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedSurfaceDeformationPrescription<PreparedSurfaceFixture<true, true, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::SurfaceDeformationPrescription<
|
||||
SurfacePrescriptionFixture<CompletePreparedSurface, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::SurfaceDeformationPrescription<
|
||||
SurfacePrescriptionFixture<CompletePreparedSurface, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::SurfaceDeformationCompilable<
|
||||
SurfacePrescriptionFixture<CompletePreparedSurface, true, true, false>, DeformationCompilationContext>
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Interior Extension Contracts Require Complete Forward And Pullback Operations",
|
||||
tags::interior_deformation_extension_type_contract
|
||||
) {
|
||||
STATIC_CHECK(mean_field::deformation::PreparedInteriorDeformationExtension<CompletePreparedInteriorExtension>);
|
||||
STATIC_CHECK(mean_field::deformation::InteriorDeformationExtension<CompleteInteriorExtension>);
|
||||
STATIC_CHECK(
|
||||
mean_field::deformation::InteriorDeformationExtensionCompilable<
|
||||
CompleteInteriorExtension, DeformationCompilationContext>
|
||||
);
|
||||
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedInteriorDeformationExtension<PreparedInteriorExtensionFixture<false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedInteriorDeformationExtension<PreparedInteriorExtensionFixture<true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedInteriorDeformationExtension<
|
||||
PreparedInteriorExtensionFixture<true, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedInteriorDeformationExtension<
|
||||
PreparedInteriorExtensionFixture<true, true, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedInteriorDeformationExtension<
|
||||
PreparedInteriorExtensionFixture<true, true, true, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedInteriorDeformationExtension<
|
||||
PreparedInteriorExtensionFixture<true, true, true, true, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(mean_field::deformation::PreparedInteriorDeformationExtension<CompletePreparedVacuumExtension>);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::InteriorDeformationExtension<
|
||||
InteriorExtensionFixture<CompletePreparedInteriorExtension, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::InteriorDeformationExtensionCompilable<
|
||||
InteriorExtensionFixture<CompletePreparedInteriorExtension, true, true, false>,
|
||||
DeformationCompilationContext>
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Vacuum Extension Contracts Require Complete Forward And Pullback Operations",
|
||||
tags::vacuum_deformation_extension_type_contract
|
||||
) {
|
||||
STATIC_CHECK(mean_field::deformation::PreparedVacuumDeformationExtension<CompletePreparedVacuumExtension>);
|
||||
STATIC_CHECK(mean_field::deformation::VacuumDeformationExtension<CompleteVacuumExtension>);
|
||||
STATIC_CHECK(
|
||||
mean_field::deformation::VacuumDeformationExtensionCompilable<
|
||||
CompleteVacuumExtension, DeformationCompilationContext>
|
||||
);
|
||||
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedVacuumDeformationExtension<PreparedVacuumExtensionFixture<false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedVacuumDeformationExtension<PreparedVacuumExtensionFixture<true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedVacuumDeformationExtension<PreparedVacuumExtensionFixture<true, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedVacuumDeformationExtension<
|
||||
PreparedVacuumExtensionFixture<true, true, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedVacuumDeformationExtension<
|
||||
PreparedVacuumExtensionFixture<true, true, true, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedVacuumDeformationExtension<
|
||||
PreparedVacuumExtensionFixture<true, true, true, true, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(mean_field::deformation::PreparedVacuumDeformationExtension<CompletePreparedInteriorExtension>);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::VacuumDeformationExtension<
|
||||
VacuumExtensionFixture<CompletePreparedVacuumExtension, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::VacuumDeformationExtensionCompilable<
|
||||
VacuumExtensionFixture<CompletePreparedVacuumExtension, true, true, false>, DeformationCompilationContext>
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Domain Deformation Contracts Require Complete Lift And Pullback Operations",
|
||||
tags::domain_deformation_type_contract
|
||||
) {
|
||||
STATIC_CHECK(mean_field::deformation::PreparedDomainDeformationOperator<PreparedDomainDeformationFixture<>>);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedDomainDeformationOperator<PreparedDomainDeformationFixture<false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedDomainDeformationOperator<PreparedDomainDeformationFixture<true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedDomainDeformationOperator<PreparedDomainDeformationFixture<true, true, false>>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
mean_field::deformation::PreparedDomainDeformationOperator<
|
||||
PreparedDomainDeformationFixture<true, true, true, false>>
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Deformation Descriptors Report Geometry And Exact Linearization Capabilities",
|
||||
tags::deformation_type_contract
|
||||
) {
|
||||
STATIC_CHECK(surfaceDescriptor.isValid());
|
||||
STATIC_CHECK(surfaceDescriptor.supportsExactNewtonLinearization());
|
||||
STATIC_CHECK(interiorDescriptor.isValid());
|
||||
STATIC_CHECK(interiorDescriptor.supportsExactNewtonLinearization());
|
||||
STATIC_CHECK(vacuumDescriptor.isValid());
|
||||
STATIC_CHECK(vacuumDescriptor.supportsExactNewtonLinearization());
|
||||
STATIC_CHECK(domainDescriptor.isValid());
|
||||
STATIC_CHECK(domainDescriptor.supportsExactNewtonLinearization());
|
||||
|
||||
constexpr auto invalidInteriorDescriptor = [] {
|
||||
auto descriptor = interiorDescriptor;
|
||||
descriptor.centerBehavior = mean_field::deformation::InteriorCenterBehavior::Unspecified;
|
||||
return descriptor;
|
||||
}();
|
||||
|
||||
constexpr auto invalidVacuumDescriptor = [] {
|
||||
auto descriptor = vacuumDescriptor;
|
||||
descriptor.outerBoundaryBehavior = mean_field::deformation::VacuumOuterBoundaryBehavior::Unspecified;
|
||||
return descriptor;
|
||||
}();
|
||||
|
||||
STATIC_CHECK_FALSE(invalidInteriorDescriptor.isValid());
|
||||
STATIC_CHECK_FALSE(invalidVacuumDescriptor.isValid());
|
||||
|
||||
CHECK(surfaceDescriptor.motionKind == mean_field::deformation::SurfaceMotionKind::Radial);
|
||||
CHECK(interiorDescriptor.centerBehavior == mean_field::deformation::InteriorCenterBehavior::FixedAtReferenceCenter);
|
||||
CHECK(
|
||||
vacuumDescriptor.outerBoundaryBehavior ==
|
||||
mean_field::deformation::VacuumOuterBoundaryBehavior::FixedAtReferenceInfinity
|
||||
);
|
||||
}
|
||||
771
tests/deformation/domain_deformation.cpp
Normal file
771
tests/deformation/domain_deformation.cpp
Normal file
@@ -0,0 +1,771 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace domain_deformation_test_utils {
|
||||
namespace deformation = mean_field::deformation;
|
||||
namespace domain = mean_field::utils::domain;
|
||||
namespace field = mean_field::field;
|
||||
|
||||
using Schema = domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
[[nodiscard]] mfem::Vector referenceCenter(const int spatialDimension) {
|
||||
mfem::Vector center(spatialDimension);
|
||||
center = 0.0;
|
||||
return center;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto makePreparedDomainDeformation(mean_field::fem::FEM &fem) {
|
||||
const field::ScalarBoundaryDofMap surfaceDofMap =
|
||||
field::make_stellar_surface_scalar_dof_map<Schema>(*fem.surfaceDeformationFes);
|
||||
const deformation::SurfaceDeformationCompilationContext surfaceContext{
|
||||
*fem.surfaceDeformationFes, surfaceDofMap
|
||||
};
|
||||
deformation::PreparedNodalRadialSurface surface = deformation::compileSurfaceDeformationPrescription(
|
||||
deformation::NodalRadialSurface{referenceCenter(fem.mesh->SpaceDimension())}, surfaceContext
|
||||
);
|
||||
|
||||
const deformation::RadialDeformationExtensionCompilationContext extensionContext =
|
||||
deformation::makeRadialDeformationExtensionCompilationContext<Schema>(
|
||||
*fem.surfaceDeformationFes, *fem.displacementFes, *fem.logicalReferenceMesh
|
||||
);
|
||||
deformation::PreparedPowerLawRadialInteriorExtension interior =
|
||||
deformation::compileInteriorDeformationExtension(
|
||||
deformation::PowerLawRadialInteriorExtension{}, extensionContext
|
||||
);
|
||||
deformation::PreparedFixedInfinityRadialVacuumExtension vacuum = deformation::compileVacuumDeformationExtension(
|
||||
deformation::FixedInfinityRadialVacuumExtension{}, extensionContext
|
||||
);
|
||||
|
||||
return deformation::composePreparedDomainDeformation(
|
||||
std::move(surface), std::move(interior), std::move(vacuum), *fem.surfaceDeformationFes,
|
||||
*fem.displacementFes, *fem.logicalReferenceMesh
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] int volumeVectorDof(
|
||||
const int scalarTrueDof,
|
||||
const int component,
|
||||
const int scalarTrueDofCount
|
||||
) {
|
||||
return scalarTrueDof + component * scalarTrueDofCount;
|
||||
}
|
||||
|
||||
[[nodiscard]] double relativeError(
|
||||
const mfem::Vector &actual,
|
||||
const mfem::Vector &expected
|
||||
) {
|
||||
REQUIRE(actual.Size() == expected.Size());
|
||||
mfem::Vector difference(actual);
|
||||
difference -= expected;
|
||||
return difference.Norml2() / std::max(expected.Norml2(), std::numeric_limits<double>::epsilon());
|
||||
}
|
||||
|
||||
[[nodiscard]] double globalInnerProduct(
|
||||
const mfem::Vector &first,
|
||||
const mfem::Vector &second,
|
||||
MPI_Comm communicator
|
||||
) {
|
||||
REQUIRE(first.Size() == second.Size());
|
||||
const double localValue = first * second;
|
||||
double globalValue = 0.0;
|
||||
MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
||||
return globalValue;
|
||||
}
|
||||
|
||||
class AnalyticNonlinearSurface final {
|
||||
public:
|
||||
[[nodiscard]] deformation::SurfaceDeformationDescriptor descriptor() const noexcept {
|
||||
return {
|
||||
.name = "AnalyticNonlinearSurface",
|
||||
.spatialDimension = 3,
|
||||
.motionKind = deformation::SurfaceMotionKind::Radial,
|
||||
.linearOnReferenceGeometry = false,
|
||||
.requiresStarShapedReferenceSurface = false,
|
||||
.hasExactDerivativeTranspose = true,
|
||||
.hasExactPullbackDerivative = true,
|
||||
.translationTreatment = deformation::GeometricGaugeTreatment::Retained,
|
||||
.orientationTreatment = deformation::GeometricGaugeTreatment::Retained
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] int parameterCount() const noexcept {
|
||||
return 2;
|
||||
}
|
||||
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
|
||||
return 2;
|
||||
}
|
||||
|
||||
void buildSurfaceDisplacement(
|
||||
const mfem::Vector ¶meters,
|
||||
mfem::Vector &surfaceDisplacement
|
||||
) const {
|
||||
surfaceDisplacement(0) = parameters(0) * parameters(0) + parameters(1);
|
||||
surfaceDisplacement(1) = parameters(0) * parameters(1);
|
||||
}
|
||||
|
||||
void applyJacobian(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
mfem::Vector &surfaceDisplacementDirection
|
||||
) const {
|
||||
surfaceDisplacementDirection(0) = 2.0 * parameters(0) * parameterDirection(0) + parameterDirection(1);
|
||||
surfaceDisplacementDirection(1) =
|
||||
parameters(1) * parameterDirection(0) + parameters(0) * parameterDirection(1);
|
||||
}
|
||||
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &surfaceDisplacementDual,
|
||||
mfem::Vector ¶meterDual
|
||||
) const {
|
||||
parameterDual(0) =
|
||||
2.0 * parameters(0) * surfaceDisplacementDual(0) + parameters(1) * surfaceDisplacementDual(1);
|
||||
parameterDual(1) = surfaceDisplacementDual(0) + parameters(0) * surfaceDisplacementDual(1);
|
||||
}
|
||||
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
const mfem::Vector &surfaceDisplacementDual,
|
||||
mfem::Vector ¶meterDualAction
|
||||
) const {
|
||||
parameterDualAction(0) = 2.0 * parameterDirection(0) * surfaceDisplacementDual(0) +
|
||||
parameterDirection(1) * surfaceDisplacementDual(1);
|
||||
parameterDualAction(1) = parameterDirection(0) * surfaceDisplacementDual(1);
|
||||
}
|
||||
};
|
||||
|
||||
class AnalyticNonlinearExtensionKernel {
|
||||
public:
|
||||
AnalyticNonlinearExtensionKernel(
|
||||
const int scalarTrueDofCount,
|
||||
const double coefficientScale
|
||||
)
|
||||
: m_scalarTrueDofCount(scalarTrueDofCount),
|
||||
m_coefficientScale(coefficientScale) {
|
||||
}
|
||||
|
||||
[[nodiscard]] int scalarTrueDofCount() const noexcept {
|
||||
return m_scalarTrueDofCount;
|
||||
}
|
||||
|
||||
[[nodiscard]] int volumeDisplacementSize() const noexcept {
|
||||
return 3 * m_scalarTrueDofCount;
|
||||
}
|
||||
|
||||
[[nodiscard]] int sharedScalarDof() const noexcept {
|
||||
return m_scalarTrueDofCount / 2;
|
||||
}
|
||||
|
||||
void build(
|
||||
const mfem::Vector &surfaceDisplacement,
|
||||
mfem::Vector &volumeDisplacement,
|
||||
const bool stellar
|
||||
) const {
|
||||
volumeDisplacement = 0.0;
|
||||
for (int vectorDof = 0; vectorDof < volumeDisplacementSize(); ++vectorDof) {
|
||||
if (!hasSupport(vectorDof % m_scalarTrueDofCount, stellar)) {
|
||||
continue;
|
||||
}
|
||||
double linearFirst = 0.0;
|
||||
double linearSecond = 0.0;
|
||||
double bilinear = 0.0;
|
||||
coefficients(vectorDof, linearFirst, linearSecond, bilinear);
|
||||
volumeDisplacement(vectorDof) = linearFirst * surfaceDisplacement(0) +
|
||||
linearSecond * surfaceDisplacement(1) +
|
||||
bilinear * surfaceDisplacement(0) * surfaceDisplacement(1);
|
||||
}
|
||||
}
|
||||
|
||||
void applyJacobian(
|
||||
const mfem::Vector &surfaceDisplacement,
|
||||
const mfem::Vector &surfaceDirection,
|
||||
mfem::Vector &volumeDirection,
|
||||
const bool stellar
|
||||
) const {
|
||||
volumeDirection = 0.0;
|
||||
for (int vectorDof = 0; vectorDof < volumeDisplacementSize(); ++vectorDof) {
|
||||
if (!hasSupport(vectorDof % m_scalarTrueDofCount, stellar)) {
|
||||
continue;
|
||||
}
|
||||
double linearFirst = 0.0;
|
||||
double linearSecond = 0.0;
|
||||
double bilinear = 0.0;
|
||||
coefficients(vectorDof, linearFirst, linearSecond, bilinear);
|
||||
volumeDirection(vectorDof) = (linearFirst + bilinear * surfaceDisplacement(1)) * surfaceDirection(0) +
|
||||
(linearSecond + bilinear * surfaceDisplacement(0)) * surfaceDirection(1);
|
||||
}
|
||||
}
|
||||
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector &surfaceDisplacement,
|
||||
const mfem::Vector &volumeDual,
|
||||
mfem::Vector &surfaceDual,
|
||||
const bool stellar
|
||||
) const {
|
||||
surfaceDual = 0.0;
|
||||
for (int vectorDof = 0; vectorDof < volumeDisplacementSize(); ++vectorDof) {
|
||||
if (!hasSupport(vectorDof % m_scalarTrueDofCount, stellar)) {
|
||||
continue;
|
||||
}
|
||||
double linearFirst = 0.0;
|
||||
double linearSecond = 0.0;
|
||||
double bilinear = 0.0;
|
||||
coefficients(vectorDof, linearFirst, linearSecond, bilinear);
|
||||
surfaceDual(0) += (linearFirst + bilinear * surfaceDisplacement(1)) * volumeDual(vectorDof);
|
||||
surfaceDual(1) += (linearSecond + bilinear * surfaceDisplacement(0)) * volumeDual(vectorDof);
|
||||
}
|
||||
}
|
||||
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector &surfaceDirection,
|
||||
const mfem::Vector &volumeDual,
|
||||
mfem::Vector &surfaceDualAction,
|
||||
const bool stellar
|
||||
) const {
|
||||
surfaceDualAction = 0.0;
|
||||
for (int vectorDof = 0; vectorDof < volumeDisplacementSize(); ++vectorDof) {
|
||||
if (!hasSupport(vectorDof % m_scalarTrueDofCount, stellar)) {
|
||||
continue;
|
||||
}
|
||||
double linearFirst = 0.0;
|
||||
double linearSecond = 0.0;
|
||||
double bilinear = 0.0;
|
||||
coefficients(vectorDof, linearFirst, linearSecond, bilinear);
|
||||
surfaceDualAction(0) += bilinear * surfaceDirection(1) * volumeDual(vectorDof);
|
||||
surfaceDualAction(1) += bilinear * surfaceDirection(0) * volumeDual(vectorDof);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasSupport(
|
||||
const int scalarTrueDof,
|
||||
const bool stellar
|
||||
) const noexcept {
|
||||
return stellar ? scalarTrueDof <= sharedScalarDof() : scalarTrueDof >= sharedScalarDof();
|
||||
}
|
||||
|
||||
private:
|
||||
void coefficients(
|
||||
const int vectorDof,
|
||||
double &linearFirst,
|
||||
double &linearSecond,
|
||||
double &bilinear
|
||||
) const noexcept {
|
||||
linearFirst = m_coefficientScale * (0.01 + 0.001 * static_cast<double>(vectorDof % 7));
|
||||
linearSecond = m_coefficientScale * (-0.02 + 0.002 * static_cast<double>(vectorDof % 5));
|
||||
bilinear = m_coefficientScale * 0.0005 * static_cast<double>(1 + vectorDof % 3);
|
||||
}
|
||||
|
||||
int m_scalarTrueDofCount;
|
||||
double m_coefficientScale;
|
||||
};
|
||||
|
||||
class AnalyticNonlinearInteriorExtension final {
|
||||
public:
|
||||
explicit AnalyticNonlinearInteriorExtension(
|
||||
const int scalarTrueDofCount,
|
||||
const bool supportEnabled = true
|
||||
)
|
||||
: m_kernel(
|
||||
scalarTrueDofCount,
|
||||
1.0
|
||||
),
|
||||
m_supportEnabled(supportEnabled) {
|
||||
}
|
||||
|
||||
[[nodiscard]] deformation::InteriorDeformationExtensionDescriptor descriptor() const noexcept {
|
||||
return {
|
||||
.name = "AnalyticNonlinearInteriorExtension",
|
||||
.spatialDimension = 3,
|
||||
.linearOnReferenceGeometry = false,
|
||||
.requiresRadialFoliation = false,
|
||||
.requiresAuxiliarySolve = false,
|
||||
.hasExactDerivativeTranspose = true,
|
||||
.hasExactPullbackDerivative = true,
|
||||
.centerBehavior = deformation::InteriorCenterBehavior::FixedAtReferenceCenter
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
|
||||
return 2;
|
||||
}
|
||||
[[nodiscard]] int interiorDisplacementSize() const noexcept {
|
||||
return m_kernel.volumeDisplacementSize();
|
||||
}
|
||||
[[nodiscard]] int scalarTrueDofCount() const noexcept {
|
||||
return m_kernel.scalarTrueDofCount();
|
||||
}
|
||||
[[nodiscard]] bool hasStellarSupport(const int scalarTrueDof) const {
|
||||
return m_supportEnabled && m_kernel.hasSupport(scalarTrueDof, true);
|
||||
}
|
||||
void buildInteriorDisplacement(
|
||||
const mfem::Vector &surface,
|
||||
mfem::Vector &volume
|
||||
) const {
|
||||
m_kernel.build(surface, volume, true);
|
||||
}
|
||||
void applyJacobian(
|
||||
const mfem::Vector &surface,
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &volume
|
||||
) const {
|
||||
m_kernel.applyJacobian(surface, direction, volume, true);
|
||||
}
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector &surface,
|
||||
const mfem::Vector &volume,
|
||||
mfem::Vector &dual
|
||||
) const {
|
||||
m_kernel.applyJacobianTranspose(surface, volume, dual, true);
|
||||
}
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &direction,
|
||||
const mfem::Vector &volume,
|
||||
mfem::Vector &dual
|
||||
) const {
|
||||
m_kernel.applyPullbackDerivative(direction, volume, dual, true);
|
||||
}
|
||||
|
||||
private:
|
||||
AnalyticNonlinearExtensionKernel m_kernel;
|
||||
bool m_supportEnabled;
|
||||
};
|
||||
|
||||
class AnalyticNonlinearVacuumExtension final {
|
||||
public:
|
||||
explicit AnalyticNonlinearVacuumExtension(const int scalarTrueDofCount)
|
||||
: m_kernel(
|
||||
scalarTrueDofCount,
|
||||
-0.7
|
||||
) {
|
||||
}
|
||||
|
||||
[[nodiscard]] deformation::VacuumDeformationExtensionDescriptor descriptor() const noexcept {
|
||||
return {
|
||||
.name = "AnalyticNonlinearVacuumExtension",
|
||||
.spatialDimension = 3,
|
||||
.linearOnReferenceGeometry = false,
|
||||
.requiresRadialFoliation = false,
|
||||
.requiresAuxiliarySolve = false,
|
||||
.hasExactDerivativeTranspose = true,
|
||||
.hasExactPullbackDerivative = true,
|
||||
.outerBoundaryBehavior = deformation::VacuumOuterBoundaryBehavior::FixedAtReferenceInfinity
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
|
||||
return 2;
|
||||
}
|
||||
[[nodiscard]] int vacuumDisplacementSize() const noexcept {
|
||||
return m_kernel.volumeDisplacementSize();
|
||||
}
|
||||
[[nodiscard]] int scalarTrueDofCount() const noexcept {
|
||||
return m_kernel.scalarTrueDofCount();
|
||||
}
|
||||
[[nodiscard]] bool hasVacuumSupport(const int scalarTrueDof) const {
|
||||
return m_kernel.hasSupport(scalarTrueDof, false);
|
||||
}
|
||||
void buildVacuumDisplacement(
|
||||
const mfem::Vector &surface,
|
||||
mfem::Vector &volume
|
||||
) const {
|
||||
m_kernel.build(surface, volume, false);
|
||||
}
|
||||
void applyJacobian(
|
||||
const mfem::Vector &surface,
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &volume
|
||||
) const {
|
||||
m_kernel.applyJacobian(surface, direction, volume, false);
|
||||
}
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector &surface,
|
||||
const mfem::Vector &volume,
|
||||
mfem::Vector &dual
|
||||
) const {
|
||||
m_kernel.applyJacobianTranspose(surface, volume, dual, false);
|
||||
}
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &direction,
|
||||
const mfem::Vector &volume,
|
||||
mfem::Vector &dual
|
||||
) const {
|
||||
m_kernel.applyPullbackDerivative(direction, volume, dual, false);
|
||||
}
|
||||
|
||||
private:
|
||||
AnalyticNonlinearExtensionKernel m_kernel;
|
||||
};
|
||||
} // namespace domain_deformation_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Domain Deformation Composes Surface Interior And Vacuum Maps With Explicit Ownership",
|
||||
tags::domain_deformation_composition
|
||||
) {
|
||||
namespace deformation = mean_field::deformation;
|
||||
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(fem.okay());
|
||||
|
||||
auto prepared = domain_deformation_test_utils::makePreparedDomainDeformation(fem);
|
||||
STATIC_CHECK(deformation::PreparedDomainDeformationOperator<decltype(prepared)>);
|
||||
|
||||
const deformation::DomainDeformationDescriptor descriptor = prepared.descriptor();
|
||||
REQUIRE(descriptor.isValid());
|
||||
CHECK(descriptor.linearOnReferenceGeometry);
|
||||
CHECK_FALSE(descriptor.requiresAuxiliarySolve);
|
||||
CHECK(descriptor.supportsExactNewtonLinearization());
|
||||
CHECK(prepared.parameterCount() == prepared.surfaceDeformationPrescription().parameterCount());
|
||||
CHECK(prepared.surfaceDisplacementSize() == prepared.surfaceDeformationPrescription().surfaceDisplacementSize());
|
||||
CHECK(prepared.volumeDisplacementSize() == fem.displacementFes->GetTrueVSize());
|
||||
CHECK(prepared.matchesCurrentDiscretization());
|
||||
|
||||
const deformation::DomainDeformationDiscretizationDependencies &dependencies =
|
||||
prepared.discretizationDependencies();
|
||||
CHECK(dependencies.physicalMeshIdentity == fem.mesh.get());
|
||||
CHECK(dependencies.logicalReferenceMeshIdentity == fem.logicalReferenceMesh.get());
|
||||
CHECK(dependencies.surfaceScalarSpaceIdentity == fem.surfaceDeformationFes.get());
|
||||
CHECK(dependencies.volumeDisplacementSpaceIdentity == fem.displacementFes.get());
|
||||
CHECK(dependencies.isCurrent());
|
||||
|
||||
const deformation::DomainDeformationCompositionReport &composition = prepared.compositionReport();
|
||||
CHECK(composition.scalarTrueDofCount == prepared.scalarTrueDofCount());
|
||||
CHECK(composition.assignedScalarDofCount() == prepared.scalarTrueDofCount());
|
||||
CHECK(composition.stellarInteriorOwnedScalarDofCount > 0);
|
||||
CHECK(composition.vacuumOwnedScalarDofCount > 0);
|
||||
CHECK(composition.sharedSurfaceScalarDofCount > 0);
|
||||
|
||||
int countedStellarOwners = 0;
|
||||
int countedVacuumOwners = 0;
|
||||
int countedSharedDofs = 0;
|
||||
for (int scalarTrueDof = 0; scalarTrueDof < prepared.scalarTrueDofCount(); ++scalarTrueDof) {
|
||||
if (prepared.volumeOwner(scalarTrueDof) == deformation::VolumeDeformationOwner::StellarInterior) {
|
||||
++countedStellarOwners;
|
||||
} else {
|
||||
++countedVacuumOwners;
|
||||
}
|
||||
countedSharedDofs += prepared.isSharedSurfaceDof(scalarTrueDof) ? 1 : 0;
|
||||
}
|
||||
CHECK(countedStellarOwners == composition.stellarInteriorOwnedScalarDofCount);
|
||||
CHECK(countedVacuumOwners == composition.vacuumOwnedScalarDofCount);
|
||||
CHECK(countedSharedDofs == composition.sharedSurfaceScalarDofCount);
|
||||
|
||||
mfem::Vector zeroParameters(prepared.parameterCount());
|
||||
zeroParameters = 0.0;
|
||||
mfem::Vector composedVolume(prepared.volumeDisplacementSize());
|
||||
prepared.buildVolumeDisplacement(zeroParameters, composedVolume);
|
||||
CHECK(composedVolume.Norml2() == 0.0);
|
||||
|
||||
mfem::Vector parameters(prepared.parameterCount());
|
||||
for (int parameter = 0; parameter < parameters.Size(); ++parameter) {
|
||||
const double index = static_cast<double>(parameter + 1);
|
||||
parameters(parameter) = 0.012 * std::sin(0.19 * index) - 0.004 * std::cos(0.31 * index);
|
||||
}
|
||||
prepared.buildVolumeDisplacement(parameters, composedVolume);
|
||||
|
||||
mfem::Vector surfaceDisplacement(prepared.surfaceDisplacementSize());
|
||||
mfem::Vector interiorVolume(prepared.volumeDisplacementSize());
|
||||
mfem::Vector vacuumVolume(prepared.volumeDisplacementSize());
|
||||
prepared.surfaceDeformationPrescription().buildSurfaceDisplacement(parameters, surfaceDisplacement);
|
||||
prepared.stellarInteriorExtension().buildInteriorDisplacement(surfaceDisplacement, interiorVolume);
|
||||
prepared.vacuumExtension().buildVacuumDisplacement(surfaceDisplacement, vacuumVolume);
|
||||
|
||||
constexpr double tolerance = 2.0e-12;
|
||||
for (int scalarTrueDof = 0; scalarTrueDof < prepared.scalarTrueDofCount(); ++scalarTrueDof) {
|
||||
for (int component = 0; component < prepared.spatialDimension(); ++component) {
|
||||
const int volumeDof =
|
||||
domain_deformation_test_utils::volumeVectorDof(scalarTrueDof, component, prepared.scalarTrueDofCount());
|
||||
const double expected =
|
||||
prepared.volumeOwner(scalarTrueDof) == deformation::VolumeDeformationOwner::StellarInterior
|
||||
? interiorVolume(volumeDof)
|
||||
: vacuumVolume(volumeDof);
|
||||
CHECK(std::abs(composedVolume(volumeDof) - expected) <= tolerance);
|
||||
if (prepared.isSharedSurfaceDof(scalarTrueDof)) {
|
||||
CHECK(std::abs(interiorVolume(volumeDof) - vacuumVolume(volumeDof)) <= tolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const deformation::PreparedDomainDeformationActionStatistics &statistics = prepared.actionStatistics();
|
||||
CHECK(statistics.volumeBuildApplications == 2);
|
||||
CHECK(statistics.jacobianApplications == 0);
|
||||
CHECK(statistics.jacobianTransposeApplications == 0);
|
||||
CHECK(statistics.pullbackDerivativeApplications == 0);
|
||||
CHECK(statistics.geometryInspections == 0);
|
||||
|
||||
mfem::Vector wrongParameters(prepared.parameterCount() + 1);
|
||||
mfem::Vector wrongVolume(prepared.volumeDisplacementSize() + 1);
|
||||
CHECK_THROWS_AS(prepared.buildVolumeDisplacement(wrongParameters, composedVolume), std::invalid_argument);
|
||||
CHECK_THROWS_AS(prepared.buildVolumeDisplacement(parameters, wrongVolume), std::invalid_argument);
|
||||
CHECK_THROWS_AS(prepared.volumeOwner(-1), std::out_of_range);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Domain Deformation Jacobian Matches Centered Difference And Its Pullback Preserves Virtual Work",
|
||||
tags::domain_deformation_linearization
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(fem.okay());
|
||||
|
||||
auto prepared = domain_deformation_test_utils::makePreparedDomainDeformation(fem);
|
||||
mfem::Vector parameters(prepared.parameterCount());
|
||||
mfem::Vector direction(prepared.parameterCount());
|
||||
for (int parameter = 0; parameter < parameters.Size(); ++parameter) {
|
||||
const double index = static_cast<double>(parameter + 1);
|
||||
parameters(parameter) = 0.008 * std::sin(0.13 * index);
|
||||
direction(parameter) = std::cos(0.17 * index) - 0.25 * std::sin(0.29 * index);
|
||||
}
|
||||
|
||||
constexpr double step = 1.0e-6;
|
||||
mfem::Vector plusParameters(parameters);
|
||||
mfem::Vector minusParameters(parameters);
|
||||
plusParameters.Add(step, direction);
|
||||
minusParameters.Add(-step, direction);
|
||||
|
||||
mfem::Vector plusVolume(prepared.volumeDisplacementSize());
|
||||
mfem::Vector minusVolume(prepared.volumeDisplacementSize());
|
||||
mfem::Vector jacobianAction(prepared.volumeDisplacementSize());
|
||||
prepared.buildVolumeDisplacement(plusParameters, plusVolume);
|
||||
prepared.buildVolumeDisplacement(minusParameters, minusVolume);
|
||||
prepared.applyJacobian(parameters, direction, jacobianAction);
|
||||
|
||||
mfem::Vector centeredDifference(plusVolume);
|
||||
centeredDifference -= minusVolume;
|
||||
centeredDifference /= 2.0 * step;
|
||||
CHECK(domain_deformation_test_utils::relativeError(jacobianAction, centeredDifference) < 3.0e-10);
|
||||
|
||||
mfem::Vector volumeDual(prepared.volumeDisplacementSize());
|
||||
for (int dof = 0; dof < volumeDual.Size(); ++dof) {
|
||||
const double index = static_cast<double>(dof + 1);
|
||||
volumeDual(dof) = std::sin(0.07 * index) + 0.4 * std::cos(0.11 * index);
|
||||
}
|
||||
mfem::Vector parameterDual(prepared.parameterCount());
|
||||
prepared.applyJacobianTranspose(parameters, volumeDual, parameterDual);
|
||||
|
||||
const double volumeWork =
|
||||
domain_deformation_test_utils::globalInnerProduct(jacobianAction, volumeDual, fem.mesh->GetComm());
|
||||
const double parameterWork =
|
||||
domain_deformation_test_utils::globalInnerProduct(direction, parameterDual, fem.mesh->GetComm());
|
||||
const double workScale = std::max({1.0, std::abs(volumeWork), std::abs(parameterWork)});
|
||||
CHECK(std::abs(volumeWork - parameterWork) <= 8.0e-13 * workScale);
|
||||
|
||||
mfem::Vector pullbackAction(prepared.parameterCount());
|
||||
prepared.applyPullbackDerivative(parameters, direction, volumeDual, pullbackAction);
|
||||
CHECK(pullbackAction.Norml2() == 0.0);
|
||||
|
||||
mfem::Vector plusTranspose(prepared.parameterCount());
|
||||
mfem::Vector minusTranspose(prepared.parameterCount());
|
||||
prepared.applyJacobianTranspose(plusParameters, volumeDual, plusTranspose);
|
||||
prepared.applyJacobianTranspose(minusParameters, volumeDual, minusTranspose);
|
||||
mfem::Vector transposeCenteredDifference(plusTranspose);
|
||||
transposeCenteredDifference -= minusTranspose;
|
||||
transposeCenteredDifference /= 2.0 * step;
|
||||
CHECK(transposeCenteredDifference.Norml2() <= 1.0e-12 * std::max(1.0, parameterDual.Norml2()));
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Nonlinear Domain Deformation Pullback Matches The Directional Derivative Of Its Complete Transpose",
|
||||
tags::domain_deformation_linearization
|
||||
) {
|
||||
namespace deformation = mean_field::deformation;
|
||||
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(fem.okay());
|
||||
|
||||
const int scalarTrueDofCount = fem.displacementFes->GetTrueVSize() / fem.mesh->SpaceDimension();
|
||||
CHECK_THROWS_AS(
|
||||
(deformation::composePreparedDomainDeformation(
|
||||
domain_deformation_test_utils::AnalyticNonlinearSurface{},
|
||||
domain_deformation_test_utils::AnalyticNonlinearInteriorExtension{scalarTrueDofCount, false},
|
||||
domain_deformation_test_utils::AnalyticNonlinearVacuumExtension{scalarTrueDofCount},
|
||||
*fem.surfaceDeformationFes, *fem.displacementFes, *fem.logicalReferenceMesh
|
||||
)),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(deformation::composePreparedDomainDeformation(
|
||||
domain_deformation_test_utils::AnalyticNonlinearSurface{},
|
||||
domain_deformation_test_utils::AnalyticNonlinearInteriorExtension{scalarTrueDofCount},
|
||||
domain_deformation_test_utils::AnalyticNonlinearVacuumExtension{scalarTrueDofCount - 1},
|
||||
*fem.surfaceDeformationFes, *fem.displacementFes, *fem.logicalReferenceMesh
|
||||
)),
|
||||
std::invalid_argument
|
||||
);
|
||||
auto prepared = deformation::composePreparedDomainDeformation(
|
||||
domain_deformation_test_utils::AnalyticNonlinearSurface{},
|
||||
domain_deformation_test_utils::AnalyticNonlinearInteriorExtension{scalarTrueDofCount},
|
||||
domain_deformation_test_utils::AnalyticNonlinearVacuumExtension{scalarTrueDofCount}, *fem.surfaceDeformationFes,
|
||||
*fem.displacementFes, *fem.logicalReferenceMesh
|
||||
);
|
||||
REQUIRE_FALSE(prepared.descriptor().linearOnReferenceGeometry);
|
||||
|
||||
mfem::Vector parameters(2);
|
||||
parameters(0) = 0.37;
|
||||
parameters(1) = -0.21;
|
||||
mfem::Vector parameterDirection(2);
|
||||
parameterDirection(0) = -0.42;
|
||||
parameterDirection(1) = 0.63;
|
||||
|
||||
mfem::Vector volumeDual(prepared.volumeDisplacementSize());
|
||||
for (int vectorDof = 0; vectorDof < volumeDual.Size(); ++vectorDof) {
|
||||
const double index = static_cast<double>(vectorDof + 1);
|
||||
volumeDual(vectorDof) = std::sin(0.013 * index) - 0.3 * std::cos(0.021 * index);
|
||||
}
|
||||
|
||||
constexpr double step = 2.0e-6;
|
||||
mfem::Vector plusParameters(parameters);
|
||||
mfem::Vector minusParameters(parameters);
|
||||
plusParameters.Add(step, parameterDirection);
|
||||
minusParameters.Add(-step, parameterDirection);
|
||||
|
||||
mfem::Vector plusVolume(prepared.volumeDisplacementSize());
|
||||
mfem::Vector minusVolume(prepared.volumeDisplacementSize());
|
||||
mfem::Vector jacobianAction(prepared.volumeDisplacementSize());
|
||||
prepared.buildVolumeDisplacement(plusParameters, plusVolume);
|
||||
prepared.buildVolumeDisplacement(minusParameters, minusVolume);
|
||||
prepared.applyJacobian(parameters, parameterDirection, jacobianAction);
|
||||
mfem::Vector centeredJacobian(plusVolume);
|
||||
centeredJacobian -= minusVolume;
|
||||
centeredJacobian /= 2.0 * step;
|
||||
CHECK(domain_deformation_test_utils::relativeError(jacobianAction, centeredJacobian) < 2.0e-10);
|
||||
|
||||
mfem::Vector parameterDual(2);
|
||||
prepared.applyJacobianTranspose(parameters, volumeDual, parameterDual);
|
||||
const double volumeWork =
|
||||
domain_deformation_test_utils::globalInnerProduct(jacobianAction, volumeDual, fem.mesh->GetComm());
|
||||
const double parameterWork =
|
||||
domain_deformation_test_utils::globalInnerProduct(parameterDirection, parameterDual, fem.mesh->GetComm());
|
||||
CHECK(
|
||||
std::abs(volumeWork - parameterWork) <= 2.0e-12 * std::max({1.0, std::abs(volumeWork), std::abs(parameterWork)})
|
||||
);
|
||||
|
||||
mfem::Vector pullbackAction(2);
|
||||
prepared.applyPullbackDerivative(parameters, parameterDirection, volumeDual, pullbackAction);
|
||||
mfem::Vector plusTranspose(2);
|
||||
mfem::Vector minusTranspose(2);
|
||||
prepared.applyJacobianTranspose(plusParameters, volumeDual, plusTranspose);
|
||||
prepared.applyJacobianTranspose(minusParameters, volumeDual, minusTranspose);
|
||||
mfem::Vector centeredPullback(plusTranspose);
|
||||
centeredPullback -= minusTranspose;
|
||||
centeredPullback /= 2.0 * step;
|
||||
CHECK(domain_deformation_test_utils::relativeError(pullbackAction, centeredPullback) < 3.0e-9);
|
||||
|
||||
const int sharedScalarDof = scalarTrueDofCount / 2;
|
||||
REQUIRE(prepared.isSharedSurfaceDof(sharedScalarDof));
|
||||
CHECK(prepared.volumeOwner(sharedScalarDof) == deformation::VolumeDeformationOwner::StellarInterior);
|
||||
|
||||
mfem::Vector surfaceDisplacement(2);
|
||||
mfem::Vector interiorVolume(prepared.volumeDisplacementSize());
|
||||
mfem::Vector vacuumVolume(prepared.volumeDisplacementSize());
|
||||
mfem::Vector composedVolume(prepared.volumeDisplacementSize());
|
||||
prepared.surfaceDeformationPrescription().buildSurfaceDisplacement(parameters, surfaceDisplacement);
|
||||
prepared.stellarInteriorExtension().buildInteriorDisplacement(surfaceDisplacement, interiorVolume);
|
||||
prepared.vacuumExtension().buildVacuumDisplacement(surfaceDisplacement, vacuumVolume);
|
||||
prepared.buildVolumeDisplacement(parameters, composedVolume);
|
||||
for (int component = 0; component < prepared.spatialDimension(); ++component) {
|
||||
const int vectorDof = sharedScalarDof + component * scalarTrueDofCount;
|
||||
CHECK(composedVolume(vectorDof) == interiorVolume(vectorDof));
|
||||
CHECK(interiorVolume(vectorDof) != vacuumVolume(vectorDof));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Domain Deformation Accepts Orientation Preserving Shapes And Rejects Folded Volume Maps",
|
||||
tags::domain_deformation_geometry
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(fem.okay());
|
||||
|
||||
auto prepared = domain_deformation_test_utils::makePreparedDomainDeformation(fem);
|
||||
mfem::Vector volumeDisplacement(prepared.volumeDisplacementSize());
|
||||
|
||||
mfem::Vector zeroParameters(prepared.parameterCount());
|
||||
zeroParameters = 0.0;
|
||||
const mean_field::deformation::DomainDeformationGeometryReport referenceReport =
|
||||
prepared.buildValidatedVolumeDisplacement(zeroParameters, volumeDisplacement, 0.99);
|
||||
CHECK(std::abs(referenceReport.minimumJacobianDeterminant - 1.0) <= 64.0 * std::numeric_limits<double>::epsilon());
|
||||
CHECK_THROWS_AS(
|
||||
prepared.buildValidatedVolumeDisplacement(zeroParameters, volumeDisplacement, 1.01), std::domain_error
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
prepared.buildValidatedVolumeDisplacement(zeroParameters, volumeDisplacement, -0.01), std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
prepared.buildValidatedVolumeDisplacement(
|
||||
zeroParameters, volumeDisplacement, std::numeric_limits<double>::quiet_NaN()
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
mfem::Vector boundedParameters(prepared.parameterCount());
|
||||
boundedParameters = 0.02;
|
||||
const mean_field::deformation::DomainDeformationGeometryReport boundedReport =
|
||||
prepared.buildValidatedVolumeDisplacement(boundedParameters, volumeDisplacement);
|
||||
CHECK(boundedReport.isOrientationPreserving());
|
||||
CHECK(boundedReport.minimumJacobianDeterminant > 0.0);
|
||||
|
||||
mfem::Vector foldingParameters(prepared.parameterCount());
|
||||
foldingParameters = -2.0;
|
||||
prepared.buildVolumeDisplacement(foldingParameters, volumeDisplacement);
|
||||
const mean_field::deformation::DomainDeformationGeometryReport foldingReport =
|
||||
prepared.inspectMappedGeometry(volumeDisplacement);
|
||||
CHECK_FALSE(foldingReport.isOrientationPreserving());
|
||||
CHECK_THROWS_AS(
|
||||
prepared.buildValidatedVolumeDisplacement(foldingParameters, volumeDisplacement), std::domain_error
|
||||
);
|
||||
|
||||
mfem::Vector nonfiniteParameters(zeroParameters);
|
||||
nonfiniteParameters(0) = std::numeric_limits<double>::quiet_NaN();
|
||||
CHECK_THROWS_AS(
|
||||
prepared.buildValidatedVolumeDisplacement(nonfiniteParameters, volumeDisplacement), std::domain_error
|
||||
);
|
||||
|
||||
CHECK(prepared.actionStatistics().geometryInspections == 6);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Domain Deformation Rejects Actions After Its Discretization Becomes Stale",
|
||||
tags::domain_deformation_composition
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(fem.okay());
|
||||
|
||||
auto prepared = domain_deformation_test_utils::makePreparedDomainDeformation(fem);
|
||||
mfem::Vector parameters(prepared.parameterCount());
|
||||
mfem::Vector parameterDirection(prepared.parameterCount());
|
||||
mfem::Vector parameterDual(prepared.parameterCount());
|
||||
mfem::Vector volume(prepared.volumeDisplacementSize());
|
||||
parameters = 0.0;
|
||||
parameterDirection = 0.0;
|
||||
volume = 0.0;
|
||||
|
||||
REQUIRE(prepared.matchesCurrentDiscretization());
|
||||
fem.mesh->UniformRefinement();
|
||||
REQUIRE_FALSE(prepared.matchesCurrentDiscretization());
|
||||
CHECK_THROWS_AS(prepared.buildVolumeDisplacement(parameters, volume), std::logic_error);
|
||||
CHECK_THROWS_AS(prepared.applyJacobian(parameters, parameterDirection, volume), std::logic_error);
|
||||
CHECK_THROWS_AS(prepared.applyJacobianTranspose(parameters, volume, parameterDual), std::logic_error);
|
||||
CHECK_THROWS_AS(
|
||||
prepared.applyPullbackDerivative(parameters, parameterDirection, volume, parameterDual), std::logic_error
|
||||
);
|
||||
CHECK_THROWS_AS(prepared.inspectMappedGeometry(volume), std::logic_error);
|
||||
}
|
||||
536
tests/deformation/nodal_radial_surface.cpp
Normal file
536
tests/deformation/nodal_radial_surface.cpp
Normal file
@@ -0,0 +1,536 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace nodal_radial_surface_test_utils {
|
||||
namespace deformation = mean_field::deformation;
|
||||
namespace domain = mean_field::utils::domain;
|
||||
namespace field = mean_field::field;
|
||||
|
||||
using Schema = domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
using PlanarBoundarySchema = domain::DomainSchema<
|
||||
domain::MaterialList<>,
|
||||
domain::BoundaryList<domain::BoundaryAttribute<domain::StellarSurface, 1>>,
|
||||
domain::RelationList<>>;
|
||||
|
||||
[[nodiscard]] mfem::Vector referenceCenter(const int spatialDimension) {
|
||||
mfem::Vector center(spatialDimension);
|
||||
center = 0.0;
|
||||
return center;
|
||||
}
|
||||
|
||||
[[nodiscard]] deformation::PreparedNodalRadialSurface makePreparedSurface(const mean_field::fem::FEM &fem) {
|
||||
const field::ScalarBoundaryDofMap surfaceDofMap =
|
||||
field::make_stellar_surface_scalar_dof_map<Schema>(*fem.surfaceDeformationFes);
|
||||
const deformation::SurfaceDeformationCompilationContext context{*fem.surfaceDeformationFes, surfaceDofMap};
|
||||
|
||||
return deformation::compileSurfaceDeformationPrescription(
|
||||
deformation::NodalRadialSurface{referenceCenter(fem.mesh->SpaceDimension())}, context
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector projectReferenceSurfacePositions(
|
||||
mfem::ParFiniteElementSpace &scalarFiniteElementSpace,
|
||||
const field::ScalarBoundaryDofMap &surfaceDofMap
|
||||
) {
|
||||
const mfem::Mesh *mesh = scalarFiniteElementSpace.GetMesh();
|
||||
REQUIRE(mesh != nullptr);
|
||||
|
||||
const int spatialDimension = mesh->SpaceDimension();
|
||||
mfem::Vector positions(spatialDimension * surfaceDofMap.local_size());
|
||||
mfem::ParGridFunction coordinateField(&scalarFiniteElementSpace);
|
||||
|
||||
for (int component = 0; component < spatialDimension; ++component) {
|
||||
mfem::FunctionCoefficient coefficient([component](const mfem::Vector &position) {
|
||||
return position(component);
|
||||
});
|
||||
coordinateField.ProjectCoefficient(coefficient);
|
||||
|
||||
mfem::Vector coordinateTrueDofs;
|
||||
coordinateField.GetTrueDofs(coordinateTrueDofs);
|
||||
const mfem::Vector surfaceCoordinates = surfaceDofMap.gather(coordinateTrueDofs);
|
||||
|
||||
for (int surfaceDof = 0; surfaceDof < surfaceDofMap.local_size(); ++surfaceDof) {
|
||||
positions(spatialDimension * surfaceDof + component) = surfaceCoordinates(surfaceDof);
|
||||
}
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
[[nodiscard]] double relativeError(
|
||||
const mfem::Vector &actual,
|
||||
const mfem::Vector &expected
|
||||
) {
|
||||
REQUIRE(actual.Size() == expected.Size());
|
||||
|
||||
mfem::Vector difference(actual);
|
||||
difference -= expected;
|
||||
|
||||
return difference.Norml2() / std::max(expected.Norml2(), std::numeric_limits<double>::epsilon());
|
||||
}
|
||||
|
||||
[[nodiscard]] double globalMeanReferenceRadius(const deformation::PreparedNodalRadialSurface &surface) {
|
||||
double localRadiusSum = 0.0;
|
||||
for (int parameterDof = 0; parameterDof < surface.parameterCount(); ++parameterDof) {
|
||||
localRadiusSum += surface.referenceRadius(parameterDof);
|
||||
}
|
||||
|
||||
const long long localCount = surface.parameterCount();
|
||||
double globalRadiusSum = 0.0;
|
||||
long long globalCount = 0;
|
||||
MPI_Allreduce(&localRadiusSum, &globalRadiusSum, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
|
||||
MPI_Allreduce(&localCount, &globalCount, 1, MPI_LONG_LONG, MPI_SUM, MPI_COMM_WORLD);
|
||||
REQUIRE(globalCount > 0);
|
||||
return globalRadiusSum / static_cast<double>(globalCount);
|
||||
}
|
||||
|
||||
struct BinaryRochePotential final {
|
||||
double primaryMass;
|
||||
double companionMass;
|
||||
double separation;
|
||||
|
||||
[[nodiscard]] double operator()(
|
||||
const double x,
|
||||
const double y,
|
||||
const double z
|
||||
) const {
|
||||
const double primaryDistance = std::sqrt(x * x + y * y + z * z);
|
||||
const double companionOffset = x - separation;
|
||||
const double companionDistance = std::sqrt(companionOffset * companionOffset + y * y + z * z);
|
||||
const double totalMass = primaryMass + companionMass;
|
||||
const double centerOfMassX = separation * companionMass / totalMass;
|
||||
const double angularSpeed2 = totalMass / (separation * separation * separation);
|
||||
const double rotationRadius2 = (x - centerOfMassX) * (x - centerOfMassX) + y * y;
|
||||
|
||||
return -primaryMass / primaryDistance - companionMass / companionDistance -
|
||||
0.5 * angularSpeed2 * rotationRadius2;
|
||||
}
|
||||
|
||||
[[nodiscard]] double firstRadialIntersection(
|
||||
const double directionX,
|
||||
const double directionY,
|
||||
const double directionZ,
|
||||
const double targetPotential,
|
||||
const double referenceScale
|
||||
) const {
|
||||
double lowerRadius = 1.0e-8 * referenceScale;
|
||||
double lowerValue =
|
||||
(*this)(lowerRadius * directionX, lowerRadius * directionY, lowerRadius * directionZ) - targetPotential;
|
||||
REQUIRE(lowerValue < 0.0);
|
||||
|
||||
double upperRadius = lowerRadius;
|
||||
double upperValue = lowerValue;
|
||||
constexpr int bracketSamples = 512;
|
||||
for (int sample = 1; sample <= bracketSamples && upperValue <= 0.0; ++sample) {
|
||||
upperRadius = 0.45 * separation * static_cast<double>(sample) / bracketSamples;
|
||||
upperValue = (*this)(upperRadius * directionX, upperRadius * directionY, upperRadius * directionZ) -
|
||||
targetPotential;
|
||||
}
|
||||
REQUIRE(upperValue > 0.0);
|
||||
|
||||
for (int iteration = 0; iteration < 80; ++iteration) {
|
||||
const double middleRadius = 0.5 * (lowerRadius + upperRadius);
|
||||
const double middleValue =
|
||||
(*this)(middleRadius * directionX, middleRadius * directionY, middleRadius * directionZ) -
|
||||
targetPotential;
|
||||
if (middleValue > 0.0) {
|
||||
upperRadius = middleRadius;
|
||||
} else {
|
||||
lowerRadius = middleRadius;
|
||||
}
|
||||
}
|
||||
return 0.5 * (lowerRadius + upperRadius);
|
||||
}
|
||||
};
|
||||
} // namespace nodal_radial_surface_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Nodal Radial Surface Satisfies The Surface Deformation Contract And Validates Its Reference Center",
|
||||
tags::nodal_radial_surface_validation
|
||||
) {
|
||||
namespace deformation = mean_field::deformation;
|
||||
namespace field = mean_field::field;
|
||||
|
||||
STATIC_CHECK(deformation::SurfaceDeformationPrescription<deformation::NodalRadialSurface>);
|
||||
STATIC_CHECK(deformation::PreparedSurfaceDeformationPrescription<deformation::PreparedNodalRadialSurface>);
|
||||
STATIC_CHECK(
|
||||
deformation::SurfaceDeformationCompilable<
|
||||
deformation::NodalRadialSurface, deformation::SurfaceDeformationCompilationContext>
|
||||
);
|
||||
|
||||
mfem::Vector center(3);
|
||||
center = 0.0;
|
||||
const deformation::NodalRadialSurface prescription{center};
|
||||
const deformation::SurfaceDeformationDescriptor descriptor = prescription.descriptor();
|
||||
|
||||
CHECK(descriptor.name == "NodalRadialSurface");
|
||||
CHECK(descriptor.spatialDimension == 3);
|
||||
CHECK(descriptor.motionKind == deformation::SurfaceMotionKind::Radial);
|
||||
CHECK(descriptor.linearOnReferenceGeometry);
|
||||
CHECK(descriptor.requiresStarShapedReferenceSurface);
|
||||
CHECK(descriptor.supportsExactNewtonLinearization());
|
||||
CHECK(descriptor.translationTreatment == deformation::GeometricGaugeTreatment::Retained);
|
||||
CHECK(descriptor.orientationTreatment == deformation::GeometricGaugeTreatment::Retained);
|
||||
|
||||
mfem::Vector emptyCenter;
|
||||
CHECK_THROWS_AS(deformation::NodalRadialSurface{emptyCenter}, std::invalid_argument);
|
||||
|
||||
mfem::Vector nonfiniteCenter(3);
|
||||
nonfiniteCenter = 0.0;
|
||||
nonfiniteCenter(1) = std::numeric_limits<double>::quiet_NaN();
|
||||
CHECK_THROWS_AS(deformation::NodalRadialSurface{nonfiniteCenter}, std::invalid_argument);
|
||||
|
||||
mfem::Mesh serialMesh = mfem::Mesh::MakeCartesian2D(4, 3, mfem::Element::QUADRILATERAL, true, 2.0, 1.5);
|
||||
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
|
||||
mfem::H1_FECollection finiteElementCollection(2, mesh.Dimension());
|
||||
mfem::ParFiniteElementSpace finiteElementSpace(&mesh, &finiteElementCollection);
|
||||
|
||||
const field::ScalarBoundaryDofMap surfaceDofMap =
|
||||
field::make_stellar_surface_scalar_dof_map<nodal_radial_surface_test_utils::PlanarBoundarySchema>(
|
||||
finiteElementSpace
|
||||
);
|
||||
const deformation::SurfaceDeformationCompilationContext context{finiteElementSpace, surfaceDofMap};
|
||||
|
||||
CHECK_THROWS_AS(deformation::compileSurfaceDeformationPrescription(prescription, context), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Nodal Radial Surface Resolves Oblate Rotating And Binary Roche Envelopes Between Surface Nodes",
|
||||
tags::nodal_radial_surface_analytic
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(fem.okay());
|
||||
|
||||
const mean_field::deformation::PreparedNodalRadialSurface surface =
|
||||
nodal_radial_surface_test_utils::makePreparedSurface(fem);
|
||||
REQUIRE(surface.spatialDimension() == 3);
|
||||
|
||||
const double referenceScale = nodal_radial_surface_test_utils::globalMeanReferenceRadius(surface);
|
||||
const double equatorialRadius = 1.08 * referenceScale;
|
||||
const double polarRadius = 0.94 * referenceScale;
|
||||
|
||||
mfem::Vector rotatingParameters(surface.parameterCount());
|
||||
for (int parameterDof = 0; parameterDof < surface.parameterCount(); ++parameterDof) {
|
||||
const double directionX = surface.radialDirection(parameterDof, 0);
|
||||
const double directionY = surface.radialDirection(parameterDof, 1);
|
||||
const double directionZ = surface.radialDirection(parameterDof, 2);
|
||||
const double targetRadius =
|
||||
1.0 / std::sqrt(
|
||||
(directionX * directionX + directionY * directionY) / (equatorialRadius * equatorialRadius) +
|
||||
directionZ * directionZ / (polarRadius * polarRadius)
|
||||
);
|
||||
rotatingParameters(parameterDof) = targetRadius - surface.referenceRadius(parameterDof);
|
||||
}
|
||||
|
||||
mfem::Vector rotatingDisplacement(surface.surfaceDisplacementSize());
|
||||
surface.buildSurfaceDisplacement(rotatingParameters, rotatingDisplacement);
|
||||
|
||||
constexpr double geometricTolerance = 3.0e-12;
|
||||
for (int parameterDof = 0; parameterDof < surface.parameterCount(); ++parameterDof) {
|
||||
double movedPosition[3]{};
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
const double direction = surface.radialDirection(parameterDof, component);
|
||||
movedPosition[component] = surface.referenceRadius(parameterDof) * direction +
|
||||
rotatingDisplacement(surface.surfaceDisplacementDof(parameterDof, component));
|
||||
}
|
||||
const double ellipsoidLevel = (movedPosition[0] * movedPosition[0] + movedPosition[1] * movedPosition[1]) /
|
||||
(equatorialRadius * equatorialRadius) +
|
||||
movedPosition[2] * movedPosition[2] / (polarRadius * polarRadius);
|
||||
CHECK(std::abs(ellipsoidLevel - 1.0) <= geometricTolerance);
|
||||
}
|
||||
|
||||
const nodal_radial_surface_test_utils::BinaryRochePotential rochePotential{
|
||||
.primaryMass = 1.0, .companionMass = 0.7, .separation = 4.0 * referenceScale
|
||||
};
|
||||
const double targetPotential = rochePotential(0.0, 0.0, referenceScale);
|
||||
|
||||
mfem::Vector rocheParameters(surface.parameterCount());
|
||||
for (int parameterDof = 0; parameterDof < surface.parameterCount(); ++parameterDof) {
|
||||
const double targetRadius = rochePotential.firstRadialIntersection(
|
||||
surface.radialDirection(parameterDof, 0), surface.radialDirection(parameterDof, 1),
|
||||
surface.radialDirection(parameterDof, 2), targetPotential, referenceScale
|
||||
);
|
||||
rocheParameters(parameterDof) = targetRadius - surface.referenceRadius(parameterDof);
|
||||
}
|
||||
|
||||
mfem::Vector rocheDisplacement(surface.surfaceDisplacementSize());
|
||||
surface.buildSurfaceDisplacement(rocheParameters, rocheDisplacement);
|
||||
|
||||
const double potentialTolerance = 2.0e-11 * std::max(1.0, std::abs(targetPotential));
|
||||
for (int parameterDof = 0; parameterDof < surface.parameterCount(); ++parameterDof) {
|
||||
double movedPosition[3]{};
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
const double direction = surface.radialDirection(parameterDof, component);
|
||||
movedPosition[component] = surface.referenceRadius(parameterDof) * direction +
|
||||
rocheDisplacement(surface.surfaceDisplacementDof(parameterDof, component));
|
||||
}
|
||||
CHECK(
|
||||
std::abs(rochePotential(movedPosition[0], movedPosition[1], movedPosition[2]) - targetPotential) <=
|
||||
potentialTolerance
|
||||
);
|
||||
}
|
||||
|
||||
auto domainDeformation = mean_field::deformation::compileDomainDeformation(
|
||||
mean_field::deformation::NodalRadialSurface{
|
||||
nodal_radial_surface_test_utils::referenceCenter(fem.mesh->SpaceDimension())
|
||||
},
|
||||
mean_field::deformation::PowerLawRadialInteriorExtension{},
|
||||
mean_field::deformation::FixedInfinityRadialVacuumExtension{}, fem
|
||||
);
|
||||
mfem::Vector volumeDisplacement(domainDeformation.volumeDisplacementSize());
|
||||
mfem::ParGridFunction displacementField(fem.displacementFes.get());
|
||||
mean_field::mapping::GridFunctionMappingEvaluator mappingEvaluator(
|
||||
*fem.domainMapperStateless, displacementField, *fem.compactificationCoordinate
|
||||
);
|
||||
|
||||
const auto sampleRepresentationError = [&](const mfem::Vector ¶meters, const auto &pointError) {
|
||||
const auto geometry = domainDeformation.buildValidatedVolumeDisplacement(parameters, volumeDisplacement);
|
||||
REQUIRE(geometry.isOrientationPreserving());
|
||||
displacementField.SetFromTrueDofs(volumeDisplacement);
|
||||
mappingEvaluator.InvalidateCache();
|
||||
|
||||
double localMaximumError = 0.0;
|
||||
double localErrorSquared = 0.0;
|
||||
double localSurfaceArea = 0.0;
|
||||
long long localSamples = 0;
|
||||
const int stellarSurfaceAttribute = nodal_radial_surface_test_utils::Schema::template boundary_attribute<
|
||||
nodal_radial_surface_test_utils::domain::StellarSurface>();
|
||||
|
||||
for (int boundaryElement = 0; boundaryElement < fem.mesh->GetNBE(); ++boundaryElement) {
|
||||
if (fem.mesh->GetBdrAttribute(boundaryElement) != stellarSurfaceAttribute) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* The stellar surface is a material interface, not an exterior
|
||||
* boundary of the complete compactified mesh. Resolve the tagged
|
||||
* boundary element to its underlying mesh face so this sampling
|
||||
* path works for both internal interfaces and true exterior
|
||||
* boundaries.
|
||||
*/
|
||||
const int face = fem.mesh->GetBdrElementFaceIndex(boundaryElement);
|
||||
REQUIRE(face >= 0);
|
||||
mfem::FaceElementTransformations *transformation = fem.mesh->GetFaceElementTransformations(face);
|
||||
REQUIRE(transformation != nullptr);
|
||||
REQUIRE(transformation->Elem1 != nullptr);
|
||||
const mfem::IntegrationRule &rule =
|
||||
mfem::IntRules.Get(transformation->GetGeometryType(), 2 * mean_field::field::Displacement::vectorOrder);
|
||||
|
||||
for (int point = 0; point < rule.GetNPoints(); ++point) {
|
||||
mean_field::mapping::FaceMappingContext context;
|
||||
const mfem::IntegrationPoint &integrationPoint = rule.IntPoint(point);
|
||||
REQUIRE(
|
||||
mappingEvaluator.EvaluateFace(
|
||||
*transformation, mean_field::mapping::FaceElementSide::element_1, integrationPoint, context
|
||||
) == mean_field::mapping::MappingStatus::valid
|
||||
);
|
||||
|
||||
const double error = pointError(context.mapping.physical_position);
|
||||
REQUIRE(std::isfinite(error));
|
||||
REQUIRE(context.physical_surface_weight > 0.0);
|
||||
localMaximumError = std::max(localMaximumError, error);
|
||||
localErrorSquared += error * error * context.physical_surface_weight;
|
||||
localSurfaceArea += context.physical_surface_weight;
|
||||
++localSamples;
|
||||
}
|
||||
}
|
||||
|
||||
double globalMaximumError = 0.0;
|
||||
double globalErrorSquared = 0.0;
|
||||
double globalSurfaceArea = 0.0;
|
||||
long long globalSamples = 0;
|
||||
MPI_Allreduce(&localMaximumError, &globalMaximumError, 1, MPI_DOUBLE, MPI_MAX, fem.mesh->GetComm());
|
||||
MPI_Allreduce(&localErrorSquared, &globalErrorSquared, 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm());
|
||||
MPI_Allreduce(&localSurfaceArea, &globalSurfaceArea, 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm());
|
||||
MPI_Allreduce(&localSamples, &globalSamples, 1, MPI_LONG_LONG, MPI_SUM, fem.mesh->GetComm());
|
||||
REQUIRE(globalSamples > 0);
|
||||
REQUIRE(globalSurfaceArea > 0.0);
|
||||
return std::array<double, 2>{globalMaximumError, std::sqrt(globalErrorSquared / globalSurfaceArea)};
|
||||
};
|
||||
|
||||
const auto rotatingErrors =
|
||||
sampleRepresentationError(rotatingParameters, [equatorialRadius, polarRadius](const mfem::Vector &position) {
|
||||
return std::abs(
|
||||
(position(0) * position(0) + position(1) * position(1)) / (equatorialRadius * equatorialRadius) +
|
||||
position(2) * position(2) / (polarRadius * polarRadius) - 1.0
|
||||
);
|
||||
});
|
||||
const auto rocheErrors =
|
||||
sampleRepresentationError(rocheParameters, [&rochePotential, targetPotential](const mfem::Vector &position) {
|
||||
return std::abs(rochePotential(position(0), position(1), position(2)) - targetPotential) /
|
||||
std::max(1.0, std::abs(targetPotential));
|
||||
});
|
||||
|
||||
INFO("Between-node oblate surface maximum level-set error = " << rotatingErrors[0]);
|
||||
INFO("Between-node oblate surface RMS level-set error = " << rotatingErrors[1]);
|
||||
INFO("Between-node Roche surface maximum normalized potential error = " << rocheErrors[0]);
|
||||
INFO("Between-node Roche surface RMS normalized potential error = " << rocheErrors[1]);
|
||||
CHECK(rotatingErrors[0] < 5.0e-3);
|
||||
CHECK(rotatingErrors[1] < 1.0e-3);
|
||||
CHECK(rocheErrors[0] < 2.0e-2);
|
||||
CHECK(rocheErrors[1] < 5.0e-3);
|
||||
|
||||
const double companionFacingRadius =
|
||||
rochePotential.firstRadialIntersection(1.0, 0.0, 0.0, targetPotential, referenceScale);
|
||||
const double companionOpposingRadius =
|
||||
rochePotential.firstRadialIntersection(-1.0, 0.0, 0.0, targetPotential, referenceScale);
|
||||
CHECK(std::abs(companionFacingRadius - companionOpposingRadius) > 1.0e-3 * referenceScale);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Nodal Radial Surface Produces The Requested Physical Radial Displacement At Every Surface Coordinate",
|
||||
tags::nodal_radial_surface_analytic
|
||||
) {
|
||||
namespace deformation = mean_field::deformation;
|
||||
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(fem.okay());
|
||||
|
||||
const deformation::PreparedNodalRadialSurface prepared = nodal_radial_surface_test_utils::makePreparedSurface(fem);
|
||||
|
||||
REQUIRE(prepared.parameterCount() > 0);
|
||||
CHECK(prepared.spatialDimension() == fem.mesh->SpaceDimension());
|
||||
CHECK(prepared.surfaceDisplacementSize() == prepared.spatialDimension() * prepared.parameterCount());
|
||||
CHECK(
|
||||
prepared.globalSurfaceDisplacementSize() ==
|
||||
static_cast<long long>(prepared.spatialDimension()) * prepared.globalParameterCount()
|
||||
);
|
||||
CHECK(
|
||||
prepared.globalSurfaceDisplacementOffset() ==
|
||||
static_cast<long long>(prepared.spatialDimension()) * prepared.globalParameterOffset()
|
||||
);
|
||||
|
||||
mfem::Vector parameters(prepared.parameterCount());
|
||||
for (int parameterDof = 0; parameterDof < parameters.Size(); ++parameterDof) {
|
||||
parameters(parameterDof) = 0.015 + 0.001 * static_cast<double>(parameterDof % 7);
|
||||
}
|
||||
|
||||
mfem::Vector displacement(prepared.surfaceDisplacementSize());
|
||||
prepared.buildSurfaceDisplacement(parameters, displacement);
|
||||
|
||||
const mfem::Vector referencePositions = nodal_radial_surface_test_utils::projectReferenceSurfacePositions(
|
||||
*fem.surfaceDeformationFes, prepared.surfaceDofMap()
|
||||
);
|
||||
|
||||
constexpr double tolerance = 2.0e-13;
|
||||
|
||||
for (int parameterDof = 0; parameterDof < prepared.parameterCount(); ++parameterDof) {
|
||||
double radiusSquared = 0.0;
|
||||
double displacementNorm2 = 0.0;
|
||||
double radialProjection = 0.0;
|
||||
|
||||
for (int component = 0; component < prepared.spatialDimension(); ++component) {
|
||||
const int surfaceDof = prepared.surfaceDisplacementDof(parameterDof, component);
|
||||
const double radialCoordinate = referencePositions(surfaceDof) - prepared.referenceCenter()(component);
|
||||
|
||||
radiusSquared += radialCoordinate * radialCoordinate;
|
||||
}
|
||||
|
||||
const double radius = std::sqrt(radiusSquared);
|
||||
REQUIRE(radius > 0.0);
|
||||
CHECK(std::abs(prepared.referenceRadius(parameterDof) - radius) <= tolerance * radius);
|
||||
|
||||
for (int component = 0; component < prepared.spatialDimension(); ++component) {
|
||||
const int surfaceDof = prepared.surfaceDisplacementDof(parameterDof, component);
|
||||
const double radialCoordinate = referencePositions(surfaceDof) - prepared.referenceCenter()(component);
|
||||
const double expectedDirection = radialCoordinate / radius;
|
||||
const double expectedDisplacement = parameters(parameterDof) * expectedDirection;
|
||||
|
||||
CHECK(std::abs(prepared.radialDirection(parameterDof, component) - expectedDirection) <= tolerance);
|
||||
CHECK(std::abs(displacement(surfaceDof) - expectedDisplacement) <= tolerance);
|
||||
|
||||
displacementNorm2 += displacement(surfaceDof) * displacement(surfaceDof);
|
||||
radialProjection += displacement(surfaceDof) * expectedDirection;
|
||||
}
|
||||
|
||||
CHECK(std::abs(std::sqrt(displacementNorm2) - parameters(parameterDof)) <= tolerance);
|
||||
CHECK(std::abs(radialProjection - parameters(parameterDof)) <= tolerance);
|
||||
|
||||
double movedRadiusSquared = 0.0;
|
||||
for (int component = 0; component < prepared.spatialDimension(); ++component) {
|
||||
const int surfaceDof = prepared.surfaceDisplacementDof(parameterDof, component);
|
||||
const double movedCoordinate =
|
||||
referencePositions(surfaceDof) + displacement(surfaceDof) - prepared.referenceCenter()(component);
|
||||
movedRadiusSquared += movedCoordinate * movedCoordinate;
|
||||
}
|
||||
CHECK(std::abs(std::sqrt(movedRadiusSquared) - (radius + parameters(parameterDof))) <= tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Nodal Radial Surface Jacobian Matches Centered Difference And Its Transpose Preserves Virtual Work",
|
||||
tags::nodal_radial_surface_linearization
|
||||
) {
|
||||
namespace deformation = mean_field::deformation;
|
||||
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(fem.okay());
|
||||
|
||||
const deformation::PreparedNodalRadialSurface prepared = nodal_radial_surface_test_utils::makePreparedSurface(fem);
|
||||
|
||||
mfem::Vector parameters(prepared.parameterCount());
|
||||
mfem::Vector direction(prepared.parameterCount());
|
||||
for (int parameterDof = 0; parameterDof < prepared.parameterCount(); ++parameterDof) {
|
||||
const double index = static_cast<double>(parameterDof + 1);
|
||||
parameters(parameterDof) = 0.013 * std::sin(0.37 * index);
|
||||
direction(parameterDof) = std::cos(0.19 * index) - 0.21 * std::sin(0.43 * index);
|
||||
}
|
||||
|
||||
constexpr double step = 1.0e-6;
|
||||
mfem::Vector plusParameters(parameters);
|
||||
mfem::Vector minusParameters(parameters);
|
||||
plusParameters.Add(step, direction);
|
||||
minusParameters.Add(-step, direction);
|
||||
|
||||
mfem::Vector plusDisplacement(prepared.surfaceDisplacementSize());
|
||||
mfem::Vector minusDisplacement(prepared.surfaceDisplacementSize());
|
||||
mfem::Vector jacobianAction(prepared.surfaceDisplacementSize());
|
||||
prepared.buildSurfaceDisplacement(plusParameters, plusDisplacement);
|
||||
prepared.buildSurfaceDisplacement(minusParameters, minusDisplacement);
|
||||
prepared.applyJacobian(parameters, direction, jacobianAction);
|
||||
|
||||
mfem::Vector centeredDifference(plusDisplacement);
|
||||
centeredDifference -= minusDisplacement;
|
||||
centeredDifference /= 2.0 * step;
|
||||
|
||||
CHECK(nodal_radial_surface_test_utils::relativeError(jacobianAction, centeredDifference) < 2.0e-11);
|
||||
|
||||
mfem::Vector surfaceDual(prepared.surfaceDisplacementSize());
|
||||
for (int surfaceDof = 0; surfaceDof < surfaceDual.Size(); ++surfaceDof) {
|
||||
const double index = static_cast<double>(surfaceDof + 1);
|
||||
surfaceDual(surfaceDof) = std::sin(0.23 * index) + 0.17 * std::cos(0.31 * index);
|
||||
}
|
||||
|
||||
mfem::Vector parameterDual(prepared.parameterCount());
|
||||
prepared.applyJacobianTranspose(parameters, surfaceDual, parameterDual);
|
||||
|
||||
const double surfaceWork = jacobianAction * surfaceDual;
|
||||
const double parameterWork = direction * parameterDual;
|
||||
const double workScale = std::max({1.0, std::abs(surfaceWork), std::abs(parameterWork)});
|
||||
CHECK(std::abs(surfaceWork - parameterWork) <= 3.0e-14 * workScale);
|
||||
|
||||
mfem::Vector pullbackDerivative(prepared.parameterCount());
|
||||
pullbackDerivative = 1.0;
|
||||
prepared.applyPullbackDerivative(parameters, direction, surfaceDual, pullbackDerivative);
|
||||
CHECK(pullbackDerivative.Norml2() == 0.0);
|
||||
|
||||
mfem::Vector wrongParameters(prepared.parameterCount() + 1);
|
||||
mfem::Vector wrongSurfaceDisplacement(prepared.surfaceDisplacementSize() + 1);
|
||||
CHECK_THROWS_AS(prepared.buildSurfaceDisplacement(wrongParameters, plusDisplacement), std::invalid_argument);
|
||||
CHECK_THROWS_AS(prepared.buildSurfaceDisplacement(parameters, wrongSurfaceDisplacement), std::invalid_argument);
|
||||
}
|
||||
491
tests/deformation/radial_extensions.cpp
Normal file
491
tests/deformation/radial_extensions.cpp
Normal file
@@ -0,0 +1,491 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace radial_extension_test_utils {
|
||||
namespace deformation = mean_field::deformation;
|
||||
namespace domain = mean_field::utils::domain;
|
||||
namespace field = mean_field::field;
|
||||
|
||||
using Schema = domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
[[nodiscard]] mfem::Vector referenceCenter(const int spatialDimension) {
|
||||
mfem::Vector center(spatialDimension);
|
||||
center = 0.0;
|
||||
return center;
|
||||
}
|
||||
|
||||
[[nodiscard]] deformation::PreparedNodalRadialSurface makePreparedSurface(const mean_field::fem::FEM &fem) {
|
||||
const field::ScalarBoundaryDofMap surfaceDofMap =
|
||||
field::make_stellar_surface_scalar_dof_map<Schema>(*fem.surfaceDeformationFes);
|
||||
const deformation::SurfaceDeformationCompilationContext context{*fem.surfaceDeformationFes, surfaceDofMap};
|
||||
return deformation::compileSurfaceDeformationPrescription(
|
||||
deformation::NodalRadialSurface{referenceCenter(fem.mesh->SpaceDimension())}, context
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] double globalInnerProduct(
|
||||
const mfem::Vector &first,
|
||||
const mfem::Vector &second,
|
||||
MPI_Comm communicator
|
||||
) {
|
||||
REQUIRE(first.Size() == second.Size());
|
||||
const double local = first * second;
|
||||
double global = 0.0;
|
||||
MPI_Allreduce(&local, &global, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
||||
return global;
|
||||
}
|
||||
|
||||
[[nodiscard]] double relativeError(
|
||||
const mfem::Vector &actual,
|
||||
const mfem::Vector &expected
|
||||
) {
|
||||
REQUIRE(actual.Size() == expected.Size());
|
||||
mfem::Vector difference(actual);
|
||||
difference -= expected;
|
||||
return difference.Norml2() / std::max(expected.Norml2(), std::numeric_limits<double>::epsilon());
|
||||
}
|
||||
|
||||
[[nodiscard]] int mfemByNodesVectorDof(
|
||||
const int scalarTrueDof,
|
||||
const int component,
|
||||
const int scalarTrueDofCount
|
||||
) {
|
||||
return scalarTrueDof + component * scalarTrueDofCount;
|
||||
}
|
||||
} // namespace radial_extension_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Radial Interior And Vacuum Extensions Advertise Closed Form Boundary Behavior",
|
||||
tags::radial_deformation_extension_validation
|
||||
) {
|
||||
namespace deformation = mean_field::deformation;
|
||||
|
||||
STATIC_CHECK(deformation::InteriorDeformationExtension<deformation::PowerLawRadialInteriorExtension>);
|
||||
STATIC_CHECK(
|
||||
deformation::PreparedInteriorDeformationExtension<deformation::PreparedPowerLawRadialInteriorExtension>
|
||||
);
|
||||
STATIC_CHECK(deformation::VacuumDeformationExtension<deformation::FixedInfinityRadialVacuumExtension>);
|
||||
STATIC_CHECK(
|
||||
deformation::PreparedVacuumDeformationExtension<deformation::PreparedFixedInfinityRadialVacuumExtension>
|
||||
);
|
||||
|
||||
const deformation::PowerLawRadialInteriorExtension interior;
|
||||
const deformation::InteriorDeformationExtensionDescriptor interiorDescriptor = interior.descriptor();
|
||||
CHECK(interior.radialPower() == 2.0);
|
||||
CHECK(interiorDescriptor.name == "PowerLawRadialInteriorExtension");
|
||||
CHECK(interiorDescriptor.linearOnReferenceGeometry);
|
||||
CHECK(interiorDescriptor.requiresRadialFoliation);
|
||||
CHECK_FALSE(interiorDescriptor.requiresAuxiliarySolve);
|
||||
CHECK(interiorDescriptor.supportsExactNewtonLinearization());
|
||||
CHECK(interiorDescriptor.centerBehavior == deformation::InteriorCenterBehavior::FixedAtReferenceCenter);
|
||||
|
||||
const deformation::FixedInfinityRadialVacuumExtension vacuum;
|
||||
const deformation::VacuumDeformationExtensionDescriptor vacuumDescriptor = vacuum.descriptor();
|
||||
CHECK(vacuumDescriptor.name == "FixedInfinityRadialVacuumExtension");
|
||||
CHECK(vacuumDescriptor.linearOnReferenceGeometry);
|
||||
CHECK(vacuumDescriptor.requiresRadialFoliation);
|
||||
CHECK_FALSE(vacuumDescriptor.requiresAuxiliarySolve);
|
||||
CHECK(vacuumDescriptor.supportsExactNewtonLinearization());
|
||||
CHECK(vacuumDescriptor.outerBoundaryBehavior == deformation::VacuumOuterBoundaryBehavior::FixedAtReferenceInfinity);
|
||||
|
||||
CHECK_THROWS_AS(deformation::PowerLawRadialInteriorExtension{0.5}, std::invalid_argument);
|
||||
CHECK_THROWS_AS(
|
||||
deformation::PowerLawRadialInteriorExtension{std::numeric_limits<double>::infinity()}, std::invalid_argument
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Radial Extensions Reproduce The Stellar Surface Fix Reference Infinity And Preserve Positive Volume Maps",
|
||||
tags::radial_deformation_extension_analytic &tags::radial_deformation_extension_mapping
|
||||
) {
|
||||
namespace deformation = mean_field::deformation;
|
||||
namespace domain = mean_field::utils::domain;
|
||||
namespace field = mean_field::field;
|
||||
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(fem.okay());
|
||||
|
||||
const deformation::PreparedNodalRadialSurface surface = radial_extension_test_utils::makePreparedSurface(fem);
|
||||
const deformation::RadialDeformationExtensionCompilationContext context =
|
||||
deformation::makeRadialDeformationExtensionCompilationContext<radial_extension_test_utils::Schema>(
|
||||
*fem.surfaceDeformationFes, *fem.displacementFes, *fem.logicalReferenceMesh
|
||||
);
|
||||
const deformation::PreparedPowerLawRadialInteriorExtension interior =
|
||||
deformation::compileInteriorDeformationExtension(deformation::PowerLawRadialInteriorExtension{}, context);
|
||||
const deformation::PreparedPowerLawRadialInteriorExtension cubicInterior =
|
||||
deformation::compileInteriorDeformationExtension(deformation::PowerLawRadialInteriorExtension{3.0}, context);
|
||||
const deformation::PreparedFixedInfinityRadialVacuumExtension vacuum =
|
||||
deformation::compileVacuumDeformationExtension(deformation::FixedInfinityRadialVacuumExtension{}, context);
|
||||
|
||||
REQUIRE(interior.surfaceDisplacementSize() == surface.surfaceDisplacementSize());
|
||||
REQUIRE(vacuum.surfaceDisplacementSize() == surface.surfaceDisplacementSize());
|
||||
REQUIRE(interior.interiorDisplacementSize() == fem.displacementFes->GetTrueVSize());
|
||||
REQUIRE(vacuum.vacuumDisplacementSize() == fem.displacementFes->GetTrueVSize());
|
||||
|
||||
constexpr double surfaceAmplitude = 0.02;
|
||||
mfem::Vector parameters(surface.parameterCount());
|
||||
parameters = surfaceAmplitude;
|
||||
mfem::Vector surfaceDisplacement(surface.surfaceDisplacementSize());
|
||||
surface.buildSurfaceDisplacement(parameters, surfaceDisplacement);
|
||||
|
||||
mfem::Vector interiorDisplacement(interior.interiorDisplacementSize());
|
||||
mfem::Vector vacuumDisplacement(vacuum.vacuumDisplacementSize());
|
||||
interior.buildInteriorDisplacement(surfaceDisplacement, interiorDisplacement);
|
||||
vacuum.buildVacuumDisplacement(surfaceDisplacement, vacuumDisplacement);
|
||||
|
||||
const int spatialDimension = fem.mesh->SpaceDimension();
|
||||
const field::ScalarBoundaryDofMap stellarSurfaceMap =
|
||||
field::make_scalar_boundary_dof_map<domain::StellarSurface, radial_extension_test_utils::Schema>(
|
||||
*fem.surfaceDeformationFes
|
||||
);
|
||||
const field::ScalarBoundaryDofMap infinitySurfaceMap =
|
||||
field::make_scalar_boundary_dof_map<domain::InfinitySurface, radial_extension_test_utils::Schema>(
|
||||
*fem.surfaceDeformationFes
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
deformation::RadialDeformationExtensionCompilationContext(
|
||||
*fem.surfaceDeformationFes, *fem.displacementFes, *fem.logicalReferenceMesh, stellarSurfaceMap,
|
||||
stellarSurfaceMap,
|
||||
domain::make_attribute_marker<domain::Stellar, radial_extension_test_utils::Schema>(*fem.mesh),
|
||||
domain::make_attribute_marker<domain::Vacuum, radial_extension_test_utils::Schema>(*fem.mesh),
|
||||
radial_extension_test_utils::Schema::template boundary_attribute<domain::StellarSurface>(),
|
||||
radial_extension_test_utils::Schema::template boundary_attribute<domain::StellarSurface>()
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
deformation::makeRadialDeformationExtensionCompilationContext<radial_extension_test_utils::Schema>(
|
||||
*fem.surfaceDeformationFes, *fem.displacementFes, *fem.mesh
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
const double stellarSurfaceRadius = context.stellarSurfaceLogicalRadius();
|
||||
const double infinitySurfaceRadius = context.infinitySurfaceLogicalRadius();
|
||||
REQUIRE(stellarSurfaceRadius > 0.0);
|
||||
REQUIRE(infinitySurfaceRadius > stellarSurfaceRadius);
|
||||
|
||||
constexpr double tolerance = 2.0e-11;
|
||||
bool hasInterpolatedSurfacePoint = false;
|
||||
for (int scalarDof = 0; scalarDof < interior.scalarTrueDofCount(); ++scalarDof) {
|
||||
const double referenceRadius = context.logicalRadius(scalarDof);
|
||||
const int interpolationEntryCount = context.surfaceInterpolationEntryCount(scalarDof);
|
||||
|
||||
if (interpolationEntryCount == 0) {
|
||||
CHECK(referenceRadius <= 64.0 * std::numeric_limits<double>::epsilon() * infinitySurfaceRadius);
|
||||
} else {
|
||||
double interpolationWeightSum = 0.0;
|
||||
for (int entry = 0; entry < interpolationEntryCount; ++entry) {
|
||||
const int surfaceCoordinate = context.surfaceGlobalCoordinate(scalarDof, entry);
|
||||
CHECK(surfaceCoordinate >= 0);
|
||||
CHECK(surfaceCoordinate < stellarSurfaceMap.global_size());
|
||||
interpolationWeightSum += context.surfaceInterpolationWeight(scalarDof, entry);
|
||||
}
|
||||
CHECK(std::abs(interpolationWeightSum - 1.0) <= tolerance);
|
||||
hasInterpolatedSurfacePoint |= interpolationEntryCount > 1;
|
||||
}
|
||||
|
||||
if (interior.hasStellarSupport(scalarDof)) {
|
||||
const double expectedWeight =
|
||||
referenceRadius == 0.0 ? 0.0 : std::pow(referenceRadius / stellarSurfaceRadius, 2.0);
|
||||
CHECK(std::abs(interior.radialWeight(scalarDof) - expectedWeight) <= tolerance);
|
||||
const double expectedCubicWeight =
|
||||
referenceRadius == 0.0 ? 0.0 : std::pow(referenceRadius / stellarSurfaceRadius, 3.0);
|
||||
CHECK(std::abs(cubicInterior.radialWeight(scalarDof) - expectedCubicWeight) <= tolerance);
|
||||
} else {
|
||||
CHECK(interior.radialWeight(scalarDof) == 0.0);
|
||||
for (int component = 0; component < spatialDimension; ++component) {
|
||||
const int volumeVectorDof = radial_extension_test_utils::mfemByNodesVectorDof(
|
||||
scalarDof, component, interior.scalarTrueDofCount()
|
||||
);
|
||||
CHECK(interiorDisplacement(volumeVectorDof) == 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
if (vacuum.hasVacuumSupport(scalarDof)) {
|
||||
const double expectedWeight =
|
||||
(infinitySurfaceRadius - referenceRadius) / (infinitySurfaceRadius - stellarSurfaceRadius);
|
||||
CHECK(std::abs(vacuum.radialWeight(scalarDof) - expectedWeight) <= tolerance);
|
||||
} else {
|
||||
CHECK(vacuum.radialWeight(scalarDof) == 0.0);
|
||||
for (int component = 0; component < spatialDimension; ++component) {
|
||||
const int volumeVectorDof = radial_extension_test_utils::mfemByNodesVectorDof(
|
||||
scalarDof, component, vacuum.scalarTrueDofCount()
|
||||
);
|
||||
CHECK(vacuumDisplacement(volumeVectorDof) == 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
CHECK(hasInterpolatedSurfacePoint);
|
||||
|
||||
const double componentValues[3]{1.25, -0.75, 2.5};
|
||||
mfem::Vector constantSurfaceDisplacement(surface.surfaceDisplacementSize());
|
||||
for (int surfaceDof = 0; surfaceDof < surface.parameterCount(); ++surfaceDof) {
|
||||
for (int component = 0; component < spatialDimension; ++component) {
|
||||
constantSurfaceDisplacement(spatialDimension * surfaceDof + component) = componentValues[component];
|
||||
}
|
||||
}
|
||||
mfem::Vector constantInteriorDisplacement(interior.interiorDisplacementSize());
|
||||
interior.buildInteriorDisplacement(constantSurfaceDisplacement, constantInteriorDisplacement);
|
||||
|
||||
mfem::Vector radialWeightTrueDofs(interior.scalarTrueDofCount());
|
||||
for (int scalarDof = 0; scalarDof < interior.scalarTrueDofCount(); ++scalarDof) {
|
||||
radialWeightTrueDofs(scalarDof) =
|
||||
interior.hasStellarSupport(scalarDof) ? interior.radialWeight(scalarDof) : 0.0;
|
||||
}
|
||||
mfem::ParGridFunction radialWeightField(fem.surfaceDeformationFes.get());
|
||||
mfem::ParGridFunction constantVectorField(fem.displacementFes.get());
|
||||
radialWeightField.SetFromTrueDofs(radialWeightTrueDofs);
|
||||
constantVectorField.SetFromTrueDofs(constantInteriorDisplacement);
|
||||
|
||||
const mfem::Array<int> stellarMarker =
|
||||
domain::make_attribute_marker<domain::Stellar, radial_extension_test_utils::Schema>(*fem.mesh);
|
||||
int sampledStellarElement = -1;
|
||||
for (int element = 0; element < fem.mesh->GetNE() && sampledStellarElement < 0; ++element) {
|
||||
const int attribute = fem.mesh->GetAttribute(element);
|
||||
if (attribute > 0 && attribute <= stellarMarker.Size() && stellarMarker[attribute - 1] != 0) {
|
||||
sampledStellarElement = element;
|
||||
}
|
||||
}
|
||||
REQUIRE(sampledStellarElement >= 0);
|
||||
const mfem::IntegrationPoint &samplePoint =
|
||||
mfem::Geometries.GetCenter(fem.mesh->GetElementBaseGeometry(sampledStellarElement));
|
||||
const double sampledRadialWeight = radialWeightField.GetValue(sampledStellarElement, samplePoint);
|
||||
mfem::Vector sampledVector(spatialDimension);
|
||||
constantVectorField.GetVectorValue(sampledStellarElement, samplePoint, sampledVector);
|
||||
for (int component = 0; component < spatialDimension; ++component) {
|
||||
CHECK(std::abs(sampledVector(component) - componentValues[component] * sampledRadialWeight) <= tolerance);
|
||||
}
|
||||
|
||||
mfem::Vector arbitrarySurfaceDisplacement(surface.surfaceDisplacementSize());
|
||||
for (int dof = 0; dof < arbitrarySurfaceDisplacement.Size(); ++dof) {
|
||||
const double index = static_cast<double>(dof + 1);
|
||||
arbitrarySurfaceDisplacement(dof) = 0.03 * std::sin(0.29 * index) - 0.01 * std::cos(0.17 * index);
|
||||
}
|
||||
mfem::Vector arbitraryInteriorDisplacement(interior.interiorDisplacementSize());
|
||||
mfem::Vector arbitraryVacuumDisplacement(vacuum.vacuumDisplacementSize());
|
||||
interior.buildInteriorDisplacement(arbitrarySurfaceDisplacement, arbitraryInteriorDisplacement);
|
||||
vacuum.buildVacuumDisplacement(arbitrarySurfaceDisplacement, arbitraryVacuumDisplacement);
|
||||
|
||||
for (int surfaceDof = 0; surfaceDof < stellarSurfaceMap.local_size(); ++surfaceDof) {
|
||||
const int scalarDof = stellarSurfaceMap.volume_true_dof(surfaceDof);
|
||||
for (int component = 0; component < spatialDimension; ++component) {
|
||||
const int surfaceVectorDof = spatialDimension * surfaceDof + component;
|
||||
const int volumeVectorDof =
|
||||
radial_extension_test_utils::mfemByNodesVectorDof(scalarDof, component, interior.scalarTrueDofCount());
|
||||
CHECK(
|
||||
std::abs(
|
||||
arbitraryInteriorDisplacement(volumeVectorDof) - arbitrarySurfaceDisplacement(surfaceVectorDof)
|
||||
) <= tolerance
|
||||
);
|
||||
CHECK(
|
||||
std::abs(
|
||||
arbitraryVacuumDisplacement(volumeVectorDof) - arbitrarySurfaceDisplacement(surfaceVectorDof)
|
||||
) <= tolerance
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (int infinityDof = 0; infinityDof < infinitySurfaceMap.local_size(); ++infinityDof) {
|
||||
const int scalarDof = infinitySurfaceMap.volume_true_dof(infinityDof);
|
||||
for (int component = 0; component < spatialDimension; ++component) {
|
||||
const int volumeVectorDof =
|
||||
radial_extension_test_utils::mfemByNodesVectorDof(scalarDof, component, vacuum.scalarTrueDofCount());
|
||||
CHECK(std::abs(vacuumDisplacement(volumeVectorDof)) <= tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
mfem::Vector combinedDisplacement(interiorDisplacement);
|
||||
for (int scalarDof = 0; scalarDof < vacuum.scalarTrueDofCount(); ++scalarDof) {
|
||||
if (!vacuum.hasVacuumSupport(scalarDof) || interior.hasStellarSupport(scalarDof)) {
|
||||
continue;
|
||||
}
|
||||
for (int component = 0; component < spatialDimension; ++component) {
|
||||
const int volumeVectorDof =
|
||||
radial_extension_test_utils::mfemByNodesVectorDof(scalarDof, component, vacuum.scalarTrueDofCount());
|
||||
combinedDisplacement(volumeVectorDof) = vacuumDisplacement(volumeVectorDof);
|
||||
}
|
||||
}
|
||||
|
||||
mfem::ParGridFunction displacement(fem.displacementFes.get());
|
||||
displacement.SetFromTrueDofs(combinedDisplacement);
|
||||
double localMinimumDeterminant = std::numeric_limits<double>::infinity();
|
||||
for (int element = 0; element < fem.mesh->GetNE(); ++element) {
|
||||
mfem::ElementTransformation *transformation = fem.mesh->GetElementTransformation(element);
|
||||
const mfem::FiniteElement *finiteElement = fem.displacementFes->GetFE(element);
|
||||
const mfem::IntegrationRule &rule =
|
||||
mfem::IntRules.Get(transformation->GetGeometryType(), finiteElement->GetOrder() + 2);
|
||||
|
||||
for (int point = 0; point < rule.GetNPoints(); ++point) {
|
||||
transformation->SetIntPoint(&rule.IntPoint(point));
|
||||
mfem::DenseMatrix displacementGradient;
|
||||
displacement.GetVectorGradient(*transformation, displacementGradient);
|
||||
for (int component = 0; component < spatialDimension; ++component) {
|
||||
displacementGradient(component, component) += 1.0;
|
||||
}
|
||||
localMinimumDeterminant = std::min(localMinimumDeterminant, displacementGradient.Det());
|
||||
}
|
||||
}
|
||||
double globalMinimumDeterminant = 0.0;
|
||||
MPI_Allreduce(&localMinimumDeterminant, &globalMinimumDeterminant, 1, MPI_DOUBLE, MPI_MIN, fem.mesh->GetComm());
|
||||
CHECK(globalMinimumDeterminant > 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Radial Extension Jacobians Match Centered Differences And Their Transposes Preserve Virtual Work",
|
||||
tags::radial_deformation_extension_linearization
|
||||
) {
|
||||
namespace deformation = mean_field::deformation;
|
||||
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(fem.okay());
|
||||
|
||||
const deformation::RadialDeformationExtensionCompilationContext context =
|
||||
deformation::makeRadialDeformationExtensionCompilationContext<radial_extension_test_utils::Schema>(
|
||||
*fem.surfaceDeformationFes, *fem.displacementFes, *fem.logicalReferenceMesh
|
||||
);
|
||||
const deformation::PreparedPowerLawRadialInteriorExtension interior =
|
||||
deformation::compileInteriorDeformationExtension(deformation::PowerLawRadialInteriorExtension{}, context);
|
||||
const deformation::PreparedFixedInfinityRadialVacuumExtension vacuum =
|
||||
deformation::compileVacuumDeformationExtension(deformation::FixedInfinityRadialVacuumExtension{}, context);
|
||||
|
||||
mfem::Vector surface(interior.surfaceDisplacementSize());
|
||||
mfem::Vector direction(interior.surfaceDisplacementSize());
|
||||
for (int dof = 0; dof < surface.Size(); ++dof) {
|
||||
const double index = static_cast<double>(dof + 1);
|
||||
surface(dof) = 0.01 * std::sin(0.17 * index);
|
||||
direction(dof) = std::cos(0.13 * index) - 0.2 * std::sin(0.31 * index);
|
||||
}
|
||||
|
||||
constexpr double step = 1.0e-6;
|
||||
mfem::Vector plusSurface(surface);
|
||||
mfem::Vector minusSurface(surface);
|
||||
plusSurface.Add(step, direction);
|
||||
minusSurface.Add(-step, direction);
|
||||
|
||||
auto checkLinearization = [&](const auto &prepared, const int volumeSize, const auto &build) {
|
||||
mfem::Vector plus(volumeSize);
|
||||
mfem::Vector minus(volumeSize);
|
||||
mfem::Vector jacobian(volumeSize);
|
||||
build(prepared, plusSurface, plus);
|
||||
build(prepared, minusSurface, minus);
|
||||
prepared.applyJacobian(surface, direction, jacobian);
|
||||
|
||||
mfem::Vector centeredDifference(plus);
|
||||
centeredDifference -= minus;
|
||||
centeredDifference /= 2.0 * step;
|
||||
CHECK(radial_extension_test_utils::relativeError(jacobian, centeredDifference) < 2.0e-10);
|
||||
|
||||
mfem::Vector volumeDual(volumeSize);
|
||||
for (int dof = 0; dof < volumeDual.Size(); ++dof) {
|
||||
const double index = static_cast<double>(dof + 1);
|
||||
volumeDual(dof) = std::sin(0.07 * index) + 0.3 * std::cos(0.11 * index);
|
||||
}
|
||||
mfem::Vector surfaceDual(surface.Size());
|
||||
prepared.applyJacobianTranspose(surface, volumeDual, surfaceDual);
|
||||
|
||||
const double volumeWork =
|
||||
radial_extension_test_utils::globalInnerProduct(jacobian, volumeDual, fem.mesh->GetComm());
|
||||
const double surfaceWork =
|
||||
radial_extension_test_utils::globalInnerProduct(direction, surfaceDual, fem.mesh->GetComm());
|
||||
const double scale = std::max({1.0, std::abs(volumeWork), std::abs(surfaceWork)});
|
||||
CHECK(std::abs(volumeWork - surfaceWork) <= 5.0e-13 * scale);
|
||||
|
||||
mfem::Vector pullback(surface.Size());
|
||||
pullback = 1.0;
|
||||
prepared.applyPullbackDerivative(surface, direction, volumeDual, pullback);
|
||||
CHECK(pullback.Norml2() == 0.0);
|
||||
};
|
||||
|
||||
checkLinearization(
|
||||
interior, interior.interiorDisplacementSize(),
|
||||
[](const auto &prepared, const mfem::Vector &input, mfem::Vector &output) {
|
||||
prepared.buildInteriorDisplacement(input, output);
|
||||
}
|
||||
);
|
||||
checkLinearization(
|
||||
vacuum, vacuum.vacuumDisplacementSize(),
|
||||
[](const auto &prepared, const mfem::Vector &input, mfem::Vector &output) {
|
||||
prepared.buildVacuumDisplacement(input, output);
|
||||
}
|
||||
);
|
||||
|
||||
mfem::Vector wrongSurface(surface.Size() + 1);
|
||||
mfem::Vector interiorOutput(interior.interiorDisplacementSize());
|
||||
mfem::Vector vacuumOutput(vacuum.vacuumDisplacementSize());
|
||||
CHECK_THROWS_AS(interior.buildInteriorDisplacement(wrongSurface, interiorOutput), std::invalid_argument);
|
||||
CHECK_THROWS_AS(vacuum.buildVacuumDisplacement(wrongSurface, vacuumOutput), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Logical Radial Deformation Remains Conforming And Orientation Preserving After Mesh Refinement",
|
||||
tags::radial_deformation_extension_mapping
|
||||
) {
|
||||
namespace deformation = mean_field::deformation;
|
||||
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 1);
|
||||
REQUIRE(fem.okay());
|
||||
|
||||
deformation::PreparedNodalRadialSurface surface = radial_extension_test_utils::makePreparedSurface(fem);
|
||||
const deformation::RadialDeformationExtensionCompilationContext context =
|
||||
deformation::makeRadialDeformationExtensionCompilationContext<radial_extension_test_utils::Schema>(
|
||||
*fem.surfaceDeformationFes, *fem.displacementFes, *fem.logicalReferenceMesh
|
||||
);
|
||||
deformation::PreparedPowerLawRadialInteriorExtension interior =
|
||||
deformation::compileInteriorDeformationExtension(deformation::PowerLawRadialInteriorExtension{}, context);
|
||||
deformation::PreparedFixedInfinityRadialVacuumExtension vacuum =
|
||||
deformation::compileVacuumDeformationExtension(deformation::FixedInfinityRadialVacuumExtension{}, context);
|
||||
auto prepared = deformation::composePreparedDomainDeformation(
|
||||
std::move(surface), std::move(interior), std::move(vacuum), *fem.surfaceDeformationFes, *fem.displacementFes,
|
||||
*fem.logicalReferenceMesh
|
||||
);
|
||||
|
||||
mfem::Vector parameters(prepared.parameterCount());
|
||||
mfem::Vector direction(prepared.parameterCount());
|
||||
for (int parameter = 0; parameter < parameters.Size(); ++parameter) {
|
||||
const double index = static_cast<double>(parameter + 1);
|
||||
const double polarDirection = prepared.surfaceDeformationPrescription().radialDirection(parameter, 2);
|
||||
const double quadrupoleValue = 0.5 * (3.0 * polarDirection * polarDirection - 1.0);
|
||||
parameters(parameter) = 0.006 - 0.001 * quadrupoleValue;
|
||||
direction(parameter) = std::sin(0.07 * index) - 0.4 * std::cos(0.13 * index);
|
||||
}
|
||||
|
||||
mfem::Vector volumeDisplacement(prepared.volumeDisplacementSize());
|
||||
prepared.buildVolumeDisplacement(parameters, volumeDisplacement);
|
||||
const deformation::DomainDeformationGeometryReport geometry = prepared.inspectMappedGeometry(volumeDisplacement);
|
||||
CAPTURE(geometry.minimumJacobianDeterminant);
|
||||
REQUIRE(geometry.isOrientationPreserving());
|
||||
|
||||
mfem::Vector jacobianAction(prepared.volumeDisplacementSize());
|
||||
mfem::Vector volumeDual(prepared.volumeDisplacementSize());
|
||||
for (int dof = 0; dof < volumeDual.Size(); ++dof) {
|
||||
const double index = static_cast<double>(dof + 1);
|
||||
volumeDual(dof) = std::cos(0.017 * index) + 0.2 * std::sin(0.023 * index);
|
||||
}
|
||||
prepared.applyJacobian(parameters, direction, jacobianAction);
|
||||
mfem::Vector parameterDual(prepared.parameterCount());
|
||||
prepared.applyJacobianTranspose(parameters, volumeDual, parameterDual);
|
||||
const double volumeWork =
|
||||
radial_extension_test_utils::globalInnerProduct(jacobianAction, volumeDual, fem.mesh->GetComm());
|
||||
const double parameterWork =
|
||||
radial_extension_test_utils::globalInnerProduct(direction, parameterDual, fem.mesh->GetComm());
|
||||
CHECK(
|
||||
std::abs(volumeWork - parameterWork) <= 2.0e-12 * std::max({1.0, std::abs(volumeWork), std::abs(parameterWork)})
|
||||
);
|
||||
}
|
||||
302
tests/deformation/surface_scalar_dof_map.cpp
Normal file
302
tests/deformation/surface_scalar_dof_map.cpp
Normal file
@@ -0,0 +1,302 @@
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace surface_scalar_dof_test_utils {
|
||||
namespace domain = mean_field::utils::domain;
|
||||
namespace field = mean_field::field;
|
||||
|
||||
using DefaultSchema = domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
using FirstBoundarySchema = domain::DomainSchema<
|
||||
domain::MaterialList<>,
|
||||
domain::BoundaryList<domain::BoundaryAttribute<domain::StellarSurface, 1>>,
|
||||
domain::RelationList<>>;
|
||||
|
||||
using SecondBoundarySchema = domain::DomainSchema<
|
||||
domain::MaterialList<>,
|
||||
domain::BoundaryList<domain::BoundaryAttribute<domain::StellarSurface, 2>>,
|
||||
domain::RelationList<>>;
|
||||
|
||||
using MissingBoundarySchema =
|
||||
domain::DomainSchema<domain::MaterialList<>, domain::BoundaryList<>, domain::RelationList<>>;
|
||||
|
||||
[[nodiscard]] mfem::Array<int> make_array(const std::initializer_list<int> values) {
|
||||
mfem::Array<int> result(static_cast<int>(values.size()));
|
||||
int index = 0;
|
||||
for (const int value : values) {
|
||||
result[index++] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Array<int> expected_boundary_true_dofs(
|
||||
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||
const int boundaryAttribute
|
||||
) {
|
||||
const mfem::Mesh *mesh = finiteElementSpace.GetMesh();
|
||||
REQUIRE(mesh != nullptr);
|
||||
REQUIRE(boundaryAttribute > 0);
|
||||
REQUIRE(boundaryAttribute <= mesh->bdr_attributes.Max());
|
||||
|
||||
mfem::Array<int> boundaryMarker(mesh->bdr_attributes.Max());
|
||||
boundaryMarker = 0;
|
||||
boundaryMarker[boundaryAttribute - 1] = 1;
|
||||
|
||||
mfem::Array<int> expected;
|
||||
finiteElementSpace.GetEssentialTrueDofs(boundaryMarker, expected);
|
||||
return expected;
|
||||
}
|
||||
|
||||
void check_equal(
|
||||
const mfem::Array<int> &actual,
|
||||
const mfem::Array<int> &expected
|
||||
) {
|
||||
REQUIRE(actual.Size() == expected.Size());
|
||||
for (int index = 0; index < actual.Size(); ++index) {
|
||||
CAPTURE(index);
|
||||
CHECK(actual[index] == expected[index]);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Mesh make_boundary_mesh() {
|
||||
return mfem::Mesh::MakeCartesian2D(6, 4, mfem::Element::QUADRILATERAL, true, 3.0, 2.0);
|
||||
}
|
||||
|
||||
template <typename SchemaT>
|
||||
concept CanMakeStellarSurfaceMap = requires(const mfem::ParFiniteElementSpace &finiteElementSpace) {
|
||||
field::make_stellar_surface_scalar_dof_map<SchemaT>(finiteElementSpace);
|
||||
};
|
||||
} // namespace surface_scalar_dof_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Scalar Boundary DOF Map Preserves Canonical Surface Coordinate Indexing",
|
||||
tags::surface_deformation_dof_unit
|
||||
) {
|
||||
namespace field = mean_field::field;
|
||||
|
||||
const field::ScalarBoundaryDofMap map(8, surface_scalar_dof_test_utils::make_array({1, 3, 6}), 5, 12);
|
||||
|
||||
CHECK(map.volume_true_dof_size() == 8);
|
||||
CHECK(map.local_size() == 3);
|
||||
CHECK(map.global_offset() == 5);
|
||||
CHECK(map.global_size() == 12);
|
||||
CHECK_FALSE(map.empty());
|
||||
|
||||
CHECK(map.volume_true_dof(0) == 1);
|
||||
CHECK(map.volume_true_dof(1) == 3);
|
||||
CHECK(map.volume_true_dof(2) == 6);
|
||||
CHECK(map.global_boundary_dof(0) == 5);
|
||||
CHECK(map.global_boundary_dof(1) == 6);
|
||||
CHECK(map.global_boundary_dof(2) == 7);
|
||||
|
||||
REQUIRE(map.local_boundary_dof(1).has_value());
|
||||
REQUIRE(map.local_boundary_dof(3).has_value());
|
||||
REQUIRE(map.local_boundary_dof(6).has_value());
|
||||
CHECK(*map.local_boundary_dof(1) == 0);
|
||||
CHECK(*map.local_boundary_dof(3) == 1);
|
||||
CHECK(*map.local_boundary_dof(6) == 2);
|
||||
CHECK_FALSE(map.local_boundary_dof(0).has_value());
|
||||
|
||||
mfem::Vector volumeValues(8);
|
||||
for (int trueDof = 0; trueDof < volumeValues.Size(); ++trueDof) {
|
||||
volumeValues(trueDof) = 10.0 + trueDof;
|
||||
}
|
||||
|
||||
const mfem::Vector boundaryValues = map.gather(volumeValues);
|
||||
REQUIRE(boundaryValues.Size() == map.local_size());
|
||||
CHECK(boundaryValues(0) == 11.0);
|
||||
CHECK(boundaryValues(1) == 13.0);
|
||||
CHECK(boundaryValues(2) == 16.0);
|
||||
|
||||
const mfem::Vector scattered = map.scatter(boundaryValues);
|
||||
REQUIRE(scattered.Size() == map.volume_true_dof_size());
|
||||
for (int trueDof = 0; trueDof < scattered.Size(); ++trueDof) {
|
||||
const std::optional<int> localBoundaryDof = map.local_boundary_dof(trueDof);
|
||||
if (localBoundaryDof.has_value()) {
|
||||
CHECK(scattered(trueDof) == boundaryValues(*localBoundaryDof));
|
||||
} else {
|
||||
CHECK(scattered(trueDof) == 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
(field::ScalarBoundaryDofMap(8, surface_scalar_dof_test_utils::make_array({3, 1}), 0, 2)), std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(field::ScalarBoundaryDofMap(8, surface_scalar_dof_test_utils::make_array({1, 3, 6}), -1, 3)),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(field::ScalarBoundaryDofMap(8, surface_scalar_dof_test_utils::make_array({1, 3, 6}), 4, 6)),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(map.global_boundary_dof(-1), std::out_of_range);
|
||||
CHECK_THROWS_AS(map.global_boundary_dof(map.local_size()), std::out_of_range);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Surface Coordinates Use The Scalar Displacement Basis And Only Surface DOFs",
|
||||
tags::surface_deformation_dof_topology
|
||||
) {
|
||||
namespace domain = mean_field::utils::domain;
|
||||
namespace field = mean_field::field;
|
||||
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(fem.okay());
|
||||
|
||||
REQUIRE(fem.surfaceDeformationFes != nullptr);
|
||||
REQUIRE(fem.displacementFes != nullptr);
|
||||
CHECK(fem.surfaceDeformationFes->GetVDim() == 1);
|
||||
CHECK(fem.surfaceDeformationFes->FEColl() == fem.displacementFes->FEColl());
|
||||
CHECK(fem.surfaceDeformationFes.get() != fem.enthalpyFes.get());
|
||||
|
||||
const field::ScalarBoundaryDofMap surfaceCoordinates =
|
||||
field::make_stellar_surface_scalar_dof_map<surface_scalar_dof_test_utils::DefaultSchema>(
|
||||
*fem.surfaceDeformationFes
|
||||
);
|
||||
|
||||
const mfem::Array<int> expected = surface_scalar_dof_test_utils::expected_boundary_true_dofs(
|
||||
*fem.surfaceDeformationFes,
|
||||
surface_scalar_dof_test_utils::DefaultSchema::template boundary_attribute<domain::StellarSurface>()
|
||||
);
|
||||
surface_scalar_dof_test_utils::check_equal(surfaceCoordinates.boundary_true_dofs(), expected);
|
||||
|
||||
CHECK(surfaceCoordinates.global_size() > 0);
|
||||
CHECK(surfaceCoordinates.global_size() < fem.surfaceDeformationFes->GlobalTrueVSize());
|
||||
|
||||
const field::FieldDofMap displacementMap =
|
||||
field::make_field_dof_map<field::Displacement, surface_scalar_dof_test_utils::DefaultSchema>(
|
||||
*fem.displacementFes
|
||||
);
|
||||
const field::FieldBoundaryDofMap vectorSurface = field::make_field_boundary_dof_map<
|
||||
field::Displacement, domain::StellarSurface, surface_scalar_dof_test_utils::DefaultSchema>(
|
||||
*fem.displacementFes, displacementMap
|
||||
);
|
||||
|
||||
long long localVectorSurfaceSize = vectorSurface.size();
|
||||
long long globalVectorSurfaceSize = 0;
|
||||
MPI_Allreduce(
|
||||
&localVectorSurfaceSize, &globalVectorSurfaceSize, 1, MPI_LONG_LONG, MPI_SUM,
|
||||
fem.surfaceDeformationFes->GetComm()
|
||||
);
|
||||
|
||||
CHECK(
|
||||
globalVectorSurfaceSize == static_cast<long long>(fem.mesh->SpaceDimension()) * surfaceCoordinates.global_size()
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Scalar Boundary DOF Resolution Uses Semantic Schema Boundary Attributes",
|
||||
tags::surface_deformation_dof_schema
|
||||
) {
|
||||
namespace domain = mean_field::utils::domain;
|
||||
namespace field = mean_field::field;
|
||||
|
||||
STATIC_CHECK(
|
||||
surface_scalar_dof_test_utils::CanMakeStellarSurfaceMap<surface_scalar_dof_test_utils::FirstBoundarySchema>
|
||||
);
|
||||
STATIC_CHECK_FALSE(
|
||||
surface_scalar_dof_test_utils::CanMakeStellarSurfaceMap<surface_scalar_dof_test_utils::MissingBoundarySchema>
|
||||
);
|
||||
|
||||
mfem::Mesh serialMesh = surface_scalar_dof_test_utils::make_boundary_mesh();
|
||||
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
|
||||
mfem::H1_FECollection finiteElementCollection(2, mesh.Dimension());
|
||||
mfem::ParFiniteElementSpace finiteElementSpace(&mesh, &finiteElementCollection);
|
||||
|
||||
const field::ScalarBoundaryDofMap firstBoundary =
|
||||
field::make_stellar_surface_scalar_dof_map<surface_scalar_dof_test_utils::FirstBoundarySchema>(
|
||||
finiteElementSpace
|
||||
);
|
||||
const field::ScalarBoundaryDofMap secondBoundary =
|
||||
field::make_stellar_surface_scalar_dof_map<surface_scalar_dof_test_utils::SecondBoundarySchema>(
|
||||
finiteElementSpace
|
||||
);
|
||||
|
||||
const mfem::Array<int> expectedFirst = surface_scalar_dof_test_utils::expected_boundary_true_dofs(
|
||||
finiteElementSpace,
|
||||
surface_scalar_dof_test_utils::FirstBoundarySchema::template boundary_attribute<domain::StellarSurface>()
|
||||
);
|
||||
const mfem::Array<int> expectedSecond = surface_scalar_dof_test_utils::expected_boundary_true_dofs(
|
||||
finiteElementSpace,
|
||||
surface_scalar_dof_test_utils::SecondBoundarySchema::template boundary_attribute<domain::StellarSurface>()
|
||||
);
|
||||
|
||||
surface_scalar_dof_test_utils::check_equal(firstBoundary.boundary_true_dofs(), expectedFirst);
|
||||
surface_scalar_dof_test_utils::check_equal(secondBoundary.boundary_true_dofs(), expectedSecond);
|
||||
|
||||
bool localMapsDiffer = firstBoundary.local_size() != secondBoundary.local_size();
|
||||
if (!localMapsDiffer) {
|
||||
for (int localDof = 0; localDof < firstBoundary.local_size(); ++localDof) {
|
||||
if (firstBoundary.volume_true_dof(localDof) != secondBoundary.volume_true_dof(localDof)) {
|
||||
localMapsDiffer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int localDifference = localMapsDiffer ? 1 : 0;
|
||||
int globalDifference = 0;
|
||||
MPI_Allreduce(&localDifference, &globalDifference, 1, MPI_INT, MPI_MAX, finiteElementSpace.GetComm());
|
||||
CHECK(globalDifference == 1);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Scalar Boundary Coordinates Have Deterministic Contiguous Parallel Ordering",
|
||||
tags::surface_deformation_dof_parallel
|
||||
) {
|
||||
namespace field = mean_field::field;
|
||||
|
||||
mfem::Mesh serialMesh = surface_scalar_dof_test_utils::make_boundary_mesh();
|
||||
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
|
||||
mfem::H1_FECollection finiteElementCollection(2, mesh.Dimension());
|
||||
mfem::ParFiniteElementSpace finiteElementSpace(&mesh, &finiteElementCollection);
|
||||
|
||||
const field::ScalarBoundaryDofMap first =
|
||||
field::make_stellar_surface_scalar_dof_map<surface_scalar_dof_test_utils::FirstBoundarySchema>(
|
||||
finiteElementSpace
|
||||
);
|
||||
const field::ScalarBoundaryDofMap second =
|
||||
field::make_stellar_surface_scalar_dof_map<surface_scalar_dof_test_utils::FirstBoundarySchema>(
|
||||
finiteElementSpace
|
||||
);
|
||||
|
||||
surface_scalar_dof_test_utils::check_equal(first.boundary_true_dofs(), second.boundary_true_dofs());
|
||||
CHECK(first.global_offset() == second.global_offset());
|
||||
CHECK(first.global_size() == second.global_size());
|
||||
|
||||
for (int localDof = 0; localDof < first.local_size(); ++localDof) {
|
||||
CAPTURE(localDof);
|
||||
CHECK(first.global_boundary_dof(localDof) == first.global_offset() + localDof);
|
||||
if (localDof > 0) {
|
||||
CHECK(first.volume_true_dof(localDof - 1) < first.volume_true_dof(localDof));
|
||||
}
|
||||
}
|
||||
|
||||
int communicatorSize = 1;
|
||||
int communicatorRank = 0;
|
||||
MPI_Comm_size(finiteElementSpace.GetComm(), &communicatorSize);
|
||||
MPI_Comm_rank(finiteElementSpace.GetComm(), &communicatorRank);
|
||||
|
||||
const long long localSize = first.local_size();
|
||||
std::vector<long long> localSizes(static_cast<std::size_t>(communicatorSize));
|
||||
MPI_Allgather(&localSize, 1, MPI_LONG_LONG, localSizes.data(), 1, MPI_LONG_LONG, finiteElementSpace.GetComm());
|
||||
|
||||
const long long expectedOffset = std::accumulate(localSizes.begin(), localSizes.begin() + communicatorRank, 0LL);
|
||||
const long long expectedGlobalSize = std::accumulate(localSizes.begin(), localSizes.end(), 0LL);
|
||||
|
||||
CHECK(first.global_offset() == expectedOffset);
|
||||
CHECK(first.global_size() == expectedGlobalSize);
|
||||
}
|
||||
Reference in New Issue
Block a user