Add native spherical harmonic surface coordinates to coupled equilibrium
This commit is contained in:
125
experiments/spherical_harmonic_surface_diagnostics.hpp
Normal file
125
experiments/spherical_harmonic_surface_diagnostics.hpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
// Include after import mean_field. These observations do not participate in
|
||||
// acceptance: an equilibrium in the selected shape space can retain force
|
||||
// components conjugate to the boundary motions excluded by that choice.
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace experiment::plateau_diagnostics {
|
||||
template <class State>
|
||||
void RecordHarmonicSurface(
|
||||
State &state,
|
||||
const mean_field::fem::FEM &fem,
|
||||
mean_field::deformation::SphericalHarmonicSurfaceOptions options,
|
||||
const std::filesystem::path &output,
|
||||
const std::string &label
|
||||
) {
|
||||
using namespace mean_field;
|
||||
using Schema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
const auto map = field::make_stellar_surface_scalar_dof_map<Schema>(*fem.surfaceDeformationFes);
|
||||
const auto surface = deformation::compileSurfaceDeformationPrescription(
|
||||
deformation::SphericalHarmonicSurface{options},
|
||||
deformation::SurfaceDeformationCompilationContext{*fem.surfaceDeformationFes, map}
|
||||
);
|
||||
mfem::Vector center(3);
|
||||
center = 0.;
|
||||
const auto extensionContext = deformation::makeRadialDeformationExtensionCompilationContext<Schema>(
|
||||
*fem.surfaceDeformationFes, *fem.displacementFes, *fem.logicalReferenceMesh
|
||||
);
|
||||
const auto nodal = deformation::composePreparedDomainDeformation(
|
||||
deformation::compileSurfaceDeformationPrescription(
|
||||
deformation::NodalRadialSurface{center},
|
||||
deformation::SurfaceDeformationCompilationContext{*fem.surfaceDeformationFes, map}
|
||||
),
|
||||
deformation::compileInteriorDeformationExtension(
|
||||
deformation::PowerLawRadialInteriorExtension{}, extensionContext
|
||||
),
|
||||
deformation::compileVacuumDeformationExtension(
|
||||
deformation::FixedInfinityRadialVacuumExtension{}, extensionContext
|
||||
),
|
||||
*fem.surfaceDeformationFes, *fem.displacementFes, *fem.logicalReferenceMesh
|
||||
);
|
||||
|
||||
const auto &physical = state.Problem().GetPhysicalOperator();
|
||||
const auto &basis = surface.nodalBasis();
|
||||
const auto &coefficients = physical.GetSurfaceDeformationParameters();
|
||||
if (coefficients.Size() != basis.Width())
|
||||
throw std::logic_error("Harmonic observer received incompatible coordinates.");
|
||||
mfem::Vector radial(basis.Height()), force(basis.Height()), volume(nodal.volumeDisplacementSize());
|
||||
basis.Mult(coefficients, radial);
|
||||
nodal.buildVolumeDisplacement(radial, volume);
|
||||
volume -= physical.GetGeneratedVolumeDisplacement();
|
||||
nodal.applyJacobianTranspose(radial, physical.GetFullMechanicalResidual(), force);
|
||||
mfem::Vector retained(basis.Width()), native(basis.Width());
|
||||
basis.MultTranspose(force, retained);
|
||||
physical.GetDomainDeformation().applyJacobianTranspose(
|
||||
coefficients, physical.GetFullMechanicalResidual(), native
|
||||
);
|
||||
native -= retained;
|
||||
for (const auto *vector : {&force, &retained, &native, &volume, &radial})
|
||||
for (int index = 0; index < vector->Size(); ++index)
|
||||
if (!std::isfinite((*vector)(index)))
|
||||
throw std::runtime_error("Harmonic observer received nonfinite geometry or force.");
|
||||
if (volume.Norml2() > 1e-11 * (1. + physical.GetGeneratedVolumeDisplacement().Norml2()) ||
|
||||
native.Norml2() > 1e-11 * (1. + retained.Norml2()))
|
||||
throw std::runtime_error("Harmonic observer failed its nodal geometry or dual-work equivalence check.");
|
||||
|
||||
// The Euclidean complement is an explicitly identified diagnostic norm,
|
||||
// not a replacement for the solver's dimensioned Riesz residual norm.
|
||||
mfem::DenseMatrix orthogonal(basis);
|
||||
mfem::Vector excluded(force), column(basis.Height());
|
||||
for (int mode = 0; mode < basis.Width(); ++mode) {
|
||||
for (int row = 0; row < basis.Height(); ++row)
|
||||
column(row) = orthogonal(row, mode);
|
||||
for (int pass = 0; pass < 2; ++pass)
|
||||
for (int previous = 0; previous < mode; ++previous) {
|
||||
long double projection = 0.;
|
||||
for (int row = 0; row < basis.Height(); ++row)
|
||||
projection += column(row) * orthogonal(row, previous);
|
||||
for (int row = 0; row < basis.Height(); ++row)
|
||||
column(row) -= projection * orthogonal(row, previous);
|
||||
}
|
||||
const double norm = column.Norml2();
|
||||
if (!(norm > 0.) || !std::isfinite(norm))
|
||||
throw std::runtime_error("Unresolved harmonic observer basis.");
|
||||
column /= norm;
|
||||
excluded.Add(-(column * force), column);
|
||||
for (int row = 0; row < basis.Height(); ++row)
|
||||
orthogonal(row, mode) = column(row);
|
||||
}
|
||||
const auto file = [&](const std::string &name) {
|
||||
std::ofstream stream(output / name);
|
||||
stream.exceptions(std::ios::badbit | std::ios::failbit);
|
||||
stream << std::setprecision(17);
|
||||
return stream;
|
||||
};
|
||||
auto modes = file(label + "_surface_coefficients.csv");
|
||||
modes << "index,degree,order,radial_length_coefficient\n";
|
||||
for (int mode = 0; mode < basis.Width(); ++mode)
|
||||
modes << mode << ',' << surface.modes()[mode].degree << ',' << surface.modes()[mode].order << ','
|
||||
<< coefficients(mode) << '\n';
|
||||
auto forces = file(label + "_surface_nodal_force.csv");
|
||||
forces << "node,radial_displacement,force,excluded_force\n";
|
||||
for (int node = 0; node < basis.Height(); ++node)
|
||||
forces << node << ',' << radial(node) << ',' << force(node) << ',' << excluded(node) << '\n';
|
||||
auto metrics = file(label + "_surface_audit.csv");
|
||||
metrics << "metric,value\nparameter_count," << basis.Width() << "\nnodal_count," << basis.Height()
|
||||
<< "\nfull_nodal_force_euclidean_norm," << force.Norml2() << "\nexcluded_nodal_force_euclidean_norm,"
|
||||
<< excluded.Norml2() << "\nmodal_force_euclidean_norm," << retained.Norml2()
|
||||
<< "\nvolume_equivalence_error," << volume.Norml2() << "\nforce_pullback_equivalence_error,"
|
||||
<< native.Norml2() << '\n';
|
||||
if (label == "initial") {
|
||||
auto savedBasis = file("surface_basis.csv");
|
||||
for (int node = 0; node < basis.Height(); ++node) {
|
||||
for (int mode = 0; mode < basis.Width(); ++mode)
|
||||
savedBasis << (mode ? "," : "") << basis(node, mode);
|
||||
savedBasis << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace experiment::plateau_diagnostics
|
||||
Reference in New Issue
Block a user