Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
kuiperzone authored Dec 20, 2019
1 parent 855cc98 commit ada17ab
Show file tree
Hide file tree
Showing 11 changed files with 818 additions and 0 deletions.
31 changes: 31 additions & 0 deletions Accumulators.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29519.181
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompensatedAccumulators", "CompensatedAccumulators\CompensatedAccumulators.csproj", "{B44E95B2-6403-4C76-873E-C338502A8CAD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompensatedAccumulators.Test", "CompensatedAccumulators.Test\CompensatedAccumulators.Test.csproj", "{E9E5D48D-A70E-4D08-9EA0-CC9EC712576F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B44E95B2-6403-4C76-873E-C338502A8CAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B44E95B2-6403-4C76-873E-C338502A8CAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B44E95B2-6403-4C76-873E-C338502A8CAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B44E95B2-6403-4C76-873E-C338502A8CAD}.Release|Any CPU.Build.0 = Release|Any CPU
{E9E5D48D-A70E-4D08-9EA0-CC9EC712576F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9E5D48D-A70E-4D08-9EA0-CC9EC712576F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9E5D48D-A70E-4D08-9EA0-CC9EC712576F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9E5D48D-A70E-4D08-9EA0-CC9EC712576F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6B955F72-BAF7-4423-A591-D2AE25DF73A8}
EndGlobalSection
EndGlobal
20 changes: 20 additions & 0 deletions CompensatedAccumulators.Test/CompensatedAccumulators.Test.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CompensatedAccumulators\CompensatedAccumulators.csproj" />
</ItemGroup>

</Project>
172 changes: 172 additions & 0 deletions CompensatedAccumulators.Test/CompensatedSumTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//---------------------------------------------------------------------------
// PROJECT : Compensated Accumulators
// COPYRIGHT : Andy Thomas (C) 2019
// WEB URL : https://kuiper.zone
// LICENSE : GPLv3
//---------------------------------------------------------------------------

using System;
using System.Diagnostics;
using Xunit;
using Xunit.Abstractions;

namespace CompensatedAccumulators.Test
{
public class CompensatedSumTest
{
/// <summary>
/// Implementation kind used in test theory data.
/// </summary>
public enum SumKind {Naive, Pairwise, Kahan, Neumaier, Klein};

private readonly ITestOutputHelper helper;

/// <summary>
/// Constructor with test helper.
/// </summary>
public CompensatedSumTest(ITestOutputHelper hlp)
{
helper = hlp;
}

/// <summary>
/// Test result of summation is not lost due to presence (and subtraction) of high values.
/// Not all accumulators will pass this test.
/// </summary>
[Theory]
[InlineData(SumKind.Neumaier)]
[InlineData(SumKind.Klein)]
public void Add_PetersTest_ExpectSumOf2(SumKind kind)
{
// https://en.wikipedia.org/wiki/Kahan_summation_algorithm
var sum = CreateSum(kind);
sum.Add(1.0);
sum.Add(1E100);
sum.Add(1.0);
sum.Add(-1E100);

// Only Neumaier and Klein both give correct answer 2.0.
// Others expected to give 0.
helper.WriteLine(sum.Value.ToString());

Assert.Equal(2.0, sum.Value, 10);
}

/// <summary>
/// PSRNG zero sum sequence. Expect better result than naive summation.
/// </summary>
[Theory]
[InlineData(SumKind.Pairwise)]
[InlineData(SumKind.Kahan)]
[InlineData(SumKind.Neumaier)]
[InlineData(SumKind.Klein)]
[InlineData(SumKind.Naive)]
public void Add_PsrngZeroSum_ExpectBetterAccuracyThanNaive(SumKind kind)
{
const double Spread = 1E7;

var sum = CreateSum(kind);
var naive = CreateSum(SumKind.Naive);

var rand = new Random(2002);

for(int n = 0; n < 1000000; ++n)
{
double x = rand.NextDouble() * Spread;
sum.Add(x);
naive.Add(x);
}

rand = new Random(2002);

for(int n = 0; n < 1000000; ++n)
{
double x = rand.NextDouble() * Spread;
sum.Add(-x);
naive.Add(-x);
}

helper.WriteLine("RESULT: " + Math.Abs(sum.Value));
helper.WriteLine("NAIVE: " + Math.Abs(naive.Value));

// Since we also test Naive as we want output, test
// LTE instead of LT.
Assert.True(Math.Abs(sum.Value) <= Math.Abs(naive.Value));
}

/// <summary>
/// Calling resets fully resets the accumulator.
/// </summary>
[Theory]
[InlineData(SumKind.Pairwise)]
[InlineData(SumKind.Kahan)]
[InlineData(SumKind.Neumaier)]
[InlineData(SumKind.Klein)]
public void Reset_PerformsFullReset(SumKind kind)
{
var sum = CreateSum(kind);

sum.Add(1.5);
sum.Clear();
Assert.Equal(0.0, sum.Value, 10);

// Restarts
sum.Add(1.5);
Assert.Equal(1.5, sum.Value, 10);
}

/// <summary>
/// Measure accumulator performance. Result written to helper. NaiveSum also test for comparison.
/// </summary>
[Theory]
[InlineData(SumKind.Pairwise)]
[InlineData(SumKind.Kahan)]
[InlineData(SumKind.Neumaier)]
[InlineData(SumKind.Klein)]
[InlineData(SumKind.Naive)]
public void Add_MeasurePerformance(SumKind kind)
{
const long TicksPerSecond = 1000 * 1000 * 10;

var sum = CreateSum(kind);

// JIT
sum.Add(1);
sum.Clear();

double elap;
long count = 0;
var sw = Stopwatch.StartNew();

do
{
for(int n = 0; n < 1000; ++n)
{
sum.Add(++count);
}

elap = (double)sw.ElapsedTicks / TicksPerSecond;

} while (elap < 1.0);

helper.WriteLine("Count: " + count);
helper.WriteLine("Elapsed: " + elap + " sec");

helper.WriteLine("RATE: " + count / elap + " per sec");
helper.WriteLine("TIME: " + 1E9 * elap / count + " ns");
}

private static ICompensatedSum CreateSum(SumKind kind)
{
switch(kind)
{
case SumKind.Pairwise: return new PairwiseSum();
case SumKind.Kahan: return new KahanSum();
case SumKind.Neumaier: return new NeumaierSum();
case SumKind.Klein: return new KleinSum();
default: return new NaiveSum();
}
}

}
}
61 changes: 61 additions & 0 deletions CompensatedAccumulators.Test/NaiveSum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//---------------------------------------------------------------------------
// PROJECT : Compensated Accumulators
// COPYRIGHT : Andy Thomas (C) 2019
// WEB URL : https://kuiper.zone
// LICENSE : GPLv3
//---------------------------------------------------------------------------

namespace CompensatedAccumulators.Test
{
/// <summary>
/// Naive sum implementation for test comparison only.
/// </summary>
public class NaiveSum : ICompensatedSum
{
/// <summary>
/// Default constructor.
/// </summary>
public NaiveSum()
{
}

/// <summary>
/// Constructor with initial value.
/// </summary>
public NaiveSum(double x)
{
Value = x;
}

/// <summary>
/// Implements <see cref="ICompensatedSum.Value"/>.
/// </summary>
public double Value { get; set; }

/// <summary>
/// Implements <see cref="ICompensatedSum.Add(double)"/>.
/// </summary>
public void Add(double x)
{
Value += x;
}

/// <summary>
/// Implements <see cref="ICompensatedSum.Clear()"/>.
/// </summary>
public void Clear()
{
Value = 0;
}

/// <summary>
/// Implements <see cref="ICompensatedSum.Clone()"/>.
/// </summary>
public ICompensatedSum Clone()
{
return new NaiveSum(Value);
}

}

}
7 changes: 7 additions & 0 deletions CompensatedAccumulators/CompensatedAccumulators.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

</Project>
36 changes: 36 additions & 0 deletions CompensatedAccumulators/ICompensatedSum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//---------------------------------------------------------------------------
// PROJECT : Compensated Accumulators
// COPYRIGHT : Andy Thomas (C) 2019
// WEB URL : https://kuiper.zone
// LICENSE : GPLv3
//---------------------------------------------------------------------------

namespace CompensatedAccumulators
{
/// <summary>
/// An interface for a compensated sum algorithm. A "compensated sum" offers reduced numerical
/// error when summing finite precision values in comparison to "naive" summation.
/// </summary>
public interface ICompensatedSum
{
/// <summary>
/// Get or sets the current summation value.
/// </summary>
double Value { get; set; }

/// <summary>
/// Adds 'x' to the <see cref="Value"/> sum, using a compensation technique designed to reduce numerical
/// error.
void Add(double x);

/// <summary>
/// Sets the sum value to zero and resets initial state.
/// </summary>
void Clear();

/// <summary>
/// Creates a "deep" copy of the instance with the same value and internal state. Returns the new instance.
/// </summary>
ICompensatedSum Clone();
}
}
Loading

0 comments on commit ada17ab

Please sign in to comment.