< Summary

Information
Class: AmbientServices.MissingSampleHandlingExtensions
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Utilities/StatisticsSampleExtrapolation.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 91
Uncovered lines: 0
Coverable lines: 91
Total lines: 259
Line coverage: 100%
Branch coverage
97%
Covered branches: 45
Total branches: 46
Branch coverage: 97.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
LeadingSampleExtrapolation(...)100%11100%
MiddleSampleExtrapolation(...)100%11100%
TrailingSampleExtrapolation(...)100%11100%
.cctor()100%11100%
LeadingSampleExtrapolation(...)100%11100%
MiddleSampleExtrapolation(...)100%11100%
TrailingSampleExtrapolation(...)100%11100%
.cctor()100%11100%
LeadingSampleExtrapolation(...)100%11100%
MiddleSampleExtrapolation(...)100%11100%
TrailingSampleExtrapolation(...)100%11100%
HandleMissingSamples()92.86%1414100%
Extrapolate()100%3232100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Utilities/StatisticsSampleExtrapolation.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3
 4namespace AmbientServices;
 5
 6/// <summary>
 7/// A class that contains extension methods for various statistics interfaces that add functions to aggregate statistic 
 8/// </summary>
 9/// <remarks>
 10/// <pitch>The client-side realization of <see cref="MissingSampleHandling"/>: fills the null gaps in a sample series th
 11/// <pledge>
 12/// Skip drops null samples (shortening the series); every other mode returns exactly one output sample per input sample
 13/// Enumeration is lazy and single-pass, but output can trail input by an arbitrary number of samples, since a gap canno
 14/// </pledge>
 15/// <plan>Implemented as a streaming state machine over the input enumeration that buffers only gap counts and the last 
 16/// </remarks>
 17public static class MissingSampleHandlingExtensions
 18{
 19    private interface IMissingSampleExtrapolator
 20    {
 21        long LeadingSampleExtrapolation(long firstNonNullSample, long secondNonNullSample, int samplesBetweenNonNullSamp
 22        long MiddleSampleExtrapolation(long firstNonNullSample, long secondNonNullSample, int missingValueIndex, int mis
 23        long TrailingSampleExtrapolation(long firstNonNullSample, long secondNonNullSample, int samplesBetweenNonNullSam
 24    }
 25    private class LinearExtrapolator : IMissingSampleExtrapolator
 26    {
 227        public static LinearExtrapolator Instance { get; } = new();
 28
 29        public long LeadingSampleExtrapolation(long firstNonNullSample, long secondNonNullSample, int samplesBetweenNonN
 30        {
 231            return firstNonNullSample - (secondNonNullSample - firstNonNullSample) / (samplesBetweenNonNullSamples + 1) 
 32        }
 33        public long MiddleSampleExtrapolation(long firstNonNullSample, long secondNonNullSample, int missingValueIndex, 
 34        {
 235            return firstNonNullSample + ((secondNonNullSample - firstNonNullSample) * (missingValueIndex + 1) + ((missin
 36        }
 37        public long TrailingSampleExtrapolation(long firstNonNullSample, long secondNonNullSample, int samplesBetweenNon
 38        {
 239            return secondNonNullSample + (secondNonNullSample - firstNonNullSample) / (samplesBetweenNonNullSamples + 1)
 40        }
 41    }
 42    private class ExponentialExtrapolator : IMissingSampleExtrapolator
 43    {
 244        public static ExponentialExtrapolator Instance { get; } = new();
 45
 46        public long LeadingSampleExtrapolation(long lowSample, long highSample, int samplesBetweenNonNullSamples, int in
 47        {
 248            return (long)Math.Round(Math.Pow(Math.E, Math.Log(lowSample) - (Math.Log(highSample) - Math.Log(lowSample)) 
 49        }
 50        public long MiddleSampleExtrapolation(long lowSample, long highSample, int missingValueIndex, int missingValueCo
 51        {
 252            return (long)Math.Round(Math.Pow(Math.E, Math.Log(lowSample) + ((Math.Log(highSample) - Math.Log(lowSample))
 53        }
 54        public long TrailingSampleExtrapolation(long lowSample, long highSample, int samplesBetweenNonNullSamples, int i
 55        {
 256            return (long)Math.Round(Math.Pow(Math.E, Math.Log(highSample) + (Math.Log(highSample) - Math.Log(lowSample))
 57        }
 58    }
 59    private class LogarithmicExtrapolator : IMissingSampleExtrapolator
 60    {
 261        public static LogarithmicExtrapolator Instance { get; } = new();
 62
 63        public long LeadingSampleExtrapolation(long lowSample, long highSample, int samplesBetweenNonNullSamples, int in
 64        {
 265            return (long)Math.Round(Math.Log(Math.Pow(Math.E, lowSample) - (Math.Pow(Math.E, highSample) - Math.Pow(Math
 66        }
 67        public long MiddleSampleExtrapolation(long lowSample, long highSample, int missingValueIndex, int missingValueCo
 68        {
 269            return (long)Math.Round(Math.Log(Math.Pow(Math.E, lowSample) + ((Math.Pow(Math.E, highSample) - Math.Pow(Mat
 70        }
 71        public long TrailingSampleExtrapolation(long lowSample, long highSample, int samplesBetweenNonNullSamples, int i
 72        {
 273            return (long)Math.Round(Math.Log(Math.Pow(Math.E, highSample) + (Math.Pow(Math.E, highSample) - Math.Pow(Mat
 74        }
 75    }
 76    /// <summary>
 77    /// Handle missing samples in the enumerated samples according to the reader's <see cref="IAmbientStatisticReader.Mi
 78    /// </summary>
 79    /// <param name="missingSamplesHandling">The <see cref="MissingSampleHandling"/> indicating how to handle null value
 80    /// <param name="samples">An enumeration of statistical samples.</param>
 81    /// <returns>An enumeration that has null samples values filled in according to <paramref name="missingSamplesHandli
 82    /// <exception cref="ArgumentNullException"><paramref name="samples"/> is null.</exception>
 83    public static IEnumerable<long?> HandleMissingSamples(this MissingSampleHandling missingSamplesHandling, IEnumerable
 84    {
 285        if (samples == null) throw new ArgumentNullException(nameof(samples));
 86
 87        switch (missingSamplesHandling)
 88        {
 89            case MissingSampleHandling.Zero:
 290                foreach (long? sample in samples)
 91                {
 292                    yield return sample ?? 0;
 93                }
 294                break;
 95            case MissingSampleHandling.LinearEstimation:            // for leading null samples, extrapolate linearly fr
 296                foreach (long? sample in Extrapolate(samples, LinearExtrapolator.Instance))
 97                {
 298                    yield return sample;
 99                }
 2100                break;
 101            case MissingSampleHandling.ExponentialEstimation:
 2102                foreach (long? sample in Extrapolate(samples, ExponentialExtrapolator.Instance))
 103                {
 2104                    yield return sample;
 105                }
 2106                break;
 107            case MissingSampleHandling.LogarithmicEstimation:
 2108                foreach (long? sample in Extrapolate(samples, LogarithmicExtrapolator.Instance))
 109                {
 2110                    yield return sample;
 111                }
 2112                break;
 113            default:
 114            case MissingSampleHandling.Skip:
 2115                foreach (long? sample in samples)
 116                {
 2117                    if (sample != null) yield return sample.Value;
 118                }
 119                break;
 120        }
 2121    }
 122
 123    private static IEnumerable<long?> Extrapolate(IEnumerable<long?> samples, IMissingSampleExtrapolator extrapolator)
 124    {
 2125        long? previousNonNullSample = null;
 2126        long? lastNonNullSample = null;
 2127        int distanceBetweenPreviousNonNullSampleAndLastNonNullSample = 0;
 2128        int missingSamplesBeforeLastNonNullSample = 0;
 2129        int missingSamplesAfterLastNonNullSample = 0;
 2130        bool needAnotherNonNullSample = true;
 2131        int sampleCount = 0;
 2132        int samplesOutput = 0;
 2133        foreach (long? sample in samples)
 134        {
 2135            ++sampleCount;
 136            // do we need another non-null sample before we can return more (extrapolated) samples?
 2137            if (needAnotherNonNullSample)
 138            {
 139                // a non-null sample
 2140                if (sample != null)
 141                {
 142                    // no null samples before this one?
 2143                    if (missingSamplesBeforeLastNonNullSample == 0)
 144                    {
 2145                        yield return sample.Value;
 2146                        ++samplesOutput;
 147                        // save this as a non-null sample, but keep looking for samples
 2148                        previousNonNullSample = lastNonNullSample;
 2149                        lastNonNullSample = sample;
 150                        // we're no longer buffering--we hit a non-null sample without null samples preceding it
 2151                        needAnotherNonNullSample = false;
 152                    }
 153                    // was there a non-null sample before this one?
 2154                    else if (lastNonNullSample == null)
 155                    {
 156                        // save this as a non-null sample, but keep looking for samples
 2157                        previousNonNullSample = lastNonNullSample;
 2158                        lastNonNullSample = sample;
 159                    }
 160                    else
 161                    {
 162                        // were there leading samples before the two non-null samples?
 2163                        for (int offset = 0; offset < missingSamplesBeforeLastNonNullSample; ++offset)
 164                        {
 165                            // emit farthest-back first: the earliest output position is the most steps before the first
 2166                            yield return extrapolator.LeadingSampleExtrapolation(lastNonNullSample.Value, sample.Value, 
 2167                            ++samplesOutput;
 168                        }
 2169                        missingSamplesBeforeLastNonNullSample = 0;
 2170                        yield return lastNonNullSample.Value;
 2171                        ++samplesOutput;
 172                        // were there missing samples since the last non-null sample?
 2173                        for (int offset = 0; offset < missingSamplesAfterLastNonNullSample; ++offset)
 174                        {
 2175                            yield return extrapolator.MiddleSampleExtrapolation(lastNonNullSample.Value, sample.Value, o
 2176                            ++samplesOutput;
 177                        }
 2178                        distanceBetweenPreviousNonNullSampleAndLastNonNullSample = missingSamplesAfterLastNonNullSample;
 2179                        missingSamplesAfterLastNonNullSample = 0;
 2180                        yield return sample.Value;
 2181                        ++samplesOutput;
 182                        // save this as the last non-null sample
 2183                        previousNonNullSample = lastNonNullSample;
 2184                        lastNonNullSample = sample;
 185                        // we're no longer buffering
 2186                        needAnotherNonNullSample = false;
 187                    }
 188                }
 2189                else if (lastNonNullSample != null) ++missingSamplesAfterLastNonNullSample;
 2190                else ++missingSamplesBeforeLastNonNullSample;
 191            }
 192            else // we just caught up, so we need to start counting missing samples again, or just output the non-null s
 193            {
 194                System.Diagnostics.Debug.Assert(lastNonNullSample != null);
 2195                if (sample != null)
 196                {
 197                    // were there missing samples since the last non-null sample?
 2198                    for (int offset = 0; offset < missingSamplesAfterLastNonNullSample; ++offset)
 199                    {
 2200                        yield return extrapolator.MiddleSampleExtrapolation(lastNonNullSample!.Value, sample.Value, offs
 2201                        ++samplesOutput;
 202                    }
 2203                    distanceBetweenPreviousNonNullSampleAndLastNonNullSample = missingSamplesAfterLastNonNullSample;
 2204                    missingSamplesAfterLastNonNullSample = 0;
 2205                    yield return sample.Value;
 2206                    ++samplesOutput;
 207                    // save this as the last non-null sample
 2208                    previousNonNullSample = lastNonNullSample;
 2209                    lastNonNullSample = sample;
 210                }
 211                else // a missing sample, so just count how many
 212                {
 2213                    ++missingSamplesAfterLastNonNullSample;
 214                }
 215            }
 216        }
 217        // were there leading samples missing?
 2218        if (missingSamplesBeforeLastNonNullSample > 0)
 219        {
 220            // were they all missing?
 2221            if (lastNonNullSample == null)
 222            {
 223                System.Diagnostics.Debug.Assert(missingSamplesBeforeLastNonNullSample == sampleCount);
 224                System.Diagnostics.Debug.Assert(samplesOutput == 0);
 225                System.Diagnostics.Debug.Assert(missingSamplesAfterLastNonNullSample == 0);
 226                // just leave everything null!
 2227                for (int offset = 0; offset < sampleCount; ++offset)
 228                {
 2229                    yield return null;
 2230                    ++samplesOutput;
 231                }
 232            }
 233            else // we got at least one sample, but since leading samples is still non-zero, we must have had only ONE s
 234            {
 235                System.Diagnostics.Debug.Assert(missingSamplesBeforeLastNonNullSample + missingSamplesAfterLastNonNullSa
 236                System.Diagnostics.Debug.Assert(samplesOutput == 0);
 237                // use the one non-null sample for all values
 2238                for (int offset = 0; offset < sampleCount; ++offset)
 239                {
 2240                    yield return lastNonNullSample.Value;
 2241                    ++samplesOutput;
 242                }
 243            }
 244        }
 245        // were there trailing missing samples?
 2246        else if (missingSamplesAfterLastNonNullSample > 0)
 247        {
 248            System.Diagnostics.Debug.Assert(previousNonNullSample != null);
 249            System.Diagnostics.Debug.Assert(lastNonNullSample != null);
 250            // loop through the trailing null values
 2251            for (int offset = 0; offset < missingSamplesAfterLastNonNullSample; ++offset)
 252            {
 2253                yield return extrapolator.TrailingSampleExtrapolation(previousNonNullSample!.Value, lastNonNullSample!.V
 2254                ++samplesOutput;
 255            }
 256        }
 257        System.Diagnostics.Debug.Assert(samplesOutput == sampleCount);
 2258    }
 259}