126 lines
6.7 KiB
C++
126 lines
6.7 KiB
C++
#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
|