Skip to content

Commit e36163b

Browse files
Add LocationEstimatorSimulation
1 parent f7c6633 commit e36163b

File tree

9 files changed

+165
-5
lines changed

9 files changed

+165
-5
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Perfolizer.Mathematics.Distributions.ContinuousDistributions;
2+
using Perfolizer.Mathematics.GenericEstimators;
3+
using Perfolizer.Mathematics.LocationEstimators;
4+
using Perfolizer.Mathematics.QuantileEstimators;
5+
using Perfolizer.Simulations.Util;
6+
7+
namespace Perfolizer.Simulations;
8+
9+
public class LocationEstimatorSimulation
10+
{
11+
public void Run(string[] args)
12+
{
13+
var simulation =
14+
new EfficiencySimulation<ILocationEstimator>((estimator, sample) => estimator.Location(sample))
15+
.AddEstimator("Mean", MeanEstimator.Instance)
16+
.AddEstimator("Median", SimpleQuantileEstimator.Instance.ToLocationEstimator())
17+
.AddEstimator("HL", HodgesLehmannEstimator.Instance)
18+
.AddDistribution("Normal", NormalDistribution.Standard.Random(1729))
19+
.AddSampleSizes(Enumerable.Range(2, 99).ToArray());
20+
21+
var rows = simulation.Simulate();
22+
23+
foreach (var row in rows)
24+
{
25+
Console.WriteLine(row.Distribution + " / N = " + row.SampleSize);
26+
foreach ((string key, double value) in row.RelativeEfficiency)
27+
Console.WriteLine(" " + key.PadRight(6) + " : " + Math.Round(value * 100, 1) + "%");
28+
Console.WriteLine();
29+
}
30+
}
31+
}

src/Perfolizer/Perfolizer.Simulations/Program.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
static class Program
44
{
5-
private static readonly Dictionary<string, Action<string[]>> Simulations = new Dictionary<string, Action<string[]>>
5+
private static readonly Dictionary<string, Action<string[]>> Simulations = new()
66
{
7-
{"RqqPelt", args => new RqqPeltSimulation().Run(args)},
8-
{"QuantileCi", args => new QuantileCiSimulation().Run(args)},
7+
{ "RqqPelt", args => new RqqPeltSimulation().Run(args) },
8+
{ "QuantileCi", args => new QuantileCiSimulation().Run(args) },
9+
{ "Location", args => new LocationEstimatorSimulation().Run(args) },
910
};
1011

1112
private static void PrintAvailableSimulations()
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using Perfolizer.Collections;
2+
using Perfolizer.Common;
3+
using Perfolizer.Mathematics.Randomization;
4+
5+
namespace Perfolizer.Simulations.Util;
6+
7+
public class EfficiencySimulation<TEstimator>(
8+
Func<TEstimator, Sample, double> estimate,
9+
int sampleCount = EfficiencySimulation<TEstimator>.DefaultSampleCount)
10+
where TEstimator : notnull
11+
{
12+
private const int DefaultSampleCount = 100_000;
13+
14+
private readonly Dictionary<string, TEstimator> estimators = new();
15+
private readonly Dictionary<string, RandomGenerator> distributions = new();
16+
private readonly HashSet<int> sampleSizes = new();
17+
18+
public EfficiencySimulation<TEstimator> AddEstimator(string name, TEstimator estimator)
19+
{
20+
if (!estimators.TryAdd(name, estimator))
21+
throw new ArgumentException($"Estimator '{name}' is already registered");
22+
return this;
23+
}
24+
25+
public EfficiencySimulation<TEstimator> AddDistribution(string name, RandomGenerator randomGenerator)
26+
{
27+
if (!distributions.TryAdd(name, randomGenerator))
28+
throw new ArgumentException($"Distribution '{name}' is already registered");
29+
return this;
30+
}
31+
32+
public EfficiencySimulation<TEstimator> AddSampleSizes(params int[] sizes)
33+
{
34+
foreach (int sampleSize in sizes)
35+
{
36+
Assertion.Positive(nameof(sizes), sampleSize);
37+
sampleSizes.Add(sampleSize);
38+
}
39+
return this;
40+
}
41+
42+
public class SimulationRow(
43+
string distribution,
44+
int sampleSize,
45+
IReadOnlyDictionary<string, double> relativeEfficiency)
46+
{
47+
public string Distribution { get; init; } = distribution;
48+
public int SampleSize { get; init; } = sampleSize;
49+
public IReadOnlyDictionary<string, double> RelativeEfficiency { get; init; } = relativeEfficiency;
50+
}
51+
52+
public IEnumerable<SimulationRow> Simulate()
53+
{
54+
if (distributions.IsEmpty())
55+
throw new InvalidOperationException("No distributions provided");
56+
if (estimators.IsEmpty())
57+
throw new InvalidOperationException("No estimators provided");
58+
if (sampleSizes.IsEmpty())
59+
throw new InvalidOperationException("No sample sizes provided");
60+
61+
foreach ((string distributionName, var randomGenerator) in distributions)
62+
foreach (int sampleSize in sampleSizes)
63+
{
64+
var samplingDistributions = new Dictionary<string, double[]>();
65+
foreach (string estimatorName in estimators.Keys)
66+
samplingDistributions[estimatorName] = new double[sampleCount];
67+
68+
for (int i = 0; i < sampleCount; i++)
69+
{
70+
var sample = new Sample(randomGenerator.Next(sampleSize));
71+
foreach ((string estimatorName, var estimator) in estimators)
72+
samplingDistributions[estimatorName][i] = estimate(estimator, sample);
73+
}
74+
75+
var mses = new Dictionary<string, double>();
76+
foreach (string estimatorName in estimators.Keys)
77+
mses[estimatorName] = Mse(samplingDistributions[estimatorName]);
78+
double minMse = mses.Values.Min();
79+
var relativeEfficiency = new Dictionary<string, double>();
80+
foreach (string estimatorName in estimators.Keys)
81+
relativeEfficiency[estimatorName] = minMse / mses[estimatorName];
82+
83+
var row = new SimulationRow(distributionName, sampleSize, relativeEfficiency);
84+
yield return row;
85+
}
86+
}
87+
88+
// The mean squared error (MSE)
89+
private static double Mse(double[] values)
90+
{
91+
double mean = values.Average();
92+
return values.Sum(x => (x - mean) * (x - mean)) / values.Length;
93+
}
94+
}

src/Perfolizer/Perfolizer/Mathematics/GenericEstimators/HodgesLehmannEstimator.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Perfolizer.Common;
22
using Perfolizer.Mathematics.Common;
3+
using Perfolizer.Mathematics.LocationEstimators;
34
using Perfolizer.Mathematics.QuantileEstimators;
45

56
namespace Perfolizer.Mathematics.GenericEstimators;
@@ -13,7 +14,7 @@ namespace Perfolizer.Mathematics.GenericEstimators;
1314
/// The weighted version is based on https://aakinshin.net/posts/whl/
1415
/// </summary>
1516
public class HodgesLehmannEstimator(IQuantileEstimator quantileEstimator)
16-
: IShiftEstimator, IMedianEstimator, IRatioEstimator
17+
: IShiftEstimator, IMedianEstimator, IRatioEstimator, ILocationEstimator
1718
{
1819
public static readonly HodgesLehmannEstimator Instance = new(SimpleQuantileEstimator.Instance);
1920

@@ -29,4 +30,6 @@ public double Median(Sample x) =>
2930

3031
private double Estimate(Sample x, Sample y, Func<double, double, double> func) =>
3132
PairwiseEstimatorHelper.Estimate(x, y, func, quantileEstimator, Probability.Median);
33+
34+
public double Location(Sample x) => Median(x);
3235
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Perfolizer.Mathematics.GenericEstimators;
2+
3+
public interface ILocationEstimator
4+
{
5+
double Location(Sample x);
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Perfolizer.Mathematics.GenericEstimators;
2+
3+
namespace Perfolizer.Mathematics.LocationEstimators;
4+
5+
public class MeanEstimator : ILocationEstimator
6+
{
7+
public static readonly MeanEstimator Instance = new();
8+
public double Location(Sample x) => x.Values.Average();
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Perfolizer.Mathematics.GenericEstimators;
2+
using Perfolizer.Mathematics.QuantileEstimators;
3+
4+
namespace Perfolizer.Mathematics.LocationEstimators;
5+
6+
public class MedianLocationEstimator(IQuantileEstimator quantileEstimator) : ILocationEstimator
7+
{
8+
public double Location(Sample x) => quantileEstimator.Median(x);
9+
}

src/Perfolizer/Perfolizer/Mathematics/QuantileEstimators/QuantileEstimatorExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using Perfolizer.Common;
22
using Perfolizer.Mathematics.Common;
3+
using Perfolizer.Mathematics.GenericEstimators;
4+
using Perfolizer.Mathematics.LocationEstimators;
35

46
namespace Perfolizer.Mathematics.QuantileEstimators;
57

@@ -43,4 +45,9 @@ public static double[] Quantiles(
4345
{
4446
return estimator.Quantiles(new Sample(values), probabilities);
4547
}
48+
49+
public static ILocationEstimator ToLocationEstimator(this IQuantileEstimator estimator)
50+
{
51+
return new MedianLocationEstimator(estimator);
52+
}
4653
}

src/Perfolizer/Perfolizer/Sample.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class Sample : IWithUnits
2727
public IReadOnlyList<double> SortedWeights => lazySortedData.Value.SortedWeights;
2828

2929
/// <summary>
30-
/// Sample size
30+
/// Sample size (always positive)
3131
/// </summary>
3232
public int Size => Values.Count;
3333

0 commit comments

Comments
 (0)