diff --git a/MathLib/Nonlinear/Root1D.h b/MathLib/Nonlinear/Root1D.h new file mode 100644 index 0000000000000000000000000000000000000000..f015d66417ca1d8addfc54f3152e7f0254fbeda5 --- /dev/null +++ b/MathLib/Nonlinear/Root1D.h @@ -0,0 +1,165 @@ +/** + * \copyright + * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#ifndef MATHLIB_NONLINEAR_ROOT1D_H +#define MATHLIB_NONLINEAR_ROOT1D_H + +#include <cassert> +#include <cmath> +#include <limits> +#include <type_traits> +#include <logog/include/logog.hpp> + +namespace MathLib +{ +namespace Nonlinear +{ + +namespace detail +{ + +//! Tells if \c a and \c b have the same sign. +inline bool same_sign(double a, double b) +{ + // note: signbit() casts integers to double and distinguishes between +0.0 and + // -0.0. However, the latter is not a problem, because if f(x0) == 0, we've + // already found the root x0 that we are searching for. + return std::signbit(a) == std::signbit(b); +} + +inline bool almost_zero(double a) +{ + return std::abs(a) <= std::numeric_limits<double>::epsilon(); +} + +} + + +//! Use the regula falsi method to find the root of some scalar function of one +//! variable. +template<typename SubType, typename Function> +class RegulaFalsi +{ +public: + //! Initializes finding a root of the \c Function \c f in the interval + //! [\c a, \c b]. + RegulaFalsi(Function const& f, double a, double b) + : _f(f), _a(a), _b(b), _fa(f(a)), _fb(f(b)) + { + static_assert( + std::is_same<double, decltype(f(0.0))>::value, + "Using this class for functions that do not return double" + " involves a lot of casts. Hence it is disabled."); + + if (detail::almost_zero(_fa)) { + _b = _a; + } else if (detail::almost_zero(_fb)) { + _a = _b; + } else if (detail::same_sign(_fa, _fb)) { + ERR("Regula falsi cannot be done, because the function values" + " at the interval ends have the same sign."); + std::abort(); + } + } + + //! Do \c num_steps iteration of regula falsi. + void step(const unsigned num_steps) + { + for (unsigned i=0; i<num_steps; ++i) + { + if (_a == _b) return; + + const double s = (_fb - _fa)/(_b - _a); + const double c = _a - _fa/s; + const double fc = _f(c); + + if (detail::almost_zero(fc)) { + _a = _b = c; + return; + } else if (!detail::same_sign(fc, _fb)) { + _a = _b; _fa = _fb; + _b = c; _fb = fc; + } else { + const double m = SubType::get_m(_fa, _fb, fc); + _fa *= m; + _b = c; + _fb = fc; + } + } + } + + //! Returns the current estimate of the root. + double getResult() const + { + if (_a == _b) return _a; + + const double s = (_fb - _fa)/(_b - _a); + const double c = _a - _fa/s; + + return c; + } + + //! Returns the size of the current search interval. + double getRange() const { return std::fabs(_a - _b); } + +private: + Function const& _f; + double _a, _b, _fa, _fb; +}; + + +/*! Creates a new instance of \c RegulaFalsi that uses the given modified iteration + * method \c SubType. + * + * \see https://en.wikipedia.org/wiki/False_position_method#Improvements_in_regula_falsi + */ +template<typename SubType, typename Function> +RegulaFalsi<SubType, Function> +makeRegulaFalsi(Function const& f, double const a, double const b) +{ + return RegulaFalsi<SubType, Function>(f, a, b); +} + + +//! Used by \c RegulaFalsi in the original regula falsi algorithm. +struct Unmodified +{ + static double get_m(const double /*fa*/, const double /*fb*/, const double /*fc*/) + { return 1.0; } +}; + +//! Used by \c RegulaFalsi in a modified version of the regula falsi algorithm. +struct Illinois +{ + static double get_m(const double /*fa*/, const double /*fb*/, const double /*fc*/) + { return 0.5; } +}; + +//! Used by \c RegulaFalsi in a modified version of the regula falsi algorithm. +struct Pegasus +{ + static double get_m(const double /*fa*/, const double fb, const double fc) + { return fb / (fb+fc); } +}; + +//! Used by \c RegulaFalsi in a modified version of the regula falsi algorithm. +struct AndersonBjorck +{ + static double get_m(const double /*fa*/, const double fb, const double fc) + { + const double f = 1.0 - fc / fb; + return (f >= 0.0) ? f : 0.5; + } +}; + +} + +} // namespace MathLib + +#endif // MATHLIB_NONLINEAR_ROOT1D_H diff --git a/Tests/MathLib/TestNonlinear1D.cpp b/Tests/MathLib/TestNonlinear1D.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7968acb414f070af66339c91c4b57abf8c8e8284 --- /dev/null +++ b/Tests/MathLib/TestNonlinear1D.cpp @@ -0,0 +1,59 @@ +/** + * \copyright + * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#include <limits> +#include <type_traits> +#include <gtest/gtest.h> +#include <logog/include/logog.hpp> +#include "MathLib/Nonlinear/Root1D.h" + +double f(double x) +{ + return x*x-1; +} + +template <typename T> +class MathLibRegulaFalsi : public ::testing::Test +{ +}; + +namespace NL = MathLib::Nonlinear; +using RegulaFalsiTypes = ::testing::Types<NL::Unmodified, NL::Illinois, NL::Pegasus, NL::AndersonBjorck>; + +TYPED_TEST_CASE(MathLibRegulaFalsi, RegulaFalsiTypes); + +TYPED_TEST(MathLibRegulaFalsi, QuadraticFunction) +{ + auto rf = NL::makeRegulaFalsi<TypeParam>(f, -0.1, 1.1); + double old_range = rf.getRange(); + + DBUG(" 0 -- x ~ %23.16g, range = %23.16g", rf.getResult(), old_range); + + for (unsigned n=0; n<10; ++n) + { + rf.step(1); + double range = rf.getRange(); + // expect that the interval of the root search shrinks + EXPECT_GT(old_range, range); + old_range = range; + DBUG("%2i -- x ~ %23.16g, range = %23.16g", n+1, rf.getResult(), range); + + if (range < std::numeric_limits<double>::epsilon()) break; + } + + auto const error = std::abs(f(rf.getResult())); + + if (!std::is_same<NL::Unmodified, TypeParam>::value) { + EXPECT_GT(std::numeric_limits<double>::epsilon(), old_range); + EXPECT_GT(std::numeric_limits<double>::epsilon(), error); + } else { + // The unmodified regula falsi method converges very slowly. + EXPECT_GT(100.0*std::numeric_limits<double>::epsilon(), error); + } +}