Files
mfem/mesh/spacing.cpp
T

699 lines
18 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 "spacing.hpp"
namespace mfem
{
std::unique_ptr<SpacingFunction> GetSpacingFunction(const SpacingType
spacingType,
Array<int> const& ipar,
Vector const& dpar)
{
Array<int> iparsub, relN;
switch (spacingType)
{
case SpacingType::UNIFORM_SPACING:
MFEM_VERIFY(ipar.Size() == 1 &&
dpar.Size() == 0, "Invalid spacing function parameters");
return std::unique_ptr<SpacingFunction>(
new UniformSpacingFunction(ipar[0]));
case SpacingType::LINEAR:
MFEM_VERIFY(ipar.Size() == 3 &&
dpar.Size() == 1, "Invalid spacing function parameters");
return std::unique_ptr<SpacingFunction>(
new LinearSpacingFunction(ipar[0], (bool) ipar[1], dpar[0],
(bool) ipar[2]));
case SpacingType::GEOMETRIC:
MFEM_VERIFY(ipar.Size() == 3 &&
dpar.Size() == 1, "Invalid spacing function parameters");
return std::unique_ptr<SpacingFunction>(
new GeometricSpacingFunction(ipar[0], (bool) ipar[1],
dpar[0], (bool) ipar[2]));
case SpacingType::BELL:
MFEM_VERIFY(ipar.Size() == 3 &&
dpar.Size() == 2, "Invalid spacing function parameters");
return std::unique_ptr<SpacingFunction>(
new BellSpacingFunction(ipar[0], (bool) ipar[1], dpar[0],
dpar[1], (bool) ipar[2]));
case SpacingType::GAUSSIAN:
MFEM_VERIFY(ipar.Size() == 3 &&
dpar.Size() == 2, "Invalid spacing function parameters");
return std::unique_ptr<SpacingFunction>(
new GaussianSpacingFunction(ipar[0], (bool) ipar[1], dpar[0],
dpar[1], (bool) ipar[2]));
case SpacingType::LOGARITHMIC:
MFEM_VERIFY(ipar.Size() == 3 &&
dpar.Size() == 1, "Invalid spacing function parameters");
return std::unique_ptr<SpacingFunction>(
new LogarithmicSpacingFunction(ipar[0], (bool) ipar[1],
(bool) ipar[2], dpar[0]));
case SpacingType::PIECEWISE:
MFEM_VERIFY(ipar.Size() >= 3, "Invalid spacing function parameters");
ipar.GetSubArray(3, ipar[1], relN);
ipar.GetSubArray(3 + ipar[1], ipar.Size() - 3 - ipar[1], iparsub);
return std::unique_ptr<SpacingFunction>(
new PiecewiseSpacingFunction(ipar[0], ipar[1],
(bool) ipar[2], relN, iparsub,
dpar));
case SpacingType::PARTIAL:
MFEM_VERIFY(ipar.Size() >= 8, "Invalid spacing function parameters");
ipar.GetSubArray(8, ipar.Size() - 8, iparsub);
return std::unique_ptr<SpacingFunction>(
new PartialSpacingFunction(ipar[0], ipar[1], ipar[2],
ipar[3], ipar[4], iparsub,
dpar, (SpacingType) ipar[5]));
default:
MFEM_ABORT("Unknown spacing type \"" << int(spacingType) << "\"");
break;
}
MFEM_ABORT("Unknown spacing type");
return std::unique_ptr<SpacingFunction>(nullptr);
}
std::unique_ptr<SpacingFunction> SpacingFunction::Clone() const
{
MFEM_ABORT("Base class SpacingFunction should not be cloned");
return std::unique_ptr<SpacingFunction>(nullptr);
}
void GeometricSpacingFunction::CalculateSpacing()
{
// GeometricSpacingFunction requires more than 1 interval. If only 1
// interval is requested, just use uniform spacing.
if (n == 1) { return; }
// Find the root of g(r) = s * (r^n - 1) - r + 1 by Newton's method.
constexpr real_t convTol = 1.0e-8;
constexpr int maxIter = 100;
const real_t s_unif = 1.0 / ((real_t) n);
r = s < s_unif ? 1.5 : 0.5; // Initial guess
bool converged = false;
for (int iter=0; iter<maxIter; ++iter)
{
const real_t g = (s * (std::pow(r,n) - 1.0)) - r + 1.0;
const real_t dg = (n * s * std::pow(r,n-1)) - 1.0;
r -= g / dg;
if (std::abs(g / dg) < convTol)
{
converged = true;
break;
}
}
MFEM_VERIFY(converged, "Convergence failure in GeometricSpacingFunction");
}
void BellSpacingFunction::CalculateSpacing()
{
s.SetSize(n);
// Bell spacing requires at least 3 intervals. If fewer than 3 are
// requested, we simply use uniform spacing.
if (n < 3)
{
s = 1.0 / ((real_t) n);
return;
}
MFEM_VERIFY(s0 + s1 < 1.0, "Sum of first and last Bell spacings must be"
<< " less than 1");
s[0] = s0;
s[n-1] = s1;
// If there are only 3 intervals, the calculation is linear and trivial.
if (n == 3)
{
s[1] = 1.0 - s0 - s1;
return;
}
// For more than 3 intervals, solve a system iteratively.
real_t urk = 1.0;
// Initialize unknown entries of s.
real_t initialGuess = (1.0 - s0 - s1) / ((real_t) (n - 2));
for (int i=1; i<n-1; ++i)
{
s[i] = initialGuess;
}
Vector wk(7);
wk = 0.0;
Vector s_new(n);
Vector a(n+2);
Vector b(n+2);
Vector alpha(n+2);
Vector beta(n+2);
Vector gamma(n+2);
a = 0.5;
a[0] = 0.0;
a[1] = 0.0;
b = a;
alpha = 0.0;
beta = 0.0;
gamma = 0.0;
gamma[1] = s0;
constexpr int maxIter = 100;
constexpr real_t convTol = 1.0e-10;
bool converged = false;
for (int iter=0; iter<maxIter; ++iter)
{
int j;
for (j = 1; j <= n - 3; j++)
{
wk[0] = (s[j] + s[j+1]) * (s[j] + s[j+1]);
wk[1] = s[j-1];
wk[2] = (s[j-1] + s[j]) * (s[j-1] + s[j]) * (s[j-1] + s[j]);
wk[3] = s[j + 2];
wk[4] = (s[j+2] + s[j+1]) * (s[j+2] + s[j+1]) * (s[j+2] + s[j+1]);
wk[5] = wk[0] * wk[1] / wk[2];
wk[6] = wk[0] * wk[3] / wk[4];
a[j+1] = a[j+1] + urk*(wk[5] - a[j+1]);
b[j+1] = b[j+1] + urk*(wk[6] - b[j+1]);
}
for (j = 2; j <= n - 2; j++)
{
wk[0] = a[j]*(1.0 - 2.0*alpha[j - 1] + alpha[j - 1]*alpha[j - 2]
+ beta[j - 2]) + b[j] + 2.0 - alpha[j - 1];
wk[1] = 1.0 / wk[0];
alpha[j] = wk[1]*(a[j]*beta[j - 1]*(2.0 - alpha[j - 2]) +
2.0*b[j] + beta[j - 1] + 1.0);
beta[j] = -b[j]*wk[1];
gamma[j] = wk[1]*(a[j]*(2.0*gamma[j - 1] - gamma[j - 2] -
alpha[j - 2]*gamma[j - 1]) + gamma[j - 1]);
}
s_new[0] = s[0];
for (j=1; j<n; ++j)
{
s_new[j] = s_new[j-1] + s[j];
}
for (j = n - 3; j >= 1; j--)
{
s_new[j] = alpha[j+1]*s_new[j + 1] +
beta[j+1]*s_new[j + 2] + gamma[j+1];
}
// Convert back from points to spacings
for (j=n-1; j>0; --j)
{
s_new[j] = s_new[j] - s_new[j-1];
}
wk[5] = wk[6] = 0.0;
for (j = n - 2; j >= 2; j--)
{
wk[5] = wk[5] + s_new[j]*s_new[j];
wk[6] = wk[6] + pow(s_new[j] - s[j], 2);
}
s = s_new;
const real_t res = sqrt(wk[6] / wk[5]);
if (res < convTol)
{
converged = true;
break;
}
}
MFEM_VERIFY(converged, "Convergence failure in BellSpacingFunction");
}
void GaussianSpacingFunction::CalculateSpacing()
{
s.SetSize(n);
// Gaussian spacing requires at least 3 intervals. If fewer than 3 are
// requested, we simply use uniform spacing.
if (n < 3)
{
s = 1.0 / ((real_t) n);
return;
}
s[0] = s0;
s[n-1] = s1;
// If there are only 3 intervals, the calculation is linear and trivial.
if (n == 3)
{
s[1] = 1.0 - s0 - s1;
return;
}
// For more than 3 intervals, solve a system iteratively.
const real_t lnz01 = log(s0 / s1);
const real_t h = 1.0 / ((real_t) n-1);
// Determine concavity by first determining linear spacing and comparing
// the total spacing to 1.
// Linear formula: z_i = z0 + (i*h) * (z1-z0), 0 <= i <= n-1
// \sum_{i=0}^{nzones-1} z_i = n * z0 + h * (z1-z0) * nz * (nz-1) / 2
const real_t slinear = n * (s0 + (h * (s1 - s0) * 0.5 * (n-1)));
MFEM_VERIFY(std::abs(slinear - 1.0) > 1.0e-8, "Bell distribution is too "
<< "close to linear.");
const real_t u = slinear < 1.0 ? 1.0 : -1.0;
real_t c = 0.3; // Initial guess
// Newton iterations
constexpr int maxIter = 10;
constexpr real_t convTol = 1.0e-8;
bool converged = false;
for (int iter=0; iter<maxIter; ++iter)
{
const real_t c2 = c * c;
const real_t m = 0.5 * (1.0 - (u * c2 * lnz01));
const real_t dmdc = -u * c * lnz01;
real_t r = 0.0; // Residual
real_t drdc = 0.0; // Derivative of residual
for (int i=0; i<n; ++i)
{
const real_t x = i * h;
const real_t ti = exp((-(x * x) + (2.0 * x * m)) * u / c2); // Gaussian
r += ti;
// Derivative of Gaussian
drdc += ((-2.0 * (-(x * x) + (2.0 * x * m)) / (c2 * c)) +
((2.0 * x * dmdc) / c2)) * ti;
}
r *= s0;
r -= 1.0; // Sum of spacings should equal 1.
if (std::abs(r) < convTol)
{
converged = true;
break;
}
drdc *= s0 * u;
// Newton update is -r / drdc, limited by factors of 1/2 and 2.
real_t dc = std::max(-r / drdc, (real_t) -0.5*c);
dc = std::min(dc, (real_t) 2.0*c);
c += dc;
}
MFEM_VERIFY(converged, "Convergence failure in GaussianSpacingFunction");
const real_t c2 = c * c;
const real_t m = 0.5 * (1.0 - (u * c2 * lnz01));
const real_t q = s0 * exp(u*m*m / c2);
for (int i=0; i<n; ++i)
{
const real_t x = (i * h) - m;
s[i] = q * exp(-u*x*x / c2);
}
}
void LogarithmicSpacingFunction::CalculateSpacing()
{
MFEM_VERIFY(n > 0 && logBase > 1.0,
"Invalid parameters in LogarithmicSpacingFunction");
if (sym) { CalculateSymmetric(); }
else { CalculateNonsymmetric(); }
}
void LogarithmicSpacingFunction::CalculateSymmetric()
{
s.SetSize(n);
const bool odd = (n % 2 == 1);
const int M0 = n / 2;
const int M = odd ? (M0 + 1) : M0;
const real_t h = 1.0 / ((real_t) M);
real_t p = 1.0; // Initialize at right endpoint of [0,1].
for (int i=M-2; i>=0; --i)
{
const real_t p_i = (pow(logBase, (i+1)*h) - 1.0) / (logBase - 1.0);
s[i+1] = p - p_i;
p = p_i;
}
s[0] = p;
// Even case for spacing: [s[0], ..., s[M-1], s[M-1], s[M-2], ..., s[0]]
// covers interval [0,2]
// Odd case for spacing: [s[0], ..., s[M-1], s[M-2], ..., s[0]]
// covers interval [0,2-s[M-1]]
const real_t t = odd ? 1.0 / (2.0 - s[M-1]) : 0.5;
for (int i=0; i<M; ++i)
{
s[i] *= t;
if (i < (M-1) || !odd)
{
s[n - i - 1] = s[i];
}
}
}
void LogarithmicSpacingFunction::CalculateNonsymmetric()
{
s.SetSize(n);
const real_t h = 1.0 / ((real_t) n);
real_t p = 1.0; // Initialize at right endpoint of [0,1].
for (int i=n-2; i>=0; --i)
{
const real_t p_i = (pow(logBase, (i+1)*h) - 1.0) / (logBase - 1.0);
s[i+1] = p - p_i;
p = p_i;
}
s[0] = p;
}
void PiecewiseSpacingFunction::SetupPieces(Array<int> const& ipar,
Vector const& dpar)
{
MFEM_VERIFY(partition.Size() == np - 1, "");
bool validPartition = true;
// Verify that partition has ascending numbers in (0,1).
for (int i=0; i<np-1; ++i)
{
partition[i] = dpar[i];
if (partition[i] <= 0.0 || partition[i] >= 1.0)
{
validPartition = false;
}
if (i > 0 && partition[i] <= partition[i-1])
{
validPartition = false;
}
}
MFEM_VERIFY(validPartition, "");
pieces.resize(np);
Array<int> ipar_p;
Vector dpar_p;
int osi = 0;
int osd = np - 1;
int n_total = 0;
for (int p=0; p<np; ++p)
{
// Setup piece p
const SpacingType type = (SpacingType) ipar[osi];
const int numIntParam = ipar[osi+1];
const int numDoubleParam = ipar[osi+2];
ipar_p.SetSize(numIntParam);
dpar_p.SetSize(numDoubleParam);
for (int i=0; i<numIntParam; ++i)
{
ipar_p[i] = ipar[osi + 3 + i];
}
for (int i=0; i<numDoubleParam; ++i)
{
dpar_p[i] = dpar[osd + i];
}
pieces[p] = GetSpacingFunction(type, ipar_p, dpar_p);
osi += 3 + numIntParam;
osd += numDoubleParam;
n_total += npartition[p];
MFEM_VERIFY(pieces[p]->Size() >= 1, "");
}
MFEM_VERIFY(osi == ipar.Size() && osd == dpar.Size(), "");
n0 = n_total;
}
void PiecewiseSpacingFunction::ScaleParameters(real_t a)
{
for (auto &p : pieces) { p->ScaleParameters(a); }
}
void PiecewiseSpacingFunction::Print(std::ostream &os) const
{
// SpacingType numIntParam numDoubleParam npartition {int params} {double params}
int inum = 3 + np;
int dnum = np-1;
for (auto& p : pieces)
{
// Add three for the type and the integer and double parameter counts.
inum += p->NumIntParameters() + 3;
dnum += p->NumDoubleParameters();
}
os << int(SpacingType::PIECEWISE) << " " << inum << " " << dnum << " "
<< n << " " << np << " " << (int) reverse << "\n";
for (auto n : npartition)
{
os << n << " ";
}
// Write integer parameters for all pieces.
Array<int> ipar;
for (auto& p : pieces)
{
MFEM_VERIFY(p->GetSpacingType() != SpacingType::PIECEWISE,
"Piecewise spacings should not be composed");
os << "\n" << int(p->GetSpacingType()) << " " << p->NumIntParameters()
<< " " << p->NumDoubleParameters();
p->GetIntParameters(ipar);
for (auto& ip : ipar)
{
os << " " << ip;
}
}
os << "\n";
for (auto p : partition)
{
os << p << " ";
}
// Write double parameters for all pieces.
Vector dpar;
for (auto& p : pieces)
{
p->GetDoubleParameters(dpar);
if (dpar.Size() > 0)
{
os << "\n";
for (auto dp : dpar)
{
os << dp << " ";
}
}
}
os << "\n";
}
void PiecewiseSpacingFunction::CalculateSpacing()
{
MFEM_VERIFY(n >= 1 && (n % n0 == 0 || n < n0), "");
const int ref = n / n0; // Refinement factor
const int cf = n0 / n; // Coarsening factor
s.SetSize(n);
bool coarsen = cf > 1 && n > 1;
// If coarsening, check whether all pieces have size divisible by cf.
if (coarsen)
{
for (int p=0; p<np; ++p)
{
const int csize = pieces[p]->Size() / cf;
if (pieces[p]->Size() != cf * csize)
{
coarsen = false;
}
}
}
if (n == 1)
{
s[0] = 1.0;
for (auto& p : pieces) { p->SetSize(1); }
return;
}
MFEM_VERIFY(coarsen || n >= n0,
"Invalid case in PiecewiseSpacingFunction::CalculateSpacing");
int n_total = 0;
for (int p=0; p<np; ++p)
{
// Calculate spacing for piece p.
if (coarsen)
{
pieces[p]->SetSize(npartition[p] / cf);
}
else
{
pieces[p]->SetSize(ref * npartition[p]);
}
const real_t p0 = (p == 0) ? 0.0 : partition[p-1];
const real_t p1 = (p == np - 1) ? 1.0 : partition[p];
const real_t h_p = p1 - p0;
for (int i=0; i<pieces[p]->Size(); ++i)
{
s[n_total + i] = h_p * pieces[p]->Eval(i);
}
n_total += pieces[p]->Size();
}
MFEM_VERIFY(n_total == n, "");
}
bool PiecewiseSpacingFunction::Nested() const
{
for (const auto &p : pieces)
{
if (!p->Nested())
{
return false;
}
}
return true;
}
void PartialSpacingFunction::SetupFull(SpacingType typeFull,
Array<int> const& ipar,
Vector const& dpar)
{
fullSpacing = GetSpacingFunction(typeFull, ipar, dpar);
}
void PartialSpacingFunction::CalculateSpacing()
{
s.SetSize(n);
if (n == 1)
{
s[0] = 1.0;
fullSpacing->SetSize(1);
return;
}
const int ref = n / num_elems;
MFEM_VERIFY(ref * num_elems == n, "Invalid number of elements");
fullSpacing->SetSize(ref * num_elems_full);
const int os = ref * first_elem;
for (int i = 0; i < n; ++i)
{
s[i] = fullSpacing->Eval(os + i);
}
// Normalize
const double d1 = s.Sum();
for (int i = 0; i < n; ++i)
{
s[i] /= d1;
}
}
void PartialSpacingFunction::ScaleParameters(real_t a)
{
fullSpacing->ScaleParameters(a);
}
void PartialSpacingFunction::Print(std::ostream &os) const
{
os << int(SpacingType::PARTIAL) << " " << NumIntParameters() << " "
<< NumDoubleParameters() << " " << n << " " << (int) reverse << "\n"
<< first_elem << " " << num_elems << " " << num_elems_full << "\n";
// Write integer parameters for the full spacing.
Array<int> ipar;
os << int(fullSpacing->GetSpacingType()) << " "
<< fullSpacing->NumIntParameters() << " "
<< fullSpacing->NumDoubleParameters();
fullSpacing->GetIntParameters(ipar);
for (auto& ip : ipar)
{
os << " " << ip;
}
os << "\n";
// Write double parameters for the full spacing.
Vector dpar;
fullSpacing->GetDoubleParameters(dpar);
if (dpar.Size() > 0)
{
for (auto dp : dpar)
{
os << dp << " ";
}
}
os << "\n";
}
} // namespace mfem