// 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 GetSpacingFunction(const SpacingType spacingType, Array const& ipar, Vector const& dpar) { Array iparsub, relN; switch (spacingType) { case SpacingType::UNIFORM_SPACING: MFEM_VERIFY(ipar.Size() == 1 && dpar.Size() == 0, "Invalid spacing function parameters"); return std::unique_ptr( new UniformSpacingFunction(ipar[0])); case SpacingType::LINEAR: MFEM_VERIFY(ipar.Size() == 3 && dpar.Size() == 1, "Invalid spacing function parameters"); return std::unique_ptr( 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( 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( 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( 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( 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( 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( 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(nullptr); } std::unique_ptr SpacingFunction::Clone() const { MFEM_ABORT("Base class SpacingFunction should not be cloned"); return std::unique_ptr(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= 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 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=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 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= 1.0) { validPartition = false; } if (i > 0 && partition[i] <= partition[i-1]) { validPartition = false; } } MFEM_VERIFY(validPartition, ""); pieces.resize(np); Array ipar_p; Vector dpar_p; int osi = 0; int osd = np - 1; int n_total = 0; for (int p=0; pSize() >= 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 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; pSize() / 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; pSetSize(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; iSize(); ++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 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 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