772 lines
34 KiB
C++
772 lines
34 KiB
C++
|
|
#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);
|
||
|
|
}
|