refactor(serif): refactored entire codebase into serif and sub namespaces
This commit is contained in:
23
src/polytrope/coeff/meson.build
Normal file
23
src/polytrope/coeff/meson.build
Normal file
@@ -0,0 +1,23 @@
|
||||
polyCoeff_sources = files(
|
||||
'private/polyCoeff.cpp'
|
||||
)
|
||||
|
||||
polyCoeff_headers = files(
|
||||
'public/polyCoeff.h'
|
||||
)
|
||||
|
||||
libPolyCoeff = static_library('polyCoeff',
|
||||
polyCoeff_sources,
|
||||
include_directories : include_directories('./public'),
|
||||
cpp_args: ['-fvisibility=default'],
|
||||
dependencies: [mfem_dep],
|
||||
install: true
|
||||
)
|
||||
|
||||
|
||||
polycoeff_dep = declare_dependency(
|
||||
include_directories : include_directories('./public'),
|
||||
link_with : libPolyCoeff,
|
||||
sources : polyCoeff_sources,
|
||||
dependencies : [mfem_dep]
|
||||
)
|
||||
64
src/polytrope/coeff/private/polyCoeff.cpp
Normal file
64
src/polytrope/coeff/private/polyCoeff.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
/* ***********************************************************************
|
||||
//
|
||||
// Copyright (C) 2025 -- The 4D-STAR Collaboration
|
||||
// File Author: Emily Boudreaux
|
||||
// Last Modified: March 19, 2025
|
||||
//
|
||||
// 4DSSE is free software; you can use it and/or modify
|
||||
// it under the terms and restrictions the GNU General Library Public
|
||||
// License version 3 (GPLv3) as published by the Free Software Foundation.
|
||||
//
|
||||
// 4DSSE is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this software; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// *********************************************************************** */
|
||||
#include "mfem.hpp"
|
||||
|
||||
#include "polyCoeff.h"
|
||||
|
||||
namespace serif {
|
||||
namespace polytrope {
|
||||
namespace polycoeff{
|
||||
double nonlinearSourceCoeff(const mfem::Vector &x)
|
||||
{
|
||||
return 1;
|
||||
// double r = x(0)*x(0) + x(1)*x(1) + x(2)*x(2);
|
||||
// return std::pow(r, 2);
|
||||
}
|
||||
|
||||
void diffusionCoeff(const mfem::Vector &x, mfem::Vector &v)
|
||||
{
|
||||
v.SetSize(3);
|
||||
v = -1;
|
||||
// double r = x(0)*x(0) + x(1)*x(1) + x(2)*x(2);
|
||||
// v = -std::pow(r, 2);
|
||||
}
|
||||
|
||||
//PERF: One area of future optimization might be turning these into matrix operations since they are
|
||||
//PERF: fundamentally just the dot product of [a b c ...] * [n^0 n^1 n^2 ...]
|
||||
double x1(const double n)
|
||||
{
|
||||
double r = 0;
|
||||
for (int i = 0; i < x1InterpCoeff::numTerms; i++) {
|
||||
r += x1InterpCoeff::coeff[i] * std::pow(n, x1InterpCoeff::power[i]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
double thetaSurfaceFlux(const double n) {
|
||||
double surfaceFlux = 0;
|
||||
for (int i = 0; i < dThetaInterpCoeff::numTerms; i++) {
|
||||
surfaceFlux += dThetaInterpCoeff::coeff[i] * std::pow(n, dThetaInterpCoeff::power[i]);
|
||||
}
|
||||
return surfaceFlux;
|
||||
}
|
||||
|
||||
} // namespace polycoeff
|
||||
} // namespace polytrope
|
||||
} // namespace serif
|
||||
70
src/polytrope/coeff/public/polyCoeff.h
Normal file
70
src/polytrope/coeff/public/polyCoeff.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/* ***********************************************************************
|
||||
//
|
||||
// Copyright (C) 2025 -- The 4D-STAR Collaboration
|
||||
// File Author: Emily Boudreaux
|
||||
// Last Modified: April 21, 2025
|
||||
//
|
||||
// 4DSSE is free software; you can use it and/or modify
|
||||
// it under the terms and restrictions the GNU General Library Public
|
||||
// License version 3 (GPLv3) as published by the Free Software Foundation.
|
||||
//
|
||||
// 4DSSE is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this software; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// *********************************************************************** */
|
||||
|
||||
#pragma once
|
||||
#include "mfem.hpp"
|
||||
#include <cmath>
|
||||
|
||||
namespace serif {
|
||||
namespace polytrope {
|
||||
namespace polycoeff
|
||||
{
|
||||
/**
|
||||
* @brief Computes the xi coefficient function.
|
||||
*
|
||||
* @param x Input vector.
|
||||
* @return double The computed xi coefficient.
|
||||
*/
|
||||
double nonlinearSourceCoeff(const mfem::Vector &x);
|
||||
|
||||
/**
|
||||
* @brief Computes the vector xi coefficient function.
|
||||
*
|
||||
* @param x Input vector.
|
||||
* @param v Output vector to store the computed xi coefficient.
|
||||
*/
|
||||
void diffusionCoeff(const mfem::Vector &x, mfem::Vector &v);
|
||||
|
||||
double x1(const double n);
|
||||
|
||||
double thetaSurfaceFlux(const double n);
|
||||
|
||||
/**
|
||||
* @brief Coefficients for the interpolations of the surface location of a polytrope
|
||||
* @param numTerms Number of terms in the polynomial interpolator
|
||||
* @param power Array of the powers of the polynomial interpolator
|
||||
* @param coeff Array of the coefficients of the polynomial interpolator
|
||||
*/
|
||||
struct x1InterpCoeff {
|
||||
constexpr static int numTerms = 51;
|
||||
constexpr static int power[51] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50};
|
||||
constexpr static double coeff[51] = {2.449260049071003e+00, 4.340987403815444e-01, 2.592462347475860e+00, -2.283794512491552e+01, 1.135535208066129e+02, -3.460093673587010e+02, 6.948139716983651e+02, -9.564368645158952e+02, 9.184546798891624e+02, -6.130308987803503e+02, 2.747228735318193e+02, -7.416795903196430e+01, 7.318638538580859e+00, 1.749441306797260e+00, -4.240148582456829e-01, -5.818809544982156e-02, 1.514877217199105e-02, 3.228634707578998e-03, -2.862524323980516e-04, -1.622486968261819e-04, -1.253644717104076e-05, 4.334945141292894e-06, 1.296452565229763e-06, 8.634802209209870e-08, -3.337511676486084e-08, -1.094796628367775e-08, -1.228178760540410e-09, 1.416744125622751e-10, 8.513777351265677e-11, 1.624582561364811e-11, 8.377207519041114e-13, -4.363812865112836e-13, -1.535862757816461e-13, -2.485085045037669e-14, -6.566281276491033e-16, 8.405047965853478e-16, 2.673804441025638e-16, 4.176337890285142e-17, 8.150073570140493e-19, -1.531673805257016e-18, -4.746996933716653e-19, -6.976825828390195e-20, 8.807513368604331e-22, 3.307739310180278e-21, 8.593260940093030e-22, 7.385969061093440e-23, -2.211130577977291e-23, -8.557291048388455e-24, -4.169359901215994e-25, 4.609379358875657e-25, -3.590870335035984e-26};
|
||||
};
|
||||
|
||||
struct dThetaInterpCoeff {
|
||||
constexpr static int numTerms = 51;
|
||||
constexpr static int power[51] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50};
|
||||
constexpr static double coeff[51] = {-8.164634353536671e-01, 8.651804355981230e-01, -6.391939109867470e-01, 4.882118355278944e-01, -3.898475553686603e-01, 3.102560043891329e-01, -2.365565113144737e-01, 1.639832755778444e-01, -9.645271947133248e-02, 4.450140636696046e-02, -1.460885194751961e-02, 2.816293786806504e-03, -8.178583546493117e-05, -8.584494556484958e-05, 8.476252127593868e-06, 2.923593421315422e-06, -2.206768214995963e-07, -1.203227957690371e-07, -3.381181730985542e-09, 4.022824706790907e-09, 7.041049107708875e-10, -3.562885681170365e-11, -3.281525407784209e-11, -5.031807464141896e-12, 2.034136401832885e-13, 2.361284283178230e-13, 4.602774507763180e-14, 1.809170850970874e-15, -1.333813332262995e-15, -4.045891156434286e-16, -5.197949512114809e-17, 2.222220713310119e-18, 2.625223897130583e-18, 6.226001466529447e-19, 6.571419077089260e-20, -6.672159423054950e-21, -4.476242224056620e-21, -9.790792477821165e-22, -9.222211318122281e-23, 1.427942034536028e-23, 7.759197090219954e-24, 1.546886518887300e-24, 9.585471984274525e-26, -4.005276449706623e-26, -1.459299762834743e-26, -1.870491354620814e-27, 2.271573838802745e-28, 1.360979028415734e-28, 1.102172718357361e-29, -6.919347646474293e-30, 4.875282352118995e-31};
|
||||
};
|
||||
|
||||
} // namespace polycoeff
|
||||
} // namespace polytrope
|
||||
} // namespace serif
|
||||
3
src/polytrope/meson.build
Normal file
3
src/polytrope/meson.build
Normal file
@@ -0,0 +1,3 @@
|
||||
subdir('coeff')
|
||||
subdir('utils')
|
||||
subdir('solver')
|
||||
55
src/polytrope/solver/meson.build
Normal file
55
src/polytrope/solver/meson.build
Normal file
@@ -0,0 +1,55 @@
|
||||
# ***********************************************************************
|
||||
#
|
||||
# Copyright (C) 2025 -- The 4D-STAR Collaboration
|
||||
# File Author: Emily Boudreaux
|
||||
# Last Modified: March 19, 2025
|
||||
#
|
||||
# 4DSSE is free software; you can use it and/or modify
|
||||
# it under the terms and restrictions the GNU General Library Public
|
||||
# License version 3 (GPLv3) as published by the Free Software Foundation.
|
||||
#
|
||||
# 4DSSE is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this software; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# *********************************************************************** #
|
||||
polySolver_sources = files(
|
||||
'private/polySolver.cpp'
|
||||
)
|
||||
|
||||
polySolver_headers = files(
|
||||
'public/polySolver.h'
|
||||
)
|
||||
|
||||
dependencies = [
|
||||
mfem_dep,
|
||||
meshio_dep,
|
||||
polycoeff_dep,
|
||||
polyutils_dep,
|
||||
macros_dep,
|
||||
probe_dep,
|
||||
quill_dep,
|
||||
config_dep,
|
||||
resourceManager_dep,
|
||||
types_dep,
|
||||
]
|
||||
|
||||
libPolySolver = static_library('polySolver',
|
||||
polySolver_sources,
|
||||
include_directories : include_directories('./public'),
|
||||
cpp_args: ['-fvisibility=default'],
|
||||
dependencies: dependencies,
|
||||
install: true
|
||||
)
|
||||
|
||||
polysolver_dep = declare_dependency(
|
||||
include_directories : include_directories('./public'),
|
||||
link_with : libPolySolver,
|
||||
sources : polySolver_sources,
|
||||
dependencies : dependencies
|
||||
)
|
||||
501
src/polytrope/solver/private/polySolver.cpp
Normal file
501
src/polytrope/solver/private/polySolver.cpp
Normal file
@@ -0,0 +1,501 @@
|
||||
/* ***********************************************************************
|
||||
//
|
||||
// Copyright (C) 2025 -- The 4D-STAR Collaboration
|
||||
// File Author: Emily Boudreaux
|
||||
// Last Modified: April 21, 2025
|
||||
//
|
||||
// 4DSSE is free software; you can use it and/or modify
|
||||
// it under the terms and restrictions the GNU General Library Public
|
||||
// License version 3 (GPLv3) as published by the Free Software Foundation.
|
||||
//
|
||||
// 4DSSE is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this software; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// *********************************************************************** */
|
||||
#include "polySolver.h"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "mfem.hpp"
|
||||
|
||||
#include "4DSTARTypes.h"
|
||||
#include "config.h"
|
||||
#include "integrators.h"
|
||||
#include "mfem.hpp"
|
||||
#include "polytropeOperator.h"
|
||||
#include "polyCoeff.h"
|
||||
#include "probe.h"
|
||||
#include "resourceManager.h"
|
||||
#include "resourceManagerTypes.h"
|
||||
#include "utilities.h"
|
||||
#include "quill/LogMacros.h"
|
||||
|
||||
namespace serif {
|
||||
namespace polytrope {
|
||||
|
||||
namespace laneEmden {
|
||||
|
||||
double a (const int k, const double n) { // NOLINT(*-no-recursion)
|
||||
if ( k == 0 ) { return 1; }
|
||||
if ( k == 1 ) { return 0; }
|
||||
else { return -(c(k-2, n)/(std::pow(k, 2)+k)); }
|
||||
|
||||
}
|
||||
|
||||
double c(const int m, const double n) { // NOLINT(*-no-recursion)
|
||||
if ( m == 0 ) { return std::pow(a(0, n), n); }
|
||||
else {
|
||||
double termOne = 1.0/(m*a(0, n));
|
||||
double acc = 0;
|
||||
for (int k = 1; k <= m; k++) {
|
||||
acc += (k*n-m+k)*a(k, n)*c(m-k, n);
|
||||
}
|
||||
return termOne*acc;
|
||||
}
|
||||
}
|
||||
|
||||
double thetaSeriesExpansion(const double xi, const double n, const int order) {
|
||||
double acc = 0;
|
||||
for (int k = 0; k < order; k++) {
|
||||
acc += a(k, n) * std::pow(xi, k);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
} // namespace laneEmden
|
||||
|
||||
|
||||
PolySolver::PolySolver(mfem::Mesh& mesh, const double n, const double order)
|
||||
: m_config(serif::config::Config::getInstance()), // Updated
|
||||
m_logManager(serif::probe::LogManager::getInstance()),
|
||||
m_logger(m_logManager.getLogger("log")),
|
||||
m_polytropicIndex(n),
|
||||
m_feOrder(order),
|
||||
m_mesh(mesh) {
|
||||
|
||||
// Use feOrder - 1 for the RT space to satisfy Brezzi-Babuska condition
|
||||
// for the H1 and RT [H(div)] spaces
|
||||
m_fecH1 = std::make_unique<mfem::H1_FECollection>(m_feOrder, m_mesh.SpaceDimension());
|
||||
m_fecRT = std::make_unique<mfem::RT_FECollection>(m_feOrder - 1, m_mesh.SpaceDimension());
|
||||
|
||||
m_feTheta = std::make_unique<mfem::FiniteElementSpace>(&m_mesh, m_fecH1.get());
|
||||
m_fePhi = std::make_unique<mfem::FiniteElementSpace>(&m_mesh, m_fecRT.get());
|
||||
|
||||
m_theta = std::make_unique<mfem::GridFunction>(m_feTheta.get());
|
||||
m_phi = std::make_unique<mfem::GridFunction>(m_fePhi.get());
|
||||
|
||||
assembleBlockSystem();
|
||||
}
|
||||
|
||||
PolySolver::PolySolver(const double n, const double order)
|
||||
: PolySolver(prepareMesh(n), n, order){}
|
||||
|
||||
mfem::Mesh& PolySolver::prepareMesh(const double n) {
|
||||
if (n > 4.99 || n < 0.0) {
|
||||
throw std::runtime_error("The polytropic index n must be less than 5.0 and greater than 0.0. Currently it is " + std::to_string(n));
|
||||
}
|
||||
const serif::resource::ResourceManager& rm = serif::resource::ResourceManager::getInstance();
|
||||
const serif::resource::types::Resource& genericResource = rm.getResource("mesh:polySphere");
|
||||
const auto &meshIO = std::get<std::unique_ptr<serif::mesh::MeshIO>>(genericResource);
|
||||
meshIO->LinearRescale(polycoeff::x1(n)); // Assumes polycoeff is now serif::polytrope::polycoeff
|
||||
return meshIO->GetMesh();
|
||||
}
|
||||
|
||||
PolySolver::~PolySolver() = default;
|
||||
|
||||
void PolySolver::assembleBlockSystem() {
|
||||
mfem::Array<int> blockOffsets = computeBlockOffsets();
|
||||
|
||||
const std::unique_ptr<formBundle> forms = buildIndividualForms(blockOffsets);
|
||||
|
||||
// const double penalty_param = m_config.get<double>("Poly::Solver::ZeroDerivativePenalty", 1.0);
|
||||
// mfem::Array<int> thetaCenterDofs, phiCenterDofs;
|
||||
// std::tie(thetaCenterDofs, phiCenterDofs) = findCenterElement();
|
||||
// mfem::SparseMatrix& D_mat = forms->D->SpMat();
|
||||
//
|
||||
// for (int i = 0; i < phiCenterDofs.Size(); ++i)
|
||||
// {
|
||||
// const int dof_idx = phiCenterDofs[i];
|
||||
// if (dof_idx >= 0 && dof_idx < D_mat.Height()) {
|
||||
// D_mat(dof_idx, dof_idx) += penalty_param;
|
||||
// }
|
||||
// }
|
||||
|
||||
// --- Build the BlockOperator ---
|
||||
m_polytropOperator = std::make_unique<PolytropeOperator>(
|
||||
std::move(forms->M),
|
||||
std::move(forms->Q),
|
||||
std::move(forms->D),
|
||||
std::move(forms->S),
|
||||
std::move(forms->f),
|
||||
blockOffsets);
|
||||
}
|
||||
|
||||
mfem::Array<int> PolySolver::computeBlockOffsets() const {
|
||||
mfem::Array<int> blockOffsets;
|
||||
blockOffsets.SetSize(3);
|
||||
blockOffsets[0] = 0;
|
||||
blockOffsets[1] = m_feTheta->GetVSize(); // Get actual number of dofs *before* applying BCs
|
||||
blockOffsets[2] = m_fePhi->GetVSize();
|
||||
blockOffsets.PartialSum(); // Cumulative sum to get the offsets
|
||||
return blockOffsets;
|
||||
}
|
||||
|
||||
std::unique_ptr<formBundle> PolySolver::buildIndividualForms(const mfem::Array<int> &blockOffsets) {
|
||||
// --- Assemble the MixedBilinear and Bilinear forms (M, D, and Q) ---
|
||||
auto forms = std::make_unique<formBundle>(
|
||||
std::make_unique<mfem::MixedBilinearForm>(m_fePhi.get(), m_feTheta.get()),
|
||||
std::make_unique<mfem::MixedBilinearForm>(m_feTheta.get(), m_fePhi.get()),
|
||||
std::make_unique<mfem::BilinearForm>(m_fePhi.get()),
|
||||
std::make_unique<mfem::BilinearForm>(m_feTheta.get()),
|
||||
std::make_unique<mfem::NonlinearForm>(m_feTheta.get())
|
||||
);
|
||||
|
||||
// --- Add the integrators to the forms ---
|
||||
forms->M->AddDomainIntegrator(new mfem::MixedVectorWeakDivergenceIntegrator()); // M ∫∇ψ^θ·N^φ dV
|
||||
forms->Q->AddDomainIntegrator(new mfem::MixedVectorGradientIntegrator()); // Q ∫ψ^φ·∇N^θ dV
|
||||
forms->D->AddDomainIntegrator(new mfem::VectorFEMassIntegrator()); // D ∫ψ^φ·N^φ dV
|
||||
forms->S->AddDomainIntegrator(new mfem::DiffusionIntegrator()); // S ∫∇ψ^θ·∇N^θ dV
|
||||
forms->f->AddDomainIntegrator(new polyMFEMUtils::NonlinearPowerIntegrator(m_polytropicIndex)); // Assumes polyMFEMUtils is now serif::polytrope::polyMFEMUtils
|
||||
|
||||
// --- Assemble and Finalize the forms ---
|
||||
assembleAndFinalizeForm(forms->M);
|
||||
assembleAndFinalizeForm(forms->Q);
|
||||
assembleAndFinalizeForm(forms->D);
|
||||
assembleAndFinalizeForm(forms->S);
|
||||
|
||||
// Note: The NonlinearForm does not need to / cannot be finalized, as it is not a matrix form. Rather, the operator
|
||||
// will evaluate the nonlinear form during the solve phase.
|
||||
|
||||
|
||||
return forms;
|
||||
}
|
||||
|
||||
void PolySolver::assembleAndFinalizeForm(auto &f) {
|
||||
// This constructs / ensures the matrix representation for each form
|
||||
// Assemble => Computes the local element matrices across the domain. Adds these to the global matrix . Adds these to the global matrix.
|
||||
// Finalize => Builds the sparsity pattern and allows the SparseMatrix representation to be extracted (CSR).
|
||||
f->Assemble();
|
||||
f->Finalize();
|
||||
}
|
||||
|
||||
void PolySolver::solve() const {
|
||||
// --- Set the initial guess for the solution ---
|
||||
setInitialGuess();
|
||||
setOperatorEssentialTrueDofs();
|
||||
|
||||
// --- Cast the GridFunctions to mfem::Vector ---
|
||||
const auto thetaVec = static_cast<mfem::Vector>(*m_theta); // NOLINT(*-slicing)
|
||||
const auto phiVec = static_cast<mfem::Vector>(*m_phi); // NOLINT(*-slicing)
|
||||
|
||||
// --- Finalize the operator ---
|
||||
// Finalize with the initial state of theta for the initial jacobian calculation
|
||||
m_polytropOperator->finalize(thetaVec);
|
||||
|
||||
// --- Broadcast initial condition to the full state vector ---
|
||||
const mfem::Array<int>& full_block_offsets = m_polytropOperator->get_block_offsets();
|
||||
mfem::Vector x_full(full_block_offsets.Last());
|
||||
mfem::BlockVector x_full_block(x_full, full_block_offsets);
|
||||
x_full_block.GetBlock(0) = thetaVec; // NOLINT(*-slicing)
|
||||
x_full_block.GetBlock(1) = phiVec; // NOLINT(*-slicing)
|
||||
|
||||
// --- Extract only the free DOFs from the full state vector ---
|
||||
const mfem::Array<int>& freeDofs = m_polytropOperator->get_free_dofs();
|
||||
mfem::Vector x_free(m_polytropOperator->get_reduced_system_size());
|
||||
x_full.GetSubVector(freeDofs, x_free); // Extract the free DOFs from the full vector
|
||||
|
||||
// --- Initialize RHS ---
|
||||
mfem::Vector zero_rhs(m_polytropOperator->get_reduced_system_size());
|
||||
zero_rhs = 0.0;
|
||||
|
||||
// --- Setup and run the Newton solver ---
|
||||
const solverBundle sb = setupNewtonSolver();
|
||||
sb.newton.Mult(zero_rhs, x_free);
|
||||
|
||||
// --- Reconstruct the full state vector from the reduced solution ---
|
||||
mfem::BlockVector solution = m_polytropOperator->reconstruct_full_block_state_vector(x_free);
|
||||
|
||||
// --- Save and view an approximate 1D solution ---
|
||||
saveAndViewSolution(solution);
|
||||
}
|
||||
|
||||
serif::types::MFEMArrayPairSet PolySolver::getEssentialTrueDof() const {
|
||||
mfem::Array<int> theta_ess_tdof_list;
|
||||
mfem::Array<int> phi_ess_tdof_list;
|
||||
|
||||
mfem::Array<int> thetaCenterDofs, phiCenterDofs; // phiCenterDofs are not used
|
||||
mfem::Array<double> thetaCenterVals, phiCenterVals;
|
||||
std::tie(thetaCenterDofs, phiCenterDofs) = findCenterElement();
|
||||
thetaCenterVals.SetSize(thetaCenterDofs.Size());
|
||||
// phiCenterVals.SetSize(phiCenterDofs.Size());
|
||||
//
|
||||
// phiCenterVals = 0.0;
|
||||
thetaCenterVals = 1.0;
|
||||
|
||||
mfem::Array<int> ess_brd(m_mesh.bdr_attributes.Max());
|
||||
ess_brd = 1;
|
||||
|
||||
mfem::Array<double> thetaSurfaceVals, phiSurfaceVals;
|
||||
m_feTheta->GetEssentialTrueDofs(ess_brd, theta_ess_tdof_list);
|
||||
m_fePhi->GetEssentialTrueDofs(ess_brd, phi_ess_tdof_list);
|
||||
|
||||
thetaSurfaceVals.SetSize(theta_ess_tdof_list.Size());
|
||||
thetaSurfaceVals = 0.0;
|
||||
phiSurfaceVals.SetSize(phi_ess_tdof_list.Size());
|
||||
phiSurfaceVals = polycoeff::thetaSurfaceFlux(m_polytropicIndex); // Assumes polycoeff is now serif::polytrope::polycoeff
|
||||
|
||||
// combine the essential dofs with the center dofs
|
||||
theta_ess_tdof_list.Append(thetaCenterDofs);
|
||||
thetaSurfaceVals.Append(thetaCenterVals);
|
||||
|
||||
// phi_ess_tdof_list.Append(phiCenterDofs);
|
||||
// phiSurfaceVals.Append(phiCenterVals);
|
||||
|
||||
serif::types::MFEMArrayPair thetaPair = std::make_pair(theta_ess_tdof_list, thetaSurfaceVals);
|
||||
serif::types::MFEMArrayPair phiPair = std::make_pair(phi_ess_tdof_list, phiSurfaceVals);
|
||||
serif::types::MFEMArrayPairSet pairSet = std::make_pair(thetaPair, phiPair);
|
||||
|
||||
return pairSet;
|
||||
}
|
||||
|
||||
std::pair<mfem::Array<int>, mfem::Array<int>> PolySolver::findCenterElement() const {
|
||||
mfem::Array<int> thetaCenterDofs;
|
||||
mfem::Array<int> phiCenterDofs;
|
||||
|
||||
// --- 1. Find the index of the single mesh vertex at the origin ---
|
||||
int center_vertex_idx = -1;
|
||||
constexpr double tol = 1e-9; // A small tolerance for floating point comparison
|
||||
|
||||
for (int i = 0; i < m_mesh.GetNV(); ++i) {
|
||||
const double* vertex_coords = m_mesh.GetVertex(i);
|
||||
if (std::abs(vertex_coords[0]) < tol &&
|
||||
std::abs(vertex_coords[1]) < tol &&
|
||||
std::abs(vertex_coords[2]) < tol) {
|
||||
|
||||
center_vertex_idx = i;
|
||||
break; // Found it, assume there's only one.
|
||||
}
|
||||
}
|
||||
|
||||
if (center_vertex_idx == -1) {
|
||||
MFEM_ABORT("Could not find the center vertex at [0,0,0]. Check mesh construction.");
|
||||
}
|
||||
|
||||
// --- 2. Get Theta (H1) DoFs associated ONLY with that vertex ---
|
||||
// CORRECTED: Use GetVertexDofs, not GetVDofs.
|
||||
m_feTheta->GetVertexDofs(center_vertex_idx, thetaCenterDofs);
|
||||
|
||||
|
||||
mfem::Array<int> central_element_ids;
|
||||
|
||||
// PERF: could probably move this to a member variable and populate during construction
|
||||
mfem::Table* vertex_to_elements_table = m_mesh.GetVertexToElementTable();
|
||||
vertex_to_elements_table->Finalize();
|
||||
mfem::Array<int> element_ids;
|
||||
vertex_to_elements_table->GetRow(center_vertex_idx, element_ids);
|
||||
delete vertex_to_elements_table;
|
||||
|
||||
for (int i = 0; i < element_ids.Size(); ++i) {
|
||||
int element_id = element_ids[i];
|
||||
mfem::Array<int> tempDofs;
|
||||
m_fePhi->GetElementDofs(element_id, tempDofs);
|
||||
|
||||
// decode negative dofs to their true, physical, dof indices
|
||||
for (int j = 0; j < tempDofs.Size(); ++j) {
|
||||
int dof = tempDofs[j];
|
||||
if (dof < 0) {
|
||||
dof = -dof - 1; // Convert to positive index
|
||||
}
|
||||
phiCenterDofs.Append(dof);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
phiCenterDofs.Sort();
|
||||
phiCenterDofs.Unique();
|
||||
|
||||
return std::make_pair(thetaCenterDofs, phiCenterDofs);
|
||||
}
|
||||
|
||||
void PolySolver::setInitialGuess() const {
|
||||
// --- Set the initial guess for the solution ---
|
||||
mfem::FunctionCoefficient thetaInitGuess (
|
||||
[this](const mfem::Vector &x) {
|
||||
const double r = x.Norml2();
|
||||
// const double radius = Probe::getMeshRadius(*m_mesh);
|
||||
// const double u = 1/radius;
|
||||
|
||||
// return (-1.0/radius) * r + 1;
|
||||
// return -std::pow((u*r), 2)+1.0; // The series expansion is a better guess; however, this is cheaper and ensures that the value at the surface is very close to zero in a way that the series expansion does not
|
||||
return laneEmden::thetaSeriesExpansion(r, m_polytropicIndex, 10);
|
||||
}
|
||||
);
|
||||
|
||||
mfem::VectorFunctionCoefficient phiSurfaceVectors (m_mesh.SpaceDimension(),
|
||||
[this](const mfem::Vector &x, mfem::Vector &y) {
|
||||
const double r = x.Norml2();
|
||||
mfem::Vector xh(x);
|
||||
xh /= r; // Normalize the vector
|
||||
y.SetSize(m_mesh.SpaceDimension());
|
||||
y = xh;
|
||||
y *= polycoeff::thetaSurfaceFlux(m_polytropicIndex); // Assumes polycoeff is now serif::polytrope::polycoeff
|
||||
}
|
||||
);
|
||||
// We want to apply specific boundary conditions to the surface
|
||||
mfem::Array<int> ess_brd(m_mesh.bdr_attributes.Max());
|
||||
ess_brd = 1;
|
||||
|
||||
// θ = 0 at surface
|
||||
mfem::ConstantCoefficient surfacePotential(0);
|
||||
|
||||
m_theta->ProjectCoefficient(thetaInitGuess);
|
||||
m_theta->ProjectBdrCoefficient(surfacePotential, ess_brd);
|
||||
|
||||
mfem::GradientGridFunctionCoefficient phiInitGuess (m_theta.get());
|
||||
m_phi->ProjectCoefficient(phiInitGuess);
|
||||
|
||||
// Note that this will not result in perfect boundary conditions
|
||||
// because it must maintain H(div) continuity, this is
|
||||
// why inhomogenous boundary conditions enforcement is needed for φ
|
||||
// This manifests in PolytropeOperator::Mult where we do not
|
||||
// just zero out the essential dof elements in the residuals vector
|
||||
// for φ; rather, we need to set this to something which will push the
|
||||
// solver towards a more consistent answer (x_φ - target)
|
||||
m_phi->ProjectBdrCoefficientNormal(phiSurfaceVectors, ess_brd);
|
||||
|
||||
auto [thetaCenterDofs, phiCenterDofs] = findCenterElement();
|
||||
|
||||
for (int i = 0; i < phiCenterDofs.Size(); ++i)
|
||||
{
|
||||
(*m_phi)(phiCenterDofs[i]) = 0.0;
|
||||
}
|
||||
|
||||
if (m_config.get<bool>("Poly:Solver:ViewInitialGuess", false)) {
|
||||
serif::probe::glVisView(*m_theta, m_mesh, "θ init");
|
||||
serif::probe::glVisView(*m_phi, m_mesh, "φ init");
|
||||
}
|
||||
std::cout << "HERE" << std::endl;
|
||||
|
||||
}
|
||||
|
||||
void PolySolver::saveAndViewSolution(const mfem::BlockVector& state_vector) const {
|
||||
mfem::BlockVector x_block(const_cast<mfem::BlockVector&>(state_vector), m_polytropOperator->get_block_offsets());
|
||||
mfem::Vector& x_theta = x_block.GetBlock(0);
|
||||
mfem::Vector& x_phi = x_block.GetBlock(1);
|
||||
|
||||
if (m_config.get<bool>("Poly:Output:View", false)) {
|
||||
serif::probe::glVisView(x_theta, *m_feTheta, "θ Solution");
|
||||
serif::probe::glVisView(x_phi, *m_fePhi, "ɸ Solution");
|
||||
}
|
||||
|
||||
// --- Extract the Solution ---
|
||||
if (m_config.get<bool>("Poly:Output:1D:Save", true)) {
|
||||
const auto solutionPath = m_config.get<std::string>("Poly:Output:1D:Path", "polytropeSolution_1D.csv");
|
||||
auto derivSolPath = "d" + solutionPath;
|
||||
|
||||
const auto rayCoLatitude = m_config.get<double>("Poly:Output:1D:RayCoLatitude", 0.0);
|
||||
const auto rayLongitude = m_config.get<double>("Poly:Output:1D:RayLongitude", 0.0);
|
||||
const auto raySamples = m_config.get<int>("Poly:Output:1D:RaySamples", 100);
|
||||
|
||||
const std::vector rayDirection = {rayCoLatitude, rayLongitude};
|
||||
|
||||
serif::probe::getRaySolution(x_theta, *m_feTheta, rayDirection, raySamples, solutionPath);
|
||||
// Probe::getRaySolution(x_phi, *m_fePhi, rayDirection, raySamples, derivSolPath);
|
||||
}
|
||||
}
|
||||
|
||||
void PolySolver::setOperatorEssentialTrueDofs() const {
|
||||
const serif::types::MFEMArrayPairSet ess_tdof_pair_set = getEssentialTrueDof();
|
||||
m_polytropOperator->set_essential_true_dofs(ess_tdof_pair_set);
|
||||
}
|
||||
|
||||
void PolySolver::LoadSolverUserParams(double &newtonRelTol, double &newtonAbsTol, int &newtonMaxIter, int &newtonPrintLevel,
|
||||
double &gmresRelTol, double &gmresAbsTol, int &gmresMaxIter, int &gmresPrintLevel) const {
|
||||
newtonRelTol = m_config.get<double>("Poly:Solver:Newton:RelTol", 1.e-4);
|
||||
newtonAbsTol = m_config.get<double>("Poly:Solver:Newton:AbsTol", 1.e-6);
|
||||
newtonMaxIter = m_config.get<int>("Poly:Solver:Newton:MaxIter", 10);
|
||||
newtonPrintLevel = m_config.get<int>("Poly:Solver:Newton:PrintLevel", 3);
|
||||
|
||||
gmresRelTol = m_config.get<double>("Poly:Solver:GMRES:RelTol", 1.e-12);
|
||||
gmresAbsTol = m_config.get<double>("Poly:Solver:GMRES:AbsTol", 1.e-12);
|
||||
gmresMaxIter = m_config.get<int>("Poly:Solver:GMRES:MaxIter", 200);
|
||||
gmresPrintLevel = m_config.get<int>("Poly:Solver:GMRES:PrintLevel", -1);
|
||||
|
||||
LOG_DEBUG(m_logger, "Newton Solver (relTol: {:0.2E}, absTol: {:0.2E}, maxIter: {}, printLevel: {})", newtonRelTol, newtonAbsTol, newtonMaxIter, newtonPrintLevel);
|
||||
LOG_DEBUG(m_logger, "GMRES Solver (relTol: {:0.2E}, absTol: {:0.2E}, maxIter: {}, printLevel: {})", gmresRelTol, gmresAbsTol, gmresMaxIter, gmresPrintLevel);
|
||||
}
|
||||
|
||||
void PolySolver::GetDofCoordinates(const mfem::FiniteElementSpace &fes, const std::string& filename) {
|
||||
mfem::Mesh *mesh = fes.GetMesh();
|
||||
double r = serif::probe::getMeshRadius(*mesh);
|
||||
std::ofstream outputFile(filename, std::ios::out | std::ios::trunc);
|
||||
outputFile << "dof,R,r,x,y,z" << '\n';
|
||||
|
||||
const int nElements = mesh->GetNE();
|
||||
|
||||
mfem::Vector coords;
|
||||
mfem::IntegrationPoint ipZero;
|
||||
double p[3] = {0.0, 0.0, 0.0};
|
||||
int actual_idx;
|
||||
ipZero.Set3(p);
|
||||
for (int i = 0; i < nElements; i++) {
|
||||
mfem::Array<int> elemDofs;
|
||||
fes.GetElementDofs(i, elemDofs);
|
||||
mfem::ElementTransformation* T = mesh->GetElementTransformation(i);
|
||||
mfem::Vector physCoord(3);
|
||||
T->Transform(ipZero, physCoord);
|
||||
for (int dofID = 0; dofID < elemDofs.Size(); dofID++) {
|
||||
if (elemDofs[dofID] < 0) {
|
||||
actual_idx = -elemDofs[dofID] - 1;
|
||||
} else {
|
||||
actual_idx = elemDofs[dofID];
|
||||
}
|
||||
outputFile << actual_idx;
|
||||
if (dofID != elemDofs.Size() - 1) {
|
||||
outputFile << "|";
|
||||
} else {
|
||||
outputFile << ",";
|
||||
}
|
||||
}
|
||||
outputFile << r << "," << physCoord.Norml2() << "," << physCoord[0] << "," << physCoord[1] << "," << physCoord[2] << '\n';
|
||||
}
|
||||
outputFile.close();
|
||||
}
|
||||
|
||||
solverBundle PolySolver::setupNewtonSolver() const {
|
||||
// --- Load configuration parameters ---
|
||||
double newtonRelTol, newtonAbsTol, gmresRelTol, gmresAbsTol;
|
||||
int newtonMaxIter, newtonPrintLevel, gmresMaxIter, gmresPrintLevel;
|
||||
LoadSolverUserParams(newtonRelTol, newtonAbsTol, newtonMaxIter, newtonPrintLevel, gmresRelTol, gmresAbsTol,
|
||||
gmresMaxIter, gmresPrintLevel);
|
||||
|
||||
solverBundle solver; // Use this solver bundle to ensure lifetime safety
|
||||
solver.solver.SetRelTol(gmresRelTol);
|
||||
solver.solver.SetAbsTol(gmresAbsTol);
|
||||
solver.solver.SetMaxIter(gmresMaxIter);
|
||||
solver.solver.SetPrintLevel(gmresPrintLevel);
|
||||
|
||||
// solver.solver.SetPreconditioner(m_polytropOperator->GetPreconditioner());
|
||||
// --- Set up the Newton solver ---
|
||||
solver.newton.SetRelTol(newtonRelTol);
|
||||
solver.newton.SetAbsTol(newtonAbsTol);
|
||||
solver.newton.SetMaxIter(newtonMaxIter);
|
||||
solver.newton.SetPrintLevel(newtonPrintLevel);
|
||||
solver.newton.SetOperator(*m_polytropOperator);
|
||||
|
||||
// --- Created the linear solver which is used to invert the jacobian ---
|
||||
solver.newton.SetSolver(solver.solver);
|
||||
|
||||
return solver;
|
||||
}
|
||||
|
||||
} // namespace polytrope
|
||||
} // namespace serif
|
||||
615
src/polytrope/solver/public/polySolver.h
Normal file
615
src/polytrope/solver/public/polySolver.h
Normal file
@@ -0,0 +1,615 @@
|
||||
/* ***********************************************************************
|
||||
//
|
||||
// Copyright (C) 2025 -- The 4D-STAR Collaboration
|
||||
// File Author: Emily Boudreaux
|
||||
// Last Modified: April 21, 2025
|
||||
//
|
||||
// 4DSSE is free software; you can use it and/or modify
|
||||
// it under the terms and restrictions the GNU General Library Public
|
||||
// License version 3 (GPLv3) as published by the Free Software Foundation.
|
||||
//
|
||||
// 4DSSE is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this software; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// *********************************************************************** */
|
||||
#pragma once
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "integrators.h"
|
||||
#include "4DSTARTypes.h"
|
||||
#include "polytropeOperator.h"
|
||||
#include "config.h"
|
||||
#include "meshIO.h"
|
||||
#include "probe.h"
|
||||
#include "quill/Logger.h"
|
||||
|
||||
namespace serif {
|
||||
namespace polytrope {
|
||||
|
||||
/**
|
||||
* @brief Namespace for Lane-Emden equation related utility functions.
|
||||
*
|
||||
* Provides functions to compute coefficients and evaluate the series expansion
|
||||
* solution to the Lane-Emden equation, which describes the structure of a
|
||||
* spherically symmetric polytropic star.
|
||||
* The Lane-Emden equation is given by:
|
||||
* \f[
|
||||
* \frac{1}{\xi^2} \frac{d}{d\xi} \left( \xi^2 \frac{d\theta}{d\xi} \right) = -\theta^n
|
||||
* \f]
|
||||
* where \f$\xi\f$ is a dimensionless radius and \f$\theta\f$ is related to the density,
|
||||
* and \f$n\f$ is the polytropic index.
|
||||
*/
|
||||
namespace laneEmden {
|
||||
/**
|
||||
* @brief Computes the coefficient \f$a_k\f$ for the Lane-Emden series expansion.
|
||||
*
|
||||
* The series solution for \f$\theta(\xi)\f$ is given by \f$\theta(\xi) = \sum_{k=0}^{\infty} a_k \xi^k\f$.
|
||||
* The coefficients \f$a_k\f$ are determined by substituting the series into the Lane-Emden equation.
|
||||
* Specifically, \f$a_0 = 1\f$, \f$a_1 = 0\f$, and for \f$k \ge 2\f$,
|
||||
* \f$a_k = -\frac{c_{k-2,n}}{k(k+1)}\f$.
|
||||
*
|
||||
* @param k The index of the coefficient.
|
||||
* @param n The polytropic index.
|
||||
* @return The value of the coefficient \f$a_k\f$.
|
||||
* @see c(const int m, const double n)
|
||||
*/
|
||||
double a (const int k, const double n);
|
||||
|
||||
/**
|
||||
* @brief Computes the auxiliary coefficient \f$c_{m,n}\f$ used in determining \f$a_k\f$.
|
||||
*
|
||||
* The term \f$\theta^n\f$ in the Lane-Emden equation can also be expanded as a series
|
||||
* \f$\theta^n(\xi) = \sum_{m=0}^{\infty} c_{m,n} \xi^m\f$.
|
||||
* The coefficients \f$c_{m,n}\f$ are related to \f$a_k\f$ by:
|
||||
* \f$c_{0,n} = a_0^n\f$
|
||||
* \f$c_{m,n} = \frac{1}{m a_0} \sum_{j=1}^{m} (j n - m + j) a_j c_{m-j,n}\f$ for \f$m > 0\f$.
|
||||
*
|
||||
* @param m The index of the coefficient.
|
||||
* @param n The polytropic index.
|
||||
* @return The value of the coefficient \f$c_{m,n}\f$.
|
||||
* @see a(const int k, const double n)
|
||||
*/
|
||||
double c(const int m, const double n);
|
||||
|
||||
/**
|
||||
* @brief Computes the Lane-Emden function \f$\theta(\xi)\f$ using a series expansion.
|
||||
*
|
||||
* Evaluates the series \f$\theta(\xi) = \sum_{k=0}^{\text{order}-1} a_k \xi^k\f$ up to a specified order.
|
||||
* This provides an approximate solution to the Lane-Emden equation, particularly accurate for small \f$\xi\f$.
|
||||
*
|
||||
* @param xi The dimensionless radius \f$\xi\f$.
|
||||
* @param n The polytropic index.
|
||||
* @param order The number of terms to include in the series expansion.
|
||||
* @return The approximate value of \f$\theta(\xi)\f$.
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* double xi = 0.5;
|
||||
* double n = 1.5;
|
||||
* int series_order = 10;
|
||||
* double theta_val = laneEmden::thetaSeriesExpansion(xi, n, series_order);
|
||||
* // theta_val will be an approximation of the Lane-Emden function at xi=0.5 for n=1.5
|
||||
* @endcode
|
||||
*/
|
||||
double thetaSeriesExpansion(const double xi, const double n, const int order);
|
||||
} // namespace laneEmden
|
||||
|
||||
/**
|
||||
* @brief Structure to manage the lifetime of MFEM solver objects.
|
||||
*
|
||||
* This structure ensures that the `mfem::GMRESSolver` outlives the `mfem::NewtonSolver`
|
||||
* that might use it. The `mfem::NewtonSolver` often takes a pointer or reference to a
|
||||
* linear solver, and if the linear solver is destroyed first, it can lead to dangling
|
||||
* pointers and crashes.
|
||||
*
|
||||
* @note The order of declaration of members is crucial: `solver` must be declared
|
||||
* before `newton` to ensure it is constructed first and destroyed last.
|
||||
*/
|
||||
struct solverBundle {
|
||||
mfem::GMRESSolver solver; ///< The linear solver (e.g., GMRES). Must be declared first.
|
||||
mfem::NewtonSolver newton; ///< The nonlinear solver (e.g., Newton). Must be declared second.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Structure to hold the various bilinear and nonlinear forms for the polytrope problem.
|
||||
*
|
||||
* This structure bundles the MFEM forms that represent the discretized weak formulation
|
||||
* of the mixed variable polytropic equations.
|
||||
* The system being solved is typically:
|
||||
* \f[
|
||||
* \begin{aligned}
|
||||
* \boldsymbol{\phi} + \nabla \theta &= \mathbf{0} \\
|
||||
* \nabla \cdot \boldsymbol{\phi} - \theta^n &= 0
|
||||
* \end{aligned}
|
||||
* \f]
|
||||
* After integration by parts and discretization, these lead to matrix equations involving
|
||||
* the forms stored here.
|
||||
*/
|
||||
struct formBundle {
|
||||
/**
|
||||
* @brief Mixed bilinear form \f$ M(\psi^\theta, N^\phi) = \int_\Omega \nabla\psi^\theta \cdot N^\phi \,dV \f$.
|
||||
* This form arises from the term \f$\nabla \theta\f$ in the first equation, tested with a vector test function \f$N^\phi\f$.
|
||||
* It couples the scalar field \f$\theta\f$ (potential) with the vector field \f$\boldsymbol{\phi}\f$ (flux).
|
||||
*/
|
||||
std::unique_ptr<mfem::MixedBilinearForm> M;
|
||||
|
||||
/**
|
||||
* @brief Mixed bilinear form \f$ Q(\psi^\phi, N^\theta) = \int_\Omega \psi^\phi \cdot \nabla N^\theta \,dV \f$.
|
||||
* This form arises from the term \f$\boldsymbol{\phi}\f$ in the first equation, tested with a scalar test function \f$N^\theta\f$,
|
||||
* after integration by parts of the \f$\nabla \theta\f$ term.
|
||||
* It can also be seen as related to the transpose of a discrete gradient operator.
|
||||
*/
|
||||
std::unique_ptr<mfem::MixedBilinearForm> Q;
|
||||
|
||||
/**
|
||||
* @brief Bilinear form \f$ D(\psi^\phi, N^\phi) = \int_\Omega \psi^\phi \cdot N^\phi \,dV \f$.
|
||||
* This is a mass matrix for the vector field \f$\boldsymbol{\phi}\f$. It arises from the \f$\boldsymbol{\phi}\f$ term
|
||||
* in the first equation when tested with a vector test function \f$N^\phi\f$.
|
||||
*/
|
||||
std::unique_ptr<mfem::BilinearForm> D;
|
||||
|
||||
/**
|
||||
* @brief Bilinear form \f$ S(\psi^\theta, N^\theta) = \int_\Omega \nabla\psi^\theta \cdot \nabla N^\theta \,dV \f$.
|
||||
* This is a stiffness matrix (Laplacian) for the scalar field \f$\theta\f$. It is used for stabilization terms
|
||||
* or alternative formulations.
|
||||
*/
|
||||
std::unique_ptr<mfem::BilinearForm> S;
|
||||
|
||||
/**
|
||||
* @brief Nonlinear form \f$ f(\theta)(\psi^\theta) = \int_\Omega \psi^\theta \cdot \theta^n \,dV \f$.
|
||||
* This form arises from the nonlinear source term \f$\theta^n\f$ in the second equation, tested with a scalar
|
||||
* test function \f$\psi^\theta\f$.
|
||||
*/
|
||||
std::unique_ptr<mfem::NonlinearForm> f;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Solves the Lane-Emden equation for a polytropic star using a mixed finite element method.
|
||||
*
|
||||
* This class sets up and solves the system of equations describing a polytropic
|
||||
* stellar model. The Lane-Emden equation, in its second-order form, is:
|
||||
* \f[
|
||||
* \frac{1}{\xi^2} \frac{d}{d\xi} \left( \xi^2 \frac{d\theta}{d\xi} \right) = -\theta^n
|
||||
* \f]
|
||||
* where \f$\xi\f$ is a dimensionless radius, \f$\theta\f$ is a dimensionless temperature/potential
|
||||
* related to density (\f$\rho = \rho_c \theta^n\f$), and \f$n\f$ is the polytropic index.
|
||||
*
|
||||
* This solver uses a mixed formulation by introducing \f$\boldsymbol{\phi} = -\nabla \theta\f$:
|
||||
* \f[
|
||||
* \begin{aligned}
|
||||
* \boldsymbol{\phi} + \nabla \theta &= \mathbf{0} \\
|
||||
* \nabla \cdot \boldsymbol{\phi} - \theta^n &= 0
|
||||
* \end{aligned}
|
||||
* \f]
|
||||
* These equations are discretized using H1 finite elements for \f$\theta\f$ and
|
||||
* RT (Raviart-Thomas) finite elements for \f$\boldsymbol{\phi}\f$. The resulting nonlinear
|
||||
* system is solved using a Newton method, with the `PolytropeOperator` class
|
||||
* defining the system residual and Jacobian.
|
||||
*
|
||||
* Boundary conditions typically include \f$\theta(\xi_c) = 1\f$ at the center (\f$\xi_c \approx 0\f$),
|
||||
* \f$\theta(\xi_1) = 0\f$ at the surface (\f$\xi_1\f$ is the first root of \f$\theta\f$), and
|
||||
* \f$\boldsymbol{\phi} \cdot \mathbf{n} = -\frac{d\theta}{d\xi}\f$ at the surface, which is related to the
|
||||
* surface gravity. At the center, \f$\boldsymbol{\phi}(\xi_c) = \mathbf{0}\f$ due to symmetry.
|
||||
*/
|
||||
class PolySolver final{
|
||||
public: // Public methods
|
||||
/**
|
||||
* @brief Constructs a PolySolver instance.
|
||||
*
|
||||
* Initializes the solver for a given polytropic index \f$n\f$ and finite element order.
|
||||
* This constructor internally calls `prepareMesh` to load and scale the mesh.
|
||||
*
|
||||
* @param n The polytropic index. Must be \f$0 \le n < 5\f$.
|
||||
* @param order The polynomial order of the H1 finite elements for \f$\theta\f$.
|
||||
* The RT elements for \f$\boldsymbol{\phi}\f$ will be of order `order-1`.
|
||||
* @throw std::runtime_error If \f$n \ge 5\f$ or \f$n < 0\f$ (via `prepareMesh`).
|
||||
*
|
||||
* @example
|
||||
* @code
|
||||
* try {
|
||||
* PolySolver solver(1.5, 2); // Polytropic index n=1.5, FE order 2 for theta
|
||||
* solver.solve();
|
||||
* } catch (const std::exception& e) {
|
||||
* std::cerr << "Error: " << e.what() << std::endl;
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
PolySolver(const double n, const double order);
|
||||
|
||||
/**
|
||||
* @brief Destructor for PolySolver.
|
||||
*
|
||||
* Defaulted, handles cleanup of owned resources like `std::unique_ptr` members.
|
||||
*/
|
||||
~PolySolver();
|
||||
|
||||
/**
|
||||
* @brief Solves the polytropic system.
|
||||
*
|
||||
* This method orchestrates the solution process:
|
||||
* 1. Sets the initial guess for \f$\theta\f$ and \f$\boldsymbol{\phi}\f$.
|
||||
* 2. Applies essential boundary conditions to the `PolytropeOperator`.
|
||||
* 3. Finalizes the `PolytropeOperator` with the initial state.
|
||||
* 4. Sets up the Newton solver (including the GMRES linear solver for Jacobian systems).
|
||||
* 5. Runs the Newton iteration to find the solution for the free DOFs.
|
||||
* 6. Reconstructs the full solution vector from the free DOFs.
|
||||
* 7. Saves and/or views the solution based on configuration.
|
||||
*
|
||||
* @throw std::runtime_error or `mfem::ErrorException` if the solver fails to converge or encounters numerical issues.
|
||||
*/
|
||||
void solve() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the polytropic index \f$n\f$.
|
||||
* @return The polytropic index.
|
||||
*/
|
||||
double getN() const { return m_polytropicIndex; }
|
||||
|
||||
/**
|
||||
* @brief Gets the finite element order used for the \f$\theta\f$ variable.
|
||||
* @return The polynomial order of the H1 finite elements.
|
||||
*/
|
||||
double getOrder() const { return m_feOrder; }
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the computational mesh.
|
||||
* @return A reference to the `mfem::Mesh` object.
|
||||
*/
|
||||
mfem::Mesh& getMesh() const { return m_mesh; }
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the solution grid function for \f$\theta\f$.
|
||||
* @return A reference to the `mfem::GridFunction` storing the \f$\theta\f$ solution.
|
||||
* @note The solution is populated after `solve()` has been successfully called.
|
||||
*/
|
||||
mfem::GridFunction& getSolution() const { return *m_theta; }
|
||||
|
||||
private: // Private Attributes
|
||||
// --- Configuration and Logging ---
|
||||
serif::config::Config& m_config; ///< Reference to the global configuration manager instance.
|
||||
serif::probe::LogManager& m_logManager; ///< Reference to the global log manager instance.
|
||||
quill::Logger* m_logger; ///< Pointer to the specific logger instance for this class.
|
||||
|
||||
// --- Physical and Discretization Parameters ---
|
||||
double m_polytropicIndex; ///< The polytropic index \f$n\f$.
|
||||
double m_feOrder; ///< The polynomial order for H1 elements (\f$\theta\f$). RT elements for \f$\boldsymbol{\phi}\f$ are of order `m_feOrder - 1`.
|
||||
|
||||
// --- MFEM Core Objects ---
|
||||
mfem::Mesh& m_mesh; ///< Reference to the computational mesh (owned by ResourceManager).
|
||||
std::unique_ptr<mfem::H1_FECollection> m_fecH1; ///< Finite Element Collection for \f$\theta\f$ (H1 elements).
|
||||
std::unique_ptr<mfem::RT_FECollection> m_fecRT; ///< Finite Element Collection for \f$\boldsymbol{\phi}\f$ (Raviart-Thomas elements).
|
||||
|
||||
std::unique_ptr<mfem::FiniteElementSpace> m_feTheta; ///< Finite Element Space for \f$\theta\f$.
|
||||
std::unique_ptr<mfem::FiniteElementSpace> m_fePhi; ///< Finite Element Space for \f$\boldsymbol{\phi}\f$.
|
||||
|
||||
// --- Solution Vectors ---
|
||||
std::unique_ptr<mfem::GridFunction> m_theta; ///< Grid function for the scalar potential \f$\theta\f$.
|
||||
std::unique_ptr<mfem::GridFunction> m_phi; ///< Grid function for the vector flux \f$\boldsymbol{\phi}\f$.
|
||||
|
||||
// --- Operator and Solver Components ---
|
||||
std::unique_ptr<PolytropeOperator> m_polytropOperator; ///< The main nonlinear operator for the mixed system.
|
||||
std::unique_ptr<mfem::OperatorJacobiSmoother> m_prec; ///< Preconditioner (currently seems unused in `polySolver.cpp`).
|
||||
|
||||
private: // Private methods
|
||||
/**
|
||||
* @brief Private constructor that takes an existing mesh.
|
||||
*
|
||||
* Initializes FE collections, spaces, grid functions, and assembles the block system.
|
||||
* This is called by the public constructor after `prepareMesh`.
|
||||
*
|
||||
* @param mesh A reference to an initialized `mfem::Mesh`.
|
||||
* @param n The polytropic index.
|
||||
* @param order The polynomial order for H1 finite elements.
|
||||
*/
|
||||
PolySolver(mfem::Mesh& mesh, double n, double order);
|
||||
|
||||
/**
|
||||
* @brief Prepares the mesh for the simulation.
|
||||
*
|
||||
* Loads a generic sphere mesh from the `ResourceManager` and scales it
|
||||
* to the dimensionless radius \f$\xi_1(n)\f$ (the first zero of the Lane-Emden function
|
||||
* for the given polytropic index \f$n\f$).
|
||||
*
|
||||
* @param n The polytropic index.
|
||||
* @return A reference to the prepared `mfem::Mesh` (owned by `ResourceManager`).
|
||||
* @throw std::runtime_error If \f$n \ge 5.0\f$ or \f$n < 0.0\f$, as \f$\xi_1(n)\f$ is typically
|
||||
* undefined or infinite outside this range for physical polytropes.
|
||||
* @throw std::runtime_error If the mesh resource "mesh:polySphere" cannot be found or loaded.
|
||||
*
|
||||
* @details The scaling factor \f$\xi_1(n)\f$ is obtained from `polycoeff::x1(n)`.
|
||||
* The mesh is expected to be a unit sphere initially.
|
||||
*/
|
||||
static mfem::Mesh& prepareMesh(double n);
|
||||
|
||||
/**
|
||||
* @brief Assembles the block system operator `m_polytropOperator`.
|
||||
*
|
||||
* This involves:
|
||||
* 1. Computing block offsets for \f$\theta\f$ and \f$\boldsymbol{\phi}\f$ variables.
|
||||
* 2. Building individual bilinear and nonlinear forms (M, Q, D, S, f) using `buildIndividualForms`.
|
||||
* 3. Constructing the `PolytropeOperator` with these forms and offsets.
|
||||
*/
|
||||
void assembleBlockSystem();
|
||||
|
||||
/**
|
||||
* @brief Compute the block offsets for the operator. These are the offsets that define which dofs belong to which variable.
|
||||
*
|
||||
* @details
|
||||
* Create the block offsets. These define the start of each block in the combined vector.
|
||||
* Block offsets will be \f$[0, N_\theta, N_\theta + N_\phi]\f$, where \f$N_\theta\f$ is the number of
|
||||
* degrees of freedom for \f$\theta\f$ and \f$N_\phi\f$ is for \f$\boldsymbol{\phi}\f$.
|
||||
* The interpretation of this is that each block tells the operator where in the flattened (1D) vector
|
||||
* the degrees of freedom or coefficients for that free parameter start and end. I.e.
|
||||
* we know that in any flattened vector will have a size \f$N_\theta + N_\phi\f$. The \f$\theta\f$ dofs will span
|
||||
* from `blockOffsets[0]` to `blockOffsets[1]` and the \f$\boldsymbol{\phi}\f$ dofs will span from `blockOffsets[1]` to `blockOffsets[2]`.
|
||||
*
|
||||
* This is the same for matrices only in 2D (rows and columns).
|
||||
*
|
||||
* The key point here is that this is fundamentally an accounting structure, it is here to keep track of what
|
||||
* parts of vectors and matrices belong to which variable.
|
||||
*
|
||||
* Also note that we use `VSize` rather than `Size`. `Size` refers to the number of true dofs (after eliminating
|
||||
* boundary conditions). `VSize` refers to the total number of dofs before BC elimination, which is needed here.
|
||||
*
|
||||
* @return `mfem::Array<int>` The offsets for the blocks in the operator.
|
||||
*
|
||||
* @pre `m_feTheta` and `m_fePhi` must be valid, populated `mfem::FiniteElementSpace` objects.
|
||||
*/
|
||||
mfem::Array<int> computeBlockOffsets() const;
|
||||
|
||||
/**
|
||||
* @brief Build the individual forms for the block operator (M, Q, D, S, and f).
|
||||
*
|
||||
* @param blockOffsets The offsets for the blocks in the operator, computed by `computeBlockOffsets`.
|
||||
* @return A `std::unique_ptr<formBundle>` containing the assembled forms.
|
||||
*
|
||||
* @note These forms are built exactly how they are defined in the derivation.
|
||||
* For example, `Mform` corresponds to \f$+M\f$, not \f$-M\f$.
|
||||
* The `PolytropeOperator` handles any necessary sign changes for the final system assembly.
|
||||
*
|
||||
* @details
|
||||
* This method initializes and assembles the discrete forms corresponding to the weak formulation
|
||||
* of the mixed polytropic system:
|
||||
* \f[
|
||||
* M = \int_\Omega \nabla\psi^\theta \cdot N^\phi \,dV \quad (\text{from } \nabla\theta \text{ term})
|
||||
* \f]
|
||||
* \f[
|
||||
* Q = \int_\Omega \psi^\phi \cdot \nabla N^\theta \,dV \quad (\text{from } \boldsymbol{\phi} \text{ term, related to } -M^T)
|
||||
* \f]
|
||||
* \f[
|
||||
* D = \int_\Omega \psi^\phi \cdot N^\phi \,dV \quad (\text{mass matrix for } \boldsymbol{\phi})
|
||||
* \f]
|
||||
* \f[
|
||||
* S = \int_\Omega \nabla\psi^\theta \cdot \nabla N^\theta \,dV \quad (\text{stiffness matrix for } \theta \text{, for stabilization})
|
||||
* \f]
|
||||
* \f[
|
||||
* f(\theta)(\psi^\theta) = \int_\Omega \psi^\theta \cdot \theta^n \,dV \quad (\text{from nonlinear term } \theta^n)
|
||||
* \f]
|
||||
* The `PolytropeOperator` will then use these to form a system like:
|
||||
* \f[
|
||||
* R(X) = \begin{pmatrix}
|
||||
* \text{nonlin_op}(\theta) + M\,\boldsymbol{\phi} \\
|
||||
* D\,\boldsymbol{\phi} - Q\,\theta
|
||||
* \end{pmatrix}
|
||||
* = \mathbf{0}
|
||||
* \f]
|
||||
* (The exact structure depends on the `PolytropeOperator`'s internal assembly, which might involve stabilization terms using S).
|
||||
*
|
||||
* @pre `m_feTheta` and `m_fePhi` must be valid, populated `mfem::FiniteElementSpace` objects.
|
||||
* `m_polytropicIndex` must be set.
|
||||
* @post The returned `formBundle` contains unique pointers to assembled (and finalized where appropriate) MFEM forms.
|
||||
*/
|
||||
std::unique_ptr<formBundle> buildIndividualForms(const mfem::Array<int>& blockOffsets);
|
||||
|
||||
/**
|
||||
* @brief Assemble and finalize a given MFEM form.
|
||||
*
|
||||
* This template function calls `Assemble()` and `Finalize()` on the provided form.
|
||||
* `Assemble()` computes local element matrices and adds them to the global matrix.
|
||||
* `Finalize()` builds the sparsity pattern and allows the `SparseMatrix` representation to be extracted.
|
||||
*
|
||||
* @tparam FormType The type of the MFEM form (e.g., `mfem::BilinearForm`, `mfem::MixedBilinearForm`).
|
||||
* @param f A reference to the form to be assembled and finalized.
|
||||
*
|
||||
* @pre `f` must be a valid form object that supports `Assemble()` and `Finalize()` methods.
|
||||
* @post `f` is assembled and finalized, and its sparse matrix representation is available.
|
||||
*
|
||||
* @note Nonlinear forms like `mfem::NonlinearForm` are typically not "finalized" in this sense,
|
||||
* as they don't directly produce a sparse matrix but are evaluated. This function is
|
||||
* intended for linear forms.
|
||||
*/
|
||||
static void assembleAndFinalizeForm(auto &f);
|
||||
|
||||
/**
|
||||
* @brief Computes the essential true degrees of freedom (DOFs) and their values for boundary conditions.
|
||||
*
|
||||
* This method determines the DOFs that correspond to:
|
||||
* 1. \f$\theta = 1\f$ at the center of the star.
|
||||
* 2. \f$\theta = 0\f$ on the surface of the star (boundary attribute 1).
|
||||
* 3. \f$\boldsymbol{\phi} \cdot \mathbf{n} = \text{surface\_flux}\f$ on the surface, where the flux is derived from \f$\theta'(\xi_1)\f$.
|
||||
* (This is applied to the normal component of \f$\boldsymbol{\phi}\f$).
|
||||
* 4. Potentially \f$\boldsymbol{\phi} = \mathbf{0}\f$ at the center (though the current implementation in `polySolver.cpp`
|
||||
* seems to set components of \f$\boldsymbol{\phi}\f$ to zero at DOFs associated with the center element(s)).
|
||||
*
|
||||
* @return An `serif::types::MFEMArrayPairSet` containing two pairs:
|
||||
* - The first pair is for \f$\theta\f$: (`mfem::Array<int>` of essential TDOF indices, `mfem::Array<double>` of corresponding values).
|
||||
* - The second pair is for \f$\boldsymbol{\phi}\f$: (`mfem::Array<int>` of essential TDOF indices, `mfem::Array<double>` of corresponding values).
|
||||
*
|
||||
* @details "True DOFs" (tdof) in MFEM refer to the actual degrees of freedom in the global system,
|
||||
* as opposed to local DOFs within an element. Essential boundary conditions fix the values of these DOFs.
|
||||
* The center condition for \f$\theta\f$ is applied to DOFs identified by `findCenterElement()`.
|
||||
* Surface conditions are applied to boundary attributes marked as essential (typically attribute 1).
|
||||
* The surface flux for \f$\boldsymbol{\phi}\f$ is obtained from `polycoeff::thetaSurfaceFlux(m_polytropicIndex)`.
|
||||
*
|
||||
* @pre `m_mesh`, `m_feTheta`, `m_fePhi` must be initialized. `m_polytropicIndex` must be set.
|
||||
* @throw `mfem::ErrorException` (via `MFEM_ABORT`) if `findCenterElement()` fails to locate the center vertex.
|
||||
*/
|
||||
serif::types::MFEMArrayPairSet getEssentialTrueDof() const;
|
||||
|
||||
/**
|
||||
* @brief Finds the degrees of freedom (DOFs) associated with the geometric center (origin) of the mesh.
|
||||
*
|
||||
* This method identifies:
|
||||
* 1. The mesh vertex located at coordinates (0,0,0).
|
||||
* 2. The H1 DOFs (\f$\theta\f$) associated with this center vertex.
|
||||
* 3. The RT DOFs (\f$\boldsymbol{\phi}\f$) associated with mesh elements that share this center vertex.
|
||||
*
|
||||
* These DOFs are used to apply boundary conditions at the center of the polytrope,
|
||||
* such as \f$\theta(\xi_c)=1\f$ and \f$\boldsymbol{\phi}(\xi_c)=\mathbf{0}\f$.
|
||||
*
|
||||
* @return A `std::pair` of `mfem::Array<int>`:
|
||||
* - `first`: Array of \f$\theta\f$ (H1) DOF indices at the center.
|
||||
* - `second`: Array of \f$\boldsymbol{\phi}\f$ (RT) DOF indices associated with central elements.
|
||||
*
|
||||
* @throw `mfem::ErrorException` (via `MFEM_ABORT`) if no vertex is found at the origin (within tolerance).
|
||||
*
|
||||
* @details For RT elements, DOFs are typically associated with faces (or edges in 2D). This method collects
|
||||
* DOFs from all elements connected to the center vertex. For H1 elements, DOFs can be directly
|
||||
* associated with vertices.
|
||||
*
|
||||
* @pre `m_mesh`, `m_feTheta`, `m_fePhi` must be initialized.
|
||||
*/
|
||||
std::pair<mfem::Array<int>, mfem::Array<int>> findCenterElement() const;
|
||||
|
||||
/**
|
||||
* @brief Sets the initial guess for the solution variables \f$\theta\f$ and \f$\boldsymbol{\phi}\f$.
|
||||
*
|
||||
* - For \f$\theta\f$:
|
||||
* - The interior initial guess is based on the Lane-Emden series expansion:
|
||||
* \f$\theta(\xi) = \sum a_k \xi^k\f$ (using `laneEmden::thetaSeriesExpansion`).
|
||||
* - The boundary condition \f$\theta(\xi_1) = 0\f$ is projected onto the surface.
|
||||
* - For \f$\boldsymbol{\phi}\f$:
|
||||
* - The initial guess is \f$\boldsymbol{\phi} = -\nabla \theta_{\text{init}}\f$, where \f$\theta_{\text{init}}\f$ is the initial guess for \f$\theta\f$.
|
||||
* - The boundary condition for the normal component \f$\boldsymbol{\phi} \cdot \mathbf{n}\f$ at the surface is projected.
|
||||
* This value is \f$\theta'(\xi_1)\f$, obtained from `polycoeff::thetaSurfaceFlux`.
|
||||
* - \f$\boldsymbol{\phi}\f$ components corresponding to central DOFs (from `findCenterElement`) are set to 0.
|
||||
*
|
||||
* The method uses `mfem::GridFunction::ProjectCoefficient` and `ProjectBdrCoefficient` (or `ProjectBdrCoefficientNormal`)
|
||||
* for these projections.
|
||||
*
|
||||
* @note The projection of boundary conditions for \f$\boldsymbol{\phi}\f$ (RT elements) might not be exact due to
|
||||
* H(div) continuity requirements. Inhomogeneous BC enforcement in `PolytropeOperator` handles this more robustly.
|
||||
*
|
||||
* @pre `m_theta`, `m_phi`, `m_mesh`, `m_feTheta`, `m_fePhi` must be initialized.
|
||||
* `m_polytropicIndex` must be set.
|
||||
* Configuration settings for viewing initial guess should be loaded if desired.
|
||||
* @post `m_theta` and `m_phi` grid functions are populated with the initial guess.
|
||||
*/
|
||||
void setInitialGuess() const;
|
||||
|
||||
/**
|
||||
* @brief Saves the 1D radial solution and optionally displays the 2D/3D solution using GLVis.
|
||||
*
|
||||
* Extracts the \f$\theta\f$ and \f$\boldsymbol{\phi}\f$ components from the converged `state_vector`.
|
||||
* - If configured (`Poly:Output:1D:Save`), it extracts a 1D solution along a specified ray
|
||||
* (defined by co-latitude and longitude) using `Probe::getRaySolution` and saves it to a CSV file.
|
||||
* - If configured (`Poly:Output:View`), it displays \f$\theta\f$ and \f$\boldsymbol{\phi}\f$ using `Probe::glVisView`.
|
||||
*
|
||||
* @param state_vector The full solution vector (block vector containing \f$\theta\f$ and \f$\boldsymbol{\phi}\f$)
|
||||
* obtained from the Newton solver, typically after reconstruction by `PolytropeOperator`.
|
||||
*
|
||||
* @pre `m_polytropOperator`, `m_feTheta`, `m_fePhi` must be initialized.
|
||||
* Configuration settings for output and viewing must be loaded.
|
||||
* @post Solution data is saved and/or visualized according to configuration.
|
||||
*/
|
||||
void saveAndViewSolution(const mfem::BlockVector& state_vector) const;
|
||||
|
||||
/**
|
||||
* @brief Sets up the Newton solver and its associated linear solver (GMRES).
|
||||
*
|
||||
* 1. Loads solver parameters (tolerances, max iterations, print levels) for both Newton
|
||||
* and GMRES from the configuration file (via `LoadSolverUserParams`).
|
||||
* 2. Creates a `solverBundle` to manage the lifetimes of `mfem::GMRESSolver` and `mfem::NewtonSolver`.
|
||||
* 3. Configures the GMRES solver with its parameters.
|
||||
* 4. Configures the Newton solver with its parameters, sets the `PolytropeOperator` as the
|
||||
* nonlinear system operator, and sets the configured GMRES solver as the linear solver
|
||||
* for inverting Jacobians.
|
||||
*
|
||||
* @return A `solverBundle` struct containing the configured Newton and GMRES solvers.
|
||||
* The ownership of the solvers within the bundle is managed by the bundle itself.
|
||||
*
|
||||
* @pre `m_polytropOperator` must be initialized and finalized.
|
||||
* Configuration settings for solver parameters must be available.
|
||||
* @post A fully configured `solverBundle` is returned, ready for `newton.Mult()`.
|
||||
*/
|
||||
solverBundle setupNewtonSolver() const;
|
||||
|
||||
/**
|
||||
* @brief Sets the essential true DOFs on the `PolytropeOperator`.
|
||||
*
|
||||
* Calls `getEssentialTrueDof()` to compute the boundary condition DOFs and values,
|
||||
* and then passes them to `m_polytropOperator->set_essential_true_dofs()`.
|
||||
* This step is crucial for the `PolytropeOperator` to correctly handle
|
||||
* boundary conditions when forming reduced systems and applying residuals/Jacobians.
|
||||
*
|
||||
* @pre `m_polytropOperator` must be initialized.
|
||||
* `m_mesh`, `m_feTheta`, `m_fePhi`, `m_polytropicIndex` must be set (for `getEssentialTrueDof`).
|
||||
* @post Essential boundary conditions are registered with the `m_polytropOperator`.
|
||||
* The `PolytropeOperator` will likely be marked as not finalized after this call.
|
||||
*/
|
||||
void setOperatorEssentialTrueDofs() const;
|
||||
|
||||
/**
|
||||
* @brief Loads Newton and GMRES solver parameters from the configuration.
|
||||
*
|
||||
* Retrieves relative tolerance, absolute tolerance, maximum iterations, and print level
|
||||
* for both the Newton solver and the GMRES linear solver from the `Config` instance.
|
||||
* Default values are used if specific configuration keys are not found.
|
||||
*
|
||||
* Configuration keys are typically prefixed with `Poly:Solver:Newton:` and `Poly:Solver:GMRES:`.
|
||||
* Example keys: `Poly:Solver:Newton:RelTol`, `Poly:Solver:GMRES:MaxIter`.
|
||||
*
|
||||
* @param[out] newtonRelTol Relative tolerance for Newton solver.
|
||||
* @param[out] newtonAbsTol Absolute tolerance for Newton solver.
|
||||
* @param[out] newtonMaxIter Maximum iterations for Newton solver.
|
||||
* @param[out] newtonPrintLevel Print level for Newton solver.
|
||||
* @param[out] gmresRelTol Relative tolerance for GMRES solver.
|
||||
* @param[out] gmresAbsTol Absolute tolerance for GMRES solver.
|
||||
* @param[out] gmresMaxIter Maximum iterations for GMRES solver.
|
||||
* @param[out] gmresPrintLevel Print level for GMRES solver.
|
||||
*
|
||||
* @pre `m_config` must be a valid reference to a `Config` object.
|
||||
* `m_logger` should be initialized if debug logging is enabled.
|
||||
* @post Output parameters are populated with values from configuration or defaults.
|
||||
* Solver parameters are logged at DEBUG level.
|
||||
*/
|
||||
void LoadSolverUserParams(double &newtonRelTol, double &newtonAbsTol, int &newtonMaxIter, int &newtonPrintLevel,
|
||||
double &gmresRelTol, double &gmresAbsTol, int &gmresMaxIter, int &gmresPrintLevel) const;
|
||||
|
||||
/**
|
||||
* @brief Utility function to get and save the coordinates of degrees of freedom for a finite element space.
|
||||
*
|
||||
* For each element in the mesh:
|
||||
* 1. Gets the DOFs associated with that element from the given `fes`.
|
||||
* 2. Gets the physical coordinates of the element's center (or a reference point).
|
||||
* 3. Writes the DOF indices, mesh radius, element center radius, and element center x,y,z coordinates
|
||||
* to the specified output file in CSV format.
|
||||
*
|
||||
* This can be useful for debugging or analyzing the distribution of DOFs.
|
||||
*
|
||||
* @param fes The `mfem::FiniteElementSpace` for which to get DOF coordinates.
|
||||
* @param filename The name of the output CSV file.
|
||||
*
|
||||
* @pre `fes` must be a valid, initialized `mfem::FiniteElementSpace`.
|
||||
* The mesh associated with `fes` must be valid.
|
||||
* @post A CSV file named `filename` is created/truncated and populated with DOF coordinate information.
|
||||
*
|
||||
* @note For DOFs shared by multiple elements, this function might list them multiple times,
|
||||
* associated with each element they belong to. The output format lists all DOFs for an element
|
||||
* on one line, pipe-separated, followed by coordinate data for that element.
|
||||
*/
|
||||
static void GetDofCoordinates(const mfem::FiniteElementSpace &fes, const std::string& filename);
|
||||
|
||||
};
|
||||
|
||||
} // namespace polytrope
|
||||
} // namespace serif
|
||||
50
src/polytrope/utils/meson.build
Normal file
50
src/polytrope/utils/meson.build
Normal file
@@ -0,0 +1,50 @@
|
||||
# ***********************************************************************
|
||||
#
|
||||
# Copyright (C) 2025 -- The 4D-STAR Collaboration
|
||||
# File Author: Emily Boudreaux
|
||||
# Last Modified: March 19, 2025
|
||||
#
|
||||
# 4DSSE is free software; you can use it and/or modify
|
||||
# it under the terms and restrictions the GNU General Library Public
|
||||
# License version 3 (GPLv3) as published by the Free Software Foundation.
|
||||
#
|
||||
# 4DSSE is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this software; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# *********************************************************************** #
|
||||
polyutils_sources = files(
|
||||
'private/integrators.cpp',
|
||||
'private/polytropeOperator.cpp',
|
||||
'private/utilities.cpp',
|
||||
)
|
||||
|
||||
dependencies = [
|
||||
mfem_dep,
|
||||
macros_dep,
|
||||
probe_dep,
|
||||
quill_dep,
|
||||
config_dep,
|
||||
types_dep,
|
||||
mfemanalysis_dep,
|
||||
]
|
||||
|
||||
libpolyutils = static_library('polyutils',
|
||||
polyutils_sources,
|
||||
include_directories : include_directories('./public'),
|
||||
cpp_args: ['-fvisibility=default'],
|
||||
dependencies: dependencies,
|
||||
install: true
|
||||
)
|
||||
|
||||
polyutils_dep = declare_dependency(
|
||||
include_directories : include_directories('./public'),
|
||||
link_with : libpolyutils,
|
||||
sources : polyutils_sources,
|
||||
dependencies : dependencies
|
||||
)
|
||||
146
src/polytrope/utils/private/integrators.cpp
Normal file
146
src/polytrope/utils/private/integrators.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
/* ***********************************************************************
|
||||
//
|
||||
// Copyright (C) 2025 -- The 4D-STAR Collaboration
|
||||
// File Author: Emily Boudreaux
|
||||
// Last Modified: April 21, 2025
|
||||
//
|
||||
// 4DSSE is free software; you can use it and/or modify
|
||||
// it under the terms and restrictions the GNU General Library Public
|
||||
// License version 3 (GPLv3) as published by the Free Software Foundation.
|
||||
//
|
||||
// 4DSSE is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this software; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// *********************************************************************** */
|
||||
#include "mfem.hpp"
|
||||
#include <cmath>
|
||||
|
||||
#include "integrators.h"
|
||||
#include "config.h"
|
||||
#include <string>
|
||||
|
||||
namespace serif {
|
||||
namespace polytrope {
|
||||
namespace polyMFEMUtils {
|
||||
NonlinearPowerIntegrator::NonlinearPowerIntegrator(const double n) :
|
||||
m_polytropicIndex(n),
|
||||
m_epsilon(serif::config::Config::getInstance().get<double>("Poly:Solver:Epsilon", 1.0e-8)) {
|
||||
|
||||
if (m_polytropicIndex < 0.0) {
|
||||
throw std::invalid_argument("Polytropic index must be non-negative.");
|
||||
}
|
||||
if (m_polytropicIndex > 5.0) {
|
||||
throw std::invalid_argument("Polytropic index must be less than 5.0.");
|
||||
}
|
||||
if (m_epsilon <= 0.0) {
|
||||
throw std::invalid_argument("Epsilon (Poly:Solver:Epsilon) must be positive non-zero.");
|
||||
}
|
||||
}
|
||||
|
||||
void NonlinearPowerIntegrator::AssembleElementVector(
|
||||
const mfem::FiniteElement &el,
|
||||
mfem::ElementTransformation &Trans,
|
||||
const mfem::Vector &elfun,
|
||||
mfem::Vector &elvect) {
|
||||
|
||||
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(el.GetGeomType(), 2 * el.GetOrder() + 3);
|
||||
int dof = el.GetDof();
|
||||
elvect.SetSize(dof);
|
||||
elvect = 0.0;
|
||||
|
||||
mfem::Vector shape(dof);
|
||||
mfem::Vector physCoord;
|
||||
for (int iqp = 0; iqp < ir->GetNPoints(); iqp++) {
|
||||
mfem::IntegrationPoint ip = ir->IntPoint(iqp);
|
||||
Trans.SetIntPoint(&ip);
|
||||
const double weight = ip.weight * Trans.Weight();
|
||||
|
||||
el.CalcShape(ip, shape);
|
||||
|
||||
double u_val = 0.0;
|
||||
for (int j = 0; j < dof; j++) {
|
||||
u_val += elfun(j) * shape(j);
|
||||
}
|
||||
|
||||
double u_nl;
|
||||
Trans.Transform(ip, physCoord);
|
||||
const double r = physCoord.Norml2();
|
||||
std::ofstream outFile("r.dat", std::ios::app);
|
||||
outFile << r << '\n';
|
||||
outFile.close();
|
||||
if (r > m_regularizationRadius) {
|
||||
if (u_val < m_epsilon) {
|
||||
u_nl = fmod(u_val, m_polytropicIndex, m_epsilon);
|
||||
} else {
|
||||
u_nl = std::pow(u_val, m_polytropicIndex);
|
||||
}
|
||||
} else {
|
||||
u_nl = 1.0 - m_polytropicIndex * m_regularizationCoeff * std::pow(r, 2);
|
||||
}
|
||||
|
||||
for (int i = 0; i < dof; i++){
|
||||
elvect(i) += shape(i) * u_nl * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NonlinearPowerIntegrator::AssembleElementGrad (
|
||||
const mfem::FiniteElement &el,
|
||||
mfem::ElementTransformation &Trans,
|
||||
const mfem::Vector &elfun,
|
||||
mfem::DenseMatrix &elmat) {
|
||||
|
||||
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(el.GetGeomType(), 2 * el.GetOrder() + 3);
|
||||
const int dof = el.GetDof();
|
||||
elmat.SetSize(dof);
|
||||
elmat = 0.0;
|
||||
mfem::Vector shape(dof);
|
||||
mfem::DenseMatrix dshape(dof, 3);
|
||||
mfem::DenseMatrix invJ(3, 3);
|
||||
mfem::Vector physCoord;
|
||||
|
||||
for (int iqp = 0; iqp < ir->GetNPoints(); iqp++) {
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(iqp);
|
||||
Trans.SetIntPoint(&ip);
|
||||
const double weight = ip.weight * Trans.Weight();
|
||||
Trans.Transform(ip, physCoord);
|
||||
double r = physCoord.Norml2();
|
||||
|
||||
el.CalcShape(ip, shape);
|
||||
|
||||
double u_val = 0.0;
|
||||
|
||||
for (int j = 0; j < dof; j++) {
|
||||
u_val += elfun(j) * shape(j);
|
||||
}
|
||||
|
||||
double d_u_nl;
|
||||
if (r > m_regularizationRadius) {
|
||||
if (u_val < m_epsilon) {
|
||||
d_u_nl = dfmod(m_epsilon, m_polytropicIndex);
|
||||
} else {
|
||||
d_u_nl = m_polytropicIndex * std::pow(u_val, m_polytropicIndex - 1.0);
|
||||
}
|
||||
} else {
|
||||
d_u_nl = 0.0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < dof; i++) {
|
||||
for (int j = 0; j < dof; j++) {
|
||||
elmat(i, j) += shape(i) * d_u_nl * shape(j) * weight;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace polyMFEMUtils
|
||||
} // namespace polytrope
|
||||
} // namespace serif
|
||||
493
src/polytrope/utils/private/polytropeOperator.cpp
Normal file
493
src/polytrope/utils/private/polytropeOperator.cpp
Normal file
@@ -0,0 +1,493 @@
|
||||
/* ***********************************************************************
|
||||
//
|
||||
// Copyright (C) 2025 -- The 4D-STAR Collaboration
|
||||
// File Author: Emily Boudreaux
|
||||
// Last Modified: April 21, 2025
|
||||
//
|
||||
// 4DSSE is free software; you can use it and/or modify
|
||||
// it under the terms and restrictions the GNU General Library Public
|
||||
// License version 3 (GPLv3) as published by the Free Software Foundation.
|
||||
//
|
||||
// 4DSSE is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this software; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// *********************************************************************** */
|
||||
#include "polytropeOperator.h"
|
||||
#include "4DSTARTypes.h"
|
||||
#include "utilities.h"
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "mfem_smout.h"
|
||||
#include <memory>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
namespace serif {
|
||||
namespace polytrope {
|
||||
// --- SchurCompliment Class Implementation ---
|
||||
|
||||
// SchurCompliment: Constructors
|
||||
SchurCompliment::SchurCompliment(
|
||||
const mfem::SparseMatrix &QOp,
|
||||
const mfem::SparseMatrix &DOp,
|
||||
const mfem::SparseMatrix &MOp,
|
||||
const mfem::Solver &GradInvOp ) :
|
||||
mfem::Operator(DOp.Height(), DOp.Width()) {
|
||||
// Initialize sizes
|
||||
SetOperator(QOp, DOp, MOp, GradInvOp);
|
||||
m_nPhi = m_DOp->Height();
|
||||
m_nTheta = m_MOp->Height();
|
||||
}
|
||||
|
||||
SchurCompliment::SchurCompliment(
|
||||
const mfem::SparseMatrix &QOp,
|
||||
const mfem::SparseMatrix &DOp,
|
||||
const mfem::SparseMatrix &MOp) :
|
||||
mfem::Operator(DOp.Height(), DOp.Width())
|
||||
{
|
||||
updateConstantTerms(QOp, DOp, MOp);
|
||||
m_nPhi = m_DOp->Height();
|
||||
m_nTheta = m_MOp->Height();
|
||||
}
|
||||
|
||||
// SchurCompliment: Public Interface Methods
|
||||
void SchurCompliment::Mult(const mfem::Vector &x, mfem::Vector &y) const {
|
||||
// Check that the input vector is the correct size
|
||||
if (x.Size() != m_nPhi) {
|
||||
MFEM_ABORT("Input vector x has size " + std::to_string(x.Size()) + ", expected " + std::to_string(m_nPhi));
|
||||
}
|
||||
if (y.Size() != m_nPhi) {
|
||||
MFEM_ABORT("Output vector y has size " + std::to_string(y.Size()) + ", expected " + std::to_string(m_nPhi));
|
||||
}
|
||||
|
||||
// Check that the operators are set
|
||||
if (m_QOp == nullptr) {
|
||||
MFEM_ABORT("QOp is null in SchurCompliment::Mult");
|
||||
}
|
||||
if (m_DOp == nullptr) {
|
||||
MFEM_ABORT("DOp is null in SchurCompliment::Mult");
|
||||
}
|
||||
if (m_MOp == nullptr) {
|
||||
MFEM_ABORT("MOp is null in SchurCompliment::Mult");
|
||||
}
|
||||
if (m_GradInvOp == nullptr) {
|
||||
MFEM_ABORT("GradInvOp is null in SchurCompliment::Mult");
|
||||
}
|
||||
|
||||
mfem::Vector v1(m_nTheta); // M * x
|
||||
m_MOp -> Mult(x, v1); // M * x
|
||||
|
||||
mfem::Vector v2(m_nTheta); // GradInv * M * x
|
||||
m_GradInvOp -> Mult(v1, v2); // GradInv * M * x
|
||||
|
||||
mfem::Vector v3(m_nPhi); // Q * GradInv * M * x
|
||||
m_QOp -> Mult(v2, v3); // Q * GradInv * M * x
|
||||
|
||||
mfem::Vector v4(m_nPhi); // D * x
|
||||
m_DOp -> Mult(x, v4); // D * x
|
||||
|
||||
subtract(v4, v3, y); // (D - Q * GradInv * M) * x
|
||||
}
|
||||
|
||||
void SchurCompliment::SetOperator(const mfem::SparseMatrix &QOp, const mfem::SparseMatrix &DOp, const mfem::SparseMatrix &MOp, const mfem::Solver &GradInvOp) {
|
||||
updateConstantTerms(QOp, DOp, MOp);
|
||||
updateInverseNonlinearJacobian(GradInvOp);
|
||||
}
|
||||
|
||||
void SchurCompliment::updateInverseNonlinearJacobian(const mfem::Solver &gradInv) {
|
||||
m_GradInvOp = &gradInv;
|
||||
}
|
||||
|
||||
// SchurCompliment: Private Helper Methods
|
||||
void SchurCompliment::updateConstantTerms(const mfem::SparseMatrix &QOp, const mfem::SparseMatrix &DOp, const mfem::SparseMatrix &MOp) {
|
||||
m_QOp = &QOp;
|
||||
m_DOp = &DOp;
|
||||
m_MOp = &MOp;
|
||||
}
|
||||
|
||||
|
||||
// --- GMRESInverter Class Implementation ---
|
||||
|
||||
// GMRESInverter: Constructor
|
||||
GMRESInverter::GMRESInverter(const SchurCompliment &op) :
|
||||
mfem::Operator(op.Height(), op.Width()),
|
||||
m_op(op) {
|
||||
m_solver.SetOperator(m_op);
|
||||
m_solver.SetMaxIter(100);
|
||||
m_solver.SetRelTol(1e-1);
|
||||
m_solver.SetAbsTol(1e-1);
|
||||
}
|
||||
|
||||
// GMRESInverter: Public Interface Methods
|
||||
void GMRESInverter::Mult(const mfem::Vector &x, mfem::Vector &y) const {
|
||||
m_solver.Mult(x, y); // Approximates m_op^-1 * x
|
||||
}
|
||||
|
||||
|
||||
// --- PolytropeOperator Class Implementation ---
|
||||
// PolytropeOperator: Constructor
|
||||
PolytropeOperator::PolytropeOperator(
|
||||
std::unique_ptr<mfem::MixedBilinearForm> M,
|
||||
std::unique_ptr<mfem::MixedBilinearForm> Q,
|
||||
std::unique_ptr<mfem::BilinearForm> D,
|
||||
std::unique_ptr<mfem::BilinearForm> S,
|
||||
std::unique_ptr<mfem::NonlinearForm> f,
|
||||
const mfem::Array<int> &blockOffsets) :
|
||||
|
||||
// TODO: Need to update this so that the size is that of the reduced system operator
|
||||
mfem::Operator(blockOffsets.Last()), // Initialize the base class with the total size of the block offset vector
|
||||
m_blockOffsets(blockOffsets),
|
||||
m_jacobian(nullptr) {
|
||||
|
||||
m_M = std::move(M);
|
||||
m_Q = std::move(Q);
|
||||
m_D = std::move(D);
|
||||
m_S = std::move(S);
|
||||
m_f = std::move(f);
|
||||
|
||||
// Use Gauss-Seidel smoother to approximate the inverse of the matrix
|
||||
// t = 0 (symmetric Gauss-Seidel), 1 (forward Gauss-Seidel), 2 (backward Gauss-Seidel)
|
||||
// iterations = 3
|
||||
m_invNonlinearJacobian = std::make_unique<mfem::GSSmoother>(0, 3);
|
||||
}
|
||||
|
||||
// PolytropeOperator: Core Operator Overrides
|
||||
void PolytropeOperator::Mult(const mfem::Vector &xFree, mfem::Vector &yFree) const {
|
||||
if (!m_isFinalized) {
|
||||
MFEM_ABORT("PolytropeOperator::Mult called before finalize");
|
||||
}
|
||||
|
||||
// TODO: confirm that the vectors xFree and m_freeDofs are always parallel
|
||||
m_state.SetSubVector(m_freeDofs, xFree); // Scatter the free dofs from the input vector xFree into the state vector
|
||||
mfem::Vector y;
|
||||
y.SetSize(m_blockOffsets.Last());
|
||||
|
||||
// -- Create BlockVector views for input x and output y
|
||||
mfem::BlockVector x_block(const_cast<mfem::Vector&>(m_state), m_blockOffsets);
|
||||
mfem::BlockVector y_block(y, m_blockOffsets);
|
||||
|
||||
// -- Get Vector views for individual blocks
|
||||
const mfem::Vector &x_theta = x_block.GetBlock(0);
|
||||
const mfem::Vector &x_phi = x_block.GetBlock(1);
|
||||
mfem::Vector &y_R0 = y_block.GetBlock(0); // Residual Block 0 (theta)
|
||||
mfem::Vector &y_R1 = y_block.GetBlock(1); // Residual Block 1 (phi)
|
||||
|
||||
int theta_size = m_blockOffsets[1] - m_blockOffsets[0];
|
||||
int phi_size = m_blockOffsets[2] - m_blockOffsets[1];
|
||||
|
||||
mfem::Vector f_term(theta_size);
|
||||
mfem::Vector Mphi_term(theta_size);
|
||||
mfem::Vector Dphi_term(phi_size);
|
||||
mfem::Vector Qtheta_term(phi_size);
|
||||
mfem::Vector Stheta_term(theta_size);
|
||||
|
||||
|
||||
// Calculate R0 and R1 terms
|
||||
// R0 = f(θ) + (1+c)Mɸ + cSθ
|
||||
// R1 = (1+c)Dɸ - (1+c)Qθ
|
||||
|
||||
MFEM_ASSERT(m_f.get() != nullptr, "NonlinearForm m_f is null in PolytropeOperator::Mult");
|
||||
|
||||
m_f->Mult(x_theta, f_term); // f(θ)
|
||||
m_M->Mult(x_phi, Mphi_term); // Mɸ
|
||||
m_D->Mult(x_phi, Dphi_term); // Dɸ
|
||||
m_Q->Mult(x_theta, Qtheta_term); // Qθ
|
||||
m_S->Mult(x_theta, Stheta_term); // Sθ
|
||||
|
||||
// PERF: these multiplications may be able to be refactored into the matrices so they only need to be done once.
|
||||
Stheta_term *= m_stabilizationCoefficient; // cSθ
|
||||
Qtheta_term *= m_IncrementedStabilizationCoefficient; // (1+c)Qθ
|
||||
Mphi_term *= m_IncrementedStabilizationCoefficient; // (1+c)Mɸ
|
||||
Dphi_term *= m_IncrementedStabilizationCoefficient; // (1+c)Dɸ
|
||||
|
||||
mfem::Vector y_R0_temp(theta_size);
|
||||
add(f_term, Mphi_term, y_R0_temp); // R0 = f(θ) + (1+c)Mɸ
|
||||
add(y_R0_temp, Stheta_term, y_R0); // R0 = f(θ) + (1+c)Mɸ + cSθ
|
||||
subtract(Dphi_term, Qtheta_term, y_R1); // R1 = (1+c)Dɸ - (1+c)Qθ
|
||||
|
||||
// --- Scatter the residual vector y into the free dofs ---
|
||||
yFree.SetSize(m_reducedBlockOffsets.Last());
|
||||
MFEM_ASSERT(m_freeDofs.Size() == m_reducedBlockOffsets.Last(), "PolytropeOperator::Mult: Size of free dofs does not match reduced block offsets size.");
|
||||
for (int i = 0, j = 0; i < y.Size(); ++i) {
|
||||
if (m_freeDofs.Find(i) != -1) {
|
||||
yFree[j] = y[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mfem::Operator& PolytropeOperator::GetGradient(const mfem::Vector &xFree) const {
|
||||
if (!m_isFinalized) {
|
||||
MFEM_ABORT("PolytropeOperator::GetGradient called before finalize");
|
||||
}
|
||||
m_state.SetSubVector(m_freeDofs, xFree); // Scatter the free dofs from the input vector xFree into the state vector
|
||||
// --- Get the gradient of f ---
|
||||
mfem::BlockVector x_block(const_cast<mfem::Vector&>(m_state), m_blockOffsets);
|
||||
const mfem::Vector& x_theta = x_block.GetBlock(0);
|
||||
|
||||
// PERF: There are a lot of copies and loops here, probably performance could be gained by flattering some of these.
|
||||
auto &grad = m_f->GetGradient(x_theta);
|
||||
// updatePreconditioner(grad);
|
||||
const auto gradMatrix = dynamic_cast<mfem::SparseMatrix*>(&grad); // ∂f(θ)/∂θ
|
||||
|
||||
if (gradMatrix == nullptr) {
|
||||
MFEM_ABORT("PolytropeOperator::GetGradient: Gradient is not a SparseMatrix.");
|
||||
}
|
||||
|
||||
m_gradReduced = std::make_unique<mfem::SparseMatrix> (
|
||||
serif::utilities::build_reduced_matrix(
|
||||
*gradMatrix,
|
||||
m_theta_ess_tdofs.first,
|
||||
m_theta_ess_tdofs.first
|
||||
)
|
||||
); // ∂f(θ)/∂θ (now reduced to only the free DOFs)
|
||||
|
||||
|
||||
// TODO: Confirm this actually does what I want it to do
|
||||
*m_gradReduced += *m_ScaledSReduced; // ∂f(θ)/∂θ + cS (reduced to only the free DOFs)
|
||||
|
||||
m_jacobian->SetBlock(0, 0, m_gradReduced.get());
|
||||
|
||||
return *m_jacobian;
|
||||
}
|
||||
|
||||
|
||||
// PolytropeOperator: Setup and Finalization
|
||||
void PolytropeOperator::finalize(const mfem::Vector &initTheta) {
|
||||
using serif::utilities::build_reduced_matrix;
|
||||
|
||||
if (m_isFinalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// These functions must be called in this order since they depend on each others post state
|
||||
// TODO: Refactor this so that either there are explicit checks to make sure the order is correct or make
|
||||
// them pure functions
|
||||
construct_matrix_representations();
|
||||
construct_reduced_block_offsets();
|
||||
construct_jacobian_constant_terms();
|
||||
scatter_boundary_conditions();
|
||||
populate_free_dof_array();
|
||||
|
||||
// Override the size based on the reduced system
|
||||
height = m_reducedBlockOffsets.Last();
|
||||
width = m_reducedBlockOffsets.Last();
|
||||
|
||||
m_isFinalized = true;
|
||||
|
||||
}
|
||||
|
||||
// PolytropeOperator: Essential True DOF Management
|
||||
void PolytropeOperator::set_essential_true_dofs(const serif::types::MFEMArrayPair& theta_ess_tdofs, const serif::types::MFEMArrayPair& phi_ess_tdofs) {
|
||||
m_isFinalized = false;
|
||||
m_theta_ess_tdofs = theta_ess_tdofs;
|
||||
m_phi_ess_tdofs = phi_ess_tdofs;
|
||||
|
||||
if (m_f) {
|
||||
m_f->SetEssentialTrueDofs(theta_ess_tdofs.first);
|
||||
} else {
|
||||
MFEM_ABORT("m_f is null in PolytropeOperator::SetEssentialTrueDofs");
|
||||
}
|
||||
}
|
||||
|
||||
void PolytropeOperator::set_essential_true_dofs(const serif::types::MFEMArrayPairSet& ess_tdof_pair_set) {
|
||||
set_essential_true_dofs(ess_tdof_pair_set.first, ess_tdof_pair_set.second);
|
||||
}
|
||||
|
||||
serif::types::MFEMArrayPairSet PolytropeOperator::get_essential_true_dofs() const {
|
||||
return std::make_pair(m_theta_ess_tdofs, m_phi_ess_tdofs);
|
||||
}
|
||||
|
||||
// PolytropeOperator: Getter Methods
|
||||
const mfem::BlockOperator &PolytropeOperator::get_jacobian_operator() const {
|
||||
if (m_jacobian == nullptr) {
|
||||
MFEM_ABORT("Jacobian has not been initialized before GetJacobianOperator() call.");
|
||||
}
|
||||
if (!m_isFinalized) {
|
||||
MFEM_ABORT("PolytropeOperator not finalized prior to call to GetJacobianOperator().");
|
||||
}
|
||||
|
||||
return *m_jacobian;
|
||||
}
|
||||
|
||||
mfem::BlockDiagonalPreconditioner& PolytropeOperator::get_preconditioner() const {
|
||||
if (m_schurPreconditioner == nullptr) {
|
||||
MFEM_ABORT("Schur preconditioner has not been initialized before GetPreconditioner() call.");
|
||||
}
|
||||
if (!m_isFinalized) {
|
||||
MFEM_ABORT("PolytropeOperator not finalized prior to call to GetPreconditioner().");
|
||||
}
|
||||
return *m_schurPreconditioner;
|
||||
}
|
||||
|
||||
int PolytropeOperator::get_reduced_system_size() const {
|
||||
if (!m_isFinalized) {
|
||||
MFEM_ABORT("PolytropeOperator not finalized prior to call to GetReducedSystemSize().");
|
||||
}
|
||||
return m_reducedBlockOffsets.Last();
|
||||
}
|
||||
|
||||
// PolytropeOperator: State Reconstruction
|
||||
const mfem::Vector &PolytropeOperator::reconstruct_full_state_vector(const mfem::Vector &reducedState) const {
|
||||
m_state.SetSubVector(m_freeDofs, reducedState); // Scatter the reduced state vector into the full state vector
|
||||
return m_state;
|
||||
}
|
||||
|
||||
const mfem::BlockVector PolytropeOperator::reconstruct_full_block_state_vector(const mfem::Vector &reducedState) const {
|
||||
m_state.SetSubVector(m_freeDofs, reducedState); // Scatter the reduced state vector into the full state vector
|
||||
mfem::BlockVector x_block(m_state, m_blockOffsets);
|
||||
return x_block;
|
||||
}
|
||||
|
||||
// PolytropeOperator: DOF Population
|
||||
void PolytropeOperator::populate_free_dof_array() {
|
||||
m_freeDofs.SetSize(0);
|
||||
for (int i = 0; i < m_blockOffsets.Last(); i++) {
|
||||
const int thetaSearchIndex = i;
|
||||
const int phiSearchIndex = i - m_blockOffsets[1];
|
||||
if (phiSearchIndex < 0){
|
||||
if (m_theta_ess_tdofs.first.Find(thetaSearchIndex) == -1) {
|
||||
m_freeDofs.Append(i);
|
||||
}
|
||||
} else {
|
||||
if (m_phi_ess_tdofs.first.Find(phiSearchIndex) == -1) {
|
||||
m_freeDofs.Append(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PolytropeOperator: Private Helper Methods - Construction and Setup
|
||||
void PolytropeOperator::construct_matrix_representations() {
|
||||
// --- Construct the sparse matrix representations of M, Q, D, and S ---
|
||||
m_Mmat = std::make_unique<mfem::SparseMatrix>(m_M->SpMat());
|
||||
m_Qmat = std::make_unique<mfem::SparseMatrix>(m_Q->SpMat());
|
||||
m_Dmat = std::make_unique<mfem::SparseMatrix>(m_D->SpMat());
|
||||
m_Smat = std::make_unique<mfem::SparseMatrix>(m_S->SpMat());
|
||||
|
||||
// --- Reduce the matrices to only the free DOFs ---
|
||||
m_MReduced = std::make_unique<mfem::SparseMatrix>(
|
||||
serif::utilities::build_reduced_matrix(
|
||||
*m_Mmat,
|
||||
m_phi_ess_tdofs.first,
|
||||
m_theta_ess_tdofs.first)
|
||||
);
|
||||
m_QReduced = std::make_unique<mfem::SparseMatrix>(
|
||||
serif::utilities::build_reduced_matrix(
|
||||
*m_Qmat,
|
||||
m_theta_ess_tdofs.first,
|
||||
m_phi_ess_tdofs.first)
|
||||
);
|
||||
m_DReduced = std::make_unique<mfem::SparseMatrix>(
|
||||
serif::utilities::build_reduced_matrix(
|
||||
*m_Dmat,
|
||||
m_phi_ess_tdofs.first,
|
||||
m_phi_ess_tdofs.first)
|
||||
);
|
||||
m_SReduced = std::make_unique<mfem::SparseMatrix>(
|
||||
serif::utilities::build_reduced_matrix(
|
||||
*m_Smat,
|
||||
m_theta_ess_tdofs.first,
|
||||
m_theta_ess_tdofs.first)
|
||||
);
|
||||
|
||||
// --- Scale the reduced matrices by the stabilization coefficients ---
|
||||
mfem::SparseMatrix MScaledTemp(*m_MReduced); // Create a temporary copy of the M matrix for scaling
|
||||
MScaledTemp *= m_IncrementedStabilizationCoefficient; // Scale the M matrix by the incremented stabilization coefficient
|
||||
m_MScaledReduced = std::make_unique<mfem::SparseMatrix>(MScaledTemp); // Store the scaled M matrix so that it persists
|
||||
|
||||
mfem::SparseMatrix QScaledTemp(*m_QReduced);
|
||||
QScaledTemp *= m_IncrementedStabilizationCoefficient; // Scale the Q matrix by the incremented stabilization coefficient
|
||||
m_QScaledReduced = std::make_unique<mfem::SparseMatrix>(QScaledTemp); // Store the scaled Q matrix so that it persists
|
||||
|
||||
mfem::SparseMatrix DRScaledTemp(*m_DReduced);
|
||||
DRScaledTemp *= m_IncrementedStabilizationCoefficient; // Scale the D matrix by the incremented stabilization coefficient
|
||||
m_DScaledReduced = std::make_unique<mfem::SparseMatrix>(DRScaledTemp); // Store the scaled D matrix so that it persists
|
||||
|
||||
// Scale the S matrix by the stabilization coefficient (so that the allocations only need to be done once)
|
||||
mfem::SparseMatrix SScaledTemp(*m_SReduced); // Create a temporary copy of the S matrix for scaling
|
||||
SScaledTemp *= m_stabilizationCoefficient; // Scale the S matrix by the stabilization coefficient
|
||||
m_ScaledSReduced = std::make_unique<mfem::SparseMatrix>(SScaledTemp); // Store the scaled S matrix so that it persists
|
||||
|
||||
// --- Create the scaled operator for -(1+c)Q ---
|
||||
m_negQ_mat = std::make_unique<mfem::ScaledOperator>(m_QScaledReduced.get(), -1.0);
|
||||
}
|
||||
|
||||
void PolytropeOperator::construct_reduced_block_offsets() {
|
||||
m_reducedBlockOffsets.SetSize(3);
|
||||
m_reducedBlockOffsets[0] = 0; // R0 block (theta)
|
||||
m_reducedBlockOffsets[1] = m_MReduced->Height(); // R1 block (theta)
|
||||
m_reducedBlockOffsets[2] = m_QReduced->Height() + m_reducedBlockOffsets[1]; // R2 block (phi + theta)
|
||||
}
|
||||
|
||||
void PolytropeOperator::construct_jacobian_constant_terms() {
|
||||
m_jacobian = std::make_unique<mfem::BlockOperator>(m_reducedBlockOffsets);
|
||||
|
||||
m_jacobian->SetBlock(0, 1, m_MScaledReduced.get()); //<- (1+c)M (constant, reduced to only free DOFs)
|
||||
m_jacobian->SetBlock(1, 0, m_negQ_mat.get()); //<- -(1+c)Q (constant, reduced to only free DOFs)
|
||||
m_jacobian->SetBlock(1, 1, m_DScaledReduced.get()); //<- (1+c)D (constant, reduced to only free DOFs)
|
||||
}
|
||||
|
||||
void PolytropeOperator::scatter_boundary_conditions() {
|
||||
mfem::Vector thetaStateValues(m_theta_ess_tdofs.first.Size());
|
||||
for (int i = 0; i < m_theta_ess_tdofs.first.Size(); i++) {
|
||||
thetaStateValues[i] = m_theta_ess_tdofs.second[i];
|
||||
}
|
||||
mfem::Vector phiStateValues(m_phi_ess_tdofs.first.Size());
|
||||
for (int i = 0; i < m_phi_ess_tdofs.first.Size(); i++) {
|
||||
phiStateValues[i] = m_phi_ess_tdofs.second[i]; // TODO: figure out if this needs to be normalized
|
||||
}
|
||||
|
||||
mfem::Array<int> phiDofIndices(m_phi_ess_tdofs.first.Size());
|
||||
for (int i = 0; i < m_phi_ess_tdofs.first.Size(); i++) {
|
||||
phiDofIndices[i] = m_phi_ess_tdofs.first[i] + m_blockOffsets[1];
|
||||
}
|
||||
|
||||
m_state.SetSize(m_blockOffsets.Last());
|
||||
m_state = 0.0;
|
||||
m_state.SetSubVector(m_theta_ess_tdofs.first, thetaStateValues);
|
||||
m_state.SetSubVector(phiDofIndices, phiStateValues);
|
||||
|
||||
}
|
||||
|
||||
// PolytropeOperator: Private Helper Methods - Jacobian and Preconditioner Updates
|
||||
void PolytropeOperator::update_inverse_nonlinear_jacobian(const mfem::Operator &grad) const {
|
||||
m_invNonlinearJacobian->SetOperator(grad);
|
||||
}
|
||||
|
||||
void PolytropeOperator::update_inverse_schur_compliment() const {
|
||||
// TODO: This entire function could probably be refactored out
|
||||
if (!m_isFinalized) {
|
||||
MFEM_ABORT("PolytropeOperator::updateInverseSchurCompliment called before finalize");
|
||||
}
|
||||
if (m_invNonlinearJacobian == nullptr) {
|
||||
MFEM_ABORT("PolytropeOperator::updateInverseSchurCompliment called before updateInverseNonlinearJacobian");
|
||||
}
|
||||
if (m_schurCompliment == nullptr) {
|
||||
MFEM_ABORT("PolytropeOperator::updateInverseSchurCompliment called before updateInverseSchurCompliment");
|
||||
}
|
||||
|
||||
m_schurCompliment->updateInverseNonlinearJacobian(*m_invNonlinearJacobian);
|
||||
|
||||
if (m_schurPreconditioner == nullptr) {
|
||||
m_schurPreconditioner = std::make_unique<mfem::BlockDiagonalPreconditioner>(m_blockOffsets);
|
||||
}
|
||||
|
||||
m_schurPreconditioner->SetDiagonalBlock(0, m_invNonlinearJacobian.get());
|
||||
m_schurPreconditioner->SetDiagonalBlock(1, m_invSchurCompliment.get());
|
||||
|
||||
}
|
||||
|
||||
void PolytropeOperator::update_preconditioner(const mfem::Operator &grad) const {
|
||||
update_inverse_nonlinear_jacobian(grad);
|
||||
update_inverse_schur_compliment();
|
||||
}
|
||||
|
||||
} // namespace polytrope
|
||||
} // namespace serif
|
||||
132
src/polytrope/utils/private/utilities.cpp
Normal file
132
src/polytrope/utils/private/utilities.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
#include "utilities.h"
|
||||
#include "mfem.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace serif::utilities {
|
||||
mfem::SparseMatrix build_reduced_matrix(
|
||||
const mfem::SparseMatrix& matrix,
|
||||
const mfem::Array<int>& trialEssentialDofs,
|
||||
const mfem::Array<int>& testEssentialDofs
|
||||
) {
|
||||
int M_orig = matrix.Height();
|
||||
int N_orig = matrix.Width();
|
||||
|
||||
// 1. Create boolean lookup tables for eliminated rows/columns
|
||||
// These tables help quickly check if an original row/column index is eliminated.
|
||||
mfem::Array<bool> row_is_eliminated(M_orig);
|
||||
row_is_eliminated = false; // Initialize all to false (no rows eliminated yet)
|
||||
for (int i = 0; i < testEssentialDofs.Size(); ++i) {
|
||||
int r_idx = testEssentialDofs[i];
|
||||
if (r_idx >= 0 && r_idx < M_orig) { // Check for valid index
|
||||
row_is_eliminated[r_idx] = true;
|
||||
}
|
||||
}
|
||||
|
||||
mfem::Array<bool> col_is_eliminated(N_orig);
|
||||
col_is_eliminated = false; // Initialize all to false (no columns eliminated yet)
|
||||
for (int i = 0; i < trialEssentialDofs.Size(); ++i) {
|
||||
int c_idx = trialEssentialDofs[i];
|
||||
if (c_idx >= 0 && c_idx < N_orig) { // Check for valid index
|
||||
col_is_eliminated[c_idx] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Create mappings from old (original) indices to new indices
|
||||
// Also, count the number of rows and columns in the new, reduced matrix.
|
||||
mfem::Array<int> old_row_to_new_row(M_orig);
|
||||
int num_new_rows = 0;
|
||||
for (int i = 0; i < M_orig; ++i) {
|
||||
if (row_is_eliminated[i]) {
|
||||
old_row_to_new_row[i] = -1; // Mark as eliminated (no corresponding new row)
|
||||
} else {
|
||||
old_row_to_new_row[i] = num_new_rows++; // Assign the next available new row index
|
||||
}
|
||||
}
|
||||
|
||||
mfem::Array<int> old_col_to_new_col(N_orig);
|
||||
int num_new_cols = 0;
|
||||
for (int i = 0; i < N_orig; ++i) {
|
||||
if (col_is_eliminated[i]) {
|
||||
old_col_to_new_col[i] = -1; // Mark as eliminated (no corresponding new column)
|
||||
} else {
|
||||
old_col_to_new_col[i] = num_new_cols++; // Assign the next available new column index
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Create the new sparse matrix with the calculated reduced dimensions.
|
||||
// It's initially empty (no non-zero entries).
|
||||
mfem::SparseMatrix A_new(num_new_rows, num_new_cols);
|
||||
|
||||
// 4. Iterate through the non-zero entries of the original matrix (A_orig).
|
||||
// A_orig is typically stored in Compressed Sparse Row (CSR) format.
|
||||
// GetI() returns row pointers, GetJ() returns column indices, GetData() returns values.
|
||||
const int* I_orig = matrix.GetI();
|
||||
const int* J_orig = matrix.GetJ();
|
||||
const double* V_orig = matrix.GetData(); // Assuming double, can be templated if needed
|
||||
|
||||
for (int r_orig = 0; r_orig < M_orig; ++r_orig) {
|
||||
// If the original row is marked for elimination, skip all its entries.
|
||||
if (row_is_eliminated[r_orig]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the new row index for the current original row.
|
||||
int r_new = old_row_to_new_row[r_orig];
|
||||
|
||||
// Iterate through non-zero entries in the current original row r_orig.
|
||||
// I_orig[r_orig] is the start index in J_orig and V_orig for row r_orig.
|
||||
// I_orig[r_orig+1]-1 is the end index.
|
||||
for (int k = I_orig[r_orig]; k < I_orig[r_orig + 1]; ++k) {
|
||||
int c_orig = J_orig[k]; // Original column index of the non-zero entry
|
||||
double val = V_orig[k]; // Value of the non-zero entry
|
||||
|
||||
if (col_is_eliminated[c_orig]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int c_new = old_col_to_new_col[c_orig];
|
||||
|
||||
A_new.Add(r_new, c_new, val);
|
||||
}
|
||||
}
|
||||
|
||||
A_new.Finalize();
|
||||
|
||||
return A_new;
|
||||
}
|
||||
|
||||
mfem::Vector build_dof_identification_vector(const mfem::Array<int>& allDofs, const::mfem::Array<int>& highlightDofs) {
|
||||
mfem::Vector v(allDofs.Size());
|
||||
v = 0.0; // Initialize the vector to zero
|
||||
v.SetSubVector(highlightDofs, 1.0); // Set the highlighted dofs to 1.0
|
||||
return v;
|
||||
}
|
||||
|
||||
mfem::GridFunction compute_curl(mfem::GridFunction& phi_gf) {
|
||||
mfem::Mesh* mesh = phi_gf.FESpace()->GetMesh();
|
||||
const int dim = mesh->Dimension();
|
||||
// Match the polynomial order of the original RT space for consistency.
|
||||
const int order = phi_gf.FESpace()->GetOrder(0);
|
||||
mfem::Vector curl_mag_vec;
|
||||
|
||||
for (int ne = 0; ne < mesh->GetNE(); ++ne) {
|
||||
if (mesh->GetElementType(ne) != mfem::Element::TRIANGLE &&
|
||||
mesh->GetElementType(ne) != mfem::Element::QUADRILATERAL &&
|
||||
mesh->GetElementType(ne) != mfem::Element::TETRAHEDRON &&
|
||||
mesh->GetElementType(ne) != mfem::Element::HEXAHEDRON) {
|
||||
throw std::invalid_argument("Mesh element type not supported for curl computation.");
|
||||
}
|
||||
mfem::IsoparametricTransformation T;
|
||||
mesh->GetElementTransformation(ne, &T);
|
||||
phi_gf.GetCurl(T, curl_mag_vec);
|
||||
std::cout << "HERE" << std::endl;
|
||||
}
|
||||
mfem::L2_FECollection fac(order, dim);
|
||||
mfem::FiniteElementSpace fs(mesh, &fac);
|
||||
mfem::GridFunction curl_gf(&fs);
|
||||
curl_gf = 0.0;
|
||||
return curl_gf;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
107
src/polytrope/utils/public/integrators.h
Normal file
107
src/polytrope/utils/public/integrators.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/* ***********************************************************************
|
||||
//
|
||||
// Copyright (C) 2025 -- The 4D-STAR Collaboration
|
||||
// File Author: Emily Boudreaux
|
||||
// Last Modified: April 21, 2025
|
||||
//
|
||||
// 4DSSE is free software; you can use it and/or modify
|
||||
// it under the terms and restrictions the GNU General Library Public
|
||||
// License version 3 (GPLv3) as published by the Free Software Foundation.
|
||||
//
|
||||
// 4DSSE is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this software; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// *********************************************************************** */
|
||||
#pragma once
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include <string>
|
||||
#include "config.h"
|
||||
#include "probe.h"
|
||||
|
||||
|
||||
/**
|
||||
* @file integrators.h
|
||||
* @brief A collection of utilities for working with MFEM and solving the lane-emden equation.
|
||||
*/
|
||||
|
||||
namespace serif {
|
||||
namespace polytrope {
|
||||
/**
|
||||
* @namespace polyMFEMUtils
|
||||
* @brief A namespace for utilities for working with MFEM and solving the lane-emden equation.
|
||||
*/
|
||||
namespace polyMFEMUtils {
|
||||
/**
|
||||
* @brief A class for nonlinear power integrator.
|
||||
*/
|
||||
class NonlinearPowerIntegrator: public mfem::NonlinearFormIntegrator {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for NonlinearPowerIntegrator.
|
||||
*
|
||||
* @param coeff The function coefficient.
|
||||
* @param n The polytropic index.
|
||||
*/
|
||||
NonlinearPowerIntegrator(double n);
|
||||
|
||||
/**
|
||||
* @brief Assembles the element vector.
|
||||
*
|
||||
* @param el The finite element.
|
||||
* @param Trans The element transformation.
|
||||
* @param elfun The element function.
|
||||
* @param elvect The element vector to be assembled.
|
||||
*/
|
||||
virtual void AssembleElementVector(const mfem::FiniteElement &el, mfem::ElementTransformation &Trans, const mfem::Vector &elfun, mfem::Vector &elvect) override;
|
||||
/**
|
||||
* @brief Assembles the element gradient.
|
||||
*
|
||||
* @param el The finite element.
|
||||
* @param Trans The element transformation.
|
||||
* @param elfun The element function.
|
||||
* @param elmat The element matrix to be assembled.
|
||||
*/
|
||||
virtual void AssembleElementGrad (const mfem::FiniteElement &el, mfem::ElementTransformation &Trans, const mfem::Vector &elfun, mfem::DenseMatrix &elmat) override;
|
||||
private:
|
||||
serif::config::Config& m_config = serif::config::Config::getInstance();
|
||||
serif::probe::LogManager& m_logManager = serif::probe::LogManager::getInstance();
|
||||
quill::Logger* m_logger = m_logManager.getLogger("log");
|
||||
double m_polytropicIndex;
|
||||
double m_epsilon;
|
||||
static constexpr double m_regularizationRadius = 0.15; ///< Regularization radius for the epsilon function, used to avoid singularities in the power law.
|
||||
static constexpr double m_regularizationCoeff = 1.0/6.0; ///< Coefficient for the regularization term, used to ensure smoothness in the power law.
|
||||
};
|
||||
|
||||
inline double dfmod(const double epsilon, const double n) {
|
||||
if (n == 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
if (n == 1.0) {
|
||||
return 1.0;
|
||||
}
|
||||
return n * std::pow(epsilon, n - 1.0);
|
||||
}
|
||||
|
||||
inline double fmod(const double theta, const double n, const double epsilon) {
|
||||
if (n == 0.0) {
|
||||
return 1.0;
|
||||
}
|
||||
// For n != 0
|
||||
const double y_prime_at_epsilon = dfmod(epsilon, n); // Uses the robust dfmod
|
||||
const double y_at_epsilon = std::pow(epsilon, n); // epsilon^n
|
||||
|
||||
// f_mod(theta) = y_at_epsilon + y_prime_at_epsilon * (theta - epsilon)
|
||||
return y_at_epsilon + y_prime_at_epsilon * (theta - epsilon);
|
||||
}
|
||||
|
||||
|
||||
} // namespace polyMFEMUtils
|
||||
} // namespace polytrope
|
||||
} // namespace serif
|
||||
399
src/polytrope/utils/public/polytropeOperator.h
Normal file
399
src/polytrope/utils/public/polytropeOperator.h
Normal file
@@ -0,0 +1,399 @@
|
||||
/* ***********************************************************************
|
||||
//
|
||||
// Copyright (C) 2025 -- The 4D-STAR Collaboration
|
||||
// File Author: Emily Boudreaux
|
||||
// Last Modified: April 21, 2025
|
||||
//
|
||||
// 4DSSE is free software; you can use it and/or modify
|
||||
// it under the terms and restrictions the GNU General Library Public
|
||||
// License version 3 (GPLv3) as published by the Free Software Foundation.
|
||||
//
|
||||
// 4DSSE is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this software; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// *********************************************************************** */
|
||||
#pragma once
|
||||
|
||||
#include "mfem.hpp"
|
||||
#include "4DSTARTypes.h"
|
||||
#include <memory>
|
||||
|
||||
#include "probe.h"
|
||||
|
||||
namespace serif {
|
||||
namespace polytrope {
|
||||
|
||||
/**
|
||||
* @brief Represents the Schur complement operator used in the solution process.
|
||||
*
|
||||
* This class computes S = D - Q * GradInv * M.
|
||||
*/
|
||||
class SchurCompliment final : public mfem::Operator {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a SchurCompliment operator.
|
||||
* @param QOp The Q matrix operator.
|
||||
* @param DOp The D matrix operator.
|
||||
* @param MOp The M matrix operator.
|
||||
* @param GradInvOp The inverse of the gradient operator.
|
||||
*/
|
||||
SchurCompliment(
|
||||
const mfem::SparseMatrix &QOp,
|
||||
const mfem::SparseMatrix &DOp,
|
||||
const mfem::SparseMatrix &MOp,
|
||||
const mfem::Solver &GradInvOp
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Constructs a SchurCompliment operator without the inverse gradient initially.
|
||||
* The inverse gradient must be set later using updateInverseNonlinearJacobian.
|
||||
* @param QOp The Q matrix operator.
|
||||
* @param DOp The D matrix operator.
|
||||
* @param MOp The M matrix operator.
|
||||
*/
|
||||
SchurCompliment(
|
||||
const mfem::SparseMatrix &QOp,
|
||||
const mfem::SparseMatrix &DOp,
|
||||
const mfem::SparseMatrix &MOp
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Destructor.
|
||||
*/
|
||||
~SchurCompliment() override = default;
|
||||
|
||||
/**
|
||||
* @brief Applies the Schur complement operator: y = (D - Q * GradInv * M) * x.
|
||||
* @param x The input vector.
|
||||
* @param y The output vector.
|
||||
*/
|
||||
void Mult(const mfem::Vector &x, mfem::Vector &y) const override;
|
||||
|
||||
/**
|
||||
* @brief Sets all operators for the Schur complement.
|
||||
* @param QOp The Q matrix operator.
|
||||
* @param DOp The D matrix operator.
|
||||
* @param MOp The M matrix operator.
|
||||
* @param GradInvOp The inverse of the gradient operator.
|
||||
*/
|
||||
void SetOperator(const mfem::SparseMatrix &QOp, const mfem::SparseMatrix &DOp, const mfem::SparseMatrix &MOp, const mfem::Solver &GradInvOp);
|
||||
|
||||
/**
|
||||
* @brief Updates the inverse of the nonlinear Jacobian (GradInv).
|
||||
* @param gradInv The new inverse gradient solver.
|
||||
*/
|
||||
void updateInverseNonlinearJacobian(const mfem::Solver &gradInv);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Updates the constant matrix terms (Q, D, M).
|
||||
* @param QOp The Q matrix operator.
|
||||
* @param DOp The D matrix operator.
|
||||
* @param MOp The M matrix operator.
|
||||
*/
|
||||
void updateConstantTerms(const mfem::SparseMatrix &QOp, const mfem::SparseMatrix &DOp, const mfem::SparseMatrix &MOp);
|
||||
|
||||
private:
|
||||
// Pointers to external operators (not owned by this class)
|
||||
const mfem::SparseMatrix* m_QOp = nullptr; ///< Pointer to the Q matrix operator.
|
||||
const mfem::SparseMatrix* m_DOp = nullptr; ///< Pointer to the D matrix operator.
|
||||
const mfem::SparseMatrix* m_MOp = nullptr; ///< Pointer to the M matrix operator.
|
||||
const mfem::Solver* m_GradInvOp = nullptr; ///< Pointer to the inverse of the gradient operator.
|
||||
|
||||
// Dimensions
|
||||
int m_nPhi = 0; ///< Dimension related to the phi variable (typically number of rows/cols of D).
|
||||
int m_nTheta = 0; ///< Dimension related to the theta variable (typically number of rows of M).
|
||||
|
||||
// Owned resources
|
||||
mutable std::unique_ptr<mfem::SparseMatrix> m_matrixForm; ///< Optional: if a matrix representation is ever explicitly formed.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Provides an approximate inverse of the SchurCompliment operator using GMRES.
|
||||
*/
|
||||
class GMRESInverter final : public mfem::Operator {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a GMRESInverter.
|
||||
* @param op The SchurCompliment operator to invert.
|
||||
*/
|
||||
explicit GMRESInverter(const SchurCompliment& op);
|
||||
|
||||
/**
|
||||
* @brief Destructor.
|
||||
*/
|
||||
~GMRESInverter() override = default;
|
||||
|
||||
/**
|
||||
* @brief Applies the GMRES solver to approximate op^-1 * x.
|
||||
* @param x The input vector.
|
||||
* @param y The output vector (approximation of op^-1 * x).
|
||||
*/
|
||||
void Mult(const mfem::Vector &x, mfem::Vector &y) const override;
|
||||
|
||||
private:
|
||||
const SchurCompliment& m_op; ///< Reference to the SchurCompliment operator to be inverted.
|
||||
mfem::GMRESSolver m_solver; ///< GMRES solver instance.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Represents the coupled nonlinear operator for the polytropic system.
|
||||
*
|
||||
* This operator defines the system F(X) = 0, where X = [θ, φ]^T,
|
||||
* and F(X) = [ f(θ) + Mφ ]
|
||||
* [ Dφ - Qθ ].
|
||||
* It handles essential boundary conditions and the construction of the Jacobian.
|
||||
*/
|
||||
class PolytropeOperator final : public mfem::Operator {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs the PolytropeOperator.
|
||||
* @param M The M bilinear form (coupling θ and φ).
|
||||
* @param Q The Q bilinear form (coupling φ and θ).
|
||||
* @param D The D bilinear form (acting on φ).
|
||||
* @param f The nonlinear form f(θ).
|
||||
* @param blockOffsets Offsets defining the blocks for θ and φ variables.
|
||||
*/
|
||||
PolytropeOperator(
|
||||
std::unique_ptr<mfem::MixedBilinearForm> M,
|
||||
std::unique_ptr<mfem::MixedBilinearForm> Q,
|
||||
std::unique_ptr<mfem::BilinearForm> D,
|
||||
std::unique_ptr<mfem::BilinearForm> S,
|
||||
std::unique_ptr<mfem::NonlinearForm> f,
|
||||
const mfem::Array<int> &blockOffsets);
|
||||
|
||||
/**
|
||||
* @brief Destructor.
|
||||
*/
|
||||
~PolytropeOperator() override = default;
|
||||
|
||||
/**
|
||||
* @brief Applies the PolytropeOperator: y = F(x).
|
||||
* This computes the residual of the nonlinear system.
|
||||
* @param xFree The vector of free (non-essential) degrees of freedom.
|
||||
* @param yFree The resulting residual vector corresponding to free DOFs.
|
||||
*/
|
||||
void Mult(const mfem::Vector &xFree, mfem::Vector &yFree) const override;
|
||||
|
||||
/**
|
||||
* @brief Computes the Jacobian of the PolytropeOperator at a given state xFree.
|
||||
* The Jacobian is of the form:
|
||||
* J = [ G M ]
|
||||
* [ -Q D ]
|
||||
* where G is the gradient of f(θ).
|
||||
* @param xFree The vector of free DOFs at which to evaluate the gradient.
|
||||
* @return A reference to the Jacobian operator.
|
||||
*/
|
||||
mfem::Operator& GetGradient(const mfem::Vector &xFree) const override;
|
||||
|
||||
/**
|
||||
* @brief Finalizes the operator setup.
|
||||
* This must be called after setting essential true DOFs and before using Mult or GetGradient.
|
||||
* It constructs reduced matrices, block offsets, and populates free DOFs.
|
||||
* @param initTheta Initial guess for θ, used to evaluate the nonlinear form if necessary during setup.
|
||||
*/
|
||||
void finalize(const mfem::Vector &initTheta);
|
||||
|
||||
/**
|
||||
* @brief Checks if the operator has been finalized.
|
||||
* @return True if finalize() has been successfully called, false otherwise.
|
||||
*/
|
||||
bool isFinalized() const { return m_isFinalized; }
|
||||
|
||||
/**
|
||||
* @brief Sets the essential true degrees of freedom for both θ and φ variables.
|
||||
* Marks the operator as not finalized.
|
||||
* @param theta_ess_tdofs Pair of arrays: (indices of essential DOFs for θ, values at these DOFs).
|
||||
* @param phi_ess_tdofs Pair of arrays: (indices of essential DOFs for φ, values at these DOFs).
|
||||
*/
|
||||
void set_essential_true_dofs(const serif::types::MFEMArrayPair& theta_ess_tdofs, const serif::types::MFEMArrayPair& phi_ess_tdofs);
|
||||
|
||||
/**
|
||||
* @brief Sets the essential true degrees of freedom using a pair of pairs.
|
||||
* Marks the operator as not finalized.
|
||||
* @param ess_tdof_pair_set A pair containing the essential TDOF pairs for theta and phi.
|
||||
*/
|
||||
void set_essential_true_dofs(const serif::types::MFEMArrayPairSet& ess_tdof_pair_set);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Reconstructs the full state vector (including essential DOFs) from a reduced state vector (free DOFs).
|
||||
* @param reducedState The vector containing only the free degrees of freedom.
|
||||
* @return Constant reference to the internal full state vector, updated with the reducedState.
|
||||
*/
|
||||
[[nodiscard]] const mfem::Vector &reconstruct_full_state_vector(const mfem::Vector &reducedState) const;
|
||||
|
||||
/**
|
||||
* @breif Reconstruct the full state vector (including essential DOFs) from a reduced state vector (free DOFs) as well as the block offsets.
|
||||
* @param reducedState The vector containing only the free degrees of freedom.
|
||||
* @return Constant reference to the internal full state vector, updated with the reducedState as a block vector.
|
||||
*/
|
||||
[[nodiscard]] const mfem::BlockVector reconstruct_full_block_state_vector(const mfem::Vector &reducedState) const;
|
||||
|
||||
/**
|
||||
* @brief Populates the internal array of free (non-essential) degree of freedom indices.
|
||||
* This is called during finalize().
|
||||
*/
|
||||
void populate_free_dof_array();
|
||||
|
||||
/// --- Getters for key internal state and operators ---
|
||||
/**
|
||||
* @brief Gets the Jacobian operator.
|
||||
* Asserts that the operator is finalized and the Jacobian has been computed.
|
||||
* @return Constant reference to the block Jacobian operator.
|
||||
*/
|
||||
const mfem::BlockOperator &get_jacobian_operator() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the block diagonal preconditioner for the Schur complement system.
|
||||
* Asserts that the operator is finalized and the preconditioner has been computed.
|
||||
* @return Reference to the block diagonal preconditioner.
|
||||
*/
|
||||
mfem::BlockDiagonalPreconditioner &get_preconditioner() const;
|
||||
|
||||
|
||||
/// --- Getters for information on internal state ---
|
||||
/**
|
||||
* @brief Gets the full state vector, including essential DOFs.
|
||||
* @return Constant reference to the internal state vector.
|
||||
*/
|
||||
const mfem::Array<int>& get_free_dofs() const { return m_freeDofs; } ///< Getter for the free DOFs array.
|
||||
|
||||
/**
|
||||
* @brief Gets the size of the reduced system (number of free DOFs).
|
||||
* Asserts that the operator is finalized.
|
||||
* @return The total number of free degrees of freedom.
|
||||
*/
|
||||
int get_reduced_system_size() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the currently set essential true degrees of freedom.
|
||||
* @return A pair containing the essential TDOF pairs for theta and phi.
|
||||
*/
|
||||
serif::types::MFEMArrayPairSet get_essential_true_dofs() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the block offsets for the full (unreduced) system.
|
||||
* @return Constant reference to the array of block offsets.
|
||||
*/
|
||||
const mfem::Array<int>& get_block_offsets() const { return m_blockOffsets; }
|
||||
|
||||
/**
|
||||
* @brief Gets the block offsets for the reduced system (after eliminating essential DOFs).
|
||||
* @return Constant reference to the array of reduced block offsets.
|
||||
*/
|
||||
const mfem::Array<int>& get_reduced_block_offsets() const {return m_reducedBlockOffsets; }
|
||||
|
||||
private:
|
||||
// --- Logging ---
|
||||
serif::probe::LogManager& m_logManager = serif::probe::LogManager::getInstance(); ///< Reference to the global log manager.
|
||||
quill::Logger* m_logger = m_logManager.getLogger("log"); ///< Pointer to the specific logger instance.
|
||||
|
||||
// --- Input Bilinear/Nonlinear Forms (owned) ---
|
||||
std::unique_ptr<mfem::MixedBilinearForm> m_M; ///< Bilinear form M, coupling θ and φ.
|
||||
std::unique_ptr<mfem::MixedBilinearForm> m_Q; ///< Bilinear form Q, coupling φ and θ.
|
||||
std::unique_ptr<mfem::BilinearForm> m_D; ///< Bilinear form D, acting on φ.
|
||||
std::unique_ptr<mfem::BilinearForm> m_S;
|
||||
std::unique_ptr<mfem::NonlinearForm> m_f; ///< Nonlinear form f, acting on θ.
|
||||
|
||||
// --- Full Matrix Representations (owned, derived from forms) ---
|
||||
std::unique_ptr<mfem::SparseMatrix> m_Mmat; ///< Sparse matrix representation of M.
|
||||
std::unique_ptr<mfem::SparseMatrix> m_Qmat; ///< Sparse matrix representation of Q.
|
||||
std::unique_ptr<mfem::SparseMatrix> m_Dmat; ///< Sparse matrix representation of D.
|
||||
std::unique_ptr<mfem::SparseMatrix> m_Smat;
|
||||
|
||||
// --- Reduced Matrix Representations (owned, after eliminating essential DOFs) ---
|
||||
std::unique_ptr<mfem::SparseMatrix> m_MReduced; ///< Reduced M matrix (free DOFs only).
|
||||
std::unique_ptr<mfem::SparseMatrix> m_QReduced; ///< Reduced Q matrix (free DOFs only).
|
||||
std::unique_ptr<mfem::SparseMatrix> m_DReduced; ///< Reduced D matrix (free DOFs only).
|
||||
std::unique_ptr<mfem::SparseMatrix> m_SReduced; ///< Reduced S matrix (free DOFs only, used for least squares stabilization).
|
||||
mutable std::unique_ptr<mfem::SparseMatrix> m_gradReduced; ///< Reduced gradient operator (G) for the nonlinear part f(θ).
|
||||
|
||||
// --- Scaled Reduced Matrix Representations (owned, after eliminating essential DOFs and scaling by stabilization coefficients) ---
|
||||
std::unique_ptr<mfem::SparseMatrix> m_MScaledReduced; ///< Scaled M matrix (free DOFs only, scaled by stabilization coefficient).
|
||||
std::unique_ptr<mfem::SparseMatrix> m_QScaledReduced; ///< Scaled Q matrix (free DOFs only, scaled by stabilization coefficient).
|
||||
std::unique_ptr<mfem::SparseMatrix> m_DScaledReduced; ///< Scaled D matrix (free DOFs only, scaled by stabilization coefficient).
|
||||
std::unique_ptr<mfem::SparseMatrix> m_ScaledSReduced; ///< Scaled S matrix (free DOFs only, scaled by stabilization coefficient).
|
||||
|
||||
|
||||
// --- Stabilization Coefficient --- (Perhapses this should be a user parameter...) // TODO: Sort out why this is negative (sign error?)
|
||||
static constexpr double m_stabilizationCoefficient = -2.0; ///< Stabilization coefficient for the system, used to more tightly couple ∇θ and φ.
|
||||
static constexpr double m_IncrementedStabilizationCoefficient = 1.0 + m_stabilizationCoefficient; ///< 1 + Stabilization coefficient for the system, used to more tightly couple ∇θ and φ.
|
||||
|
||||
// --- State Vectors and DOF Management ---
|
||||
mutable mfem::Vector m_state; ///< Full state vector [θ, φ]^T, including essential DOFs.
|
||||
mfem::Array<int> m_freeDofs; ///< Array of indices for free (non-essential) DOFs.
|
||||
|
||||
// --- Block Offsets ---
|
||||
const mfem::Array<int> m_blockOffsets; ///< Block offsets for the full system [0, size(θ), size(θ)+size(φ)].
|
||||
mfem::Array<int> m_reducedBlockOffsets; ///< Block offsets for the reduced system (free DOFs).
|
||||
|
||||
// --- Essential Boundary Conditions ---
|
||||
serif::types::MFEMArrayPair m_theta_ess_tdofs; ///< Essential true DOFs for θ (indices and values).
|
||||
serif::types::MFEMArrayPair m_phi_ess_tdofs; ///< Essential true DOFs for φ (indices and values).
|
||||
|
||||
// --- Jacobian and Preconditioner Components (owned, mutable) ---
|
||||
std::unique_ptr<mfem::ScaledOperator> m_negQ_mat; ///< Scaled operator for -Q_reduced.
|
||||
mutable std::unique_ptr<mfem::BlockOperator> m_jacobian; ///< Jacobian operator J = [G M; -Q D]_reduced.
|
||||
mutable std::unique_ptr<SchurCompliment> m_schurCompliment; ///< Schur complement S = D_reduced - Q_reduced * G_inv_reduced * M_reduced.
|
||||
mutable std::unique_ptr<GMRESInverter> m_invSchurCompliment; ///< Approximate inverse of the Schur complement.
|
||||
mutable std::unique_ptr<mfem::Solver> m_invNonlinearJacobian; ///< Solver for the inverse of the G block (gradient of f(θ)_reduced).
|
||||
mutable std::unique_ptr<mfem::BlockDiagonalPreconditioner> m_schurPreconditioner; ///< Block diagonal preconditioner for the system.
|
||||
|
||||
// --- State Flags ---
|
||||
bool m_isFinalized = false; ///< Flag indicating if finalize() has been called.
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Constructs the sparse matrix representations of M, Q, and D, and their reduced forms.
|
||||
* Called during finalize().
|
||||
*/
|
||||
void construct_matrix_representations();
|
||||
|
||||
/**
|
||||
* @brief Constructs the block offsets for the reduced system.
|
||||
* Called during finalize().
|
||||
*/
|
||||
void construct_reduced_block_offsets();
|
||||
|
||||
/**
|
||||
* @brief Constructs the constant terms of the Jacobian operator (M, -Q, D).
|
||||
* The (0,0) block (gradient of f) is set in GetGradient.
|
||||
* Called during finalize().
|
||||
*/
|
||||
void construct_jacobian_constant_terms();
|
||||
|
||||
/**
|
||||
* @brief Scatters the values of essential boundary conditions into the full state vector.
|
||||
* Called during finalize().
|
||||
*/
|
||||
void scatter_boundary_conditions();
|
||||
|
||||
/**
|
||||
* @brief Updates the solver for the inverse of the nonlinear Jacobian block (G_00).
|
||||
* @param grad The gradient operator (G_00) of the nonlinear part f(θ).
|
||||
*/
|
||||
void update_inverse_nonlinear_jacobian(const mfem::Operator &grad) const;
|
||||
|
||||
/**
|
||||
* @brief Updates the inverse Schur complement operator and its components.
|
||||
* This is typically called after the nonlinear Jacobian part has been updated.
|
||||
*/
|
||||
void update_inverse_schur_compliment() const;
|
||||
|
||||
/**
|
||||
* @brief Updates the preconditioner components.
|
||||
* This involves updating the inverse nonlinear Jacobian and then the inverse Schur complement.
|
||||
* @param grad The gradient operator (G_00) of the nonlinear part f(θ).
|
||||
*/
|
||||
void update_preconditioner(const mfem::Operator &grad) const;
|
||||
};
|
||||
|
||||
} // namespace polytrope
|
||||
} // namespace serif
|
||||
62
src/polytrope/utils/public/utilities.h
Normal file
62
src/polytrope/utils/public/utilities.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "mfem.hpp"
|
||||
|
||||
namespace serif::utilities {
|
||||
[[nodiscard]] mfem::SparseMatrix build_reduced_matrix(
|
||||
const mfem::SparseMatrix& matrix,
|
||||
const mfem::Array<int>& trialEssentialDofs,
|
||||
const mfem::Array<int>& testEssentialDofs
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Generate a vector of 1s and 0s where 1 elemetns cooresponds to queried dofs. Useful for degugging
|
||||
* @param allDofs array, counding from 0, of all dofs in the system
|
||||
* @param highlightDofs the dofs that you want to identify
|
||||
* @return
|
||||
*
|
||||
* *Example Usage:*
|
||||
* One could use this to identify, for example, which dofs are being identified as the central dofs
|
||||
* @code
|
||||
* ...
|
||||
* mfem::Array<int> phiDofs, thetaDofs;
|
||||
* phiDofs.SetSize(m_fePhi->GetNDofs());
|
||||
* thetaDofs.SetSize(m_feTheta->GetNDofs());
|
||||
* const mfem::Vector phiHighlightVector = serif::utilities::build_dof_identification_vector(phiDofs, phiCenterDofs);
|
||||
* const mfem::Vector thetaHighlightVector = serif::utilities::build_dof_identification_vector(thetaDofs, thetaCenterDofs);
|
||||
* Probe::glVisView(
|
||||
* const_cast<mfem::Vector&>(phiHighlightVector),
|
||||
* *m_fePhi,
|
||||
* "Phi Center Dofs"
|
||||
* );
|
||||
* Probe::glVisView(
|
||||
* const_cast<mfem::Vector&>(thetaHighlightVector),
|
||||
* *m_feTheta,
|
||||
* "Theta Center Dofs"
|
||||
* );
|
||||
* @endcode
|
||||
*/
|
||||
mfem::Vector build_dof_identification_vector(
|
||||
const mfem::Array<int>& allDofs,
|
||||
const::mfem::Array<int>& highlightDofs
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Computes the curl of a given H(div) grid function (e.g., from an RT space).
|
||||
*
|
||||
* This function is crucial for diagnosing spurious, non-physical modes in mixed FEM
|
||||
* formulations where the curl of a gradient field is expected to be zero.
|
||||
*
|
||||
* @param phi_gf The GridFunction representing the vector field (e.g., φ). It is expected
|
||||
* to be in an H(div)-conforming space like Raviart-Thomas (RT).
|
||||
* @return A std::pair containing two new grid functions:
|
||||
* - pair.first: A unique_ptr to the vector curl field (∇ × φ). This field will
|
||||
* be in an H(curl)-conforming Nedelec (ND) space.
|
||||
* - pair.second: A unique_ptr to the scalar magnitude of the curl (||∇ × φ||).
|
||||
* This field will be in an L2 space.
|
||||
*
|
||||
* @note The returned unique_ptrs manage the lifetime of the new GridFunctions and their
|
||||
* associated FiniteElementSpaces and FECollections, preventing memory leaks.
|
||||
*/
|
||||
mfem::GridFunction compute_curl(mfem::GridFunction& phi_gf);
|
||||
}
|
||||
Reference in New Issue
Block a user