Files
MeanField/experiments/reduced_surface_operators.hpp

528 lines
28 KiB
C++

#pragma once
// Include after importing mean_field. These maps change the discrete shape
// space; they are not an invertible preconditioner of the original equations.
#include <algorithm>
#include <cmath>
#include <concepts>
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <vector>
#include <mfem.hpp>
#include <mpi.h>
namespace experiment::plateau_diagnostics {
struct ReducedSurfaceCoordinateOptions final {
double relativeRankTolerance{1.e-12};
double orthogonalityTolerance{5.e-12};
};
struct ReducedSurfaceCoordinateReport final {
double stateOrthogonalityError{0.0};
double residualOrthogonalityError{0.0};
double stateReconstructionError{0.0};
double residualReconstructionError{0.0};
double stateMinimumIndependentFraction{1.0};
double residualMinimumIndependentFraction{1.0};
};
namespace reduced_surface_detail {
inline void RequireFinite(const mfem::Vector &value, const char *description) {
for (int i = 0; i < value.Size(); ++i)
if (!std::isfinite(value[i])) throw std::invalid_argument(description);
}
inline double StableNorm(const mfem::Vector &value) {
double norm = 0.0;
for (int i = 0; i < value.Size(); ++i) norm = std::hypot(norm, value[i]);
return norm;
}
inline double DotColumn(const mfem::DenseMatrix &matrix, int column, const mfem::Vector &value) {
long double result = 0.0L;
for (int i = 0; i < matrix.Height(); ++i)
result += static_cast<long double>(matrix(i, column)) * value[i];
return static_cast<double>(result);
}
struct QRReport final {
double orthogonalityError{0.0};
double reconstructionError{0.0};
double minimumIndependentFraction{1.0};
};
// Reorthogonalization preserves the advertised coordinate identities
// without a new dense linear-algebra dependency. Rejecting a dependent
// mode keeps the scientific degree/mode ordering explicit in artifacts.
inline QRReport ThinQR(
const mfem::DenseMatrix &input,
mfem::DenseMatrix &basis,
mfem::DenseMatrix &transform,
ReducedSurfaceCoordinateOptions options
) {
const int rows = input.Height(), columns = input.Width();
basis.SetSize(rows, columns);
transform.SetSize(columns, columns);
basis = 0.0;
transform = 0.0;
QRReport report;
mfem::Vector column(rows);
for (int j = 0; j < columns; ++j) {
for (int i = 0; i < rows; ++i) column[i] = input(i, j);
RequireFinite(column, "The reduced surface basis contains nonfinite entries.");
const double originalNorm = StableNorm(column);
if (!std::isfinite(originalNorm) || originalNorm <= 0.0)
throw std::invalid_argument("The reduced surface basis has a zero or overflowing column.");
for (int pass = 0; pass < 2; ++pass) {
for (int previous = 0; previous < j; ++previous) {
const double projection = DotColumn(basis, previous, column);
if (!std::isfinite(projection))
throw std::invalid_argument("The reduced surface QR projection is nonfinite.");
transform(previous, j) += projection;
for (int i = 0; i < rows; ++i) column[i] -= projection * basis(i, previous);
}
}
const double independentNorm = StableNorm(column);
const double fraction = independentNorm / originalNorm;
if (!std::isfinite(fraction) || fraction <= options.relativeRankTolerance)
throw std::invalid_argument("The reduced surface basis is numerically rank deficient.");
report.minimumIndependentFraction = std::min(report.minimumIndependentFraction, fraction);
transform(j, j) = independentNorm;
for (int i = 0; i < rows; ++i) basis(i, j) = column[i] / independentNorm;
}
double inputNorm = 0.0, errorNorm = 0.0;
for (int j = 0; j < columns; ++j) {
for (int l = 0; l < columns; ++l) {
long double product = 0.0L;
for (int i = 0; i < rows; ++i)
product += static_cast<long double>(basis(i, j)) * basis(i, l);
report.orthogonalityError = std::max(
report.orthogonalityError, std::abs(static_cast<double>(product) - (j == l ? 1.0 : 0.0))
);
}
for (int i = 0; i < rows; ++i) {
long double reconstructed = 0.0L;
for (int l = 0; l <= j; ++l)
reconstructed += static_cast<long double>(basis(i, l)) * transform(l, j);
inputNorm = std::hypot(inputNorm, input(i, j));
errorNorm = std::hypot(errorNorm, static_cast<double>(reconstructed) - input(i, j));
}
}
report.reconstructionError = errorNorm / inputNorm;
if (!std::isfinite(report.reconstructionError) ||
!std::isfinite(report.orthogonalityError) ||
report.orthogonalityError > options.orthogonalityTolerance ||
report.reconstructionError > options.orthogonalityTolerance)
throw std::invalid_argument("The reduced surface QR failed its coordinate consistency check.");
return report;
}
} // namespace reduced_surface_detail
class ReducedSurfaceCoordinates final {
public:
ReducedSurfaceCoordinates(
const mfem::DenseMatrix &physicalBasis,
const mfem::Vector &stateFactors,
const mfem::Vector &residualFactors,
int shapeValueOffset,
int shapeResidualOffset,
MPI_Comm communicator,
ReducedSurfaceCoordinateOptions options = {}
)
: m_physicalBasis(physicalBasis), m_stateFactors(stateFactors), m_residualFactors(residualFactors),
m_shapeValueOffset(shapeValueOffset), m_shapeResidualOffset(shapeResidualOffset) {
int ranks = 0;
if (MPI_Comm_size(communicator, &ranks) != MPI_SUCCESS || ranks != 1)
throw std::invalid_argument("The reduced surface experiment currently requires one MPI rank.");
if (!std::isfinite(options.relativeRankTolerance) || options.relativeRankTolerance <= 0.0 ||
options.relativeRankTolerance >= 1.0 || !std::isfinite(options.orthogonalityTolerance) ||
options.orthogonalityTolerance <= 0.0 || options.orthogonalityTolerance >= 1.0)
throw std::invalid_argument("Invalid reduced surface coordinate tolerances.");
const int size = stateFactors.Size(), shape = physicalBasis.Height(), modes = physicalBasis.Width();
if (size != residualFactors.Size() || shape <= 0 || modes <= 0 || modes > shape || shape > size ||
shapeValueOffset < 0 || shapeValueOffset > size - shape ||
shapeResidualOffset < 0 || shapeResidualOffset > size - shape)
throw std::invalid_argument("Invalid reduced surface dimensions or distinct block offsets.");
for (int i = 0; i < size; ++i)
if (!std::isfinite(stateFactors[i]) || stateFactors[i] <= 0.0 ||
!std::isfinite(residualFactors[i]) || residualFactors[i] <= 0.0)
throw std::invalid_argument("Reduced surface normalization factors must be finite and positive.");
mfem::DenseMatrix stateImage(shape, modes), residualDualImage(shape, modes);
for (int j = 0; j < modes; ++j) {
for (int i = 0; i < shape; ++i) {
stateImage(i, j) = stateFactors[shapeValueOffset + i] * physicalBasis(i, j);
residualDualImage(i, j) = physicalBasis(i, j) / residualFactors[shapeResidualOffset + i];
}
}
// State and residual scales act on different layouts and are not
// interchangeable. Q_r^T Fhat = T_r^{-T} B^T Fphysical preserves
// precisely the physical Galerkin equations with an invertible
// reduced residual scaling, even when Q_x differs from Q_r.
const auto state = reduced_surface_detail::ThinQR(stateImage, m_stateBasis, m_stateTransform, options);
const auto residual = reduced_surface_detail::ThinQR(
residualDualImage, m_residualBasis, m_residualTransform, options
);
m_report = {
state.orthogonalityError, residual.orthogonalityError,
state.reconstructionError, residual.reconstructionError,
state.minimumIndependentFraction, residual.minimumIndependentFraction
};
}
[[nodiscard]] int FullSize() const noexcept { return m_stateFactors.Size(); }
[[nodiscard]] int ReducedSize() const noexcept { return FullSize() - ShapeSize() + ModeCount(); }
[[nodiscard]] int ShapeSize() const noexcept { return m_physicalBasis.Height(); }
[[nodiscard]] int ModeCount() const noexcept { return m_physicalBasis.Width(); }
[[nodiscard]] int ShapeValueOffset() const noexcept { return m_shapeValueOffset; }
[[nodiscard]] int ShapeResidualOffset() const noexcept { return m_shapeResidualOffset; }
[[nodiscard]] const mfem::DenseMatrix &PhysicalBasis() const noexcept { return m_physicalBasis; }
[[nodiscard]] const mfem::DenseMatrix &StateBasis() const noexcept { return m_stateBasis; }
[[nodiscard]] const mfem::DenseMatrix &ResidualBasis() const noexcept { return m_residualBasis; }
[[nodiscard]] const mfem::DenseMatrix &StateTransform() const noexcept { return m_stateTransform; }
[[nodiscard]] const mfem::DenseMatrix &ResidualTransform() const noexcept { return m_residualTransform; }
[[nodiscard]] const ReducedSurfaceCoordinateReport &Report() const noexcept { return m_report; }
[[nodiscard]] bool MatchesNormalization(
const mfem::Vector &stateFactors, const mfem::Vector &residualFactors
) const noexcept {
if (stateFactors.Size() != FullSize() || residualFactors.Size() != FullSize()) return false;
for (int i = 0; i < FullSize(); ++i)
if (stateFactors[i] != m_stateFactors[i] || residualFactors[i] != m_residualFactors[i]) return false;
return true;
}
void InjectState(const mfem::Vector &reduced, mfem::Vector &full) const {
Inject(reduced, full, m_shapeValueOffset, m_stateBasis);
}
void RestrictState(const mfem::Vector &full, mfem::Vector &reduced) const {
Restrict(full, reduced, m_shapeValueOffset, m_stateBasis);
}
void RestrictResidual(const mfem::Vector &full, mfem::Vector &reduced) const {
Restrict(full, reduced, m_shapeResidualOffset, m_residualBasis);
}
void InjectResidual(const mfem::Vector &reduced, mfem::Vector &full) const {
Inject(reduced, full, m_shapeResidualOffset, m_residualBasis);
}
private:
void Inject(
const mfem::Vector &reduced, mfem::Vector &full, int offset, const mfem::DenseMatrix &basis
) const {
if (reduced.Size() != ReducedSize() || (&reduced == &full))
throw std::invalid_argument("Reduced surface injection requires distinct correctly sized vectors.");
reduced_surface_detail::RequireFinite(reduced, "Cannot inject a nonfinite reduced surface vector.");
full.SetSize(FullSize());
for (int i = 0; i < offset; ++i) full[i] = reduced[i];
for (int i = 0; i < ShapeSize(); ++i) {
long double value = 0.0L;
for (int j = 0; j < ModeCount(); ++j)
value += static_cast<long double>(basis(i, j)) * reduced[offset + j];
full[offset + i] = static_cast<double>(value);
}
for (int i = offset + ShapeSize(); i < FullSize(); ++i)
full[i] = reduced[i - ShapeSize() + ModeCount()];
reduced_surface_detail::RequireFinite(full, "Reduced surface injection overflowed.");
}
void Restrict(
const mfem::Vector &full, mfem::Vector &reduced, int offset, const mfem::DenseMatrix &basis
) const {
if (full.Size() != FullSize() || (&full == &reduced))
throw std::invalid_argument("Reduced surface restriction requires distinct correctly sized vectors.");
reduced_surface_detail::RequireFinite(full, "Cannot restrict a nonfinite full surface vector.");
reduced.SetSize(ReducedSize());
for (int i = 0; i < offset; ++i) reduced[i] = full[i];
for (int j = 0; j < ModeCount(); ++j) {
long double value = 0.0L;
for (int i = 0; i < ShapeSize(); ++i)
value += static_cast<long double>(basis(i, j)) * full[offset + i];
reduced[offset + j] = static_cast<double>(value);
}
for (int i = offset + ShapeSize(); i < FullSize(); ++i)
reduced[i - ShapeSize() + ModeCount()] = full[i];
reduced_surface_detail::RequireFinite(reduced, "Reduced surface restriction overflowed.");
}
mfem::DenseMatrix m_physicalBasis, m_stateBasis, m_residualBasis, m_stateTransform, m_residualTransform;
mfem::Vector m_stateFactors, m_residualFactors;
int m_shapeValueOffset, m_shapeResidualOffset;
ReducedSurfaceCoordinateReport m_report;
};
template <std::derived_from<mfem::Operator> FullOperator>
class ReducedSurfaceOperator final : public mfem::Operator {
public:
ReducedSurfaceOperator(const FullOperator &operation, const ReducedSurfaceCoordinates &coordinates)
: mfem::Operator(coordinates.ReducedSize()), m_operation(&operation), m_coordinates(&coordinates),
m_fullDirection(coordinates.FullSize()), m_fullAction(coordinates.FullSize()) {
Refresh();
}
// Refresh does not prepare the physical problem. It adopts an already
// prepared generation while requiring the frozen coordinate scales.
void Refresh() {
m_current = false;
if (m_operation->Height() != m_coordinates->FullSize() ||
m_operation->Width() != m_coordinates->FullSize())
throw std::invalid_argument("Reduced surface operator needs the matching square full Jacobian.");
if constexpr (requires { m_operation->IsPrepared(); })
if (!m_operation->IsPrepared()) throw std::invalid_argument("The full Jacobian is not prepared.");
if constexpr (requires { m_operation->GetNormalization(); }) {
const auto &normalization = m_operation->GetNormalization();
if (!m_coordinates->MatchesNormalization(normalization.StateFactors(), normalization.ResidualFactors()))
throw std::invalid_argument("The frozen reduced surface normalization has changed.");
}
if constexpr (requires { m_operation->GetProblem().GetPreparationGeneration(); })
m_generation = m_operation->GetProblem().GetPreparationGeneration();
m_current = true;
}
[[nodiscard]] bool IsCurrent() const {
if (!m_current || m_operation->Height() != m_coordinates->FullSize() ||
m_operation->Width() != m_coordinates->FullSize()) return false;
if constexpr (requires { m_operation->IsPrepared(); })
if (!m_operation->IsPrepared()) return false;
if constexpr (requires { m_operation->GetProblem().GetPreparationGeneration(); })
if (m_generation != m_operation->GetProblem().GetPreparationGeneration()) return false;
if constexpr (requires { m_operation->GetNormalization(); }) {
const auto &normalization = m_operation->GetNormalization();
if (!m_coordinates->MatchesNormalization(normalization.StateFactors(), normalization.ResidualFactors()))
return false;
}
return true;
}
void Mult(const mfem::Vector &direction, mfem::Vector &action) const override {
if (!IsCurrent()) throw std::invalid_argument("The reduced surface Jacobian is stale.");
m_coordinates->InjectState(direction, m_fullDirection);
m_operation->Mult(m_fullDirection, m_fullAction);
m_coordinates->RestrictResidual(m_fullAction, action);
}
[[nodiscard]] const FullOperator &GetFullOperator() const noexcept { return *m_operation; }
private:
const FullOperator *m_operation;
const ReducedSurfaceCoordinates *m_coordinates;
mutable mfem::Vector m_fullDirection, m_fullAction;
std::uint64_t m_generation{0};
bool m_current{false};
};
template <class FullInverse>
requires std::derived_from<FullInverse, mfem::Solver> &&
mean_field::solver::LinearPreconditionerApplicationContractAvailable<FullInverse>
class ReducedSurfacePreconditioner final : public mfem::Solver {
public:
// Projecting an inverse is an approximation, not an inverse identity.
// In particular a flexible underlying application remains flexible.
static constexpr auto applicationContract =
mean_field::solver::linearPreconditionerApplicationContract<FullInverse>;
ReducedSurfacePreconditioner(FullInverse &inverse, const ReducedSurfaceCoordinates &coordinates)
: mfem::Solver(coordinates.ReducedSize()), m_inverse(&inverse), m_coordinates(&coordinates),
m_fullResidual(coordinates.FullSize()), m_fullCorrection(coordinates.FullSize()) {
if (!IsCurrent()) throw std::invalid_argument("The full reduced-surface inverse is not current.");
iterative_mode = false;
}
void SetOperator(const mfem::Operator &operation) override {
if (operation.Height() != Height() || operation.Width() != Width())
throw std::invalid_argument("The projected inverse received an incompatible reduced operator.");
// The physical inverse is already bound to the authoritative full
// operator. Passing this reduced operator into it would be invalid.
if (!IsCurrent()) throw std::invalid_argument("The projected inverse is stale.");
}
[[nodiscard]] bool IsCurrent() const {
if (m_inverse->Height() != m_coordinates->FullSize() ||
m_inverse->Width() != m_coordinates->FullSize() || m_inverse->iterative_mode) return false;
if constexpr (requires { m_inverse->IsCurrent(); })
if (!m_inverse->IsCurrent()) return false;
return true;
}
void Mult(const mfem::Vector &residual, mfem::Vector &correction) const override {
if (!IsCurrent()) throw std::invalid_argument("The projected inverse is stale.");
m_coordinates->InjectResidual(residual, m_fullResidual);
m_fullCorrection = 0.0;
m_inverse->Mult(m_fullResidual, m_fullCorrection);
m_coordinates->RestrictState(m_fullCorrection, correction);
}
private:
FullInverse *m_inverse;
const ReducedSurfaceCoordinates *m_coordinates;
mutable mfem::Vector m_fullResidual, m_fullCorrection;
};
struct ReducedSurfaceAlgebraSelfCheckReport final {
int checks{0};
double maximumError{0.0};
bool rejectedDependentBasis{false};
bool rejectedNonfiniteBasis{false};
bool rejectedStaleOperator{false};
};
namespace reduced_surface_detail {
class SelfCheckOperator final : public mfem::Operator {
public:
explicit SelfCheckOperator(int size) : mfem::Operator(size), matrix(size) {
matrix = 0.0;
for (int i = 0; i < size; ++i) {
matrix(i, i) = 2.0 + i;
for (int j = 0; j < size; ++j)
if (i != j) matrix(i, j) = 0.03 * (1 + i - 2 * j);
}
}
void Mult(const mfem::Vector &x, mfem::Vector &y) const override {
++applications;
y.SetSize(Height());
matrix.Mult(x, y);
}
[[nodiscard]] bool IsPrepared() const noexcept { return true; }
[[nodiscard]] const SelfCheckOperator &GetProblem() const noexcept { return *this; }
[[nodiscard]] std::uint64_t GetPreparationGeneration() const noexcept { return generation; }
mfem::DenseMatrix matrix;
std::uint64_t generation{1};
mutable int applications{0};
};
class SelfCheckInverse final : public mfem::Solver {
public:
static constexpr auto applicationContract =
mean_field::preconditioning::ApplicationContract::stationary_linear;
explicit SelfCheckInverse(int size) : mfem::Solver(size) { iterative_mode = false; }
void SetOperator(const mfem::Operator &) override {}
[[nodiscard]] bool IsCurrent() const noexcept { return true; }
void Mult(const mfem::Vector &x, mfem::Vector &y) const override {
++applications;
y.SetSize(Height());
for (int i = 0; i < Height(); ++i) y[i] = x[i] / (2.0 + i);
}
mutable int applications{0};
};
} // namespace reduced_surface_detail
// Tiny deterministic algebra only: this exercises different value/row
// offsets and non-proportional scales without constructing any FEM state.
[[nodiscard]] inline ReducedSurfaceAlgebraSelfCheckReport RunReducedSurfaceAlgebraSelfCheck(MPI_Comm comm) {
ReducedSurfaceAlgebraSelfCheckReport report;
const auto check = [&report](double error) {
++report.checks;
if (!std::isfinite(error) || error > 2.e-12)
throw std::runtime_error("Reduced surface algebra self-check failed.");
report.maximumError = std::max(report.maximumError, std::abs(error));
};
const auto vectorError = [&check](const mfem::Vector &a, const mfem::Vector &b) {
if (a.Size() != b.Size()) throw std::runtime_error("Reduced surface self-check size mismatch.");
double error = 0.0;
for (int i = 0; i < a.Size(); ++i) error = std::max(error, std::abs(a[i] - b[i]));
check(error);
};
mfem::Vector stateFactors(7), residualFactors(7), full(7), reduced(6);
for (int i = 0; i < 7; ++i) {
stateFactors[i] = 0.7 + 0.3 * i;
residualFactors[i] = 1.9 - 0.13 * i;
full[i] = 0.2 - 0.17 * i;
}
for (int i = 0; i < 6; ++i) reduced[i] = 0.11 * (i + 1);
mfem::DenseMatrix physical(3, 2);
physical(0, 0) = 1.0; physical(1, 0) = 0.5; physical(2, 0) = 0.0;
physical(0, 1) = 0.2; physical(1, 1) = -0.3; physical(2, 1) = 1.0;
ReducedSurfaceCoordinates coordinates(physical, stateFactors, residualFactors, 1, 4, comm);
check(coordinates.Report().stateOrthogonalityError);
check(coordinates.Report().residualOrthogonalityError);
mfem::Vector injected, restricted, roundtrip, other;
coordinates.InjectState(reduced, injected);
coordinates.RestrictState(injected, roundtrip);
vectorError(reduced, roundtrip);
coordinates.RestrictState(full, restricted);
check(std::abs(injected * full - reduced * restricted));
coordinates.InjectResidual(reduced, injected);
coordinates.RestrictResidual(injected, roundtrip);
vectorError(reduced, roundtrip);
coordinates.RestrictResidual(full, restricted);
check(std::abs(injected * full - reduced * restricted));
// Verify the physical dual identity independently of InjectResidual:
// T_r^T rhat_shape = B^T (D_r^{-1} Fhat)_shape.
for (int j = 0; j < 2; ++j) {
double lhs = 0.0, rhs = 0.0;
for (int l = 0; l < 2; ++l) lhs += coordinates.ResidualTransform()(l, j) * restricted[4 + l];
for (int i = 0; i < 3; ++i) rhs += physical(i, j) * full[4 + i] / residualFactors[4 + i];
check(std::abs(lhs - rhs));
}
// The two QR bases really differ in this fixture; transpose reuse
// would therefore fail the physical dual identity above.
double mapDifference = 0.0;
for (int j = 0; j < 2; ++j)
for (int i = 0; i < 3; ++i)
mapDifference += std::abs(coordinates.StateBasis()(i, j) - coordinates.ResidualBasis()(i, j));
if (mapDifference < 1.e-3) throw std::runtime_error("Reduced surface dual-map fixture is ineffective.");
++report.checks;
reduced_surface_detail::SelfCheckOperator fullOperator(7);
ReducedSurfaceOperator operation(fullOperator, coordinates);
operation.Mult(reduced, roundtrip);
coordinates.InjectState(reduced, injected);
fullOperator.Mult(injected, other);
coordinates.RestrictResidual(other, restricted);
vectorError(roundtrip, restricted);
reduced_surface_detail::SelfCheckInverse fullInverse(7);
ReducedSurfacePreconditioner inverse(fullInverse, coordinates);
inverse.SetOperator(operation);
const int oldApplications = fullOperator.applications;
inverse.Mult(reduced, roundtrip);
coordinates.InjectResidual(reduced, injected);
fullInverse.Mult(injected, other);
coordinates.RestrictState(other, restricted);
vectorError(roundtrip, restricted);
check(fullOperator.applications - oldApplications);
++fullOperator.generation;
try { operation.Mult(reduced, roundtrip); }
catch (const std::invalid_argument &) { report.rejectedStaleOperator = true; }
if (!report.rejectedStaleOperator || operation.IsCurrent())
throw std::runtime_error("Reduced surface stale-generation guard failed.");
++report.checks;
operation.Refresh();
operation.Mult(reduced, roundtrip);
mfem::DenseMatrix identity(3);
identity = 0.0;
for (int i = 0; i < 3; ++i) identity(i, i) = 1.0;
ReducedSurfaceCoordinates fullCoordinates(identity, stateFactors, residualFactors, 1, 4, comm);
fullCoordinates.InjectState(full, injected);
vectorError(full, injected);
fullCoordinates.RestrictResidual(full, restricted);
vectorError(full, restricted);
ReducedSurfaceOperator fullRecovery(fullOperator, fullCoordinates);
fullRecovery.Mult(full, roundtrip);
fullOperator.Mult(full, other);
vectorError(roundtrip, other);
ReducedSurfacePreconditioner inverseRecovery(fullInverse, fullCoordinates);
inverseRecovery.Mult(full, roundtrip);
fullInverse.Mult(full, other);
vectorError(roundtrip, other);
mfem::DenseMatrix invalid(physical);
for (int i = 0; i < 3; ++i) invalid(i, 1) = invalid(i, 0);
try { ReducedSurfaceCoordinates rejected(invalid, stateFactors, residualFactors, 1, 4, comm); }
catch (const std::invalid_argument &) { report.rejectedDependentBasis = true; }
invalid = physical;
invalid(0, 0) = std::numeric_limits<double>::quiet_NaN();
try { ReducedSurfaceCoordinates rejected(invalid, stateFactors, residualFactors, 1, 4, comm); }
catch (const std::invalid_argument &) { report.rejectedNonfiniteBasis = true; }
if (!report.rejectedDependentBasis || !report.rejectedNonfiniteBasis)
throw std::runtime_error("Reduced surface basis validity guards failed.");
report.checks += 2;
return report;
}
} // namespace experiment::plateau_diagnostics