diff --git a/dune/fufem/CMakeLists.txt b/dune/fufem/CMakeLists.txt index e9631a5811003b301aad25141219b38514a084c2..b074c9925e03a4433105f13a65fe68118b1c4630 100644 --- a/dune/fufem/CMakeLists.txt +++ b/dune/fufem/CMakeLists.txt @@ -22,6 +22,7 @@ install(FILES boundingbox.hh box.hh concept.hh + convexpolyhedron.hh dataio.hh dgindexset.hh dgpqkindexset.hh @@ -42,6 +43,7 @@ install(FILES mappedmatrix.hh matlab_io.hh orientedsubface.hh + polyhedrondistance.hh prolongboundarypatch.hh readbitfield.hh referenceelementhelper.hh diff --git a/dune/fufem/convexpolyhedron.hh b/dune/fufem/convexpolyhedron.hh new file mode 100644 index 0000000000000000000000000000000000000000..5b78d25f66f2ffe0975c7931ce1ca2f70a6c0d73 --- /dev/null +++ b/dune/fufem/convexpolyhedron.hh @@ -0,0 +1,31 @@ +#ifndef DUNE_FUFEM_CONVEXPOLYHEDRON_HH +#define DUNE_FUFEM_CONVEXPOLYHEDRON_HH + +#include <dune/fufem/arithmetic.hh> + +template <class Coordinate> struct ConvexPolyhedron { + std::vector<Coordinate> vertices; + + template <class DynamicVector> + Coordinate barycentricToGlobal(DynamicVector const &b) const { + assert(b.size() == vertices.size()); + Coordinate r(0); + for (size_t i = 0; i < b.size(); i++) + Arithmetic::addProduct(r, b[i], vertices[i]); + return r; + } + + template <class DynamicVector> + void sanityCheck(DynamicVector const &b, double tol) const { + double sum = 0; + for (size_t i = 0; i < b.size(); i++) { + if (b[i] < -tol) + DUNE_THROW(Dune::Exception, "No barycentric coords " << b[1] << " nr. " + << i); + sum += b[i]; + } + if (sum > 1 + tol) + DUNE_THROW(Dune::Exception, "No barycentric coords, sum: " << sum); + } +}; +#endif diff --git a/dune/fufem/polyhedrondistance.hh b/dune/fufem/polyhedrondistance.hh new file mode 100644 index 0000000000000000000000000000000000000000..b76e397a89f43da22c40403bbc902cd43c81099d --- /dev/null +++ b/dune/fufem/polyhedrondistance.hh @@ -0,0 +1,219 @@ +#ifndef DUNE_FUFEM_POLYHEDRONDISTANCE_HH +#define DUNE_FUFEM_POLYHEDRONDISTANCE_HH + +// Based on the closest point projection from dune-contact + +#include <dune/common/dynvector.hh> +#include <dune/common/dynmatrix.hh> + +#include <dune/fufem/convexpolyhedron.hh> + +template <class Coordinate, class Matrix> +void populateMatrix(ConvexPolyhedron<Coordinate> const &segment, + Matrix &matrix) { + size_t const nCorners = segment.vertices.size(); + for (size_t i = 0; i < nCorners; i++) + for (size_t j = 0; j <= i; j++) + matrix[i][j] = segment.vertices[i] * segment.vertices[j]; + // make symmetric + for (size_t i = 0; i < nCorners; i++) + for (size_t j = i + 1; j < nCorners; j++) + matrix[i][j] = matrix[j][i]; +} + +/** + * The functional to be minimized is + * + * 0.5*norm(\sum_i lambda_i corner_i - target)^2 + * + * where lambda_i are barycentric coordinates, i.e. + * + * 0 \le lambda_i \le 1 and \sum_i lambda_i=1 + * + * The resulting quadratic minimization problem is given by + * + * 0.5 lambda^T*A*lambda - l^T*lambda + * + * with A_ij = (corner_i,corner_j) and l_i = (corner_i, target) + */ +template <class Coordinate> +void approximate(Coordinate const &target, Dune::DynamicMatrix<double> const &A, + Dune::DynamicVector<double> const &l, + ConvexPolyhedron<Coordinate> const &segment, + Dune::DynamicVector<double> &sol) { + size_t nCorners = segment.vertices.size(); + + // compute the residual + Dune::DynamicVector<double> rhs = l; + // compute rhs -= A*sol_ + for (size_t k = 0; k < nCorners; k++) + for (size_t j = 0; j < nCorners; j++) + rhs[k] -= A[k][j] * sol[j]; + + // use the edge vectors as search directions + double alpha = 0.0; + for (size_t j1 = 0; j1 < nCorners - 1; ++j1) { + for (size_t j2 = j1 + 1; j2 < nCorners; ++j2) { + // compute matrix entry and rhs for edge direction + double a = A[j1][j1] - A[j1][j2] - A[j2][j1] + A[j2][j2]; + double b = rhs[j1] - rhs[j2]; + + // compute minimizer for correction + if (a > 0) { + alpha = b / a; + + double const largestAlpha = sol[j2]; + double const smallestAlpha = -sol[j1]; + + // project alpha such that we stay positive + if (alpha > largestAlpha) + alpha = largestAlpha; + else if (alpha < smallestAlpha) + alpha = smallestAlpha; + } else { + // if the quadratic term vanishes, we have a linear function, which + // attains its extrema on the boundary + double sum = sol[j1] + sol[j2]; + + double lValue = 0.5 * A[j1][j1] * sum * sum - rhs[j1] * sum; + double uValue = 0.5 * A[j2][j2] * sum * sum - rhs[j2] * sum; + + alpha = lValue < uValue ? sol[j2] : -sol[j1]; + } + // apply correction + sol[j1] += alpha; + sol[j2] -= alpha; + + // update the local residual for corrections + for (size_t p = 0; p < nCorners; p++) + rhs[p] -= (A[p][j1] - A[p][j2]) * alpha; + } + } +} + +// Point-to-Polyhedron +template <class Coordinate> +double distance(const Coordinate &target, + const ConvexPolyhedron<Coordinate> &segment, + double correctionTolerance) { + size_t nCorners = segment.vertices.size(); + double const barycentricTolerance = 1e-14; + + Dune::DynamicMatrix<double> A(nCorners, nCorners); + populateMatrix(segment, A); + + Dune::DynamicVector<double> l(nCorners); + for (size_t i = 0; i < nCorners; i++) + l[i] = target * segment.vertices[i]; + + // choose a feasible initial solution + Dune::DynamicVector<double> sol(nCorners); + for (size_t i = 0; i < nCorners; i++) + sol[i] = 1.0 / nCorners; + + Coordinate oldCoordinates = segment.barycentricToGlobal(sol); + Coordinate newCoordinates; + + size_t maxIterations = 1000; + size_t iterations; + for (iterations = 0; iterations < maxIterations; ++iterations) { + approximate(target, A, l, segment, sol); + + newCoordinates = segment.barycentricToGlobal(sol); + if ((oldCoordinates - newCoordinates).two_norm() < correctionTolerance) + break; + + oldCoordinates = newCoordinates; + } + assert(iterations < maxIterations); + + segment.sanityCheck(sol, barycentricTolerance); + return (target - newCoordinates).two_norm(); +} + +// Polyhedron-to-Polyhedron +template <class Coordinate> +double distance(const ConvexPolyhedron<Coordinate> &s1, + const ConvexPolyhedron<Coordinate> &s2, + double valueCorrectionTolerance) { + size_t nCorners1 = s1.vertices.size(); + size_t nCorners2 = s2.vertices.size(); + double const barycentricTolerance = 1e-14; + + // choose feasible initial solutions + Dune::DynamicVector<double> sol1(nCorners1); + for (size_t i = 0; i < s1.vertices.size(); i++) + sol1[i] = 1.0 / s1.vertices.size(); + auto c1 = s1.barycentricToGlobal(sol1); + + Dune::DynamicVector<double> sol2(nCorners2); + for (size_t i = 0; i < nCorners2; i++) + sol2[i] = 1.0 / nCorners2; + auto c2 = s2.barycentricToGlobal(sol2); + + double oldDistance = (c2 - c1).two_norm(); + + Dune::DynamicMatrix<double> A1(nCorners1, nCorners1); + populateMatrix(s1, A1); + + Dune::DynamicMatrix<double> A2(nCorners2, nCorners2); + populateMatrix(s2, A2); + + size_t maxIterations = 1000; + size_t iterations; + for (iterations = 0; iterations < maxIterations; ++iterations) { + Dune::DynamicVector<double> l2(nCorners2); + for (size_t i = 0; i < nCorners2; i++) + l2[i] = c1 * s2.vertices[i]; + + approximate(c1, A2, l2, s2, sol2); + c2 = s2.barycentricToGlobal(sol2); + + Dune::DynamicVector<double> l1(nCorners1); + for (size_t i = 0; i < nCorners1; i++) + l1[i] = c2 * s1.vertices[i]; + approximate(c2, A1, l1, s1, sol1); + c1 = s1.barycentricToGlobal(sol1); + + double const newDistance = (c2 - c1).two_norm(); + assert(newDistance - oldDistance <= 1e-12); // debugging + if (oldDistance - newDistance <= valueCorrectionTolerance) { + s1.sanityCheck(sol1, barycentricTolerance); + s2.sanityCheck(sol2, barycentricTolerance); + return newDistance; + } + + oldDistance = newDistance; + } + assert(false); +} + +// Point-to-Geometry convenience method +template <class Geometry> +double distance(typename Geometry::GlobalCoordinate const &point, + Geometry const &geo, double valueCorrectionTolerance) { + using Coordinate = typename Geometry::GlobalCoordinate; + + ConvexPolyhedron<Coordinate> polyhedron; + polyhedron.vertices.resize(geo.corners()); + for (size_t i = 0; i < polyhedron.vertices.size(); ++i) + polyhedron.vertices[i] = geo.corner(i); + + return distance(point, polyhedron, valueCorrectionTolerance); +} + +// Polyhedron-to-Geometry convenience method +template <class Geometry> +double distance(ConvexPolyhedron<typename Geometry::GlobalCoordinate> const &p1, + Geometry const &geo, double valueCorrectionTolerance) { + using Coordinate = typename Geometry::GlobalCoordinate; + + ConvexPolyhedron<Coordinate> p2; + p2.vertices.resize(geo.corners()); + for (size_t i = 0; i < p2.vertices.size(); ++i) + p2.vertices[i] = geo.corner(i); + + return distance(p2, p1, valueCorrectionTolerance); +} + +#endif diff --git a/dune/fufem/test/CMakeLists.txt b/dune/fufem/test/CMakeLists.txt index 0ddef8540db67fa2d62122a62e5416a80b9145cb..63f63734fcb2319d4b3111af3b00fdd2ada07914 100644 --- a/dune/fufem/test/CMakeLists.txt +++ b/dune/fufem/test/CMakeLists.txt @@ -34,6 +34,7 @@ set(OTHER_TESTS pgmtest ppmtest serializationtest + test-polyhedral-minimisation ) set(TESTS ${OTHER_TESTS} ${GRID_BASED_TESTS}) diff --git a/dune/fufem/test/test-polyhedral-minimisation.cc b/dune/fufem/test/test-polyhedral-minimisation.cc new file mode 100644 index 0000000000000000000000000000000000000000..38e37efa37e7f5e4c678bc795bb87d7d83b33426 --- /dev/null +++ b/dune/fufem/test/test-polyhedral-minimisation.cc @@ -0,0 +1,91 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <dune/common/fvector.hh> + +#include <dune/fufem/polyhedrondistance.hh> + +int main() { + using LocalVector = Dune::FieldVector<double, 2>; + + auto const test = + [&](ConvexPolyhedron<LocalVector> const &p1, + ConvexPolyhedron<LocalVector> const &p2, double analyticalDistance) { + LocalVector target; + { + double const error = + std::abs(analyticalDistance - distance(p1, p2, 1e-12)); + std::cout << "error: " << error << std::endl; + assert(error < 1e-12); + } + }; + { + /* + * Calculate the distance between two triangles, where it is attained at a + * face-vertex pair + * + * O + * |\ + * | \ + * O O--O + * |\ + * | \ + * O--O + */ + double const analyticalDistance = std::sqrt(2.0); + ConvexPolyhedron<LocalVector> const bs1 = { + { { 0, 0 }, { 0, 2 }, { 2, 0 } } + }; + ConvexPolyhedron<LocalVector> const bs2 = { + { { 2, 2 }, { 2, 4 }, { 4, 2 } } + }; + test(bs1, bs2, analyticalDistance); + } + + { + /* + * Calculate the distance between two triangles, where it is + * attained in a face-face pair + * + * O--O + * \ | + * \| + * O O + * |\ + * | \ + * O--O + */ + double const analyticalDistance = 2.0 * std::sqrt(2.0); + ConvexPolyhedron<LocalVector> const bs1 = { + { { 0, 0 }, { 0, 2 }, { 2, 0 } } + }; + ConvexPolyhedron<LocalVector> const bs2 = { + { { 4, 4 }, { 2, 4 }, { 4, 2 } } + }; + test(bs1, bs2, analyticalDistance); + } + + { + /* + * Calculate the distance between two triangles, where it is + * attained in a vertex-vertex pair + * + * O + * |\ + * | \ + * O--O O--O + * \ | + * \| + * O + */ + double const analyticalDistance = 2.0; + ConvexPolyhedron<LocalVector> const bs1 = { + { { 2, 2 }, { 0, 2 }, { 2, 0 } } + }; + ConvexPolyhedron<LocalVector> const bs2 = { + { { 4, 2 }, { 4, 4 }, { 6, 2 } } + }; + test(bs1, bs2, analyticalDistance); + } +}