818 lines
24 KiB
C++
818 lines
24 KiB
C++
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
|
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
|
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
|
//
|
|
// This file is part of the MFEM library. For more information and source code
|
|
// availability visit https://mfem.org.
|
|
//
|
|
// MFEM is free software; you can redistribute it and/or modify it under the
|
|
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
|
// CONTRIBUTING.md for details
|
|
|
|
#include <algorithm>
|
|
#include <ctime>
|
|
|
|
#include "../../examples/ex33.hpp"
|
|
#include "spde_solver.hpp"
|
|
|
|
namespace mfem
|
|
{
|
|
namespace spde
|
|
{
|
|
|
|
// Helper function that determines if output should be printed to the console.
|
|
// The output is printed if the rank is 0 and if the print level is greater than
|
|
// 0. The rank is retrieved via the fespace.
|
|
bool PrintOutput(const ParFiniteElementSpace *fespace_ptr, int print_level)
|
|
{
|
|
return (fespace_ptr->GetMyRank() == 0 && print_level > 0);
|
|
}
|
|
|
|
void Boundary::PrintInfo(std::ostream &os) const
|
|
{
|
|
os << "\n<Boundary Info>\n"
|
|
<< " Boundary Conditions:\n";
|
|
for (const auto &it : boundary_attributes)
|
|
{
|
|
os << " Boundary " << it.first << ": ";
|
|
switch (it.second)
|
|
{
|
|
case BoundaryType::kNeumann:
|
|
os << "Neumann";
|
|
break;
|
|
case BoundaryType::kDirichlet:
|
|
os << "Dirichlet";
|
|
break;
|
|
case BoundaryType::kRobin:
|
|
os << "Robin, coefficient: " << robin_coefficient;
|
|
break;
|
|
case BoundaryType::kPeriodic:
|
|
os << "Periodic";
|
|
break;
|
|
default:
|
|
os << "Undefined";
|
|
break;
|
|
}
|
|
os << "\n";
|
|
}
|
|
bool first_print_statement = true;
|
|
// If the map is not empty
|
|
if (!dirichlet_coefficients.empty())
|
|
{
|
|
os << " Inhomogeneous Dirichlet defined on ";
|
|
for (const auto &it : dirichlet_coefficients)
|
|
{
|
|
if (!first_print_statement)
|
|
{
|
|
os << ", ";
|
|
}
|
|
else
|
|
{
|
|
first_print_statement = false;
|
|
}
|
|
os << it.first << "(=" << it.second << ")";
|
|
}
|
|
os << "\n";
|
|
}
|
|
os << "<Boundary Info>\n\n";
|
|
}
|
|
|
|
void Boundary::VerifyDefinedBoundaries(const Mesh &mesh) const
|
|
{
|
|
// Verify that all defined boundaries are actually defined on the
|
|
// mesh, i.e. if the keys of boundary attributes appear in the boundary
|
|
// attributes of the mesh.
|
|
mfem::out << "\n<Boundary Verify>\n";
|
|
const Array<int> boundary(mesh.bdr_attributes);
|
|
for (const auto &it : boundary_attributes)
|
|
{
|
|
if (boundary.Find(it.first) == -1)
|
|
{
|
|
MFEM_ABORT(" Boundary "
|
|
<< it.first
|
|
<< " is not defined on the mesh but in Boundary class."
|
|
<< "Exiting...")
|
|
}
|
|
}
|
|
|
|
/// Verify if all boundary attributes appear as keys in the
|
|
/// boundary attributes, if not let the user know that we use Neumann by
|
|
/// default.
|
|
std::vector<int> boundary_attributes_keys;
|
|
for (int i = 0; i < boundary.Size(); i++)
|
|
{
|
|
if (boundary_attributes.find(boundary[i]) == boundary_attributes.end())
|
|
{
|
|
boundary_attributes_keys.push_back(boundary[i]);
|
|
}
|
|
}
|
|
if (!boundary_attributes_keys.empty())
|
|
{
|
|
mfem::out << " Boundaries (";
|
|
for (const auto &it : boundary_attributes_keys)
|
|
{
|
|
mfem::out << it << ", ";
|
|
}
|
|
mfem::out << ") are defined on the mesh but not in the";
|
|
mfem::out << " boundary attributes (Use Neumann).";
|
|
}
|
|
|
|
/// Check if any periodic boundary is registered
|
|
for (const auto &it : boundary_attributes)
|
|
{
|
|
if (it.second == BoundaryType::kPeriodic)
|
|
{
|
|
MFEM_ABORT(" Periodic boundaries must be defined on the mesh"
|
|
<< ", not in Boundaries. Exiting...")
|
|
}
|
|
}
|
|
|
|
mfem::out << "\n<Boundary Verify>\n\n";
|
|
}
|
|
|
|
void Boundary::ComputeBoundaryError(const ParGridFunction &solution)
|
|
{
|
|
const ParFiniteElementSpace &fes = *solution.ParFESpace();
|
|
const ParMesh &pmesh = *fes.GetParMesh();
|
|
|
|
if (PrintOutput(&fes, 1))
|
|
{
|
|
mfem::out << "<Boundary::ComputeBoundaryError>"
|
|
<< "\n";
|
|
mfem::out << " GetVDim: " << fes.GetVDim() << "\n";
|
|
}
|
|
|
|
real_t alpha{0.0};
|
|
real_t beta{1.0};
|
|
real_t gamma{0.0};
|
|
|
|
// Index i needs to be incremented by one to map to the boundary attributes
|
|
// in the mesh.
|
|
for (int i = 0; i < pmesh.bdr_attributes.Max(); i++)
|
|
{
|
|
real_t error{0};
|
|
real_t avg{0};
|
|
Array<int> bdr(pmesh.bdr_attributes.Max());
|
|
bdr = 0;
|
|
bdr[i] = 1;
|
|
|
|
UpdateIntegrationCoefficients(i + 1, alpha, beta, gamma);
|
|
avg = IntegrateBC(solution, bdr, alpha, beta, gamma, error);
|
|
if (PrintOutput(&fes, 1))
|
|
{
|
|
mfem::out << "->Boundary " << i + 1 << "\n";
|
|
mfem::out << " Alpha : " << alpha << "\n";
|
|
mfem::out << " Beta : " << beta << "\n";
|
|
mfem::out << " Gamma : " << gamma << "\n";
|
|
mfem::out << " Average : " << avg << "\n";
|
|
mfem::out << " Error : " << error << "\n\n";
|
|
}
|
|
}
|
|
|
|
if (PrintOutput(&fes, 1))
|
|
{
|
|
mfem::out << "<Boundary::ComputeBoundaryError>" << std::endl;
|
|
}
|
|
}
|
|
|
|
void Boundary::UpdateIntegrationCoefficients(int i, real_t &alpha, real_t &beta,
|
|
real_t &gamma)
|
|
{
|
|
// Check if i is a key in boundary_attributes
|
|
if (boundary_attributes.find(i) != boundary_attributes.end())
|
|
{
|
|
switch (boundary_attributes[i])
|
|
{
|
|
case BoundaryType::kNeumann:
|
|
alpha = 1.0;
|
|
beta = 0.0;
|
|
gamma = 0.0;
|
|
break;
|
|
case BoundaryType::kDirichlet:
|
|
alpha = 0.0;
|
|
beta = 1.0;
|
|
if (dirichlet_coefficients.find(i) != dirichlet_coefficients.end())
|
|
{
|
|
gamma = dirichlet_coefficients[i];
|
|
}
|
|
else
|
|
{
|
|
gamma = 0.0;
|
|
}
|
|
break;
|
|
case BoundaryType::kRobin:
|
|
alpha = 1.0;
|
|
beta = robin_coefficient;
|
|
gamma = 0.0;
|
|
break;
|
|
default:
|
|
alpha = 1.0;
|
|
beta = 0.0;
|
|
gamma = 0.0;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If i is not a key in boundary_attributes, it corresponds to Neumann.
|
|
alpha = 1.0;
|
|
beta = 0.0;
|
|
gamma = 0.0;
|
|
}
|
|
}
|
|
|
|
void Boundary::AddHomogeneousBoundaryCondition(int boundary,
|
|
BoundaryType type)
|
|
{
|
|
boundary_attributes[boundary] = type;
|
|
}
|
|
|
|
void Boundary::AddInhomogeneousDirichletBoundaryCondition(int boundary,
|
|
real_t coefficient)
|
|
{
|
|
boundary_attributes[boundary] = BoundaryType::kDirichlet;
|
|
dirichlet_coefficients[boundary] = coefficient;
|
|
}
|
|
|
|
void Boundary::SetRobinCoefficient(real_t coefficient)
|
|
{
|
|
robin_coefficient = coefficient;
|
|
}
|
|
|
|
real_t IntegrateBC(const ParGridFunction &x, const Array<int> &bdr,
|
|
real_t alpha, real_t beta, real_t gamma, real_t &glb_err)
|
|
{
|
|
real_t loc_vals[3];
|
|
real_t &nrm = loc_vals[0];
|
|
real_t &avg = loc_vals[1];
|
|
real_t &error = loc_vals[2];
|
|
|
|
nrm = 0.0;
|
|
avg = 0.0;
|
|
error = 0.0;
|
|
|
|
const bool a_is_zero = alpha == 0.0;
|
|
const bool b_is_zero = beta == 0.0;
|
|
|
|
const ParFiniteElementSpace &fes = *x.ParFESpace();
|
|
MFEM_ASSERT(fes.GetVDim() == 1, "");
|
|
ParMesh &mesh = *fes.GetParMesh();
|
|
Vector shape;
|
|
Vector loc_dofs;
|
|
Vector w_nor;
|
|
DenseMatrix dshape;
|
|
Array<int> dof_ids;
|
|
for (int i = 0; i < mesh.GetNBE(); i++)
|
|
{
|
|
if (bdr[mesh.GetBdrAttribute(i) - 1] == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FaceElementTransformations *FTr = mesh.GetBdrFaceTransformations(i);
|
|
if (FTr == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FiniteElement &fe = *fes.GetFE(FTr->Elem1No);
|
|
MFEM_ASSERT(fe.GetMapType() == FiniteElement::VALUE, "");
|
|
const int int_order = 2 * fe.GetOrder() + 3;
|
|
const IntegrationRule &ir = IntRules.Get(FTr->FaceGeom, int_order);
|
|
|
|
fes.GetElementDofs(FTr->Elem1No, dof_ids);
|
|
x.GetSubVector(dof_ids, loc_dofs);
|
|
if (!a_is_zero)
|
|
{
|
|
const int sdim = FTr->Face->GetSpaceDim();
|
|
w_nor.SetSize(sdim);
|
|
dshape.SetSize(fe.GetDof(), sdim);
|
|
}
|
|
if (!b_is_zero)
|
|
{
|
|
shape.SetSize(fe.GetDof());
|
|
}
|
|
for (int j = 0; j < ir.GetNPoints(); j++)
|
|
{
|
|
const IntegrationPoint &ip = ir.IntPoint(j);
|
|
IntegrationPoint eip;
|
|
FTr->Loc1.Transform(ip, eip);
|
|
FTr->Face->SetIntPoint(&ip);
|
|
real_t face_weight = FTr->Face->Weight();
|
|
real_t val = 0.0;
|
|
if (!a_is_zero)
|
|
{
|
|
FTr->Elem1->SetIntPoint(&eip);
|
|
fe.CalcPhysDShape(*FTr->Elem1, dshape);
|
|
CalcOrtho(FTr->Face->Jacobian(), w_nor);
|
|
val += alpha * dshape.InnerProduct(w_nor, loc_dofs) / face_weight;
|
|
}
|
|
if (!b_is_zero)
|
|
{
|
|
fe.CalcShape(eip, shape);
|
|
val += beta * (shape * loc_dofs);
|
|
}
|
|
|
|
// Measure the length of the boundary
|
|
nrm += ip.weight * face_weight;
|
|
|
|
// Integrate alpha * n.Grad(x) + beta * x
|
|
avg += val * ip.weight * face_weight;
|
|
|
|
// Integrate |alpha * n.Grad(x) + beta * x - gamma|^2
|
|
val -= gamma;
|
|
error += (val * val) * ip.weight * face_weight;
|
|
}
|
|
}
|
|
|
|
real_t glb_vals[3];
|
|
MPI_Allreduce(loc_vals, glb_vals, 3, MPITypeMap<real_t>::mpi_type, MPI_SUM,
|
|
fes.GetComm());
|
|
|
|
real_t glb_nrm = glb_vals[0];
|
|
real_t glb_avg = glb_vals[1];
|
|
glb_err = glb_vals[2];
|
|
|
|
// Normalize by the length of the boundary
|
|
if (std::abs(glb_nrm) > 0.0)
|
|
{
|
|
glb_err /= glb_nrm;
|
|
glb_avg /= glb_nrm;
|
|
}
|
|
|
|
// Compute l2 norm of the error in the boundary condition (negative
|
|
// quadrature weights may produce negative 'error')
|
|
glb_err = (glb_err >= 0.0) ? sqrt(glb_err) : -sqrt(-glb_err);
|
|
|
|
// Return the average value of alpha * n.Grad(x) + beta * x
|
|
return glb_avg;
|
|
}
|
|
|
|
SPDESolver::SPDESolver(real_t nu, const Boundary &bc,
|
|
ParFiniteElementSpace *fespace, real_t l1, real_t l2,
|
|
real_t l3, real_t e1, real_t e2, real_t e3)
|
|
: k_(fespace),
|
|
m_(fespace),
|
|
fespace_ptr_(fespace),
|
|
bc_(bc),
|
|
nu_(nu),
|
|
l1_(l1),
|
|
l2_(l2),
|
|
l3_(l3),
|
|
e1_(e1),
|
|
e2_(e2),
|
|
e3_(e3)
|
|
{
|
|
if (PrintOutput(fespace_ptr_, print_level_))
|
|
{
|
|
mfem::out << "<SPDESolver> Initialize Solver .." << std::endl;
|
|
}
|
|
StopWatch sw;
|
|
sw.Start();
|
|
|
|
// Resize the marker arrays for the boundary conditions
|
|
// Number of boundary attributes in the mesh
|
|
int nbc{0};
|
|
const auto &bdr_attributes = fespace_ptr_->GetParMesh()->bdr_attributes;
|
|
if (bdr_attributes.Size() > 0)
|
|
{
|
|
// Assumes a contiguous range of boundary attributes (1, 2, 3, ...)
|
|
nbc = bdr_attributes.Max() - bdr_attributes.Min() + 1;
|
|
}
|
|
dbc_marker_.SetSize(nbc);
|
|
rbc_marker_.SetSize(nbc);
|
|
dbc_marker_ = 0;
|
|
rbc_marker_ = 0;
|
|
|
|
// Fill the marker arrays for the boundary conditions. We decrement the number
|
|
// it.first by one because the boundary attributes in the mesh start at 1 and
|
|
// the marker arrays start at 0.
|
|
for (const auto &it : bc_.boundary_attributes)
|
|
{
|
|
switch (it.second)
|
|
{
|
|
case BoundaryType::kDirichlet:
|
|
dbc_marker_[it.first - 1] = 1;
|
|
break;
|
|
case BoundaryType::kRobin:
|
|
rbc_marker_[it.first - 1] = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Handle homogeneous Dirichlet boundary conditions
|
|
// Note: for non zero DBC we usually need to project the boundary onto the
|
|
// solution. This is not necessary in this case since the boundary is
|
|
// homogeneous. For inhomogeneous Dirichlet we consider a lifting scheme.
|
|
fespace_ptr_->GetEssentialTrueDofs(dbc_marker_, ess_tdof_list_);
|
|
|
|
// Compute the rational approximation coefficients.
|
|
int dim = fespace_ptr_->GetParMesh()->Dimension();
|
|
int space_dim = fespace_ptr_->GetParMesh()->SpaceDimension();
|
|
alpha_ = (nu_ + dim / 2.0) / 2.0; // fractional exponent
|
|
integer_order_of_exponent_ = static_cast<int>(std::floor(alpha_));
|
|
real_t exponent_to_approximate = alpha_ - integer_order_of_exponent_;
|
|
|
|
// Compute the rational approximation coefficients.
|
|
ComputeRationalCoefficients(exponent_to_approximate);
|
|
|
|
// Set the bilinear forms.
|
|
|
|
// Assemble stiffness matrix
|
|
auto diffusion_tensor =
|
|
ConstructMatrixCoefficient(l1_, l2_, l3_, e1_, e2_, e3_, nu_, space_dim);
|
|
MatrixConstantCoefficient diffusion_coefficient(diffusion_tensor);
|
|
k_.AddDomainIntegrator(new DiffusionIntegrator(diffusion_coefficient));
|
|
ConstantCoefficient robin_coefficient(bc_.robin_coefficient);
|
|
k_.AddBoundaryIntegrator(new MassIntegrator(robin_coefficient), rbc_marker_);
|
|
k_.Assemble(0);
|
|
|
|
// Assemble mass matrix
|
|
ConstantCoefficient one(1.0);
|
|
m_.AddDomainIntegrator(new MassIntegrator(one));
|
|
m_.Assemble(0);
|
|
|
|
// Form matrices for the linear system
|
|
Array<int> empty;
|
|
k_.FormSystemMatrix(empty, stiffness_);
|
|
m_.FormSystemMatrix(empty, mass_bc_);
|
|
|
|
// Get the restriction and prolongation matrix for transformations
|
|
restriction_matrix_ = fespace->GetRestrictionMatrix();
|
|
prolongation_matrix_ = fespace->GetProlongationMatrix();
|
|
|
|
// Resize the vectors B and X to the appropriate size
|
|
if (prolongation_matrix_)
|
|
{
|
|
B_.SetSize(prolongation_matrix_->Width());
|
|
}
|
|
else
|
|
{
|
|
mfem::err << "<SPDESolver> prolongation matrix is not defined" << std::endl;
|
|
}
|
|
if (restriction_matrix_)
|
|
{
|
|
X_.SetSize(restriction_matrix_->Height());
|
|
}
|
|
else
|
|
{
|
|
mfem::err << "<SPDESolver> restriction matrix is not defined" << std::endl;
|
|
}
|
|
|
|
sw.Stop();
|
|
if (PrintOutput(fespace_ptr_, print_level_))
|
|
{
|
|
mfem::out << "<SPDESolver::Timing> matrix assembly " << sw.RealTime()
|
|
<< " [s]" << std::endl;
|
|
}
|
|
}
|
|
|
|
void SPDESolver::Solve(ParLinearForm &b, ParGridFunction &x)
|
|
{
|
|
// ------------------------------------------------------------------------
|
|
// Solve the PDE (A)^N g = f, i.e. compute g = (A)^{-1}^N f iteratively.
|
|
// ------------------------------------------------------------------------
|
|
|
|
StopWatch sw;
|
|
sw.Start();
|
|
|
|
// Zero initialize x to avoid touching uninitialized memory
|
|
x = 0.0;
|
|
|
|
ParGridFunction helper_gf(fespace_ptr_);
|
|
helper_gf = 0.0;
|
|
|
|
if (integer_order_of_exponent_ > 0)
|
|
{
|
|
if (PrintOutput(fespace_ptr_, print_level_))
|
|
{
|
|
mfem::out << "<SPDESolver> Solving PDE (A)^" << integer_order_of_exponent_
|
|
<< " u = f" << std::endl;
|
|
}
|
|
ActivateRepeatedSolve();
|
|
Solve(b, helper_gf, 1.0, 1.0, integer_order_of_exponent_);
|
|
if (integer_order_)
|
|
{
|
|
// If the exponent is an integer, we can directly add the solution to the
|
|
// final solution and return.
|
|
x += helper_gf;
|
|
if (!bc_.dirichlet_coefficients.empty())
|
|
{
|
|
LiftSolution(x);
|
|
}
|
|
return;
|
|
}
|
|
UpdateRHS(b);
|
|
DeactivateRepeatedSolve();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Solve the (remaining) fractional PDE by solving M integer order PDEs and
|
|
// adding up the solutions.
|
|
// ------------------------------------------------------------------------
|
|
if (!integer_order_)
|
|
{
|
|
// Iterate over all expansion coefficient that contribute to the
|
|
// solution.
|
|
for (int i = 0; i < coeffs_.Size(); i++)
|
|
{
|
|
if (PrintOutput(fespace_ptr_, print_level_))
|
|
{
|
|
mfem::out << "\n<SPDESolver> Solving PDE -Δ u + " << -poles_[i]
|
|
<< " u = " << coeffs_[i] << " g " << std::endl;
|
|
}
|
|
helper_gf = 0.0;
|
|
Solve(b, helper_gf, 1.0 - poles_[i], coeffs_[i]);
|
|
x += helper_gf;
|
|
}
|
|
}
|
|
|
|
// Apply the inhomogeneous Dirichlet boundary conditions.
|
|
if (!bc_.dirichlet_coefficients.empty())
|
|
{
|
|
LiftSolution(x);
|
|
}
|
|
|
|
sw.Stop();
|
|
if (PrintOutput(fespace_ptr_, print_level_))
|
|
{
|
|
mfem::out << "<SPDESolver::Timing> all PCG solves " << sw.RealTime()
|
|
<< " [s]" << std::endl;
|
|
}
|
|
}
|
|
|
|
void SPDESolver::SetupRandomFieldGenerator(int seed)
|
|
{
|
|
delete b_wn;
|
|
integ =
|
|
new WhiteGaussianNoiseDomainLFIntegrator(fespace_ptr_->GetComm(), seed);
|
|
b_wn = new ParLinearForm(fespace_ptr_);
|
|
b_wn->AddDomainIntegrator(integ);
|
|
}
|
|
|
|
void SPDESolver::GenerateRandomField(ParGridFunction &x)
|
|
{
|
|
if (!b_wn)
|
|
{
|
|
MFEM_ABORT("Need to call SPDESolver::SetupRandomFieldGenerator(...) first");
|
|
}
|
|
// Create stochastic load
|
|
b_wn->Assemble();
|
|
real_t normalization = ConstructNormalizationCoefficient(
|
|
nu_, l1_, l2_, l3_, fespace_ptr_->GetParMesh()->Dimension());
|
|
(*b_wn) *= normalization;
|
|
|
|
// Call back to solve to generate the random field
|
|
Solve(*b_wn, x);
|
|
}
|
|
|
|
real_t SPDESolver::ConstructNormalizationCoefficient(real_t nu, real_t l1,
|
|
real_t l2, real_t l3,
|
|
int dim)
|
|
{
|
|
// Computation considers squaring components, computing determinant, and
|
|
// squaring
|
|
real_t det = 0;
|
|
if (dim == 1)
|
|
{
|
|
det = l1;
|
|
}
|
|
else if (dim == 2)
|
|
{
|
|
det = l1 * l2;
|
|
}
|
|
else if (dim == 3)
|
|
{
|
|
det = l1 * l2 * l3;
|
|
}
|
|
const real_t gamma1 = tgamma(nu + static_cast<real_t>(dim) / 2.0);
|
|
const real_t gamma2 = tgamma(nu);
|
|
return sqrt(pow(2 * M_PI, dim / 2.0) * det * gamma1 /
|
|
(gamma2 * pow(nu, dim / 2.0)));
|
|
}
|
|
|
|
DenseMatrix SPDESolver::ConstructMatrixCoefficient(real_t l1, real_t l2,
|
|
real_t l3, real_t e1,
|
|
real_t e2, real_t e3,
|
|
real_t nu, int dim)
|
|
{
|
|
if (dim == 3)
|
|
{
|
|
// Compute cosine and sine of the angles e1, e2, e3
|
|
const real_t c1 = cos(e1);
|
|
const real_t s1 = sin(e1);
|
|
const real_t c2 = cos(e2);
|
|
const real_t s2 = sin(e2);
|
|
const real_t c3 = cos(e3);
|
|
const real_t s3 = sin(e3);
|
|
|
|
// Fill the rotation matrix R with the Euler angles.
|
|
DenseMatrix R(3, 3);
|
|
R(0, 0) = c1 * c3 - c2 * s1 * s3;
|
|
R(0, 1) = -c1 * s3 - c2 * c3 * s1;
|
|
R(0, 2) = s1 * s2;
|
|
R(1, 0) = c3 * s1 + c1 * c2 * s3;
|
|
R(1, 1) = c1 * c2 * c3 - s1 * s3;
|
|
R(1, 2) = -c1 * s2;
|
|
R(2, 0) = s2 * s3;
|
|
R(2, 1) = c3 * s2;
|
|
R(2, 2) = c2;
|
|
|
|
// Multiply the rotation matrix R with the translation vector.
|
|
Vector l(3);
|
|
l(0) = std::pow(l1, 2);
|
|
l(1) = std::pow(l2, 2);
|
|
l(2) = std::pow(l3, 2);
|
|
l *= (1 / (2.0 * nu));
|
|
|
|
// Compute result = R^t diag(l) R
|
|
DenseMatrix res(3, 3);
|
|
R.Transpose();
|
|
MultADBt(R, l, R, res);
|
|
return res;
|
|
}
|
|
else if (dim == 2)
|
|
{
|
|
const real_t c1 = cos(e1);
|
|
const real_t s1 = sin(e1);
|
|
DenseMatrix Rt(2, 2);
|
|
Rt(0, 0) = c1;
|
|
Rt(0, 1) = s1;
|
|
Rt(1, 0) = -s1;
|
|
Rt(1, 1) = c1;
|
|
Vector l(2);
|
|
l(0) = std::pow(l1, 2);
|
|
l(1) = std::pow(l2, 2);
|
|
l *= (1 / (2.0 * nu));
|
|
DenseMatrix res(2, 2);
|
|
MultADAt(Rt,l,res);
|
|
return res;
|
|
}
|
|
else
|
|
{
|
|
DenseMatrix res(1, 1);
|
|
res(0, 0) = std::pow(l1, 2) / (2.0 * nu);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
void SPDESolver::Solve(const ParLinearForm &b, ParGridFunction &x, real_t alpha,
|
|
real_t beta, int exponent)
|
|
{
|
|
// Form system of equations. This is less general than
|
|
// BilinearForm::FormLinearSystem and kind of resembles the necessary subset
|
|
// of instructions that we need in this case.
|
|
if (prolongation_matrix_)
|
|
{
|
|
prolongation_matrix_->MultTranspose(b, B_);
|
|
}
|
|
else
|
|
{
|
|
B_ = b;
|
|
}
|
|
B_ *= beta;
|
|
|
|
if (!apply_lift_)
|
|
{
|
|
// Initialize X_ to zero. Important! Might contain nan/inf -> crash.
|
|
X_ = 0.0;
|
|
}
|
|
else
|
|
{
|
|
restriction_matrix_->Mult(x, X_);
|
|
}
|
|
|
|
HypreParMatrix *Op =
|
|
Add(1.0, stiffness_, alpha, mass_bc_); // construct Operator
|
|
HypreParMatrix *Ae = Op->EliminateRowsCols(ess_tdof_list_);
|
|
Op->EliminateBC(*Ae, ess_tdof_list_, X_, B_); // only for homogeneous BC
|
|
|
|
for (int i = 0; i < exponent; i++)
|
|
{
|
|
// Solve the linear system Op X_ = B_
|
|
SolveLinearSystem(Op);
|
|
k_.RecoverFEMSolution(X_, b, x);
|
|
if (repeated_solve_)
|
|
{
|
|
// Prepare for next iteration. X is a primal and B is a dual vector. B_
|
|
// must be updated to represent X_ in the next step. Instead of copying
|
|
// it, we must transform it appropriately.
|
|
GridFunctionCoefficient gfc(&x);
|
|
ParLinearForm previous_solution(fespace_ptr_);
|
|
previous_solution.AddDomainIntegrator(new DomainLFIntegrator(gfc));
|
|
previous_solution.Assemble();
|
|
prolongation_matrix_->MultTranspose(previous_solution, B_);
|
|
Op->EliminateBC(*Ae, ess_tdof_list_, X_, B_);
|
|
}
|
|
}
|
|
delete Ae;
|
|
delete Op;
|
|
}
|
|
|
|
void SPDESolver::LiftSolution(ParGridFunction &x)
|
|
{
|
|
// Set lifting flag
|
|
apply_lift_ = true;
|
|
|
|
// Lifting of the solution takes care of inhomogeneous boundary conditions.
|
|
// See doi:10.1016/j.jcp.2019.109009; section 2.6
|
|
if (PrintOutput(fespace_ptr_, print_level_))
|
|
{
|
|
mfem::out << "\n<SPDESolver> Applying inhomogeneous DBC" << std::endl;
|
|
}
|
|
|
|
// Define temporary grid function for lifting.
|
|
ParGridFunction helper_gf(fespace_ptr_);
|
|
helper_gf = 0.0;
|
|
|
|
// Project the boundary conditions onto the solution space.
|
|
for (const auto &bc : bc_.dirichlet_coefficients)
|
|
{
|
|
Array<int> marker(fespace_ptr_->GetParMesh()->bdr_attributes.Max());
|
|
marker = 0;
|
|
marker[bc.first - 1] = 1;
|
|
ConstantCoefficient cc(bc.second);
|
|
helper_gf.ProjectBdrCoefficient(cc, marker);
|
|
}
|
|
|
|
// Create linear form for the right hand side.
|
|
ParLinearForm b(fespace_ptr_);
|
|
ConstantCoefficient zero(0.0);
|
|
b.AddDomainIntegrator(new DomainLFIntegrator(zero));
|
|
b.Assemble();
|
|
|
|
// Solve the PDE for the lifting.
|
|
Solve(b, helper_gf, 1.0, 1.0);
|
|
|
|
// Add the lifting to the solution.
|
|
x += helper_gf;
|
|
|
|
// Reset the lifting flag.
|
|
apply_lift_ = false;
|
|
}
|
|
|
|
void SPDESolver::UpdateRHS(ParLinearForm &b) const
|
|
{
|
|
if (!repeated_solve_)
|
|
{
|
|
// This function is only relevant for repeated solves.
|
|
return;
|
|
}
|
|
if (restriction_matrix_)
|
|
{
|
|
// This effectively writes the solution of the previous iteration X_ to the
|
|
// linear form b. Note that at the end of solve we update B_ = Mass * X_.
|
|
restriction_matrix_->MultTranspose(B_, b);
|
|
}
|
|
else
|
|
{
|
|
b = B_;
|
|
}
|
|
}
|
|
|
|
void SPDESolver::SolveLinearSystem(const HypreParMatrix *Op)
|
|
{
|
|
HypreBoomerAMG prec(*Op);
|
|
prec.SetPrintLevel(-1);
|
|
CGSolver cg(fespace_ptr_->GetComm());
|
|
cg.SetRelTol(1e-12);
|
|
cg.SetMaxIter(2000);
|
|
cg.SetPrintLevel(3);
|
|
cg.SetPreconditioner(prec);
|
|
cg.SetOperator(*Op);
|
|
cg.SetPrintLevel(std::max(0, print_level_ - 1));
|
|
cg.Mult(B_, X_);
|
|
}
|
|
|
|
void SPDESolver::ComputeRationalCoefficients(real_t exponent)
|
|
{
|
|
if (abs(exponent) > 1e-12)
|
|
{
|
|
if (PrintOutput(fespace_ptr_, print_level_))
|
|
{
|
|
mfem::out << "<SPDESolver> Approximating the fractional exponent "
|
|
<< exponent << std::endl;
|
|
}
|
|
ComputePartialFractionApproximation(exponent, coeffs_, poles_);
|
|
|
|
// If the example is build without LAPACK, the exponent
|
|
// might be modified by the function call above.
|
|
alpha_ = exponent + integer_order_of_exponent_;
|
|
}
|
|
else
|
|
{
|
|
integer_order_ = true;
|
|
if (PrintOutput(fespace_ptr_, print_level_))
|
|
{
|
|
mfem::out << "<SPDESolver> Treating integer order PDE." << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
SPDESolver::~SPDESolver() { delete b_wn; }
|
|
|
|
} // namespace spde
|
|
} // namespace mfem
|