feat(poly): major work on preconditioner for block form of lane emden equation

working on a "smart" schur compliment preconditioner for the block form of the lane emden equation. Currently this is stub and should not be considered usable
This commit is contained in:
2025-04-09 15:17:55 -04:00
parent acf5367556
commit 08b68c22de
14 changed files with 525 additions and 118 deletions

View File

@@ -21,34 +21,33 @@
#include "mfem.hpp"
#include <memory>
#include <string>
#include <stdexcept>
#include <string>
#include <utility>
#include "polySolver.h"
#include "integrators.h"
#include "polyCoeff.h"
#include "4DSTARTypes.h"
#include "config.h"
#include "integrators.h"
#include "operator.h"
#include "polyCoeff.h"
#include "probe.h"
#include "resourceManager.h"
#include "resourceManagerTypes.h"
#include "operator.h"
#include "debug.h"
#include "quill/LogMacros.h"
namespace laneEmden {
double a (int k, double n) {
double a (int k, 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(int m, double n) {
double c(int m, double n) { // NOLINT(*-no-recursion)
if ( m == 0 ) { return std::pow(a(0, n), n); }
else {
double termOne = 1.0/(m*a(0, n));
@@ -60,7 +59,7 @@ namespace laneEmden {
}
}
double thetaSerieseExpansion(double xi, double n, int order) {
double thetaSeriesExpansion(double xi, double n, int order) {
double acc = 0;
for (int k = 0; k < order; k++) {
acc += a(k, n) * std::pow(xi, k);
@@ -101,7 +100,7 @@ PolySolver::PolySolver(double n, double order) {
}
PolySolver::~PolySolver() {}
PolySolver::~PolySolver() = default;
void PolySolver::assembleBlockSystem() {
@@ -121,20 +120,8 @@ void PolySolver::assembleBlockSystem() {
blockOffsets[2] = feSpaces[1]->GetVSize();
blockOffsets.PartialSum();
// Coefficients
mfem::ConstantCoefficient negOneCoeff(-1.0);
mfem::ConstantCoefficient oneCoeff(1.0);
mfem::Vector negOneVec(mfem::Vector(3));
mfem::Vector oneVec(mfem::Vector(3));
negOneVec = -1.0;
oneVec = 1.0;
mfem::VectorConstantCoefficient negOneVCoeff(negOneVec);
mfem::VectorConstantCoefficient oneVCoeff(oneVec);
// Add integrators to block form one by one
// We add integrators cooresponding to each term in the weak form
// We add integrators corresponding to each term in the weak form
// The block form of the residual matrix
// ⎡ 0 -M ⎤ ⎡ θ ⎤ + ⎡f(θ)⎤ = ⎡ 0 ⎤ = R(X)
// ⎣ -Q D ⎦ ⎣ Φ ⎦ ⎣ 0 ⎦ ⎣ 0 ⎦
@@ -171,6 +158,7 @@ void PolySolver::assembleBlockSystem() {
// --- Assemble the NonlinearForm (f) ---
auto fform = std::make_unique<mfem::NonlinearForm>(m_feTheta.get());
mfem::ConstantCoefficient oneCoeff(1.0);
fform->AddDomainIntegrator(new polyMFEMUtils::NonlinearPowerIntegrator(oneCoeff, m_polytropicIndex));
// TODO: Add essential boundary conditions to the nonlinear form
@@ -185,7 +173,7 @@ void PolySolver::assembleBlockSystem() {
}
void PolySolver::solve(){
void PolySolver::solve() const {
// --- Set the initial guess for the solution ---
setInitialGuess();
@@ -203,6 +191,14 @@ void PolySolver::solve(){
mfem::NewtonSolver newtonSolver = setupNewtonSolver();
// EMB 2025: Calling Mult gets the gradient of the operator for the NewtonSolver
// This then is set as the operator for the solver for NewtonSolver
// The solver (assuming it is an iterative solver) then sets the
// operator for its preconditioner to this.
// What this means is that there is no need to manually deal
// with updating the preconditioner at every newton step as the
// changes to the jacobian are automatically propagated through the
// solving chain. This is at least true with MFEM 4.8-rc0
newtonSolver.Mult(zero_rhs, state_vector);
// --- Save and view the solution ---
@@ -211,25 +207,40 @@ void PolySolver::solve(){
}
std::pair<mfem::Array<int>, mfem::Array<int>> PolySolver::getEssentialTrueDof() {
SSE::MFEMArrayPairSet PolySolver::getEssentialTrueDof() const {
mfem::Array<int> theta_ess_tdof_list;
mfem::Array<int> phi_ess_tdof_list;
mfem::Array<int> centerDofs = findCenterElement();
mfem::Array<int> thetaCenterDofs, phiCenterDofs;
mfem::Array<double> thetaCenterVals, phiCenterVals;
std::tie(thetaCenterDofs, phi_ess_tdof_list) = findCenterElement();
thetaCenterVals.SetSize(thetaCenterDofs.Size());
phiCenterVals.SetSize(phi_ess_tdof_list.Size());
phi_ess_tdof_list.Append(centerDofs);
thetaCenterVals = 1.0;
phiCenterVals = 0.0;
mfem::Array<int> ess_brd(m_mesh->bdr_attributes.Max());
ess_brd = 1;
m_feTheta->GetEssentialTrueDofs(ess_brd, theta_ess_tdof_list);
// combine the essential dofs with the center dofs
theta_ess_tdof_list.Append(centerDofs);
return std::make_pair(theta_ess_tdof_list, phi_ess_tdof_list);
mfem::Array<double> thetaSurfaceVals;
m_feTheta->GetEssentialTrueDofs(ess_brd, theta_ess_tdof_list);
thetaSurfaceVals.SetSize(theta_ess_tdof_list.Size());
thetaSurfaceVals = 0.0;
// combine the essential dofs with the center dofs
theta_ess_tdof_list.Append(thetaCenterDofs);
thetaSurfaceVals.Append(thetaCenterVals);
SSE::MFEMArrayPair thetaPair = std::make_pair(theta_ess_tdof_list, thetaSurfaceVals);
SSE::MFEMArrayPair phiPair = std::make_pair(phi_ess_tdof_list, phiCenterVals);
SSE::MFEMArrayPairSet pairSet = std::make_pair(thetaPair, phiPair);
return pairSet;
}
mfem::Array<int> PolySolver::findCenterElement() {
mfem::Array<int> centerDofs;
std::pair<mfem::Array<int>, mfem::Array<int>> PolySolver::findCenterElement() const {
mfem::Array<int> thetaCenterDofs;
mfem::Array<int> phiCenterDofs;
mfem::DenseMatrix centerPoint(m_mesh->SpaceDimension(), 1);
centerPoint(0, 0) = 0.0;
centerPoint(1, 0) = 0.0;
@@ -241,12 +252,14 @@ mfem::Array<int> PolySolver::findCenterElement() {
mfem::Array<int> tempDofs;
for (int i = 0; i < elementIDs.Size(); i++) {
m_feTheta->GetElementDofs(elementIDs[i], tempDofs);
centerDofs.Append(tempDofs);
thetaCenterDofs.Append(tempDofs);
m_fePhi->GetElementDofs(elementIDs[i], tempDofs);
phiCenterDofs.Append(tempDofs);
}
return centerDofs;
return std::make_pair(thetaCenterDofs, phiCenterDofs);
}
void PolySolver::setInitialGuess() {
void PolySolver::setInitialGuess() const {
// --- Set the initial guess for the solution ---
mfem::FunctionCoefficient thetaInitGuess (
[this](const mfem::Vector &x) {
@@ -261,39 +274,38 @@ void PolySolver::setInitialGuess() {
[this](const mfem::Vector &x, mfem::Vector &v) {
double radius = Probe::getMeshRadius(*m_mesh);
double u = -1/std::pow(radius,2);
v(0) = 2*std::abs(x(0))*u;
v(1) = 2*std::abs(x(1))*u;
v(2) = 2*std::abs(x(2))*u;
v(0) = 2*x(0)*u;
v(1) = 2*x(1)*u;
v(2) = 2*x(2)*u;
}
);
m_theta->ProjectCoefficient(thetaInitGuess);
m_phi->ProjectCoefficient(phiInitGuess);
if (m_config.get<bool>("Poly:Solver:ViewInitialGuess", false)) {
Probe::glVisView(*m_theta, *m_mesh, "initialGuess");
Probe::glVisView(*m_theta, *m_mesh, "θ init");
Probe::glVisView(*m_phi, *m_mesh, "ɸ init");
}
}
void PolySolver::saveAndViewSolution(const mfem::BlockVector& state_vector) {
void PolySolver::saveAndViewSolution(const mfem::BlockVector& state_vector) const {
mfem::BlockVector x_block(const_cast<mfem::BlockVector&>(state_vector), m_polytropOperator->GetBlockOffsets());
mfem::Vector& x_theta = x_block.GetBlock(0);
mfem::Vector& x_phi = x_block.GetBlock(1);
bool doView = m_config.get<bool>("Poly:Output:View", false);
if (doView) {
if (m_config.get<bool>("Poly:Output:View", false)) {
Probe::glVisView(x_theta, *m_feTheta, "θ Solution");
Probe::glVisView(x_phi, *m_fePhi, "ɸ Solution");
}
// --- Extract the Solution ---
bool write11DSolution = m_config.get<bool>("Poly:Output:1D:Save", true);
if (write11DSolution) {
std::string solutionPath = m_config.get<std::string>("Poly:Output:1D:Path", "polytropeSolution_1D.csv");
std::string derivSolPath = "d" + solutionPath;
if (bool write11DSolution = m_config.get<bool>("Poly:Output:1D:Save", true)) {
auto solutionPath = m_config.get<std::string>("Poly:Output:1D:Path", "polytropeSolution_1D.csv");
auto derivSolPath = "d" + solutionPath;
double rayCoLatitude = m_config.get<double>("Poly:Output:1D:RayCoLatitude", 0.0);
double rayLongitude = m_config.get<double>("Poly:Output:1D:RayLongitude", 0.0);
int raySamples = m_config.get<int>("Poly:Output:1D:RaySamples", 100);
auto rayCoLatitude = m_config.get<double>("Poly:Output:1D:RayCoLatitude", 0.0);
auto rayLongitude = m_config.get<double>("Poly:Output:1D:RayLongitude", 0.0);
auto raySamples = m_config.get<int>("Poly:Output:1D:RaySamples", 100);
std::vector rayDirection = {rayCoLatitude, rayLongitude};
@@ -302,10 +314,10 @@ void PolySolver::saveAndViewSolution(const mfem::BlockVector& state_vector) {
}
}
void PolySolver::setupOperator() {
mfem::Array<int> theta_ess_tdof_list, phi_ess_tdof_list;
std::tie(theta_ess_tdof_list, phi_ess_tdof_list) = getEssentialTrueDof();
m_polytropOperator->SetEssentialTrueDofs(theta_ess_tdof_list, phi_ess_tdof_list);
void PolySolver::setupOperator() const {
SSE::MFEMArrayPairSet ess_tdof_pair_set = getEssentialTrueDof();
m_polytropOperator->SetEssentialTrueDofs(ess_tdof_pair_set);
// -- Finalize the operator --
m_polytropOperator->finalize();
@@ -316,20 +328,52 @@ void PolySolver::setupOperator() {
}
}
mfem::NewtonSolver PolySolver::setupNewtonSolver(){
// --- Load configuration parameters ---
double newtonRelTol = m_config.get<double>("Poly:Solver:Newton:RelTol", 1e-7);
double newtonAbsTol = m_config.get<double>("Poly:Solver:Newton:AbsTol", 1e-7);
int newtonMaxIter = m_config.get<int>("Poly:Solver:Newton:MaxIter", 200);
int newtonPrintLevel = m_config.get<int>("Poly:Solver:Newton:PrintLevel", 1);
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", 1e-7);
newtonAbsTol = m_config.get<double>("Poly:Solver:Newton:AbsTol", 1e-7);
newtonMaxIter = m_config.get<int>("Poly:Solver:Newton:MaxIter", 200);
newtonPrintLevel = m_config.get<int>("Poly:Solver:Newton:PrintLevel", 1);
double gmresRelTol = m_config.get<double>("Poly:Solver:GMRES:RelTol", 1e-10);
double gmresAbsTol = m_config.get<double>("Poly:Solver:GMRES:AbsTol", 1e-12);
int gmresMaxIter = m_config.get<int>("Poly:Solver:GMRES:MaxIter", 2000);
int gmresPrintLevel = m_config.get<int>("Poly:Solver:GMRES:PrintLevel", 0);
gmresRelTol = m_config.get<double>("Poly:Solver:GMRES:RelTol", 1e-10);
gmresAbsTol = m_config.get<double>("Poly:Solver:GMRES:AbsTol", 1e-12);
gmresMaxIter = m_config.get<int>("Poly:Solver:GMRES:MaxIter", 2000);
gmresPrintLevel = m_config.get<int>("Poly:Solver:GMRES:PrintLevel", 0);
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);
}
mfem::BlockDiagonalPreconditioner PolySolver::build_preconditioner() const {
// --- Set up the preconditioners. The non-linear form will use a Chebyshev Preconditioner while the linear terms will use a simpler Jacobi preconditioner ---
mfem::BlockDiagonalPreconditioner prec(m_polytropOperator->GetBlockOffsets());
const mfem::BlockOperator &jacobian = m_polytropOperator->GetJacobianOperator();
// Get all the blocks. J00 -> Non-linear form (df(θ)/dθ), J01-> -M, J10 -> -Q, J11 -> D
const mfem::Operator& J00 = jacobian.GetBlock(0, 0);
mfem::Vector J00diag;
J00.AssembleDiagonal(J00diag);
SSE::MFEMArrayPairSet ess_tdof_pair_set = m_polytropOperator->GetEssentialTrueDofs();
// TODO: This order may need to be tuned (EMB)
// --- ess_tdof_pair_set.first -> (theta dof ids, theta dof vals).first -> theta dof ids
// --- ess_tdof_pair_set.second -> (phi dof ids, phi dof vals).first -> phi dof ids
mfem::OperatorChebyshevSmoother J00Prec(J00, J00diag, ess_tdof_pair_set.first.first, 2);
mfem::OperatorJacobiSmoother J11Prec(m_polytropOperator->GetJ11diag(), ess_tdof_pair_set.second.first);
prec.SetDiagonalBlock(0, &J00Prec);
prec.SetDiagonalBlock(1, &J11Prec);
return prec;
}
mfem::NewtonSolver 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);
// --- Set up the Newton solver ---
mfem::NewtonSolver newtonSolver;
@@ -338,13 +382,18 @@ mfem::NewtonSolver PolySolver::setupNewtonSolver(){
newtonSolver.SetMaxIter(newtonMaxIter);
newtonSolver.SetPrintLevel(newtonPrintLevel);
newtonSolver.SetOperator(*m_polytropOperator);
// --- Created the linear solver which is used to invert the jacobian ---
mfem::GMRESSolver gmresSolver;
gmresSolver.SetRelTol(gmresRelTol);
gmresSolver.SetAbsTol(gmresAbsTol);
gmresSolver.SetMaxIter(gmresMaxIter);
gmresSolver.SetPrintLevel(gmresPrintLevel);
// build_preconditioner();
newtonSolver.SetSolver(gmresSolver);
// newtonSolver.SetAdaptiveLinRtol();
return newtonSolver;
}
}