Add native spherical harmonic surface coordinates to coupled equilibrium
This commit is contained in:
@@ -172,6 +172,51 @@ export namespace mean_field::deformation {
|
||||
return m_surface.parameterCount();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasNodalRadialParameters() const noexcept {
|
||||
if constexpr (requires { PreparedSurface::hasNodalRadialParameters; }) {
|
||||
return PreparedSurface::hasNodalRadialParameters;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool supportsSurfaceParameterMetric() const noexcept {
|
||||
if constexpr (requires { PreparedSurface::hasCanonicalNodalVectorTrace; }) {
|
||||
return PreparedSurface::hasCanonicalNodalVectorTrace;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reference surface metrics need the prescription tangent before any
|
||||
// physical state has been prepared. Exposing that tangent avoids
|
||||
// making normalization depend on the arbitrary interior mesh lift.
|
||||
void applySurfaceJacobian(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
mfem::Vector &surfaceDirection
|
||||
) const {
|
||||
requireCurrentDiscretization();
|
||||
requireParameterSize(parameters);
|
||||
requireParameterSize(parameterDirection);
|
||||
if (surfaceDirection.Size() != surfaceDisplacementSize()) {
|
||||
throw std::invalid_argument("The surface tangent output has an incompatible trace size.");
|
||||
}
|
||||
m_surface.applyJacobian(parameters, parameterDirection, surfaceDirection);
|
||||
}
|
||||
|
||||
void applySurfaceJacobianTranspose(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &surfaceDual,
|
||||
mfem::Vector ¶meterDual
|
||||
) const {
|
||||
requireCurrentDiscretization();
|
||||
requireParameterSize(parameters);
|
||||
requireParameterSize(parameterDual);
|
||||
if (surfaceDual.Size() != surfaceDisplacementSize()) {
|
||||
throw std::invalid_argument("The surface transpose input has an incompatible trace size.");
|
||||
}
|
||||
m_surface.applyJacobianTranspose(parameters, surfaceDual, parameterDual);
|
||||
}
|
||||
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
|
||||
return m_surface.surfaceDisplacementSize();
|
||||
}
|
||||
@@ -580,6 +625,30 @@ export namespace mean_field::deformation {
|
||||
return m_implementation->parameterCount();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasNodalRadialParameters() const noexcept {
|
||||
return m_implementation->hasNodalRadialParameters();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool supportsSurfaceParameterMetric() const noexcept {
|
||||
return m_implementation->supportsSurfaceParameterMetric();
|
||||
}
|
||||
|
||||
void applySurfaceJacobian(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
mfem::Vector &surfaceDirection
|
||||
) const {
|
||||
m_implementation->applySurfaceJacobian(parameters, parameterDirection, surfaceDirection);
|
||||
}
|
||||
|
||||
void applySurfaceJacobianTranspose(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &surfaceDual,
|
||||
mfem::Vector ¶meterDual
|
||||
) const {
|
||||
m_implementation->applySurfaceJacobianTranspose(parameters, surfaceDual, parameterDual);
|
||||
}
|
||||
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
|
||||
return m_implementation->surfaceDisplacementSize();
|
||||
}
|
||||
@@ -654,10 +723,22 @@ export namespace mean_field::deformation {
|
||||
private:
|
||||
class Interface {
|
||||
public:
|
||||
virtual ~Interface() = default;
|
||||
virtual ~Interface() = default;
|
||||
|
||||
[[nodiscard]] virtual DomainDeformationDescriptor descriptor() const noexcept = 0;
|
||||
[[nodiscard]] virtual int parameterCount() const noexcept = 0;
|
||||
[[nodiscard]] virtual DomainDeformationDescriptor descriptor() const noexcept = 0;
|
||||
[[nodiscard]] virtual int parameterCount() const noexcept = 0;
|
||||
[[nodiscard]] virtual bool hasNodalRadialParameters() const noexcept = 0;
|
||||
[[nodiscard]] virtual bool supportsSurfaceParameterMetric() const noexcept = 0;
|
||||
virtual void applySurfaceJacobian(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const = 0;
|
||||
virtual void applySurfaceJacobianTranspose(
|
||||
const mfem::Vector &,
|
||||
const mfem::Vector &,
|
||||
mfem::Vector &
|
||||
) const = 0;
|
||||
[[nodiscard]] virtual int surfaceDisplacementSize() const noexcept = 0;
|
||||
[[nodiscard]] virtual int volumeDisplacementSize() const noexcept = 0;
|
||||
[[nodiscard]] virtual bool matchesCurrentDiscretization() const noexcept = 0;
|
||||
@@ -705,6 +786,46 @@ export namespace mean_field::deformation {
|
||||
[[nodiscard]] int parameterCount() const noexcept override {
|
||||
return m_preparedDeformation.parameterCount();
|
||||
}
|
||||
[[nodiscard]] bool hasNodalRadialParameters() const noexcept override {
|
||||
if constexpr (requires { m_preparedDeformation.hasNodalRadialParameters(); }) {
|
||||
return m_preparedDeformation.hasNodalRadialParameters();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool supportsSurfaceParameterMetric() const noexcept override {
|
||||
if constexpr (requires(const PreparedDeformation &prepared, const mfem::Vector &x, mfem::Vector &y) {
|
||||
{ prepared.supportsSurfaceParameterMetric() } noexcept -> std::same_as<bool>;
|
||||
{ prepared.applySurfaceJacobian(x, x, y) } -> std::same_as<void>;
|
||||
{ prepared.applySurfaceJacobianTranspose(x, x, y) } -> std::same_as<void>;
|
||||
}) {
|
||||
return m_preparedDeformation.supportsSurfaceParameterMetric();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void applySurfaceJacobian(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &trace
|
||||
) const override {
|
||||
if constexpr (requires { m_preparedDeformation.applySurfaceJacobian(parameters, direction, trace); }) {
|
||||
m_preparedDeformation.applySurfaceJacobian(parameters, direction, trace);
|
||||
} else {
|
||||
throw std::logic_error("This domain deformation does not expose its surface tangent.");
|
||||
}
|
||||
}
|
||||
void applySurfaceJacobianTranspose(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &trace,
|
||||
mfem::Vector &dual
|
||||
) const override {
|
||||
if constexpr (requires {
|
||||
m_preparedDeformation.applySurfaceJacobianTranspose(parameters, trace, dual);
|
||||
}) {
|
||||
m_preparedDeformation.applySurfaceJacobianTranspose(parameters, trace, dual);
|
||||
} else {
|
||||
throw std::logic_error("This domain deformation does not expose its surface transpose.");
|
||||
}
|
||||
}
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept override {
|
||||
return m_preparedDeformation.surfaceDisplacementSize();
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ export namespace mean_field::deformation {
|
||||
|
||||
class PreparedNodalRadialSurface final {
|
||||
public:
|
||||
static constexpr bool hasNodalRadialParameters = true;
|
||||
// Trace entries follow dimension * canonical boundary-node index + component.
|
||||
static constexpr bool hasCanonicalNodalVectorTrace = true;
|
||||
[[nodiscard]] SurfaceDeformationDescriptor descriptor() const noexcept;
|
||||
|
||||
[[nodiscard]] int parameterCount() const noexcept;
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
module;
|
||||
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include <mfem/linalg/densemat.hpp>
|
||||
|
||||
export module mean_field:deformation.spherical_harmonic_surface;
|
||||
|
||||
export import :deformation.nodal_radial_surface;
|
||||
|
||||
export namespace mean_field::deformation {
|
||||
struct SphericalHarmonicSurfaceOptions final {
|
||||
int maximumDegree{4};
|
||||
// A cutoff retains both real sine/cosine partners. Zero restricts the
|
||||
// shape to axisymmetry; an omitted cutoff retains every order.
|
||||
std::optional<int> maximumAzimuthalOrder{};
|
||||
|
||||
void validate() const;
|
||||
};
|
||||
|
||||
struct SphericalHarmonicMode final {
|
||||
int degree;
|
||||
int order;
|
||||
|
||||
constexpr bool operator==(const SphericalHarmonicMode &) const = default;
|
||||
};
|
||||
|
||||
class PreparedSphericalHarmonicSurface;
|
||||
|
||||
class SphericalHarmonicSurface final {
|
||||
public:
|
||||
using PreparedType = PreparedSphericalHarmonicSurface;
|
||||
|
||||
explicit SphericalHarmonicSurface(SphericalHarmonicSurfaceOptions options = {});
|
||||
explicit SphericalHarmonicSurface(
|
||||
mfem::Vector referenceCenter,
|
||||
SphericalHarmonicSurfaceOptions options = {}
|
||||
);
|
||||
|
||||
[[nodiscard]] const mfem::Vector &referenceCenter() const noexcept;
|
||||
[[nodiscard]] const SphericalHarmonicSurfaceOptions &options() const noexcept;
|
||||
[[nodiscard]] int modeCount() const noexcept;
|
||||
[[nodiscard]] SurfaceDeformationDescriptor descriptor() const noexcept;
|
||||
void validate() const;
|
||||
|
||||
private:
|
||||
mfem::Vector m_referenceCenter;
|
||||
SphericalHarmonicSurfaceOptions m_options;
|
||||
};
|
||||
|
||||
// Coefficients have units of radial length. The fixed reference functions
|
||||
// satisfy integral(Y_lm^2 dOmega)=1, with Condon--Shortley phase, negative
|
||||
// orders denoting sine and positive orders cosine. FE interpolation of
|
||||
// their nodal samples need not retain continuous spherical orthogonality.
|
||||
class PreparedSphericalHarmonicSurface final {
|
||||
public:
|
||||
static constexpr bool hasNodalRadialParameters = false;
|
||||
// Trace entries follow dimension * canonical boundary-node index + component.
|
||||
static constexpr bool hasCanonicalNodalVectorTrace = true;
|
||||
|
||||
[[nodiscard]] SurfaceDeformationDescriptor descriptor() const noexcept;
|
||||
[[nodiscard]] int parameterCount() const noexcept;
|
||||
[[nodiscard]] long long globalParameterCount() const noexcept;
|
||||
[[nodiscard]] long long globalParameterOffset() const noexcept;
|
||||
[[nodiscard]] int spatialDimension() const noexcept;
|
||||
[[nodiscard]] int surfaceNodeCount() const noexcept;
|
||||
[[nodiscard]] int surfaceDisplacementSize() const noexcept;
|
||||
[[nodiscard]] long long globalSurfaceDisplacementSize() const noexcept;
|
||||
[[nodiscard]] long long globalSurfaceDisplacementOffset() const noexcept;
|
||||
[[nodiscard]] int surfaceDisplacementDof(
|
||||
int surfaceNode,
|
||||
int component
|
||||
) const;
|
||||
[[nodiscard]] double radialDirection(
|
||||
int surfaceNode,
|
||||
int component
|
||||
) const;
|
||||
[[nodiscard]] double referenceRadius(int surfaceNode) const;
|
||||
[[nodiscard]] const mfem::Vector &referenceCenter() const noexcept;
|
||||
[[nodiscard]] const field::ScalarBoundaryDofMap &surfaceDofMap() const noexcept;
|
||||
[[nodiscard]] std::span<const SphericalHarmonicMode> modes() const noexcept;
|
||||
[[nodiscard]] const mfem::DenseMatrix &nodalBasis() const noexcept;
|
||||
|
||||
void buildSurfaceDisplacement(
|
||||
const mfem::Vector ¶meters,
|
||||
mfem::Vector &surfaceDisplacement
|
||||
) const;
|
||||
void applyJacobian(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
mfem::Vector &surfaceDisplacementDirection
|
||||
) const;
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &surfaceDisplacementDual,
|
||||
mfem::Vector ¶meterDual
|
||||
) const;
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
const mfem::Vector &surfaceDisplacementDual,
|
||||
mfem::Vector ¶meterDualAction
|
||||
) const;
|
||||
|
||||
private:
|
||||
friend PreparedSphericalHarmonicSurface compileSurfaceDeformationPrescription(
|
||||
const SphericalHarmonicSurface &,
|
||||
const SurfaceDeformationCompilationContext &
|
||||
);
|
||||
|
||||
PreparedSphericalHarmonicSurface(
|
||||
PreparedNodalRadialSurface radialSurface,
|
||||
std::vector<SphericalHarmonicMode> modes,
|
||||
mfem::DenseMatrix nodalBasis
|
||||
);
|
||||
void requireParameters(const mfem::Vector ¶meters) const;
|
||||
void requireSurfaceSize(const mfem::Vector &surfaceDisplacement) const;
|
||||
void applyEmbedding(
|
||||
const mfem::Vector ¶meters,
|
||||
mfem::Vector &surfaceDisplacement
|
||||
) const;
|
||||
|
||||
PreparedNodalRadialSurface m_radialSurface;
|
||||
std::vector<SphericalHarmonicMode> m_modes;
|
||||
mfem::DenseMatrix m_nodalBasis;
|
||||
};
|
||||
|
||||
[[nodiscard]] PreparedSphericalHarmonicSurface compileSurfaceDeformationPrescription(
|
||||
const SphericalHarmonicSurface &prescription,
|
||||
const SurfaceDeformationCompilationContext &context
|
||||
);
|
||||
|
||||
static_assert(SurfaceDeformationPrescription<SphericalHarmonicSurface>);
|
||||
static_assert(PreparedSurfaceDeformationPrescription<PreparedSphericalHarmonicSurface>);
|
||||
static_assert(SurfaceDeformationCompilable<
|
||||
SphericalHarmonicSurface,
|
||||
SurfaceDeformationCompilationContext>);
|
||||
} // namespace mean_field::deformation
|
||||
18
libmeanfield/interface/deformation/surface_metric.cppm
Normal file
18
libmeanfield/interface/deformation/surface_metric.cppm
Normal file
@@ -0,0 +1,18 @@
|
||||
module;
|
||||
|
||||
#include <mfem/linalg/vector.hpp>
|
||||
|
||||
export module mean_field:deformation.surface_metric;
|
||||
|
||||
export import :deformation.domain_deformation;
|
||||
export import :fem;
|
||||
|
||||
export namespace mean_field::deformation {
|
||||
// A reference parameter metric depends on the surface tangent, not the
|
||||
// interior mesh lift. Assembly is out of line to keep MFEM quadrature
|
||||
// implementation headers out of every normalization/preconditioner user.
|
||||
[[nodiscard]] mfem::Vector assembleSurfaceParameterMassDiagonal(
|
||||
const fem::FEM &finiteElements,
|
||||
const PreparedDomainDeformationRuntime &deformation
|
||||
);
|
||||
} // namespace mean_field::deformation
|
||||
Reference in New Issue
Block a user