Commit 00066647 by Dennis Gläser

### Merge branch 'fix/invert-poly-doc-and-test' into 'master'

```[math][invertCubicPoly] Improve doc. Add possibility to specify...

Closes #1025

See merge request !2599```
parents e070e79b 3d073c17
 ... @@ -198,25 +198,30 @@ int invertQuadraticPolynomial(SolContainer &sol, ... @@ -198,25 +198,30 @@ int invertQuadraticPolynomial(SolContainer &sol, template template void invertCubicPolynomialPostProcess_(SolContainer &sol, void invertCubicPolynomialPostProcess_(SolContainer &sol, int numSol, int numSol, Scalar a, Scalar a, Scalar b, Scalar c, Scalar d, Scalar b, std::size_t numIterations = 1) Scalar c, Scalar d) { { // do one Newton iteration on the analytic solution if the const auto eval = [&](auto x){ return d + x*(c + x*(b + x*a)); }; // precision is increased const auto evalDeriv = [&](auto x){ return c + x*(2*b + x*3*a); }; for (int i = 0; i < numSol; ++i) { Scalar x = sol[i]; Scalar fOld = d + x*(c + x*(b + x*a)); Scalar fPrime = c + x*(2*b + x*3*a); // do numIterations Newton iterations on the analytic solution if (fPrime == 0.0) // and update result if the precision is increased continue; for (int i = 0; i < numSol; ++i) x -= fOld/fPrime; { Scalar x = sol[i]; Scalar fCurrent = eval(x); const Scalar fOld = fCurrent; for (int j = 0; j < numIterations; ++j) { const Scalar fPrime = evalDeriv(x); if (fPrime == 0.0) break; x -= fCurrent/fPrime; fCurrent = eval(x); } Scalar fNew = d + x*(c + x*(b + x*a)); using std::abs; using std::abs; if (abs(fNew) < abs(fOld)) if (abs(fCurrent) < abs(fOld)) sol[i] = x; sol[i] = x; } } } } ... @@ -229,22 +234,24 @@ void invertCubicPolynomialPostProcess_(SolContainer &sol, ... @@ -229,22 +234,24 @@ void invertCubicPolynomialPostProcess_(SolContainer &sol, * The polynomial is defined as * The polynomial is defined as * \f[ p(x) = a\; x^3 + + b\;x^3 + c\;x + d \f] * \f[ p(x) = a\; x^3 + + b\;x^3 + c\;x + d \f] * * * This method teturns the number of solutions which are in the real * This method returns the number of solutions which are in the real * numbers. The "sol" argument contains the real roots of the cubic * numbers. The "sol" argument contains the real roots of the cubic * polynomial in order with the smallest root first. * polynomial in order with the smallest root first. * * * \note The closer the roots are to each other the less * precise the inversion becomes. Increase number of post-processing iterations for improved results. * * \param sol Container into which the solutions are written * \param sol Container into which the solutions are written * \param a The coefficiont for the cubic term * \param a The coefficient for the cubic term * \param b The coefficiont for the quadratic term * \param b The coefficient for the quadratic term * \param c The coefficiont for the linear term * \param c The coefficient for the linear term * \param d The coefficiont for the constant term * \param d The coefficient for the constant term * \param numPostProcessIterations The number of iterations to increase precision of the analytical result */ */ template template int invertCubicPolynomial(SolContainer *sol, int invertCubicPolynomial(SolContainer *sol, Scalar a, Scalar a, Scalar b, Scalar c, Scalar d, Scalar b, std::size_t numPostProcessIterations = 1) Scalar c, Scalar d) { { // reduces to a quadratic polynomial // reduces to a quadratic polynomial if (a == 0) if (a == 0) ... @@ -334,7 +341,7 @@ int invertCubicPolynomial(SolContainer *sol, ... @@ -334,7 +341,7 @@ int invertCubicPolynomial(SolContainer *sol, // Choose the root that is safe against the loss of precision // Choose the root that is safe against the loss of precision // that can cause wDisc to be equal to q*q/4 despite p != 0. // that can cause wDisc to be equal to q*q/4 despite p != 0. // We do not want u to be zero in that case. Mathematically, // We do not want u to be zero in that case. Mathematically, // we are happy with either root. // we are happy with either root. const Scalar u = [&]{ const Scalar u = [&]{ return q > 0 ? cbrt(-0.5*q - sqrt(wDisc)) : cbrt(-0.5*q + sqrt(wDisc)); return q > 0 ? cbrt(-0.5*q - sqrt(wDisc)) : cbrt(-0.5*q + sqrt(wDisc)); }(); }(); ... @@ -344,7 +351,7 @@ int invertCubicPolynomial(SolContainer *sol, ... @@ -344,7 +351,7 @@ int invertCubicPolynomial(SolContainer *sol, // does not produce a division by zero. the remaining two // does not produce a division by zero. the remaining two // roots of u are rotated by +- 2/3*pi in the complex plane // roots of u are rotated by +- 2/3*pi in the complex plane // and thus not considered here // and thus not considered here invertCubicPolynomialPostProcess_(sol, 1, a, b, c, d); invertCubicPolynomialPostProcess_(sol, 1, a, b, c, d, numPostProcessIterations); return 1; return 1; } } else { // the negative discriminant case: else { // the negative discriminant case: ... @@ -404,7 +411,7 @@ int invertCubicPolynomial(SolContainer *sol, ... @@ -404,7 +411,7 @@ int invertCubicPolynomial(SolContainer *sol, // post process the obtained solution to increase numerical // post process the obtained solution to increase numerical // precision // precision invertCubicPolynomialPostProcess_(sol, 3, a, b, c, d); invertCubicPolynomialPostProcess_(sol, 3, a, b, c, d, numPostProcessIterations); // sort the result // sort the result using std::sort; using std::sort; ... ...
 ... @@ -44,6 +44,7 @@ ... @@ -44,6 +44,7 @@ #include #include #include #include #include namespace Test { namespace Test { ... @@ -264,31 +265,43 @@ int main() ... @@ -264,31 +265,43 @@ int main() std::uniform_real_distribution<> dis(-10.0, 10.0); std::uniform_real_distribution<> dis(-10.0, 10.0); auto threeRandomNumbers = [&]() mutable { auto threeRandomNumbers = [&]() mutable { const auto x = dis(gen); const auto x = dis(gen); auto y = dis(gen); while (x == y) { y = dis(gen); } auto y = dis(gen); while (Dune::FloatCmp::eq(x, y, 1e-10)) { y = dis(gen); } auto z = dis(gen); while (x == z || y == z) { z = dis(gen); } auto z = dis(gen); while (Dune::FloatCmp::eq(x, z, 1e-10) || Dune::FloatCmp::eq(y, z, 1e-10)) { z = dis(gen); } return std::make_tuple(x, y, z); return std::make_tuple(x, y, z); }; }; const auto checkRoots = [](const std::array& roots, const auto checkRoots = [](const std::array& roots, const std::array& ref, const std::array& ref, int n) const std::array& coeff, int n, bool throwing = true) -> bool { { const auto refToString = [&](){ std::string refStr{}; for (int i = 0; i < n; i++) refStr += Dumux::Fmt::format("{:.14e},", ref[i]); return refStr; }; for (int i = 0; i < n; i++) for (int i = 0; i < n; i++) { { using std::isfinite; using std::isfinite; if (!isfinite(roots[i])) if (!isfinite(roots[i])) DUNE_THROW(Dune::Exception, "Expecting finite root"); DUNE_THROW(Dune::Exception, "Expecting finite root"); if (std::none_of(ref.begin(), ref.end(), [&](auto r){ return Dune::FloatCmp::eq(roots[i], r, 1e-10); })) if (throwing && std::none_of(ref.begin(), ref.end(), [&](auto r){ return Dune::FloatCmp::eq(roots[i], r, 1e-8); })) DUNE_THROW(Dune::Exception, "[invertCubicPolynomial] Root " DUNE_THROW(Dune::Exception, "[invertCubicPolynomial] Root " << Dumux::Fmt::format( << i << " of " << n << ": " << roots[i] "{} of {}: {:.14e}, does not match reference roots = [{}].\n", i+1, n, roots[i], refToString()) << " does not match reference"); << Dumux::Fmt::format("Polynomial: {:+.5e}x³ {:+.5e}x² {:+.5e}x {:+.5e}\n", coeff[0], coeff[1], coeff[2], coeff[3])); else return false; } } return true; }; }; std::array roots{}; std::array roots{}; for (double scaling : {1.0, -1.0, 5.0}) for (double scaling : {1.0, -1.0, 5.0}) { { for (int i = 0; i < 1000; ++i) for (int i = 0; i < 100000; ++i) { { const auto [x, y, z] = threeRandomNumbers(); const auto [x, y, z] = threeRandomNumbers(); ... @@ -298,7 +311,12 @@ int main() ... @@ -298,7 +311,12 @@ int main() int numRoots = Dumux::invertCubicPolynomial(roots.data(), a, b, c, d); int numRoots = Dumux::invertCubicPolynomial(roots.data(), a, b, c, d); if (numRoots != 3) if (numRoots != 3) DUNE_THROW(Dune::Exception, "Expecting three roots! Got " << numRoots); DUNE_THROW(Dune::Exception, "Expecting three roots! Got " << numRoots); checkRoots(roots, {x, y, z}, numRoots); if (!checkRoots(roots, {x, y, z}, {a, b, c, d}, numRoots, false)) { // Try to increase Newton iterations for increased precision and try again int numRoots = Dumux::invertCubicPolynomial(roots.data(), a, b, c, d, 10); checkRoots(roots, {x, y, z}, {a, b, c, d}, numRoots); } } } // test for two unique roots // test for two unique roots ... @@ -307,7 +325,12 @@ int main() ... @@ -307,7 +325,12 @@ int main() int numRoots = Dumux::invertCubicPolynomial(roots.data(), 0.0, b, c, d); int numRoots = Dumux::invertCubicPolynomial(roots.data(), 0.0, b, c, d); if (numRoots != 2) if (numRoots != 2) DUNE_THROW(Dune::Exception, "Expecting two roots! Got " << numRoots); DUNE_THROW(Dune::Exception, "Expecting two roots! Got " << numRoots); checkRoots(roots, {x, z}, numRoots); if (!checkRoots(roots, {x, z}, {0.0, b, c, d}, numRoots, false)) { // Try to increase Newton iterations for increased precision and try again int numRoots = Dumux::invertCubicPolynomial(roots.data(), 0.0, b, c, d, 10); checkRoots(roots, {x, z}, {0.0, b, c, d}, numRoots); } } } // test for one unique root // test for one unique root ... @@ -316,7 +339,12 @@ int main() ... @@ -316,7 +339,12 @@ int main() int numRoots = Dumux::invertCubicPolynomial(roots.data(), 0.0, 0.0, c, d); int numRoots = Dumux::invertCubicPolynomial(roots.data(), 0.0, 0.0, c, d); if (numRoots != 1) if (numRoots != 1) DUNE_THROW(Dune::Exception, "Expecting one root! Got " << numRoots); DUNE_THROW(Dune::Exception, "Expecting one root! Got " << numRoots); checkRoots(roots, {x}, numRoots); if (!checkRoots(roots, {x}, {0.0, 0.0, c, d}, numRoots, false)) { // Try to increase Newton iterations for increased precision and try again int numRoots = Dumux::invertCubicPolynomial(roots.data(), 0.0, 0.0, c, d, 10); checkRoots(roots, {x}, {0.0, 0.0, c, d}, numRoots); } } } } } } } ... ...
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!