diff --git a/dumux/common/geometry/CMakeLists.txt b/dumux/common/geometry/CMakeLists.txt index c18c814cf536b887bf2345cec4220294a1367453..f5e830fc3a5a0b382f1f805664295de1142f487f 100644 --- a/dumux/common/geometry/CMakeLists.txt +++ b/dumux/common/geometry/CMakeLists.txt @@ -1,6 +1,7 @@ install(FILES boundingboxtree.hh diameter.hh +distance.hh geometricentityset.hh geometryintersection.hh grahamconvexhull.hh @@ -9,6 +10,7 @@ intersectionentityset.hh intersectspointgeometry.hh intersectspointsimplex.hh makegeometry.hh +normal.hh refinementquadraturerule.hh triangulation.hh DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dumux/common/geometry) diff --git a/dumux/common/geometry/distance.hh b/dumux/common/geometry/distance.hh new file mode 100644 index 0000000000000000000000000000000000000000..47fccf32ea9057aed73b1a2b5ce77c33783d20b5 --- /dev/null +++ b/dumux/common/geometry/distance.hh @@ -0,0 +1,181 @@ +/***************************************************************************** + * See the file COPYING for full copying permissions. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + *****************************************************************************/ +/*! + * \file + * \ingroup Geometry + * \brief Helper functions for distance queries + */ +#ifndef DUMUX_GEOMETRY_DISTANCE_HH +#define DUMUX_GEOMETRY_DISTANCE_HH + +#include <dune/common/fvector.hh> +#include <dune/geometry/quadraturerules.hh> + +namespace Dumux { + +/*! + * \ingroup Geometry + * \brief Compute the average distance from a point to a geometry by integration + */ +template<class Geometry> +inline typename Geometry::ctype +averageDistancePointGeometry(const typename Geometry::GlobalCoordinate& p, + const Geometry& geometry, + std::size_t integrationOrder = 2) +{ + typename Geometry::ctype avgDist = 0.0; + const auto& quad = Dune::QuadratureRules<typename Geometry::ctype, Geometry::mydimension>::rule(geometry.type(), integrationOrder); + for (const auto& qp : quad) + avgDist += (geometry.global(qp.position())-p).two_norm()*qp.weight()*geometry.integrationElement(qp.position()); + return avgDist/geometry.volume(); +} + +/*! + * \ingroup Geometry + * \brief Compute the distance from a point to a line through the points a and b + */ +template<class Point> +inline typename Point::value_type +distancePointLine(const Point& p, const Point& a, const Point& b) +{ + const auto ab = b - a; + const auto t = (p - a)*ab/ab.two_norm2(); + auto proj = a; + proj.axpy(t, ab); + return (proj - p).two_norm(); +} + +/*! + * \ingroup Geometry + * \brief Compute the distance from a point to a line given by a geometry with two corners + * \note We currently lack the a representation of a line geometry. This convenience function + * assumes a segment geometry (with two corners) is passed which represents a line geometry. + */ +template<class Geometry> +inline typename Geometry::ctype +distancePointLine(const typename Geometry::GlobalCoordinate& p, const Geometry& geometry) +{ + static_assert(Geometry::mydimension == 1, "Geometry has to be a line"); + const auto& a = geometry.corner(0); + const auto& b = geometry.corner(1); + return distancePointLine(p, a, b); +} + +/*! + * \ingroup Geometry + * \brief Compute the distance from a point to the segment connecting the points a and b + */ +template<class Point> +inline typename Point::value_type +distancePointSegment(const Point& p, const Point& a, const Point& b) +{ + const auto ab = b - a; + auto t = (p - a)*ab; + + if (t <= 0.0) + return (a - p).two_norm(); + + const auto lengthSq = ab.two_norm2(); + if (t >= lengthSq) + return (b - p).two_norm(); + + auto proj = a; + proj.axpy(t/lengthSq, ab); + return (proj - p).two_norm(); +} + +/*! + * \ingroup Geometry + * \brief Compute the distance from a point to a given segment geometry + */ +template<class Geometry> +inline typename Geometry::ctype +distancePointSegment(const typename Geometry::GlobalCoordinate& p, const Geometry& geometry) +{ + static_assert(Geometry::mydimension == 1, "Geometry has to be a segment"); + const auto& a = geometry.corner(0); + const auto& b = geometry.corner(1); + return distancePointSegment(p, a, b); +} + +/*! + * \ingroup Geometry + * \brief Compute the shortest distance between two points + */ +template<class ctype, int dimWorld> +inline ctype distance(const Dune::FieldVector<ctype, dimWorld>& a, + const Dune::FieldVector<ctype, dimWorld>& b) +{ return (a-b).two_norm(); } + + + +namespace Detail { + +// helper struct to compute distance between two geometries, specialized below +template<class Geo1, class Geo2, + int dimWorld = Geo1::coorddimension, + int dim1 = Geo1::mydimension, int dim2 = Geo2::mydimension> +struct GeometryDistance +{ + static_assert(Geo1::coorddimension == Geo2::coorddimension, "Geometries have to have the same coordinate dimensions"); + static auto distance(const Geo1& geo1, const Geo2& geo2) + { + DUNE_THROW(Dune::NotImplemented, "Geometry distance computation not implemented for dimworld = " + << dimWorld << ", dim1 = " << dim1 << ", dim2 = " << dim2); + } +}; + +// distance point-point +template<class Geo1, class Geo2, int dimWorld> +struct GeometryDistance<Geo1, Geo2, dimWorld, 0, 0> +{ + static_assert(Geo1::coorddimension == Geo2::coorddimension, "Geometries have to have the same coordinate dimensions"); + static auto distance(const Geo1& geo1, const Geo2& geo2) + { return Dumux::distance(geo1.corner(0), geo2.corner(0)); } +}; + +// distance segment-point +template<class Geo1, class Geo2, int dimWorld> +struct GeometryDistance<Geo1, Geo2, dimWorld, 1, 0> +{ + static_assert(Geo1::coorddimension == Geo2::coorddimension, "Geometries have to have the same coordinate dimensions"); + static auto distance(const Geo1& geo1, const Geo2& geo2) + { return distancePointSegment(geo2.corner(0), geo1); } +}; + +// distance point-segment +template<class Geo1, class Geo2, int dimWorld> +struct GeometryDistance<Geo1, Geo2, dimWorld, 0, 1> +{ + static_assert(Geo1::coorddimension == Geo2::coorddimension, "Geometries have to have the same coordinate dimensions"); + static auto distance(const Geo1& geo1, const Geo2& geo2) + { return distancePointSegment(geo1.corner(0), geo2); } +}; + +} // end namespace Detail + +/*! + * \ingroup Geometry + * \brief Compute the shortest distance between two geometries + */ +template<class Geo1, class Geo2> +inline auto distance(const Geo1& geo1, const Geo2& geo2) +{ return Detail::GeometryDistance<Geo1, Geo2>::distance(geo1, geo2); } + +} // end namespace Dumux + +#endif diff --git a/dumux/common/geometry/normal.hh b/dumux/common/geometry/normal.hh new file mode 100644 index 0000000000000000000000000000000000000000..a6e95ecb170b8c75a6df877f8667742f85585ee7 --- /dev/null +++ b/dumux/common/geometry/normal.hh @@ -0,0 +1,77 @@ +/***************************************************************************** + * See the file COPYING for full copying permissions. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + *****************************************************************************/ +/*! + * \file + * \ingroup Geometry + * \brief Helper functions for normals + */ +#ifndef DUMUX_GEOMETRY_NORMAL_HH +#define DUMUX_GEOMETRY_NORMAL_HH + +#include <algorithm> +#include <dune/common/float_cmp.hh> + +namespace Dumux { + +/*! + * \ingroup Geometry + * \brief Create a vector normal to the given one (v is expected to be non-zero) + * \note This returns some orthogonal vector with arbitrary length + */ +template<class Vector> +inline Vector normal(const Vector& v) +{ + static_assert(Vector::size() > 1, "normal expects a coordinate dimension > 1"); + + if constexpr (Vector::size() == 2) + return Vector({-v[1], v[0]}); + + const auto it = std::find_if(v.begin(), v.end(), [](const auto& x) { return Dune::FloatCmp::ne(x, 0.0); }); + const auto index = std::distance(v.begin(), it); + if (index != Vector::size()-1) + { + Vector normal(0.0); + normal[index] = -v[index+1]; + normal[index+1] = v[index]; + return normal; + } + else + { + Vector normal(0.0); + normal[index-1] = -v[index]; + normal[index] = v[index-1]; + return normal; + + } +} + +/*! + * \ingroup Geometry + * \brief Create a vector normal to the given one (v is expected to be non-zero) + * \note This returns some orthogonal vector with unit length + */ +template<class Vector> +inline Vector unitNormal(const Vector& v) +{ + auto normal = Dumux::normal(v); + normal /= normal.two_norm(); + return normal; +} + +} // end namespace Dumux + +#endif diff --git a/dumux/multidomain/embedded/circlepoints.hh b/dumux/multidomain/embedded/circlepoints.hh index ae9aee1a705b5f7964650138e2a6437306af6c58..6145915bb08de6c6c0246cc95eda90b8ca9a6091 100644 --- a/dumux/multidomain/embedded/circlepoints.hh +++ b/dumux/multidomain/embedded/circlepoints.hh @@ -28,9 +28,11 @@ #include <vector> #include <cmath> -#include <dumux/common/math.hh> #include <dune/common/exceptions.hh> +#include <dumux/common/math.hh> +#include <dumux/common/geometry/normal.hh> + namespace Dumux::EmbeddedCoupling { /*! @@ -54,11 +56,6 @@ void circlePoints(std::vector<GlobalPosition>& points, assert(sincos.size() % 2 == 0 && "Sample angles have to be pairs of sin/cos so size needs to be even."); static_assert(GlobalPosition::dimension == 3, "Only implemented for world dimension 3"); - using std::abs; - using ctype = typename GlobalPosition::value_type; - - constexpr ctype eps = 1.5e-7; - // resize the points vector const std::size_t numPoints = sincos.size()/2; points.resize(numPoints); @@ -68,16 +65,7 @@ void circlePoints(std::vector<GlobalPosition>& points, n /= n.two_norm(); // calculate a vector u perpendicular to n - GlobalPosition u; - if (abs(n[0]) < eps && abs(n[1]) < eps) - if (abs(n[2]) < eps) - DUNE_THROW(Dune::MathError, "The normal vector has to be non-zero!"); - else - u = {0, 1, 0}; - else - u = {-n[1], n[0], 0}; - - u *= radius/u.two_norm(); + auto u = unitNormal(n); u*= radius; // the circle parameterization is p(t) = r*cos(t)*u + r*sin(t)*(n x u) + c auto tangent = crossProduct(u, n); diff --git a/dumux/multidomain/embedded/couplingmanager1d3d.hh b/dumux/multidomain/embedded/couplingmanager1d3d.hh index 0b9c09038ccae294cb8eee4ce11746cde9868b9f..53e647e594d54014599d6d30fa6a6e1c62b7ed31 100644 --- a/dumux/multidomain/embedded/couplingmanager1d3d.hh +++ b/dumux/multidomain/embedded/couplingmanager1d3d.hh @@ -33,6 +33,7 @@ #include <dumux/common/properties.hh> #include <dumux/common/indextraits.hh> +#include <dumux/common/geometry/distance.hh> #include <dumux/multidomain/embedded/pointsourcedata.hh> #include <dumux/multidomain/embedded/integrationpointsource.hh> #include <dumux/multidomain/embedded/couplingmanagerbase.hh> @@ -1040,7 +1041,7 @@ public: this->pointSourceData().emplace_back(std::move(psData)); // compute average distance to bulk cell - this->averageDistanceToBulkCell().push_back(this->computeDistance(outside.geometry(), globalPos)); + this->averageDistanceToBulkCell().push_back(averageDistancePointGeometry(globalPos, outside.geometry())); // export the lowdim coupling stencil // we insert all vertices / elements and make it unique later diff --git a/dumux/multidomain/embedded/couplingmanagerbase.hh b/dumux/multidomain/embedded/couplingmanagerbase.hh index b130b9edd1021e8fbe7d2675fa4e68c3b90dbafb..7e0bc1621e0b82637b51466673d4f64ede749f16 100644 --- a/dumux/multidomain/embedded/couplingmanagerbase.hh +++ b/dumux/multidomain/embedded/couplingmanagerbase.hh @@ -36,6 +36,7 @@ #include <dune/geometry/quadraturerules.hh> #include <dumux/common/properties.hh> +#include <dumux/common/geometry/distance.hh> #include <dumux/common/geometry/intersectingentities.hh> #include <dumux/discretization/method.hh> #include <dumux/multidomain/couplingmanager.hh> @@ -318,7 +319,7 @@ public: this->pointSourceData().emplace_back(std::move(psData)); // compute average distance to bulk cell - averageDistanceToBulkCell_.push_back(computeDistance(outside.geometry(), globalPos)); + averageDistanceToBulkCell_.push_back(averageDistancePointGeometry(globalPos, outside.geometry())); // export the lowdim coupling stencil // we insert all vertices / elements and make it unique later @@ -477,16 +478,6 @@ protected: glue_->build(bulkGridGeometry.boundingBoxTree(), lowDimGridGeometry.boundingBoxTree()); } - template<class Geometry, class GlobalPosition> - Scalar computeDistance(const Geometry& geometry, const GlobalPosition& p) const - { - Scalar avgDist = 0.0; - const auto& quad = Dune::QuadratureRules<Scalar, bulkDim>::rule(geometry.type(), 5); - for (auto&& qp : quad) - avgDist += (geometry.global(qp.position())-p).two_norm()*qp.weight(); - return avgDist; - } - //! Return reference to point source data vector member std::vector<PointSourceData>& pointSourceData() { return pointSourceData_; } diff --git a/test/common/geometry/CMakeLists.txt b/test/common/geometry/CMakeLists.txt index f1a6d093ce7bb5caa35dd1dfdf4a9921e674c87c..6e57be7f6d0a318670f5bbc84edc0d64000ddcc0 100644 --- a/test/common/geometry/CMakeLists.txt +++ b/test/common/geometry/CMakeLists.txt @@ -6,6 +6,8 @@ dumux_add_test(SOURCES test_1d3d_intersection.cc LABELS unit) dumux_add_test(SOURCES test_1d2d_intersection.cc LABELS unit) dumux_add_test(SOURCES test_2d2d_intersection.cc LABELS unit) dumux_add_test(SOURCES test_2d3d_intersection.cc LABELS unit) +dumux_add_test(SOURCES test_distance.cc LABELS unit) +dumux_add_test(SOURCES test_normal.cc LABELS unit) dumux_add_test(SOURCES test_graham_convex_hull.cc LABELS unit) dumux_add_test(SOURCES test_intersectingentity_cartesiangrid.cc LABELS unit) dumux_add_test(SOURCES test_circlepoints.cc LABELS unit) diff --git a/test/common/geometry/test_distance.cc b/test/common/geometry/test_distance.cc new file mode 100644 index 0000000000000000000000000000000000000000..3fbed2c96d991718eaf916e83a39bc3f6a2d1557 --- /dev/null +++ b/test/common/geometry/test_distance.cc @@ -0,0 +1,164 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/***************************************************************************** + * See the file COPYING for full copying permissions. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + *****************************************************************************/ +/*! + * \file + * \brief Test for distance computations. + */ +#include <config.h> + +#include <vector> +#include <random> +#include <cmath> + +#include <dune/common/fvector.hh> +#include <dune/common/float_cmp.hh> +#include <dune/common/exceptions.hh> + +#include <dune/geometry/affinegeometry.hh> +#include <dune/geometry/multilineargeometry.hh> + +#include <dumux/common/geometry/distance.hh> +#include <dumux/common/geometry/normal.hh> + +// helper function to make point geometry from field vector +template<class Point, int dimWorld> +Point makePoint(const Dune::FieldVector<typename Point::ctype, dimWorld>& p) +{ + using GlobalPosition = Dune::FieldVector<typename Point::ctype, dimWorld>; + using Corners = std::vector<GlobalPosition>; + return { Dune::GeometryTypes::vertex, Corners{{p}} }; +} + +// helper function to make a segment geometry from field vectors +template<class Segment, int dimWorld> +Segment makeSegment(const Dune::FieldVector<typename Segment::ctype, dimWorld>& p1, + const Dune::FieldVector<typename Segment::ctype, dimWorld>& p2) +{ + using GlobalPosition = Dune::FieldVector<typename Segment::ctype, dimWorld>; + using Corners = std::vector<GlobalPosition>; + return { Dune::GeometryTypes::line, Corners{{p1, p2}} }; +} + +// sample a point on a sphere with the given radius +template<class Point> +Point samplePointOnSphere(typename Point::ctype radius) +{ + static std::default_random_engine generator(std::random_device{}()); + static constexpr int dimWorld = Point::coorddimension; + static_assert(dimWorld > 1, "This only works in 2d or 3d"); + + // sample a point from random coordinates + std::uniform_real_distribution<double> distro(-radius, radius); + + auto p = dimWorld == 2 ? makePoint<Point, dimWorld>({distro(generator), distro(generator)}) + : makePoint<Point, dimWorld>({distro(generator), distro(generator), distro(generator)}); + + // project it onto the sphere + auto pos = p.corner(0); + pos *= radius/pos.two_norm(); + p = makePoint<Point>(pos); + + return p; +} + +// checks the result of a distance computation +template<class ctype> +void checkGeometryDistance(ctype expected, ctype computed, const std::string& geometryPairName) +{ + if (Dune::FloatCmp::ne(expected, computed)) + DUNE_THROW(Dune::InvalidStateException, "Unexpected " << geometryPairName << " distance "); +} + +// checks the distances between various points with points/segments/lines +template<class Point, class Segment> +void runTests() +{ + using namespace Dumux; + + using ctype = typename Point::ctype; + using GlobalPosition = typename Point::GlobalCoordinate; + + const auto origin = makePoint<Point>(GlobalPosition(0.0)); + for (ctype scale : {1e-12, 1.0, 1e12}) + { + // check distances for some random points in a known distance + for (int i = 0; i < 20; ++i) + { + const auto p = samplePointOnSphere<Point>(scale); + + // test point-point distance + checkGeometryDistance(scale, distance(origin, p), "point-point"); + + // test point-segment distance (where projection is on the segment) + auto n = normal(p.corner(0)); + n *= scale/n.two_norm(); + + auto segment = makeSegment<Segment>(origin.corner(0) + n, p.corner(0) + n); + checkGeometryDistance(scale, distance(segment, p), "point-segment"); + + // test point-segment distance (where projection is NOT on segment) + auto v = p.corner(0); + + using std::sqrt; + auto segment2 = makeSegment<Segment>(segment.corner(0) - v, segment.corner(1) - v); + checkGeometryDistance(sqrt(2.0)*scale, distance(segment2, p), "segment-segment"); + + v *= 2.0; + auto segment3 = makeSegment<Segment>(segment.corner(0) + v, segment.corner(1) + v); + checkGeometryDistance(sqrt(2.0)*scale, distance(segment2, p), "segment-segment"); + + // for lines, we should always get a distance of scale + checkGeometryDistance(scale, distancePointLine(p.corner(0), segment.corner(0), segment.corner(1)), "segment-segment"); + checkGeometryDistance(scale, distancePointLine(p.corner(0), segment2.corner(0), segment2.corner(1)), "segment-segment"); + checkGeometryDistance(scale, distancePointLine(p.corner(0), segment3.corner(0), segment3.corner(1)), "segment-segment"); + } + } +} + +int main(int argc, char** argv) try +{ + // affine geometries (2d) + runTests< Dune::AffineGeometry<double, 0, 2>, Dune::AffineGeometry<double, 1, 2> >(); + + // affine geometries (3d) + runTests< Dune::AffineGeometry<double, 0, 3>, Dune::AffineGeometry<double, 1, 3> >(); + + // multilinear geometries (2d) + runTests< Dune::MultiLinearGeometry<double, 0, 2>, Dune::MultiLinearGeometry<double, 1, 2> >(); + + // multilinear geometries (3d) + runTests< Dune::MultiLinearGeometry<double, 0, 3>, Dune::MultiLinearGeometry<double, 1, 3> >(); + + // mixed types (2d) + runTests< Dune::AffineGeometry<double, 0, 2>, Dune::MultiLinearGeometry<double, 1, 2> >(); + runTests< Dune::MultiLinearGeometry<double, 0, 2>, Dune::AffineGeometry<double, 1, 2> >(); + + // mixed types (3d) + runTests< Dune::AffineGeometry<double, 0, 3>, Dune::MultiLinearGeometry<double, 1, 3> >(); + runTests< Dune::MultiLinearGeometry<double, 0, 3>, Dune::AffineGeometry<double, 1, 3> >(); + + return 0; +} +// ////////////////////////////////// +// Error handler +// ///////////////////////////////// +catch (const Dune::Exception& e) { + std::cout << e << std::endl; + return 1; +} diff --git a/test/common/geometry/test_normal.cc b/test/common/geometry/test_normal.cc new file mode 100644 index 0000000000000000000000000000000000000000..2a95def1b644225b2042a9a4e3a31b8b6efd397a --- /dev/null +++ b/test/common/geometry/test_normal.cc @@ -0,0 +1,94 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/***************************************************************************** + * See the file COPYING for full copying permissions. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + *****************************************************************************/ +/*! + * \file + * \brief Test for normal computation + */ +#include <config.h> + +#include <random> +#include <algorithm> + +#include <dune/common/exceptions.hh> +#include <dune/common/fvector.hh> + +#include <dumux/common/geometry/normal.hh> + +template<class P> +void testOrthogonality(const P& normal, const P& vector, double scale, const std::string& caseName = "") +{ + const auto eps = Dune::FloatCmp::DefaultEpsilon<double, Dune::FloatCmp::absolute>::value()*scale*100; + if (Dune::FloatCmp::ne<double, Dune::FloatCmp::absolute>(normal*vector, 0.0, eps)) + DUNE_THROW(Dune::Exception, "Normal not orthogonal, n: " << normal << ", p: " << vector + << ", dim: " << P::size() << ", sp: " << normal*vector + << ", scale: " << scale << ", epsilon: " << eps << ", case: " << caseName); +} + +template<int dim, std::size_t repetitions = 100000> +void testNormal() +{ + std::default_random_engine gen(std::random_device{}()); + std::uniform_real_distribution<double> rnd(-1.0, 1.0), rndIdx(0.0, dim-1); + Dune::FieldVector<double, dim> p(0.0); + + for (double scale : {1.0, 1e3, 1e-12, 1e12}) + { + for (std::size_t i = 0; i < repetitions; ++i) + { + std::generate(p.begin(), p.end(), [&]{ return rnd(gen)*scale; }); + const auto n = Dumux::normal(p); + testOrthogonality(n, p, scale, "rnd"); + } + + // one coordinate != 0 + for (std::size_t i = 0; i < repetitions; ++i) + { + p = 0.0; + const auto randomIndex = (std::size_t) std::round(rndIdx(gen)); + p[randomIndex] = scale; + testOrthogonality(Dumux::normal(p), p, scale, "rnd-one-nonzero"); + p[randomIndex] = scale*1e-20; + testOrthogonality(Dumux::normal(p), p, scale, "rnd-one-small"); + } + + // two coordinates != 0 + for (std::size_t i = 0; i < repetitions; ++i) + { + p = 0.0; + const auto randomIndex0 = (std::size_t) std::round(rndIdx(gen)); + const auto randomIndex1 = (std::size_t) std::round(rndIdx(gen)); + p[randomIndex0] = scale; + p[randomIndex1] = scale; + testOrthogonality(Dumux::normal(p), p, scale, "rnd-two-nonzero"); + p[randomIndex0] = scale*1e-20; + p[randomIndex1] = scale*1e-20; + testOrthogonality(Dumux::normal(p), p, scale, "rnd-two-small"); + } + } +} + +int main(int argc, char** argv) +{ + testNormal<2>(); + testNormal<3>(); + testNormal<4>(); + testNormal<10>(); + + return 0; +}