diff --git a/dumux/common/doubleexpintegrationconstants.hh b/dumux/common/doubleexpintegrationconstants.hh new file mode 100644 index 0000000000000000000000000000000000000000..9c5fdaee4d6795bfe8ffd1b473970bff24fd3969 --- /dev/null +++ b/dumux/common/doubleexpintegrationconstants.hh @@ -0,0 +1,498 @@ +// -*- 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/>. * + * * + * This version is modified after the original version by John D. Cook * + * see https://www.codeproject.com/ * + * Articles/31550/Fast-Numerical-Integration * + * which is licensed under BSD-2-clause, which reads as follows: * + * Copyright John D. Cook * + * * + * Redistribution and use in source and binary forms, with or without * + * modification, are permitted provided that the following * + * conditions are met: * + * 1. Redistributions of source code must retain the above * + * copyright notice, this list of conditions * + and the following disclaimer. * + * 2. Redistributions in binary form must reproduce the above * + * copyright notice, this list of conditions * + * and the following disclaimer * + * in the documentation and/or other materials * + * provided with the distribution. * + * * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * + * * + *****************************************************************************/ + +#ifndef DUMUX_COMMON_DOUBLEEXP_INTEGRATION_CONSTANTS_HH +#define DUMUX_COMMON_DOUBLEEXP_INTEGRATION_CONSTANTS_HH + +/* +The double exponential rule is based on the observation that the trapezoid rule converges +very rapidly for functions on the entire real line that go to zero like exp( - exp(t) ). +The change of variables x = tanh( pi sinh(t) /2) transforms an integral over [-1, 1] +into an integral with integrand suited to the double exponential rule. + +The transformed integral is infinite, but we truncate the domain of integration to [-3, 3]. +The limit '3' was chosen for two reasons: for t = 3, the transformed x values +are nearly equal to 1 for 12 or more significant figures. Also, for t = 3, the +smallest weights are 12 orders of magnitude smaller than the largest weights; setting +the cutoff larger than 3 would not have a significant impact on the integral value +unless there is a strong singularity at one of the end points. + +The change of variables x(t) is an odd function, i.e. x(-t) = -x(t), and so we need only +store the positive x values. Also, the derivative w(t) = x'(t) is even, i.e. w(-t) = w(t), +and so we need only store the weights corresponding to positive values of x. + +The integration first applies the trapezoid rule to [-3, 3] in steps of size 1. +Then it subsequently cuts the step size in half each time, comparing the results. +Integration stops when subsequent iterations are close enough together or the maximum +integration points have been used. +By cutting h in half, the previous integral can be reused; we only need evaluate the +integrand at the newly added points. + +Finally, note that we're not strictly using the trapezoid rule: we don't treat the +end points differently. This is because we assume the values at the ends of the interval +hardly matter due to the rapid decay of the integrand. + +All values below were calculated with Mathematica. +*/ + +namespace Dumux { + +constexpr double doubleExponentialIntegrationAbcissas[] = +{ + // 1st layer abcissas: transformed 0, 1, 2, 3 + 0.00000000000000000000, + 0.95136796407274694573, + 0.99997747719246159286, + 0.99999999999995705839, + // 2nd layer abcissas: transformed 1/2, 3/2, 5/2 + 0.67427149224843582608, + 0.99751485645722438683, + 0.99999998887566488198, + // 3rd layer abcissas: transformed 1/4, 3/4, ... + 0.37720973816403417379, + 0.85956905868989663517, + 0.98704056050737689169, + 0.99968826402835320905, + 0.99999920473711471266, + 0.99999999995285644818, + // 4th layer abcissas: transformed 1/8, 3/8, ... + 0.19435700332493543161, + 0.53914670538796776905, + 0.78060743898320029925, + 0.91487926326457461091, + 0.97396686819567744856, + 0.99405550663140214329, + 0.99906519645578584642, + 0.99990938469514399984, + 0.99999531604122052843, + 0.99999989278161241838, + 0.99999999914270509218, + 0.99999999999823216531, + // 5th layer abcissa: transformed 1/16, 3/16, ... + 0.097923885287832333262, + 0.28787993274271591456, + 0.46125354393958570440, + 0.61027365750063894488, + 0.73101803479256151149, + 0.82331700550640237006, + 0.88989140278426019808, + 0.93516085752198468323, + 0.96411216422354729193, + 0.98145482667733517003, + 0.99112699244169880223, + 0.99610866543750854254, + 0.99845420876769773751, + 0.99945143443527460584, + 0.99982882207287494166, + 0.99995387100562796075, + 0.99998948201481850361, + 0.99999801714059543208, + 0.99999969889415261122, + 0.99999996423908091534, + 0.99999999678719909830, + 0.99999999978973286224, + 0.99999999999039393352, + 0.99999999999970809734, + // 6th layer abcissas: transformed 1/32, 3/32, ... + 0.049055967305077886315, + 0.14641798429058794053, + 0.24156631953888365838, + 0.33314226457763809244, + 0.41995211127844715849, + 0.50101338937930910152, + 0.57558449063515165995, + 0.64317675898520470128, + 0.70355000514714201566, + 0.75669390863372994941, + 0.80279874134324126576, + 0.84221924635075686382, + 0.87543539763040867837, + 0.90301328151357387064, + 0.92556863406861266645, + 0.94373478605275715685, + 0.95813602271021369012, + 0.96936673289691733517, + 0.97797623518666497298, + 0.98445883116743083087, + 0.98924843109013389601, + 0.99271699719682728538, + 0.99517602615532735426, + 0.99688031812819187372, + 0.99803333631543375402, + 0.99879353429880589929, + 0.99928111192179195541, + 0.99958475035151758732, + 0.99976797159956083506, + 0.99987486504878034648, + 0.99993501992508242369, + 0.99996759306794345976, + 0.99998451990227082442, + 0.99999293787666288565, + 0.99999693244919035751, + 0.99999873547186590954, + 0.99999950700571943689, + 0.99999981889371276701, + 0.99999993755407837378, + 0.99999997987450320175, + 0.99999999396413420165, + 0.99999999832336194826, + 0.99999999957078777261, + 0.99999999989927772326, + 0.99999999997845533741, + 0.99999999999582460688, + 0.99999999999927152627, + 0.99999999999988636130, + // 7th layer abcissas: transformed 1/64, 3/64, ... + 0.024539763574649160379, + 0.073525122985671294475, + 0.12222912220155764235, + 0.17046797238201051811, + 0.21806347346971200463, + 0.26484507658344795046, + 0.31065178055284596083, + 0.35533382516507453330, + 0.39875415046723775644, + 0.44078959903390086627, + 0.48133184611690504422, + 0.52028805069123015958, + 0.55758122826077823080, + 0.59315035359195315880, + 0.62695020805104287950, + 0.65895099174335012438, + 0.68913772506166767176, + 0.71750946748732412721, + 0.74407838354734739913, + 0.76886868676824658459, + 0.79191549237614211447, + 0.81326360850297385168, + 0.83296629391941087564, + 0.85108400798784873261, + 0.86768317577564598669, + 0.88283498824466895513, + 0.89661425428007602579, + 0.90909831816302043511, + 0.92036605303195280235, + 0.93049693799715340631, + 0.93957022393327475539, + 0.94766419061515309734, + 0.95485549580502268541, + 0.96121861515111640753, + 0.96682537031235585284, + 0.97174454156548730892, + 0.97604156025657673933, + 0.97977827580061576265, + 0.98301279148110110558, + 0.98579936302528343597, + 0.98818835380074264243, + 0.99022624046752774694, + 0.99195566300267761562, + 0.99341551316926403900, + 0.99464105571251119672, + 0.99566407681695316965, + 0.99651305464025377317, + 0.99721334704346870224, + 0.99778739195890653083, + 0.99825491617199629344, + 0.99863314864067747762, + 0.99893703483351217373, + 0.99917944893488591716, + 0.99937140114093768690, + 0.99952223765121720422, + 0.99963983134560036519, + 0.99973076151980848263, + 0.99980048143113838630, + 0.99985347277311141171, + 0.99989338654759256426, + 0.99992317012928932869, + 0.99994518061445869309, + 0.99996128480785666613, + 0.99997294642523223656, + 0.99998130127012072679, + 0.99998722128200062811, + 0.99999136844834487344, + 0.99999423962761663478, + 0.99999620334716617675, + 0.99999752962380516793, + 0.99999841381096473542, + 0.99999899541068996962, + 0.99999937270733536947, + 0.99999961398855024275, + 0.99999976602333243312, + 0.99999986037121459941, + 0.99999991800479471056, + 0.99999995264266446185, + 0.99999997311323594362, + 0.99999998500307631173, + 0.99999999178645609907, + 0.99999999558563361584, + 0.99999999767323673790, + 0.99999999879798350040, + 0.99999999939177687583, + 0.99999999969875436925, + 0.99999999985405611550, + 0.99999999993088839501, + 0.99999999996803321674, + 0.99999999998556879008, + 0.99999999999364632387, + 0.99999999999727404948, + 0.99999999999886126543, + 0.99999999999953722654, + 0.99999999999981720098, + 0.99999999999992987953 +}; // end abcissas + +constexpr double doubleExponentialIntegrationWeights[] = +{ + // First layer weights + 1.5707963267948966192, + 0.230022394514788685, + 0.00026620051375271690866, + 1.3581784274539090834e-12, + // 2nd layer weights + 0.96597657941230114801, + 0.018343166989927842087, + 2.1431204556943039358e-7, + // 3rd layer weights + 1.3896147592472563229, + 0.53107827542805397476, + 0.076385743570832304188, + 0.0029025177479013135936, + 0.000011983701363170720047, + 1.1631165814255782766e-9, + // 4th layer weights + 1.5232837186347052132, + 1.1934630258491569639, + 0.73743784836154784136, + 0.36046141846934367417, + 0.13742210773316772341, + 0.039175005493600779072, + 0.0077426010260642407123, + 0.00094994680428346871691, + 0.000062482559240744082891, + 1.8263320593710659699e-6, + 1.8687282268736410132e-8, + 4.9378538776631926964e-11, + // 5th layer weights + 1.5587733555333301451, + 1.466014426716965781, + 1.297475750424977998, + 1.0816349854900704074, + 0.85017285645662006895, + 0.63040513516474369106, + 0.44083323627385823707, + 0.290240679312454185, + 0.17932441211072829296, + 0.10343215422333290062, + 0.055289683742240583845, + 0.027133510013712003219, + 0.012083543599157953493, + 0.0048162981439284630173, + 0.0016908739981426396472, + 0.00051339382406790336017, + 0.00013205234125609974879, + 0.000028110164327940134749, + 4.8237182032615502124e-6, + 6.4777566035929719908e-7, + 6.5835185127183396672e-8, + 4.8760060974240625869e-9, + 2.5216347918530148572e-10, + 8.6759314149796046502e-12, + // 6th layer weights + 1.5677814313072218572, + 1.5438811161769592204, + 1.4972262225410362896, + 1.4300083548722996676, + 1.3452788847662516615, + 1.2467012074518577048, + 1.1382722433763053734, + 1.0240449331118114483, + 0.90787937915489531693, + 0.79324270082051671787, + 0.68306851634426375464, + 0.57967810308778764708, + 0.48475809121475539287, + 0.39938474152571713515, + 0.32408253961152890402, + 0.258904639514053516, + 0.20352399885860174519, + 0.15732620348436615027, + 0.11949741128869592428, + 0.089103139240941462841, + 0.065155533432536205042, + 0.046668208054846613644, + 0.032698732726609031113, + 0.022379471063648476483, + 0.014937835096050129696, + 0.0097072237393916892692, + 0.0061300376320830301252, + 0.0037542509774318343023, + 0.0022250827064786427022, + 0.0012733279447082382027, + 0.0007018595156842422708, + 0.00037166693621677760301, + 0.00018856442976700318572, + 0.000091390817490710122732, + 0.000042183183841757600604, + 0.000018481813599879217116, + 7.6595758525203162562e-6, + 2.9916615878138787094e-6, + 1.0968835125901264732e-6, + 3.7595411862360630091e-7, + 1.1992442782902770219e-7, + 3.5434777171421953043e-8, + 9.6498888961089633609e-9, + 2.4091773256475940779e-9, + 5.482835779709497755e-10, + 1.1306055347494680536e-10, + 2.0989335404511469109e-11, + 3.4841937670261059685e-12, + // 7th layer weights + 1.5700420292795931467, + 1.5640214037732320999, + 1.5520531698454121192, + 1.5342817381543034316, + 1.5109197230741697127, + 1.48224329788553807, + 1.4485862549613225916, + 1.4103329714462590129, + 1.3679105116808964881, + 1.3217801174437728579, + 1.2724283455378627082, + 1.2203581095793582207, + 1.1660798699324345766, + 1.1101031939653403796, + 1.0529288799552666556, + 0.99504180404613271514, + 0.93690461274566793366, + 0.87895234555278212039, + 0.82158803526696470334, + 0.7651792989089561367, + 0.71005590120546898385, + 0.65650824613162753076, + 0.60478673057840362158, + 0.55510187800363350959, + 0.5076251588319080997, + 0.4624903980553677613, + 0.41979566844501548066, + 0.37960556938665160999, + 0.3419537959230168323, + 0.30684590941791694932, + 0.27426222968906810637, + 0.24416077786983990868, + 0.21648020911729617038, + 0.19114268413342749532, + 0.16805663794826916233, + 0.14711941325785693248, + 0.12821973363120098675, + 0.11123999898874453035, + 0.096058391865189467849, + 0.082550788110701737654, + 0.070592469906866999352, + 0.060059642358636300319, + 0.05083075757257047107, + 0.042787652157725676034, + 0.035816505604196436523, + 0.029808628117310126969, + 0.024661087314753282511, + 0.020277183817500123926, + 0.016566786254247575375, + 0.013446536605285730674, + 0.010839937168255907211, + 0.0086773307495391815854, + 0.0068957859690660035329, + 0.0054388997976239984331, + 0.0042565295990178580165, + 0.0033044669940348302363, + 0.0025440657675291729678, + 0.0019418357759843675814, + 0.0014690143599429791058, + 0.0011011261134519383862, + 0.00081754101332469493115, + 0.00060103987991147422573, + 0.00043739495615911687786, + 0.00031497209186021200274, + 0.00022435965205008549104, + 0.00015802788400701191949, + 0.00011002112846666697224, + 0.000075683996586201477788, + 0.000051421497447658802092, + 0.0000344921247593431977, + 0.000022832118109036146591, + 0.000014908514031870608449, + 9.5981941283784710776e-6, + 6.0899100320949039256e-6, + 3.8061983264644899045e-6, + 2.3421667208528096843e-6, + 1.4183067155493917523e-6, + 8.4473756384859863469e-7, + 4.9458288702754198508e-7, + 2.8449923659159806339e-7, + 1.6069394579076224911e-7, + 8.9071395140242387124e-8, + 4.8420950198072369669e-8, + 2.579956822953589238e-8, + 1.3464645522302038796e-8, + 6.8784610955899001111e-9, + 3.4371856744650090511e-9, + 1.6788897682161906807e-9, + 8.0099784479729665356e-10, + 3.7299501843052790038e-10, + 1.6939457789411646876e-10, + 7.4967397573818224522e-11, + 3.230446433325236576e-11, + 1.3542512912336274432e-11, + 5.5182369468174885821e-12, + 2.1835922099233609052e-12 +}; // end weights + +} // end namespace Dumux + +#endif // include guard diff --git a/dumux/common/doubleexpintegrator.hh b/dumux/common/doubleexpintegrator.hh new file mode 100644 index 0000000000000000000000000000000000000000..693abc3b045146803e9ac755b834115732779f5c --- /dev/null +++ b/dumux/common/doubleexpintegrator.hh @@ -0,0 +1,206 @@ +// -*- 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/>. * + * * + * This version is modified after the original version by John D. Cook * + * see https://www.codeproject.com/ * + * Articles/31550/Fast-Numerical-Integration * + * which is licensed under BSD-2-clause, which reads as follows: * + * Copyright John D. Cook * + * * + * Redistribution and use in source and binary forms, with or without * + * modification, are permitted provided that the following * + * conditions are met: * + * 1. Redistributions of source code must retain the above * + * copyright notice, this list of conditions * + and the following disclaimer. * + * 2. Redistributions in binary form must reproduce the above * + * copyright notice, this list of conditions * + * and the following disclaimer * + * in the documentation and/or other materials * + * provided with the distribution. * + * * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * + * * + *****************************************************************************/ + +#ifndef DUMUX_COMMON_DOUBLEEXP_INTEGRATOR_HH +#define DUMUX_COMMON_DOUBLEEXP_INTEGRATOR_HH + +#include <cmath> +#include <limits> + +#include <dune/common/std/type_traits.hh> +#include <dumux/common/doubleexpintegrationconstants.hh> + +namespace Dumux { + +/*! + * \brief Numerical integration in one dimension using the double exponential method of M. Mori. + */ +template<class Scalar> +class DoubleExponentialIntegrator +{ +public: + /*! + * \brief Integrate an analytic function over a finite interval + * \param f the integrand (invocable with a single scalar) + * \param a lower limit of integration + * \param b upper limit of integration + * \param targetAbsoluteError desired bound on error + * \param numFunctionEvaluations number of function evaluations used + * \param f errorEstimate estimate for error in integration + * \return The value of the integral + */ + template<class Function, + typename std::enable_if_t<Dune::Std::is_invocable<Function, Scalar>::value>...> + static Scalar integrate(const Function& f, + const Scalar a, + const Scalar b, + const Scalar targetAbsoluteError, + int& numFunctionEvaluations, + Scalar& errorEstimate) + { + // Apply the linear change of variables x = ct + d + // $$\int_a^b f(x) dx = c \int_{-1}^1 f( ct + d ) dt$$ + // c = (b-a)/2, d = (a+b)/2 + + const Scalar c = 0.5*(b - a); + const Scalar d = 0.5*(a + b); + return integrateCore_(f, c, d, targetAbsoluteError, numFunctionEvaluations, errorEstimate, + doubleExponentialIntegrationAbcissas, doubleExponentialIntegrationWeights); + } + + /*! + * \brief Integrate an analytic function over a finite interval. + * \note This version overloaded to not require arguments passed in for + * function evaluation counts or error estimates. + * \param f the integrand (invocable with a single scalar) + * \param a lower integral bound + * \param b upper integral bound + * \param targetAbsoluteError desired absolute error in the result + * \return The value of the integral. + */ + template<class Function, + typename std::enable_if_t<Dune::Std::is_invocable<Function, Scalar>::value>...> + static Scalar integrate(const Function& f, + const Scalar a, + const Scalar b, + const Scalar targetAbsoluteError) + { + int numFunctionEvaluations; + Scalar errorEstimate; + return integrate(f, a, b, targetAbsoluteError, numFunctionEvaluations, errorEstimate); + } + +private: + // Integrate f(cx + d) with the given integration constants + template<class Function> + static Scalar integrateCore_(const Function& f, + const Scalar c, // slope of change of variables + const Scalar d, // intercept of change of variables + Scalar targetAbsoluteError, + int& numFunctionEvaluations, + Scalar& errorEstimate, + const double* abcissas, + const double* weights) + { + targetAbsoluteError /= c; + + // Offsets to where each level's integration constants start. + // The last element is not a beginning but an end. + static const int offsets[] = {1, 4, 7, 13, 25, 49, 97, 193}; + static const int numLevels = sizeof(offsets)/sizeof(int) - 1; + + Scalar newContribution = 0.0; + Scalar integral = 0.0; + Scalar h = 1.0; + errorEstimate = std::numeric_limits<Scalar>::max(); + Scalar previousDelta, currentDelta = std::numeric_limits<Scalar>::max(); + + integral = f(c*abcissas[0] + d) * weights[0]; + int i; + for (i = offsets[0]; i != offsets[1]; ++i) + integral += weights[i]*(f(c*abcissas[i] + d) + f(-c*abcissas[i] + d)); + + for (int level = 1; level != numLevels; ++level) + { + h *= 0.5; + newContribution = 0.0; + for (i = offsets[level]; i != offsets[level+1]; ++i) + newContribution += weights[i]*(f(c*abcissas[i] + d) + f(-c*abcissas[i] + d)); + newContribution *= h; + + // difference in consecutive integral estimates + previousDelta = currentDelta; + using std::abs; + currentDelta = abs(0.5*integral - newContribution); + integral = 0.5*integral + newContribution; + + // Once convergence kicks in, error is approximately squared at each step. + // Determine whether we're in the convergent region by looking at the trend in the error. + if (level == 1) + continue; // previousDelta meaningless, so cannot check convergence. + + // Exact comparison with zero is harmless here. Could possibly be replaced with + // a small positive upper limit on the size of currentDelta, but determining + // that upper limit would be difficult. At worse, the loop is executed more + // times than necessary. But no infinite loop can result since there is + // an upper bound on the loop variable. + if (currentDelta == 0.0) + break; + + using std::log; + const Scalar rate = log( currentDelta )/log( previousDelta ); // previousDelta != 0 or would have been kicked out previously + + if (rate > 1.9 && rate < 2.1) + { + // If convergence theory applied perfectly, r would be 2 in the convergence region. + // r close to 2 is good enough. We expect the difference between this integral estimate + // and the next one to be roughly delta^2. + errorEstimate = currentDelta*currentDelta; + } + else + { + // Not in the convergence region. Assume only that error is decreasing. + errorEstimate = currentDelta; + } + + if (errorEstimate < 0.1*targetAbsoluteError) + break; + } + + numFunctionEvaluations = 2*i - 1; + errorEstimate *= c; + return c*integral; + } +}; + +} // end namespace Dumux + +#endif diff --git a/dumux/common/geometry/CMakeLists.txt b/dumux/common/geometry/CMakeLists.txt index fd5972a41baebb36223d8a7cb7fbdd7756423c19..77968db349d5c6834f0dfa6a7e1054fe8bebd64d 100644 --- a/dumux/common/geometry/CMakeLists.txt +++ b/dumux/common/geometry/CMakeLists.txt @@ -9,5 +9,6 @@ intersectingentities.hh intersectspointgeometry.hh intersectspointsimplex.hh makegeometry.hh +refinementquadraturerule.hh triangulation.hh DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dumux/common/geometry) diff --git a/dumux/common/geometry/refinementquadraturerule.hh b/dumux/common/geometry/refinementquadraturerule.hh new file mode 100644 index 0000000000000000000000000000000000000000..114ec0960ad3bc70f8c813bf5df5f4db44bac39e --- /dev/null +++ b/dumux/common/geometry/refinementquadraturerule.hh @@ -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 + * \ingroup Geometry + * \author Timo Koch + * \brief A quadrature based on refinement + */ + +#ifndef DUMUX_COMMON_REFINEMENT_QUADRATURERULE_HH +#define DUMUX_COMMON_REFINEMENT_QUADRATURERULE_HH + +#include <dune/geometry/quadraturerules.hh> +#include <dune/geometry/virtualrefinement.hh> +#include <dumux/common/math.hh> + +namespace Dumux { + +/*! + * \ingroup Geometry + * \brief A "quadrature" based on virtual refinement + */ +template<typename ct, int mydim> +class RefinementQuadratureRule : public Dune::QuadratureRule<ct, mydim> +{ +public: + //! The space dimension + static constexpr int dim = mydim; + + //! The highest quadrature order available + static constexpr int highest_order = 1; + + ~RefinementQuadratureRule() final {} + + RefinementQuadratureRule(Dune::GeometryType type, int levels) + : Dune::QuadratureRule<ct, dim>(type, highest_order) + { + auto& refinement = Dune::buildRefinement<dim, ct>(type, type); + const auto tag = Dune::refinementLevels(levels); + + // get the vertices + const auto numVertices = refinement.nVertices(tag); + std::vector<Dune::FieldVector<ct, dim>> points; points.reserve(numVertices); + auto vIt = refinement.vBegin(tag); + const auto vItEnd = refinement.vEnd(tag); + for (; vIt != vItEnd; ++vIt) + points.emplace_back(vIt.coords()); + + // go over all elements and get center and volume + const auto numElements = refinement.nElements(tag); + this->reserve(numElements); + auto eIt = refinement.eBegin(tag); + const auto eItEnd = refinement.eEnd(tag); + for (; eIt != eItEnd; ++eIt) + { + // quadrature point in the centroid of the sub-element and weight is its volume + const auto weight = computeVolume_(type, points, eIt.vertexIndices()); + this->emplace_back(eIt.coords(), weight); + } + } + +private: + template<class VertexIndices> + ct computeVolume_(Dune::GeometryType t, const std::vector<Dune::FieldVector<ct, dim>>& points, const VertexIndices& indices) const + { + if (t != Dune::GeometryTypes::simplex(2)) + DUNE_THROW(Dune::NotImplemented, "Only implemented for 2d simplices"); + + const auto ab = points[indices[1]] - points[indices[0]]; + const auto ac = points[indices[2]] - points[indices[0]]; + using std::abs; + return abs(crossProduct(ab, ac))*0.5; + } +}; + +} // end namespace Dumux + +#endif diff --git a/dumux/common/integrate.hh b/dumux/common/integrate.hh index f458f763655027d37d1d3e093c01d8ad33f482e4..d2ece0d8b6b3b32672790bd87c128da80a161da4 100644 --- a/dumux/common/integrate.hh +++ b/dumux/common/integrate.hh @@ -29,11 +29,13 @@ #include <dune/geometry/quadraturerules.hh> #include <dune/common/concept.hh> +#include <dune/common/std/type_traits.hh> #if HAVE_DUNE_FUNCTIONS #include <dune/functions/gridfunctions/gridfunction.hh> #endif +#include <dumux/common/doubleexpintegrator.hh> #include <dumux/discretization/evalsolution.hh> #include <dumux/discretization/elementsolution.hh> #include <dumux/common/typetraits/typetraits.hh> @@ -251,6 +253,24 @@ auto integrateL2Error(const GridView& gv, } #endif +/*! + * \brief Integrate a scalar function + * \param f the integrand (invocable with a single scalar) + * \param a lower integral bound + * \param b upper integral bound + * \param targetAbsoluteError desired absolute error in the result + * \return The value of the integral + */ +template<class Scalar, class Function, + typename std::enable_if_t<Dune::Std::is_invocable<Function, Scalar>::value>...> +Scalar integrateScalarFunction(const Function& f, + const Scalar lowerBound, + const Scalar upperBound, + const Scalar targetAbsoluteError = 1e-13) +{ + return DoubleExponentialIntegrator<Scalar>::integrate(f, lowerBound, upperBound, targetAbsoluteError); +} + } // end namespace Dumux #endif diff --git a/dumux/nonlinear/CMakeLists.txt b/dumux/nonlinear/CMakeLists.txt index 92640a44c6e3e6da21a55c9a677c213957358e24..2c5c2050a30e68e34df4e6f3a9f6f50fcf28b3db 100644 --- a/dumux/nonlinear/CMakeLists.txt +++ b/dumux/nonlinear/CMakeLists.txt @@ -1,4 +1,5 @@ install(FILES +findscalarroot.hh newtonconvergencewriter.hh newtonsolver.hh staggerednewtonconvergencewriter.hh diff --git a/dumux/nonlinear/findscalarroot.hh b/dumux/nonlinear/findscalarroot.hh new file mode 100644 index 0000000000000000000000000000000000000000..7c230d47fe9f5f9f57a10b3dc6349ea31235eca9 --- /dev/null +++ b/dumux/nonlinear/findscalarroot.hh @@ -0,0 +1,206 @@ +// -*- 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 + * \ingroup Common + * \brief Root finding algorithms for scalar functions + */ +#ifndef DUMUX_COMMON_SCALAR_ROOT_FINDING_HH +#define DUMUX_COMMON_SCALAR_ROOT_FINDING_HH + +#include <cmath> +#include <limits> + +#include <dune/common/std/type_traits.hh> + +#include <dumux/common/exceptions.hh> +#include <dumux/common/parameters.hh> +#include <dumux/common/numericdifferentiation.hh> + +namespace Dumux { + +/*! + * \ingroup Common + * \brief Newton's root finding algorithm for scalar functions (secant method) + * \param xOld initial guess + * \param residual Residual function + * \param derivative Derivative of the residual + * \param tol Relative shift tolerance + * \param maxIter Maximum number of iterations + * \todo replace std::result_of by std::invoke_result for C++17 + */ +template<class Scalar, class ResFunc, class DerivFunc, + typename std::enable_if_t<Dune::Std::is_invocable<ResFunc, Scalar>::value && + Dune::Std::is_invocable<DerivFunc, Scalar>::value>...> +Scalar findScalarRootNewton(Scalar xOld, const ResFunc& residual, const DerivFunc& derivative, + const Scalar tol = 1e-13, const int maxIter = 200) +{ + Scalar xNew = xOld; + Scalar r = residual(xNew); + + int n = maxIter; + Scalar relativeShift = std::numeric_limits<Scalar>::max(); + while (relativeShift > tol && n > 0) + { + xNew = xOld - r/derivative(xOld); + r = residual(xNew); + + using std::abs; using std::max; + relativeShift = abs(xOld-xNew)/max(abs(xOld), abs(xNew)); + xOld = xNew; + n--; + } + + using std::isfinite; + if (!isfinite(r)) + DUNE_THROW(NumericalProblem, "Residual is not finite: " << r << " after " << maxIter - n << " iterations!"); + + if (relativeShift > tol) + DUNE_THROW(NumericalProblem, "Scalar newton solver did not converge after " << maxIter << " iterations!"); + + return xNew; +} + +/*! + * \ingroup Common + * \brief Newton's root finding algorithm for scalar functions (secant method) + * \note The derivative is numerically computed. If the derivative is know use signature with derivative function. + * \param xOld initial guess + * \param residual Residual function + * \param tol Relative shift tolerance + * \param maxIter Maximum number of iterations + */ +template<class Scalar, class ResFunc, + typename std::enable_if_t<Dune::Std::is_invocable<ResFunc, Scalar>::value>...> +Scalar findScalarRootNewton(Scalar xOld, const ResFunc& residual, + const Scalar tol = 1e-13, const int maxIter = 200) +{ + const auto eps = NumericDifferentiation::epsilon(xOld); + auto derivative = [&](const auto x){ return (residual(x + eps)-residual(x))/eps; }; + return findScalarRootNewton(xOld, residual, derivative, tol, maxIter); +} + +/*! + * \ingroup Common + * \brief Brent's root finding algorithm for scalar functions + * \note Modified from pseudo-code on wikipedia: https://en.wikipedia.org/wiki/Brent%27s_method + * \note See also R.P. Brent "An algorithm with guaranteed convergence for finding a zero of a function", The Computer Journal (1971). + * \note This is usually more robust than Newton's method + * \param a Lower bound + * \param b Upper bound + * \param residual Residual function + * \param tol Relative shift tolerance + * \param maxIter Maximum number of iterations + */ +template<class Scalar, class ResFunc, + typename std::enable_if_t<Dune::Std::is_invocable<ResFunc, Scalar>::value>...> +Scalar findScalarRootBrent(Scalar a, Scalar b, const ResFunc& residual, + const Scalar tol = 1e-13, const int maxIter = 200) +{ + // precompute the residuals (minimize function evaluations) + Scalar fa = residual(a); + Scalar fb = residual(b); + Scalar fs = 0.0; + + // check if the root is inside the given interval + using std::signbit; + if (!signbit(fa*fb)) + DUNE_THROW(NumericalProblem, "Brent's algorithm failed: [a,b] does not contain any, or no uniquely defined root!"); + + // sort bounds + using std::abs; using std::swap; + if (abs(fa) < abs(fb)) + { + swap(a, b); + swap(fa, fb); + } + + Scalar c = a; + Scalar fc = fa; + Scalar d = 0.0; + Scalar s = 0.0; + bool flag = true; + + for (int i = 0; i < maxIter; ++i) + { + // stopping criterion + using std::max; + if (abs(b-a) < tol*max(abs(a), abs(b))) + return b; + + // inverse quadratic interpolation + if (fa != fc && fb != fc) + { + const auto fab = fa-fb; + const auto fac = fa-fc; + const auto fbc = fb-fc; + s = a*fb*fc/(fab*fac) - b*fa*fc/(fab*fbc) + c*fa*fb/(fac*fbc); + } + + // secant method + else + { + s = b - fb*(b-a)/(fb-fa); + } + + // bisection method + if ( (s < (3*a + b)*0.25 || s > b) + || (flag && abs(s-b) >= abs(b-c)*0.5) + || (!flag && abs(s-b) >= abs(c-d)*0.5) + || (flag && abs(b-c) < tol*max(abs(b), abs(c))) + || (!flag && abs(c-d) < tol*max(abs(c), abs(d))) ) + { + s = (a+b)*0.5; + flag = true; + } + else + flag = false; + + // compute residual at new guess + fs = residual(s); + d = c; + c = b; + fc = fb; + + // check on which side of the root s falls + if (fa*fs < 0.0) + { + b = s; + fb = fs; + } + else + { + a = s; + fa = fs; + } + + // sort if necessary + if (abs(fa) < abs(fb)) + { + swap(a, b); + swap(fa, fb); + } + } + + DUNE_THROW(NumericalProblem, "Brent's algorithm didn't converge after " << maxIter << " iterations!"); +} + +} // end namespace Dumux + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 416332236d6dd28c43906415eda98911378d9bb3..b5c7428a4b7e7b3edb40d5f60c4fd3972c74014d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,5 +4,6 @@ add_subdirectory(freeflow) add_subdirectory(io) add_subdirectory(material) add_subdirectory(multidomain) +add_subdirectory(nonlinear) add_subdirectory(porousmediumflow) add_subdirectory(discretization) diff --git a/test/common/CMakeLists.txt b/test/common/CMakeLists.txt index 96a770ad978cb059e815d44e68eb29eb45bab6ab..80146db8c5a314ffa848258085b72f327ca779b1 100644 --- a/test/common/CMakeLists.txt +++ b/test/common/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(boundingboxtree) add_subdirectory(functions) add_subdirectory(geometry) +add_subdirectory(integrate) add_subdirectory(math) add_subdirectory(parameters) add_subdirectory(propertysystem) @@ -8,4 +9,4 @@ add_subdirectory(spline) add_subdirectory(timeloop) add_subdirectory(typetraits) -dumux_add_test(SOURCES test_partial.cc) +dumux_add_test(SOURCES test_partial.cc LABELS unit) diff --git a/test/common/geometry/CMakeLists.txt b/test/common/geometry/CMakeLists.txt index f1b5b270e8401ebd8378ea580afbdcd0209fe0df..c2e2c5c6c713490e9ef35f6d98a3e371408db367 100644 --- a/test/common/geometry/CMakeLists.txt +++ b/test/common/geometry/CMakeLists.txt @@ -6,3 +6,4 @@ 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_graham_convex_hull.cc LABELS unit) dumux_add_test(SOURCES test_makegeometry.cc LABELS unit) +dumux_add_test(SOURCES test_refinementquadraturerule.cc LABELS unit) diff --git a/test/common/geometry/test_refinementquadraturerule.cc b/test/common/geometry/test_refinementquadraturerule.cc new file mode 100644 index 0000000000000000000000000000000000000000..7511e351a67b87cdce9bd712de69bb261cee1955 --- /dev/null +++ b/test/common/geometry/test_refinementquadraturerule.cc @@ -0,0 +1,41 @@ +#include <config.h> + +#include <iostream> +#include <array> + +#include <dune/common/fvector.hh> +#include <dune/common/exceptions.hh> +#include <dune/common/float_cmp.hh> +#include <dumux/common/geometry/refinementquadraturerule.hh> +#include <dune/geometry/affinegeometry.hh> + +int main (int argc, char *argv[]) try +{ + using namespace Dumux; + + // make a simplex + const auto simplex = Dune::AffineGeometry<double, 2, 3>( + Dune::GeometryTypes::simplex(2), + std::array<Dune::FieldVector<double, 3>, 3>{{{0.0, 0.0, 1.0}, {2.0, 3.0, 4.0}, {3.0, -1.0, 1.0}}} + ); + + // make quadrature rule + const auto quad = RefinementQuadratureRule<double, 2>(Dune::GeometryTypes::simplex(2), /*refinementlevels=*/3); + + // integrate volume + double volume = 0.0; + for (const auto& qp : quad) + volume += qp.weight()*simplex.integrationElement(qp.position()); + + if (Dune::FloatCmp::ne(volume, simplex.volume())) + DUNE_THROW(Dune::Exception, "Integration yields wrong simplex volume: " << volume << ", should be " << simplex.volume()); + + return 0; +} +// ////////////////////////////////// +// Error handler +// ///////////////////////////////// +catch (const Dune::Exception& e) { + std::cout << e << std::endl; + return 1; +} diff --git a/test/common/integrate/CMakeLists.txt b/test/common/integrate/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..54f255b05100a9714fe6b8ecd2fbbbb253feacaa --- /dev/null +++ b/test/common/integrate/CMakeLists.txt @@ -0,0 +1 @@ +dumux_add_test(SOURCES test_integratescalar.cc LABELS unit) diff --git a/test/common/integrate/test_integratescalar.cc b/test/common/integrate/test_integratescalar.cc new file mode 100644 index 0000000000000000000000000000000000000000..9841040ef4b9d6b10782b1f36d8dcd5458dee16f --- /dev/null +++ b/test/common/integrate/test_integratescalar.cc @@ -0,0 +1,90 @@ +#include <config.h> + +#include <iostream> +#include <iomanip> +#include <cmath> + +#include <dune/common/exceptions.hh> +#include <dune/common/float_cmp.hh> +#include <dumux/common/integrate.hh> +#include <dumux/nonlinear/findscalarroot.hh> + +namespace Dumux { + +class VanGenuchtenKrw +{ +public: + VanGenuchtenKrw(double alpha, double n, double l = 0.5) + : alpha_(alpha), n_(n), m_(1.0 - 1.0/n), l_(l) + {} + + double operator()(double pw) const + { + using std::pow; + const auto pc = 1.0e5-pw; + const auto swe = pow(pow(alpha_*pc, n_) + 1.0, -m_); + const auto r = 1.0 - pow(1.0 - pow(swe, 1.0/m_), m_); + return pow(swe, l_)*r*r; + } + +private: + double alpha_, n_, m_, l_; +}; + +} // end namespace Dumux + +int main(int argc, char* argv[]) try +{ + using namespace Dumux; + + { + // test some integral where we know the analytical expression + const auto func = [](const double x){ return std::exp(2.0*x); }; + const auto exactIntegral = 0.5*(std::exp(10.0)-std::exp(-2.0)); + const auto numIntegral = integrateScalarFunction(func, -1.0, 5.0, 1e-8); + if (Dune::FloatCmp::ne<double, Dune::FloatCmp::CmpStyle::absolute>(exactIntegral, numIntegral, 1e-8)) + DUNE_THROW(Dune::Exception, "Wrong integral: " << numIntegral << ", should be " << exactIntegral); + } + + { + // test some integral where we know the analytical expression + const auto func = [](const double x){ return pow(1.0 - x, 5.0)*pow(x, -1.0/3.0); }; + const auto exactIntegral = 2187.0/5236.0; + const auto numIntegral = integrateScalarFunction(func, 0.0, 1.0, 1e-8); + if (Dune::FloatCmp::ne<double, Dune::FloatCmp::CmpStyle::absolute>(exactIntegral, numIntegral, 1e-8)) + DUNE_THROW(Dune::Exception, "Wrong integral: " << numIntegral << ", should be " << exactIntegral); + } + + { + // relative permeability function of water pressure + const auto krFunc = VanGenuchtenKrw(/*alpha=*/1e-4, /*n=*/3.0); + + // introduce a transformed variable u(p) = int_{-1.5e6}^{p} kr(q)dq + const auto kirchhoffTransform = [&](const double p){ + return integrateScalarFunction(krFunc, -1.5e6, p, 1e-16); + }; + + // compute the inverse of the transformation numerically + const auto inverseTransform = [&](const auto u){ + const auto residual = [&, u](const auto& p){ return u - kirchhoffTransform(p); }; + return findScalarRootBrent(-1.6e6, 1.0e5, residual, 1e-10, 200000); + }; + + // compute transformation and inverse for several pressures + for (const auto p : {0.9e5, 0.1e5, 0.0, -5.0e5, -1.5e6}) + { + const auto u = kirchhoffTransform(p); + const auto pNum = inverseTransform(u); + if (Dune::FloatCmp::ne<double, Dune::FloatCmp::CmpStyle::absolute>(p, pNum, 0.001)) + DUNE_THROW(Dune::Exception, "Inverse did not recover original pressure value: " + << std::setprecision(15) << pNum << ", should be " << p); + } + } + + return 0; +} +catch (const Dune::Exception& e) +{ + std::cout << e << std::endl; + return 1; +} diff --git a/test/nonlinear/CMakeLists.txt b/test/nonlinear/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..3640235bdcfbdd1b4fa0aacce2527f8cc8114bf9 --- /dev/null +++ b/test/nonlinear/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(findscalarroot) diff --git a/test/nonlinear/findscalarroot/CMakeLists.txt b/test/nonlinear/findscalarroot/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..004545b738f41d14f80c12ced54202de6cafaf80 --- /dev/null +++ b/test/nonlinear/findscalarroot/CMakeLists.txt @@ -0,0 +1,2 @@ +dumux_add_test(SOURCES test_findscalarroot.cc + LABELS unit nonlinear) diff --git a/test/nonlinear/findscalarroot/test_findscalarroot.cc b/test/nonlinear/findscalarroot/test_findscalarroot.cc new file mode 100644 index 0000000000000000000000000000000000000000..88dcbea99255a45c4abeb97747ddd4b0c75db6bb --- /dev/null +++ b/test/nonlinear/findscalarroot/test_findscalarroot.cc @@ -0,0 +1,50 @@ +#include <config.h> + +#include <iostream> +#include <iomanip> +#include <cmath> + +#include <dune/common/exceptions.hh> +#include <dune/common/float_cmp.hh> +#include <dumux/nonlinear/findscalarroot.hh> + +int main(int argc, char* argv[]) try +{ + using namespace Dumux; + + const auto func = [](const double x){ return x*x - 5.0; }; + const auto deriv = [](const double x){ return 2*x; }; + const auto absTol = 1e-15; + const auto relTol = 1e-15; + + const auto rootNewton = findScalarRootNewton(5.0, func, deriv, absTol); + const auto rootNewtonNumDiff = findScalarRootNewton(5.0, func, absTol); + const auto rootBrent = findScalarRootBrent(0.0, 5.0, func, relTol); + const auto rootExact = std::sqrt(5.0); + + if (Dune::FloatCmp::ne(rootExact, rootNewton, absTol)) + DUNE_THROW(Dune::Exception, "Wrong root for Newton algorithm (exact derivative)! Relative error: " << std::abs(rootExact-rootNewton)/rootExact); + if (Dune::FloatCmp::ne(rootExact, rootNewtonNumDiff, absTol)) + DUNE_THROW(Dune::Exception, "Wrong root for Newton algorithm (numeric differentiation)! Relative error: " << std::abs(rootExact-rootNewtonNumDiff)/rootExact); + if (Dune::FloatCmp::ne(rootExact, rootBrent, absTol)) + DUNE_THROW(Dune::Exception, "Wrong root for Brent algorithm! Relative error: " << std::abs(rootExact-rootBrent)/rootExact); + + std::cout << "Positive root of f(x) = (x*x - 5) is\n" << std::setprecision(15) + << "-- Exact: " << rootExact << "\n" + << "-- Newton (exact derivative): " << rootNewton << "\n" + << "-- Newton (numeric derivative): " << rootNewtonNumDiff << "\n" + << "-- Brent: " << rootBrent << std::endl; + + return 0; + +} +catch (const Dune::Exception& e) +{ + std::cout << e << std::endl; + return 1; +} +catch (...) +{ + std::cout << "Unknown exception thrown!" << std::endl; + return 1; +}