You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
9.8 KiB
205 lines
9.8 KiB
#ifndef ALGOIM_HOCP_HPP
|
|
#define ALGOIM_HOCP_HPP
|
|
|
|
/* Contains the main drivers for the high-order closest point algorithm, mainly for rectangular
|
|
Cartesian grids, as described in the paper
|
|
R. I. Saye, High-order methods for computing distances to implicitly defined surfaces,
|
|
Communications in Applied Mathematics and Computational Science, 9(1), 107-141 (2014),
|
|
http://dx.doi.org/10.2140/camcos.2014.9.107 */
|
|
|
|
#include <vector>
|
|
#include "multiloop.hpp"
|
|
#include "kdtree.hpp"
|
|
#include "newtoncp.hpp"
|
|
#include "stencilpoly.hpp"
|
|
#include "utility.hpp"
|
|
|
|
namespace algoim
|
|
{
|
|
namespace detail
|
|
{
|
|
// A CellPoly struct contains the interpolated polynomial for a particular grid cell
|
|
template<int N, typename Poly>
|
|
struct CellPoly
|
|
{
|
|
Poly poly; // The polynomial
|
|
uvector<int,N> i; // Coordinates of the cell
|
|
CellPoly(const Poly& poly, const uvector<int,N>& i) : poly(poly), i(i) {}
|
|
};
|
|
|
|
// Given a Cartesian grid-defined level set function phi, interpolate phi on each relevant grid cell to
|
|
// create a collection of polynomials. If interpAllCells is true, then every grid cell in the domain
|
|
// is interpolated, otherwise only those cells for which phi changes sign on the vertices are considered.
|
|
template<int N, typename Phi, typename Poly>
|
|
void createCellPolynomials(const uvector<int,N>& ext, const Phi& phi, const uvector<double,N>& dx, bool interpAllCells, std::vector<CellPoly<N,Poly>>& cells)
|
|
{
|
|
if (interpAllCells)
|
|
{
|
|
for (MultiLoop<N> i(0, ext - 1); ~i; ++i)
|
|
cells.push_back(CellPoly<N,Poly>(Poly(i(), phi, dx), i()));
|
|
}
|
|
else
|
|
{
|
|
for (MultiLoop<N> i(0, ext - 1); ~i; ++i)
|
|
{
|
|
int s = 0;
|
|
for (MultiLoop<N> j(i(), i() + 2); ~j; ++j)
|
|
s += (phi(j()) >= 0.0) ? 1 : -1;
|
|
if (std::abs(s) < 1 << N)
|
|
cells.push_back(CellPoly<N,Poly>(Poly(i(), phi, dx), i()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Given a collection of cell-generated polynomials, sample their zero level set to generate the
|
|
// seed points in the cloud, where subcellExt is the number of subcells to use per cell. The output
|
|
// is a collection of points and the corresponding index of the polynomial from which they were generated.
|
|
template<int N, typename Poly>
|
|
void samplePolynomials(const std::vector<CellPoly<N,Poly>>& cells, int subcellExt, const uvector<double,N>& dx,
|
|
const uvector<double,N>& xmin, std::vector<uvector<double,N>>& points, std::vector<int>& pointcells)
|
|
{
|
|
points.clear();
|
|
pointcells.clear();
|
|
// subcellsqrradius is the induced squared radius of each subcell (used to discard points if they go too far)
|
|
double subcellsqrradius = util::sqr(1.25*norm(dx)*0.5/subcellExt);
|
|
// tolerance 1% of subcell size
|
|
double isotolsqr = std::max(1.0e-25, 1e-4*subcellsqrradius);
|
|
for (size_t i = 0, len = cells.size(); i < len; ++i)
|
|
{
|
|
for (MultiLoop<N> j(0, subcellExt); ~j; ++j)
|
|
{
|
|
uvector<double,N> x = (j() + 0.5)*dx/subcellExt;
|
|
uvector<double,N> y = x;
|
|
if (newtonIso(x, cells[i].poly, isotolsqr, 10) > 0 && sqrnorm(x - y) < subcellsqrradius)
|
|
{
|
|
points.push_back(cells[i].i*dx + xmin + x);
|
|
pointcells.push_back(static_cast<int>(i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The ComputeHighOrderCP struct is the main engine for computing high-order accurate closest points.
|
|
It accepts as parameters:
|
|
- bandrsqr: squared radius of the narrow band
|
|
- overlapr: radius of the bounding ball used in Newton's method
|
|
- cptolsqr: tolerance used to determine convergence in Newton's method
|
|
- cells: collection of polynomials
|
|
- kdtree: tree containing the seed points
|
|
- points: collection of seed points
|
|
- pointcells: index of the polynomial corresponding to each seed point
|
|
- dx: grid cell size
|
|
- xmin: coordinate of lower-left vertex of cell 0 */
|
|
template<int N, typename Poly>
|
|
struct ComputeHighOrderCP
|
|
{
|
|
const double bandrsqr, overlapr, cptolsqr;
|
|
const std::vector<detail::CellPoly<N,Poly>>& cells;
|
|
const KDTree<double,N>& kdtree;
|
|
const std::vector<uvector<double,N>>& points;
|
|
const std::vector<int>& pointcells;
|
|
const uvector<double,N> dx;
|
|
const uvector<double,N> xmin;
|
|
|
|
ComputeHighOrderCP(double bandrsqr, double overlapr, double cptolsqr, const std::vector<detail::CellPoly<N,Poly>>& cells,
|
|
const KDTree<double,N>& kdtree, const std::vector<uvector<double,N>>& points, const std::vector<int>& pointcells,
|
|
const uvector<double,N>& dx, const uvector<double,N>& xmin)
|
|
: bandrsqr(bandrsqr), overlapr(overlapr), cptolsqr(cptolsqr), cells(cells), kdtree(kdtree), points(points), pointcells(pointcells), dx(dx), xmin(xmin)
|
|
{}
|
|
|
|
// Given x, compute the closest point to x; returns false if the closest seed point is outside
|
|
// the narrow band
|
|
bool compute(const uvector<double,N>& x, uvector<double,N>& cp, double* signedDist = 0) const
|
|
{
|
|
// Find the closest point in the cloud
|
|
int index = kdtree.nearest(x, bandrsqr);
|
|
|
|
// If there is no point within the defined band radius, return false
|
|
if (index < 0)
|
|
return false;
|
|
|
|
// Fetch the polynomial corresponding to the closest point in the cloud and calculate the reference point
|
|
// and initial guess of the closest point in the local coordinate system
|
|
const detail::CellPoly<N,Poly>& cell = cells[pointcells[index]];
|
|
uvector<double,N> ref = x - (cell.i*dx + xmin);
|
|
cp = points[index] - (cell.i*dx + xmin);
|
|
|
|
// Execute Newton's method
|
|
newtonCP<N,Poly>(cp, ref, cell.poly, overlapr, cptolsqr, 20);
|
|
|
|
// If the signed distance is also requested, the sign can (usually) be determined
|
|
// according to alignment of the normal and polynomial gradient at the closest point
|
|
if (signedDist)
|
|
{
|
|
uvector<double,N> grad = cell.poly.grad(cp);
|
|
if (dot(ref - cp, grad) >= 0.0)
|
|
*signedDist = norm(ref - x);
|
|
else
|
|
*signedDist = -norm(ref - x);
|
|
}
|
|
|
|
// Adjust the closest point such that it is in the global coordinate system
|
|
cp += cell.i*dx + xmin;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// Reinitialiase a Cartesian grid-defined level set function phi in a narrow band of radius r using the
|
|
// high-order closest point algorithm. The template parameter Degree specifies the type of
|
|
// polynomial to use. The given phi should implement operator()(uvector<int,N> i) accepting a grid
|
|
// coordinate 'i', where 0 <= i < extent
|
|
template<int N, int Degree, typename Phi>
|
|
void reinit(Phi& phi, const uvector<int,N>& extent, const uvector<double,N>& dx, double r)
|
|
{
|
|
// Determine the type of polynomial to use based on given Degree and dimension N
|
|
typedef typename StencilPoly<N,Degree>::T_Poly Poly;
|
|
|
|
// In this simple implementation of a reinitialisation algorithm, boundaries of phi
|
|
// are treated with a simple type of constant extrapolation, similar to how Neumann
|
|
// boundary conditions are specified with ghost layers. In other words, if phi(i)
|
|
// is requested and i is outside the domain, then the nearest grid point to i is
|
|
// used to define phi(i).
|
|
auto phiextrap = [&](uvector<int,N> i)
|
|
{
|
|
for (int dim = 0; dim < N; ++dim)
|
|
{
|
|
if (i(dim) < 0)
|
|
i(dim) = 0;
|
|
else if (i(dim) >= extent(dim))
|
|
i(dim) = extent(dim) - 1;
|
|
}
|
|
return phi(i);
|
|
};
|
|
|
|
// Find all cells containing the interface and construct the high-order polynomials
|
|
std::vector<detail::CellPoly<N,Poly>> cells;
|
|
detail::createCellPolynomials(extent, phiextrap, dx, false, cells);
|
|
|
|
// Using the polynomials, sample the zero level set in each cell to create a cloud of seed points
|
|
std::vector<uvector<double,N>> points;
|
|
std::vector<int> pointcells;
|
|
detail::samplePolynomials<N,Poly>(cells, 2, dx, 0.0, points, pointcells);
|
|
|
|
// Construct a k-d tree from the seed points
|
|
KDTree<double,N> kdtree(points);
|
|
|
|
// Pass everything to the closest point computation engine
|
|
ComputeHighOrderCP<N,Poly> hocp(r < std::numeric_limits<double>::max() ? r*r : std::numeric_limits<double>::max(), // squared bandradius
|
|
0.5*max(dx), // amount that each polynomial overlaps / size of the bounding ball in Newton's method
|
|
util::sqr(std::max(1.0e-14, std::pow(max(dx), Poly::order))), // tolerance to determine convergence
|
|
cells, kdtree, points, pointcells, dx, 0.0);
|
|
|
|
// For each grid point in the domain, compute the closest point and use this to re-define phi as the signed distance function
|
|
for (MultiLoop<N> i(0, extent); ~i; ++i)
|
|
{
|
|
uvector<double,N> x = i()*dx, cp;
|
|
if (hocp.compute(x, cp))
|
|
phi(i()) = (phi(i()) >= 0.0)? norm(x - cp) : -norm(x - cp);
|
|
else
|
|
phi(i()) = (phi(i()) >= 0.0)? std::numeric_limits<double>::max() : -std::numeric_limits<double>::max();
|
|
}
|
|
}
|
|
} // namespace algoim
|
|
|
|
#endif
|
|
|