Skip to content
Snippets Groups Projects
Commit 0cc6592e authored by Dmitri Naumov's avatar Dmitri Naumov
Browse files

Merge branch 'KahanSum' into 'master'

Kahan sum

See merge request ogs/ogs!5065
parents 7552550e a9094651
No related branches found
No related tags found
No related merge requests found
/**
* \file
* \copyright
* Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org)
* Distributed under a Modified BSD License.
* See accompanying file LICENSE.txt or
* http://www.opengeosys.org/project/license
*/
#pragma once
#include <spdlog/fmt/bundled/ostream.h>
#include <iomanip>
#include <ostream>
#include <range/v3/range/concepts.hpp>
#include <range/v3/range/traits.hpp>
namespace MathLib
{
class KahanSum;
}
template <>
struct fmt::formatter<MathLib::KahanSum> : fmt::ostream_formatter
{
};
namespace MathLib
{
class KahanSum
{
public:
explicit constexpr KahanSum(double const value = 0) : value_(value) {}
explicit constexpr KahanSum(ranges::range auto const& range) : value_(0)
{
for (auto const v : range)
{
*this += v;
}
}
constexpr KahanSum operator+(double const increment) const
{
KahanSum result = *this;
return result += increment;
}
constexpr KahanSum operator-(double const increment) const
{
KahanSum result = *this;
return result += -increment;
}
constexpr KahanSum& operator-=(double const increment)
{
return *this += -increment;
}
constexpr KahanSum& operator+=(double const increment)
{
double const y = increment - correction_;
double const t = value_ + y;
correction_ = (t - value_) - y;
value_ = t;
return *this;
}
constexpr double value() const { return value_; }
constexpr double operator()() const { return value_; }
friend inline std::ostream& operator<<(std::ostream& os, KahanSum const& x)
{
auto const precision = os.precision();
return os << std::setprecision(
std::numeric_limits<double>::max_digits10)
<< x.value() << " (± " << x.correction_ << ')'
<< std::setprecision(precision);
}
private:
double value_;
double correction_ = 0.;
};
} // namespace MathLib
/**
* \file
* \copyright
* Copyright (c) 2012-2024, 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 "MathLib/KahanSum.h"
#include <gtest/gtest.h>
#include <range/v3/view/reverse.hpp>
#include "Tests/AutoCheckTools.h"
using namespace MathLib;
namespace ac = autocheck;
struct NonNegativeDoubleListGenerator
{
using result_type = std::vector<double>;
result_type operator()(std::size_t const size) const
{
result_type r;
r.reserve(size);
auto positive_double_gen =
ac::map(&ac::absoluteValue, ac::generator<double>{});
std::generate_n(std::back_inserter(r), size,
ac::fix(size, positive_double_gen));
return r;
}
};
struct MathLibKahanSumPropertyTest : public ::testing::Test
{
ac::gtest_reporter gtest_reporter;
};
TEST_F(MathLibKahanSumPropertyTest, SortedSequences)
{
auto property = [](const std::vector<double>& sequence)
{
// Sort to get an optimal behaviour for sum computation.
std::vector<double> sorted{sequence};
std::sort(sorted.begin(), sorted.end(),
[](double const a, double const b)
{ return std::abs(a) < std::abs(b); });
KahanSum const a{sorted};
// Reverse sort yields worst sum behaviour resulting in maximum error.
KahanSum const b{sorted | ranges::views::reverse};
if (a() == b())
{
return true;
}
// Testing the relative error to be within the theoretical limits. Keep
// in mind, that the sequence is constructed with non-negative numbers
// and the condition number is 1.
return std::abs(a() - b()) / ((a() + b()) / 2.) <
2. * std::numeric_limits<double>::epsilon();
};
// The non-negative double list generator is used to keep the condition
// number, which is defined as sum|a_i|/|sum a_i|, equal to 1.
autocheck::check<std::vector<double>>(
property, 1000, ac::make_arbitrary(NonNegativeDoubleListGenerator{}),
gtest_reporter);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment