Files
mfem/fem/bounds.cpp
T
2026-01-14 14:14:31 -08:00

758 lines
22 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.
// Implementation of bounds
#include "bounds.hpp"
#include <limits>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
namespace mfem
{
using namespace std;
void PLBound::Setup(const int nb_i, const int ncp_i,
const int b_type_i, const int cp_type_i,
const real_t tol_i)
{
MFEM_VERIFY(b_type_i >= 0 && b_type_i <= 2, "Bases not supported. "
"Please read class description to see supported types.");
MFEM_VERIFY(cp_type_i == 0 || cp_type_i == 1,
"Control point type not supported. Please read class "
"description to see supported types.");
nb = nb_i;
ncp = ncp_i;
b_type = b_type_i;
cp_type = cp_type_i;
tol = tol_i;
lbound.SetSize(ncp, nb);
ubound.SetSize(ncp, nb);
nodes.SetSize(nb);
weights.SetSize(nb);
control_points.SetSize(ncp);
auto scalenodes = [](const Vector &in, const real_t a, const real_t b) -> Vector
{
Vector outVec(in.Size());
real_t maxv = in.Max();
real_t minv = in.Min();
for (int i = 0; i < in.Size(); i++)
{
outVec(i) = a + (b-a)*(in(i)-minv)/(maxv-minv);
}
return outVec;
};
MFEM_VERIFY(ncp >= 2,"At least 2 control points are required.");
if (cp_type == 0) // GL + End Point
{
control_points(0) = 0.0;
control_points(ncp-1) = 1.0;
if (ncp > 2)
{
const real_t *x = poly1d.GetPoints(ncp-3, 0);
MFEM_VERIFY(x, "Error in getting points.");
for (int i = 0; i < ncp-2; i++)
{
control_points(i+1) = x[i];
}
}
}
else if (cp_type == 1) // Chebyshev
{
auto GetChebyshevNodes = [](int n) -> Vector
{
Vector cheb(n);
for (int i = 0; i < n; ++i)
{
cheb(i) = -cos(M_PI * (static_cast<real_t>(i) / (n - 1)));
}
return cheb;
};
control_points = GetChebyshevNodes(ncp);
}
else
{
MFEM_ABORT("Unsupported interval points. Use [0,1].\n");
}
control_points = scalenodes(control_points, 0.0, 1.0); // rescale to [0,1]
Poly_1D::Basis &basis1d(poly1d.GetBasis(nb-1, b_type));
// Initialize bounds
lbound = 0.0;
ubound = 0.0;
Vector bmv(nb), bpv(nb), bv(nb); // basis values
Vector bdmv(nb), bdpv(nb), bdv(nb); // basis derivative values
Vector vals(3);
// See Section 3.1.1 of https://arxiv.org/pdf/2501.12349 for explanation of
// procedure below.
for (int j = 0; j < ncp; j++)
{
real_t x = control_points(j);
real_t xm = x;
if (j != 0)
{
xm = 0.5*(control_points(j-1)+control_points(j));
}
real_t xp = x;
if (j != ncp-1)
{
xp = 0.5*(control_points(j)+control_points(j+1));
}
basis1d.Eval(xm, bmv, bdmv);
basis1d.Eval(xp, bpv, bdpv);
basis1d.Eval(x, bv);
real_t dm = x-xm;
real_t dp = x-xp;
for (int i = 0; i < nb; i++)
{
if (j == 0)
{
lbound(j,i) = bv(i);
ubound(j,i) = bv(i);
}
else if (j == ncp-1)
{
lbound(j,i) = bv(i);
ubound(j,i) = bv(i);
}
else
{
vals(0) = bv(i);
vals(1) = bmv(i) + dm*bdmv(i);
vals(2) = bpv(i) + dp*bdpv(i);
lbound(j,i) = vals.Min()-tol; // tolerance for good measure
ubound(j,i) = vals.Max()+tol; // tolerance for good measure
if (b_type == 2)
{
lbound(j,i) = std::max(lbound(j,i),0_r);
}
}
}
}
IntegrationRule irule(nb);
if (b_type == 0)
{
QuadratureFunctions1D::GaussLegendre(nb, &irule);
for (int i = 0; i < nb; i++)
{
weights(i) = irule.IntPoint(i).weight;
nodes(i) = irule.IntPoint(i).x;
}
}
else if (b_type == 1)
{
QuadratureFunctions1D::GaussLobatto(nb, &irule);
for (int i = 0; i < nb; i++)
{
weights(i) = irule.IntPoint(i).weight;
nodes(i) = irule.IntPoint(i).x;
}
}
else if (b_type == 2)
{
QuadratureFunctions1D::ClosedUniform(nb, &irule);
for (int i = 0; i < nb; i++)
{
weights(i) = irule.IntPoint(i).weight;
nodes(i) = irule.IntPoint(i).x;
}
}
if (b_type == 2)
{
nodes_int.SetSize(nb);
weights_int.SetSize(nb);
IntegrationRule irule_int(nb);
{
QuadratureFunctions1D::GaussLobatto(nb, &irule_int);
for (int i = 0; i < nb; i++)
{
weights_int(i) = irule_int.IntPoint(i).weight;
nodes_int(i) = irule_int.IntPoint(i).x;
}
}
SetupBernsteinBasisMat(basisMatNodes, nodes);
// Setup memory for lu factors
basisMatLU = basisMatNodes;
lu_ip.SetSize(nb);
// Compute lu factors
LUFactors lu(basisMatLU.GetData(), lu_ip.GetData());
bool factor = lu.Factor(nb);
MFEM_VERIFY(factor,"Failure in LU factorization in PLBound.");
// Setup the Bernstein basis matrix for the GLL integration points. This
// is used to compute linear fit.
SetupBernsteinBasisMat(basisMatInt, nodes_int);
}
else
{
nodes_int.SetDataAndSize(nodes.GetData(), nb);
weights_int.SetDataAndSize(weights.GetData(), nb);
}
}
PLBound::PLBound(const FiniteElementSpace *fes, const int ncp_i,
const int cp_type_i)
{
MFEM_VERIFY(!fes->IsVariableOrder(),
"Variable order meshes not yet supported.");
const char *name = fes->FEColl()->Name();
string cname = name;
cp_type = cp_type_i;
b_type = BasisType::Invalid;
nb = fes->GetMaxElementOrder()+1;
tol = 0.0;
int minncp = 2;
if (nb > 12)
{
minncp = 2*nb;
}
else if (!strncmp(name, "H1_", 3) && strncmp(name, "H1_Trace_", 9))
{
// H1 GLL
b_type = BasisType::GaussLobatto;
minncp = min_ncp_gll_x[cp_type][nb-2];
}
else if (!strncmp(name, "H1Pos_", 6) && strncmp(name, "H1Pos_Trace_", 12))
{
// H1 Positive
b_type = BasisType::Positive;
minncp = min_ncp_pos_x[cp_type][nb-2];
}
else if (!strncmp(name, "L2_", 3) && strncmp(name, "L2_T", 4))
{
// L2 Gauss-Legendre
b_type = BasisType::GaussLegendre;
minncp = min_ncp_gl_x[cp_type][nb-2];
}
else if (!strncmp(name, "L2_T1", 5))
{
// L2 GLL
b_type = BasisType::GaussLobatto;
minncp = min_ncp_gll_x[cp_type][nb-2];
}
else if (!strncmp(name, "L2_T2", 5))
{
// L2 Positive
b_type = BasisType::Positive;
minncp = min_ncp_pos_x[cp_type][nb-2];
}
else
{
MFEM_ABORT("Only H1 GLL/Positive & L2 GL/GLL/Positive bases supported.");
}
ncp = std::max(minncp, ncp_i);
Setup(nb, ncp, b_type, cp_type, tol);
}
void PLBound::Get1DBounds(const Vector &coeff, Vector &intmin,
Vector &intmax) const
{
real_t x,w;
intmin.SetSize(ncp);
intmax.SetSize(ncp);
intmin = 0.0;
intmax = 0.0;
Vector coeffm;
real_t a0 = 0.0;
real_t a1 = 0.0;
Vector nodal_vals, nodal_integ_vals;
if (b_type == 2) // compute values at equispaced nodes and GLL nodes
{
nodal_vals.SetSize(nb);
nodal_integ_vals.SetSize(nb);
Vector shape(nb);
for (int i = 0; i < nb; i++)
{
basisMatNodes.GetRow(i, shape);
nodal_vals(i) = shape*coeff;
basisMatInt.GetRow(i, shape);
nodal_integ_vals(i) = shape*coeff;
}
}
else
{
nodal_vals.SetDataAndSize(coeff.GetData(), nb);
nodal_integ_vals.SetDataAndSize(coeff.GetData(), nb);
}
// compute L2 projection for linear bases: a0 + a1*x
if (proj)
{
coeffm.SetSize(nb);
coeffm = 0.0;
for (int i = 0; i < nb; i++)
{
x = 2.0*nodes_int(i)-1;
w = 2.0*weights_int(i);
a0 += 0.5*nodal_integ_vals(i)*w;
a1 += 1.5*nodal_integ_vals(i)*w*x;
}
// offset the linear fit from nodal values
for (int i = 0; i < nb; i++)
{
x = 2.0*nodes(i)-1;
coeffm(i) = nodal_vals(i) - a0 - a1*x;
}
// compute coefficients for Bernstein
if (b_type == 2)
{
LUFactors lu(basisMatLU.GetData(), lu_ip.GetData());
lu.Solve(nb, 1, coeffm.GetData());
}
// initialize the bounds to be the linear fit
for (int j = 0; j < ncp; j++)
{
x = 2.0*control_points(j)-1;
intmin(j) = a0 + a1*x;
intmax(j) = intmin(j);
}
}
else
{
coeffm.SetDataAndSize(coeff.GetData(), nb);
}
for (int i = 0; i < nb; i++)
{
real_t c = coeffm(i);
for (int j = 0; j < ncp; j++)
{
intmin(j) += min(lbound(j,i)*c, ubound(j,i)*c);
intmax(j) += max(lbound(j,i)*c, ubound(j,i)*c);
}
}
}
void PLBound::Get2DBounds(const Vector &coeff, Vector &intmin,
Vector &intmax) const
{
intmin.SetSize(ncp*ncp);
intmax.SetSize(ncp*ncp);
intmin = 0.0;
intmax = 0.0;
Vector intminT(ncp*nb);
Vector intmaxT(ncp*nb);
// Get bounds for each row of the solution
for (int i = 0; i < nb; i++)
{
Vector solcoeff(coeff.GetData()+i*nb, nb);
Vector intminrow(intminT.GetData()+i*ncp, ncp);
Vector intmaxrow(intmaxT.GetData()+i*ncp, ncp);
Get1DBounds(solcoeff, intminrow, intmaxrow);
}
Vector intminT2 = intminT;
// Compute a0 and a1 for each column of nodes
Vector a0V(ncp), a1V(ncp);
a0V = 0.0;
a1V = 0.0;
real_t x,w,t;
if (proj)
{
if (b_type == 2)
{
// Note: DenseMatrix uses column-major ordering so we will need to
// transpose the matrix.
DenseMatrix intminTM(intminT.GetData(), ncp, nb),
intmaxTM(intmaxT.GetData(), ncp, nb),
intmeanTM(ncp, nb);
DenseMatrix minvalsM(nb, ncp), maxvalsM(nb, ncp), meanintvalsM(nb, ncp);
MultABt(basisMatNodes, intminTM, minvalsM);
MultABt(basisMatNodes, intmaxTM, maxvalsM);
intmeanTM = intminTM;
intmeanTM += intmaxTM;
intmeanTM *= 0.5;
MultABt(basisMatInt, intmeanTM, meanintvalsM);
// Compute the linear fit along each column and then offset it from
// the bounds on the coefficient.
// Note: Since Bernstein bases are positive, we can use the lower
// bounds to compute the lower bounding polynomial and subtract the
// linear fit before finding the Bernstein coefficients corresponding
// to the perturbation. Same for upper bounds. If the bases were not
// always positive, it is not yet clear if the perturbation
// coefficients will be this straightforward to compute.
for (int j = 0; j < ncp; j++) // row of interval points
{
for (int i = 0; i < nb; i++)
{
x = 2.0*nodes_int(i)-1; // x-coordinate
w = 2.0*weights_int(i); // weight
t = meanintvalsM(i,j);
a0V(j) += 0.5*t*w;
a1V(j) += 1.5*t*w*x;
}
// Offset linear fit
for (int i = 0; i < nb; i++)
{
x = 2.0*nodes(i)-1; // x-coordinate
minvalsM(i,j) -= a0V(j) + a1V(j)*x;
maxvalsM(i,j) -= a0V(j) + a1V(j)*x;
}
// Compute Bernstein coefficients
LUFactors lu(basisMatLU.GetData(), lu_ip.GetData());
lu.Solve(nb, 1, minvalsM.GetColumn(j));
lu.Solve(nb, 1, maxvalsM.GetColumn(j));
for (int i = 0; i < nb; i++)
{
intminT(i*ncp+j) = minvalsM(i,j);
intmaxT(i*ncp+j) = maxvalsM(i,j);
}
}
}
else
{
for (int j = 0; j < nb; j++) // row of nodes
{
x = 2.0*nodes(j)-1; // x-coordinate
w = 2.0*weights(j); // weight
for (int i = 0; i < ncp; i++) // column of interval points
{
t = 0.5*(intminT(j*ncp+i)+intmaxT(j*ncp+i));
a0V(i) += 0.5*t*w;
a1V(i) += 1.5*t*w*x;
}
}
// offset the linear fit from nodal values
for (int j = 0; j < nb; j++) // row of nodes
{
x = 2.0*nodes(j)-1; // x-coordinate
for (int i = 0; i < ncp; i++) // column of interval points
{
t = a0V(i) + a1V(i)*x;
intminT(j*ncp+i) -= t;
intmaxT(j*ncp+i) -= t;
}
}
}
// Initialize bounds using a0 and a1 values
for (int j = 0; j < ncp; j++) // row j
{
x = 2.0*control_points(j)-1;
for (int i = 0; i < ncp; i++) // column i
{
intmin(j*ncp+i) = a0V(i) + a1V(i)*x;
intmax(j*ncp+i) = intmin(j*ncp+i);
}
}
}
// Compute bounds
int id1 = 0, id2 = 0;
Vector vals(4);
for (int j = 0; j < nb; j++)
{
for (int i = 0; i < ncp; i++) // ith column
{
real_t w0 = intminT(id1++);
real_t w1 = intmaxT(id2++);
for (int k = 0; k < ncp; k++) // kth row
{
vals(0) = w0*lbound(k,j);
vals(1) = w0*ubound(k,j);
vals(2) = w1*lbound(k,j);
vals(3) = w1*ubound(k,j);
intmin(k*ncp+i) += vals.Min();
intmax(k*ncp+i) += vals.Max();
}
}
}
}
void PLBound::Get3DBounds(const Vector &coeff, Vector &intmin,
Vector &intmax) const
{
int nb2 = nb*nb,
ncp2 = ncp*ncp,
ncp3 = ncp*ncp*ncp;
intmin.SetSize(ncp3);
intmax.SetSize(ncp3);
intmin = 0.0;
intmax = 0.0;
Vector intminT(ncp2*nb);
Vector intmaxT(ncp2*nb);
// Get bounds for each slice of the solution
for (int i = 0; i < nb; i++)
{
Vector solcoeff(coeff.GetData()+i*nb2, nb2);
Vector intminrow(intminT.GetData()+i*ncp2, ncp2);
Vector intmaxrow(intmaxT.GetData()+i*ncp2, ncp2);
Get2DBounds(solcoeff, intminrow, intmaxrow);
}
DenseMatrix intminTM(intminT.GetData(), ncp2, nb),
intmaxTM(intmaxT.GetData(), ncp2, nb);
// Compute a0 and a1 for each tower of nodes
Vector a0V(ncp2), a1V(ncp2);
a0V = 0.0;
a1V = 0.0;
real_t x,w,t;
if (proj)
{
if (b_type == 2) // Bernstein bases
{
// Compute the mean coefficients along each tower.
for (int j = 0; j < ncp2; j++) // slice of interval points
{
Vector meanBounds(nb), minBounds(nb), maxBounds(nb);
intminTM.GetRow(j, minBounds);
intmaxTM.GetRow(j, maxBounds);
for (int i = 0; i < nb; i++) // column of nodes
{
meanBounds(i) = 0.5*(minBounds(i)+maxBounds(i));
}
Vector meanNodalIntVals(nb);
Vector minNodalVals(nb);
Vector maxNodalVals(nb);
Vector row(nb);
for (int i = 0; i < nb; i++)
{
basisMatNodes.GetRow(i, row);
minNodalVals(i) = row*minBounds;
maxNodalVals(i) = row*maxBounds;
basisMatInt.GetRow(i, row);
meanNodalIntVals(i) = row*meanBounds;
}
// linear fit along each tower
for (int i = 0; i < nb; i++)
{
x = 2.0*nodes_int(i)-1; // x-coordinate
w = 2.0*weights_int(i); // weight
a0V(j) += 0.5*meanNodalIntVals(i)*w;
a1V(j) += 1.5*meanNodalIntVals(i)*w*x;
}
// offset the linear fit from bounding coefficients
for (int i = 0; i < nb; i++)
{
x = 2.0*nodes(i)-1; // x-coordinate
minNodalVals(i) -= a0V(j) + a1V(j)*x;
maxNodalVals(i) -= a0V(j) + a1V(j)*x;
}
// Compute Bernstein coefficients
LUFactors lu(basisMatLU.GetData(), lu_ip.GetData());
lu.Solve(nb, 1, minNodalVals.GetData());
lu.Solve(nb, 1, maxNodalVals.GetData());
for (int i = 0; i < nb; i++)
{
intminT(i*ncp2+j) = minNodalVals(i);
intmaxT(i*ncp2+j) = maxNodalVals(i);
}
}
}
else
{
// nodal bases
for (int j = 0; j < nb; j++) // tower of nodes
{
x = 2.0*nodes(j)-1; // x-coordinate
w = 2.0*weights(j); // weight
for (int i = 0; i < ncp2; i++) // slice of interval points
{
t = 0.5*(intminT(j*ncp2+i)+intmaxT(j*ncp2+i));
a0V(i) += 0.5*t*w;
a1V(i) += 1.5*t*w*x;
}
}
// offset the linear fit from nodal values
for (int j = 0; j < nb; j++) // row of nodes
{
x = 2.0*nodes(j)-1; // x-coordinate
for (int i = 0; i < ncp2; i++) // column of interval points
{
t = a0V(i) + a1V(i)*x;
intminT(j*ncp2+i) -= t;
intmaxT(j*ncp2+i) -= t;
}
}
}
// Initialize bounds using a0 and a1 values
for (int j = 0; j < ncp; j++) // slice j
{
x = 2.0*control_points(j)-1;
for (int i = 0; i < ncp2; i++) // tower i
{
intmin(j*ncp2+i) = a0V(i) + a1V(i)*x;
intmax(j*ncp2+i) = a0V(i) + a1V(i)*x;
}
}
}
// Compute bounds
int id1 = 0, id2 = 0;
Vector vals(4);
for (int j = 0; j < nb; j++)
{
for (int i = 0; i < ncp2; i++) // ith tower
{
real_t w0 = intminT(id1++);
real_t w1 = intmaxT(id2++);
for (int k = 0; k < ncp; k++) // kth slice
{
vals(0) = w0*lbound(k,j);
vals(1) = w0*ubound(k,j);
vals(2) = w1*lbound(k,j);
vals(3) = w1*ubound(k,j);
intmin(k*ncp2+i) += vals.Min();
intmax(k*ncp2+i) += vals.Max();
}
}
}
}
void PLBound::GetNDBounds(const int rdim, const Vector &coeff,
Vector &intmin, Vector &intmax) const
{
if (rdim == 1)
{
Get1DBounds(coeff, intmin, intmax);
}
else if (rdim == 2)
{
Get2DBounds(coeff, intmin, intmax);
}
else if (rdim == 3)
{
Get3DBounds(coeff, intmin, intmax);
}
else
{
MFEM_ABORT("Currently not supported.");
}
}
void PLBound::SetupBernsteinBasisMat(DenseMatrix &basisMat,
Vector &nodesBern) const
{
const int nbern = nodesBern.Size();
L2_SegmentElement el(nbern-1, 2);
// we use L2 to leverage lexicographic order
Array<int> ordering = el.GetLexicographicOrdering();
basisMat.SetSize(nbern, nbern);
Vector shape(nbern);
IntegrationPoint ip;
for (int i = 0; i < nbern; i++)
{
ip.x = nodesBern(i);
el.CalcShape(ip, shape);
basisMat.SetRow(i, shape);
}
}
DenseMatrix PLBound::GetBoundingMatrix(int dim, bool is_lower) const
{
if (dim > 1)
{
const int ncpd = static_cast<int>(std::pow(ncp, dim));
const int nbd = static_cast<int>(std::pow(nb, dim));
DenseMatrix boundND(ncpd, nbd);
Vector phimin, phimax, col;
Vector coeffs(nbd);
coeffs = 0.0;
for (int j = 0; j < nbd; j++)
{
coeffs(j) = 1.0;
boundND.GetColumnReference(j, col);
GetNDBounds(dim, coeffs, phimin, phimax);
col = is_lower ? phimin : phimax;
coeffs(j) = 0.0;
}
return boundND;
}
return is_lower ? lbound : ubound;
}
DenseMatrix PLBound::GetLowerBoundMatrix(int dim) const
{
return GetBoundingMatrix(dim, true);
}
DenseMatrix PLBound::GetUpperBoundMatrix(int dim) const
{
return GetBoundingMatrix(dim, false);
}
constexpr int PLBound::min_ncp_gl_x[2][11];
constexpr int PLBound::min_ncp_gll_x[2][11];
constexpr int PLBound::min_ncp_pos_x[2][11];
int PLBound::GetMinimumPointsForGivenBases(int nb_i, int b_type_i,
int cp_type_i) const
{
MFEM_VERIFY(b_type_i >= 0 && b_type_i <= 2, "Invalid node type. Specify 0 "
"for GL, 1 for GLL, and 2 for positive " "bases.");
MFEM_VERIFY(cp_type_i == 0 || cp_type_i == 1, "Invalid control point type. "
"Specify 0 for GL+end points, 1 for Chebyshev.");
if (nb_i > 12)
{
MFEM_ABORT("GetMinimumPointsForGivenBases can only be used for maximum "
"order = 11, i.e. nb=12. 2*nb points should be sufficient to "
"bound the bases up to nb = 30.");
}
else if (b_type_i == 0)
{
return min_ncp_gl_x[cp_type_i][nb_i-2];
}
else if (b_type_i == 1)
{
return min_ncp_gll_x[cp_type_i][nb_i-2];
}
else if (b_type_i == 2)
{
return min_ncp_pos_x[cp_type_i][nb_i-2];
}
return 0;
}
void PLBound::Print(std::ostream &outp) const
{
outp << "PLBound nb: " << nb << std::endl;
outp << "PLBound ncp: " << ncp << std::endl;
outp << "PLBound b_type: " << b_type << std::endl;
outp << "PLBound cp_type: " << cp_type << std::endl;
outp << "Print nodes: " << std::endl;
nodes.Print(outp);
outp << "Print weights: " << std::endl;
weights.Print(outp);
outp << "Print control_points: " << std::endl;
control_points.Print(outp);
outp << "Print lower bounds: " << std::endl;
lbound.Print(outp);
outp << "Print upper bounds: " << std::endl;
ubound.Print(outp);
}
}