File: SequentialAnomalyDetectionTransformBase.cs
Web Access
Project: src\src\Microsoft.ML.TimeSeries\Microsoft.ML.TimeSeries.csproj (Microsoft.ML.TimeSeries)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.IO;
using System.Threading;
using Microsoft.ML.CommandLine;
using Microsoft.ML.Data;
using Microsoft.ML.Internal.CpuMath;
using Microsoft.ML.Internal.Utilities;
using Microsoft.ML.Runtime;
 
namespace Microsoft.ML.Transforms.TimeSeries
{
    /// <summary>
    /// The type of the martingale.
    /// </summary>
    public enum MartingaleType : byte
    {
        /// <summary>
        /// (None) No martingale is used.
        /// </summary>
        None,
        /// <summary>
        /// (Power) The Power martingale is used.
        /// </summary>
        Power,
        /// <summary>
        /// (Mixture) The Mixture martingale is used.
        /// </summary>
        Mixture
    }
 
    /// <summary>
    /// The side of anomaly detection.
    /// </summary>
    public enum AnomalySide : byte
    {
        /// <summary>
        /// (Positive) Only positive anomalies are detected.
        /// </summary>
        Positive,
        /// <summary>
        /// (Negative) Only negative anomalies are detected.
        /// </summary>
        Negative,
        /// <summary>
        /// (TwoSided) Both positive and negative anomalies are detected.
        /// </summary>
        TwoSided
    }
 
    /// <summary>
    /// The score that should be thresholded to generate alerts.
    /// </summary>
    internal enum AlertingScore : byte
    {
        /// <summary>
        /// (RawScore) The raw anomaly score is thresholded.
        /// </summary>
        RawScore,
        /// <summary>
        /// (PValueScore) The p-value score is thresholded.
        /// </summary>
        PValueScore,
        /// <summary>
        /// (MartingaleScore) The martingale score is thresholded.
        /// </summary>
        MartingaleScore
    }
 
    /// <summary>
    /// The base class that can be inherited by the 'Argument' classes in the derived classes containing the shared input parameters.
    /// </summary>
    internal abstract class ArgumentsBase
    {
        [Argument(ArgumentType.Required, HelpText = "The name of the source column", ShortName = "src",
            SortOrder = 1, Purpose = SpecialPurpose.ColumnName)]
        public string Source;
 
        [Argument(ArgumentType.Required, HelpText = "The name of the new column", ShortName = "name",
            SortOrder = 2)]
        public string Name;
 
        [Argument(ArgumentType.AtMostOnce, HelpText = "The argument that determines whether to detect positive or negative anomalies, or both", ShortName = "side",
            SortOrder = 3)]
        public AnomalySide Side = AnomalySide.TwoSided;
 
        [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the sliding window for computing the p-value.", ShortName = "wnd",
            SortOrder = 4)]
        public int WindowSize = 1;
 
        [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the initial window for computing the p-value as well as training if needed. The default value is set to 0, which means there is no initial window considered.",
            ShortName = "initwnd", SortOrder = 5)]
        public int InitialWindowSize = 0;
 
        [Argument(ArgumentType.AtMostOnce, HelpText = "The martingale used for scoring",
            ShortName = "martingale", SortOrder = 6)]
        public MartingaleType Martingale = MartingaleType.Power;
 
        [Argument(ArgumentType.AtMostOnce, HelpText = "The argument that determines whether anomalies should be detected based on the raw anomaly score, the p-value or the martingale score",
            ShortName = "alert", SortOrder = 7)]
        public AlertingScore AlertOn = AlertingScore.MartingaleScore;
 
        [Argument(ArgumentType.AtMostOnce, HelpText = "The epsilon parameter for the Power martingale",
            ShortName = "eps", SortOrder = 8)]
        public Double PowerMartingaleEpsilon = 0.1;
 
        [Argument(ArgumentType.Required, HelpText = "The threshold for alerting",
            ShortName = "thr", SortOrder = 9)]
        public Double AlertThreshold;
    }
 
    // REVIEW: This base class and its children classes generate one output column of type VBuffer<Double> to output 3 different anomaly scores as well as
    // the alert flag. Ideally these 4 output information should be put in four seaparate columns instead of one VBuffer<> column. However, this is not currently
    // possible due to our design restriction. This must be fixed in the next version and will potentially affect the children classes.
    /// <summary>
    /// The base class for sequential anomaly detection transforms that supports the p-value as well as the martingales scores computation from the sequence of
    /// raw anomaly scores whose calculation is specified by the children classes. This class also provides mechanism for the threshold-based alerting on
    /// the raw anomaly score, the p-value score or the martingale score. Currently, this class supports Power and Mixture martingales.
    /// For more details, please refer to http://arxiv.org/pdf/1204.3251.pdf
    /// </summary>
    /// <typeparam name="TInput">The type of the input sequence</typeparam>
    /// <typeparam name="TState">The type of the input sequence</typeparam>
    internal abstract class SequentialAnomalyDetectionTransformBase<TInput, TState> : SequentialTransformerBase<TInput, VBuffer<Double>, TState>
    where TState : SequentialAnomalyDetectionTransformBase<TInput, TState>.AnomalyDetectionStateBase, new()
    {
        // Determines the side of anomaly detection for this transform.
        internal AnomalySide Side;
 
        // Determines the type of martingale used by this transform.
        internal MartingaleType Martingale;
 
        // The epsilon parameter used by the Power martingale.
        internal Double PowerMartingaleEpsilon;
 
        // Determines the score that should be thresholded to generate alerts by this transform.
        internal AlertingScore ThresholdScore;
 
        // Determines the threshold for generating alerts.
        internal Double AlertThreshold;
 
        // The size of the VBuffer in the dst column.
        internal int OutputLength;
 
        // The minimum value for p-values. The smaller p-values are ceiled to this value.
        internal const Double MinPValue = 1e-8;
 
        // The maximun value for p-values. The larger p-values are floored to this value.
        internal const Double MaxPValue = 1 - MinPValue;
 
        private static int GetOutputLength(AlertingScore alertingScore, IHostEnvironment host)
        {
            switch (alertingScore)
            {
                case AlertingScore.RawScore:
                    return 2;
                case AlertingScore.PValueScore:
                    return 3;
                case AlertingScore.MartingaleScore:
                    return 4;
                default:
                    throw host.Except("The alerting score can be only (0) RawScore, (1) PValueScore or (2) MartingaleScore.");
            }
        }
 
        private protected SequentialAnomalyDetectionTransformBase(int windowSize, int initialWindowSize, string inputColumnName, string outputColumnName, string name, IHostEnvironment env,
            AnomalySide anomalySide, MartingaleType martingale, AlertingScore alertingScore, Double powerMartingaleEpsilon,
            Double alertThreshold)
            : base(Contracts.CheckRef(env, nameof(env)).Register(name), windowSize, initialWindowSize, outputColumnName, inputColumnName, new VectorDataViewType(NumberDataViewType.Double, GetOutputLength(alertingScore, env)))
        {
            Host.CheckUserArg(Enum.IsDefined(typeof(MartingaleType), martingale), nameof(ArgumentsBase.Martingale), "Value is undefined.");
            Host.CheckUserArg(Enum.IsDefined(typeof(AnomalySide), anomalySide), nameof(ArgumentsBase.Side), "Value is undefined.");
            Host.CheckUserArg(Enum.IsDefined(typeof(AlertingScore), alertingScore), nameof(ArgumentsBase.AlertOn), "Value is undefined.");
            Host.CheckUserArg(martingale != MartingaleType.None || alertingScore != AlertingScore.MartingaleScore, nameof(ArgumentsBase.Martingale), "A martingale type should be specified if alerting is based on the martingale score.");
            Host.CheckUserArg(windowSize > 0 || alertingScore == AlertingScore.RawScore, nameof(ArgumentsBase.AlertOn),
                "When there is no windowed buffering (i.e., " + nameof(ArgumentsBase.WindowSize) + " = 0), the alert can be generated only based on the raw score (i.e., "
                + nameof(ArgumentsBase.AlertOn) + " = " + nameof(AlertingScore.RawScore) + ")");
            Host.CheckUserArg(0 < powerMartingaleEpsilon && powerMartingaleEpsilon < 1, nameof(ArgumentsBase.PowerMartingaleEpsilon), "Should be in (0,1).");
            Host.CheckUserArg(alertThreshold >= 0, nameof(ArgumentsBase.AlertThreshold), "Must be non-negative.");
            Host.CheckUserArg(alertingScore != AlertingScore.PValueScore || (0 <= alertThreshold && alertThreshold <= 1), nameof(ArgumentsBase.AlertThreshold), "Must be in [0,1].");
 
            ThresholdScore = alertingScore;
            Side = anomalySide;
            Martingale = martingale;
            PowerMartingaleEpsilon = powerMartingaleEpsilon;
            AlertThreshold = alertThreshold;
            OutputLength = GetOutputLength(ThresholdScore, Host);
        }
 
        private protected SequentialAnomalyDetectionTransformBase(ArgumentsBase args, string name, IHostEnvironment env)
            : this(args.WindowSize, args.InitialWindowSize, args.Source, args.Name, name, env, args.Side, args.Martingale,
                args.AlertOn, args.PowerMartingaleEpsilon, args.AlertThreshold)
        {
        }
 
        private protected SequentialAnomalyDetectionTransformBase(IHostEnvironment env, ModelLoadContext ctx, string name)
            : base(Contracts.CheckRef(env, nameof(env)).Register(name), ctx)
        {
            // *** Binary format ***
            // <base>
            // byte: _martingale
            // byte: _alertingScore
            // byte: _anomalySide
            // Double: _powerMartingaleEpsilon
            // Double: _alertThreshold
 
            byte temp;
            temp = ctx.Reader.ReadByte();
            Host.CheckDecode(Enum.IsDefined(typeof(MartingaleType), temp));
            Martingale = (MartingaleType)temp;
 
            temp = ctx.Reader.ReadByte();
            Host.CheckDecode(Enum.IsDefined(typeof(AlertingScore), temp));
            ThresholdScore = (AlertingScore)temp;
 
            Host.CheckDecode(Martingale != MartingaleType.None || ThresholdScore != AlertingScore.MartingaleScore);
            Host.CheckDecode(WindowSize > 0 || ThresholdScore == AlertingScore.RawScore);
 
            temp = ctx.Reader.ReadByte();
            Host.CheckDecode(Enum.IsDefined(typeof(AnomalySide), temp));
            Side = (AnomalySide)temp;
 
            PowerMartingaleEpsilon = ctx.Reader.ReadDouble();
            Host.CheckDecode(0 < PowerMartingaleEpsilon && PowerMartingaleEpsilon < 1);
 
            AlertThreshold = ctx.Reader.ReadDouble();
            Host.CheckDecode(AlertThreshold >= 0);
            Host.CheckDecode(ThresholdScore != AlertingScore.PValueScore || (0 <= AlertThreshold && AlertThreshold <= 1));
 
            OutputLength = GetOutputLength(ThresholdScore, Host);
        }
 
        private protected override void SaveModel(ModelSaveContext ctx)
        {
            Host.CheckValue(ctx, nameof(ctx));
            ctx.CheckAtModel();
 
            Host.Assert(Enum.IsDefined(typeof(MartingaleType), Martingale));
            Host.Assert(Enum.IsDefined(typeof(AlertingScore), ThresholdScore));
            Host.Assert(Martingale != MartingaleType.None || ThresholdScore != AlertingScore.MartingaleScore);
            Host.Assert(WindowSize > 0 || ThresholdScore == AlertingScore.RawScore);
            Host.Assert(Enum.IsDefined(typeof(AnomalySide), Side));
            Host.Assert(0 < PowerMartingaleEpsilon && PowerMartingaleEpsilon < 1);
            Host.Assert(AlertThreshold >= 0);
            Host.Assert(ThresholdScore != AlertingScore.PValueScore || (0 <= AlertThreshold && AlertThreshold <= 1));
 
            // *** Binary format ***
            // <base>
            // byte: _martingale
            // byte: _alertingScore
            // byte: _anomalySide
            // Double: _powerMartingaleEpsilon
            // Double: _alertThreshold
 
            base.SaveModel(ctx);
            ctx.Writer.Write((byte)Martingale);
            ctx.Writer.Write((byte)ThresholdScore);
            ctx.Writer.Write((byte)Side);
            ctx.Writer.Write(PowerMartingaleEpsilon);
            ctx.Writer.Write(AlertThreshold);
        }
 
        /// <summary>
        /// Calculates the betting function for the Power martingale in the log scale.
        /// For more details, please refer to http://arxiv.org/pdf/1204.3251.pdf.
        /// </summary>
        /// <param name="p">The p-value</param>
        /// <param name="epsilon">The epsilon</param>
        /// <returns>The Power martingale betting function value in the natural logarithmic scale.</returns>
        internal Double LogPowerMartigaleBettingFunc(Double p, Double epsilon)
        {
            Host.Assert(MinPValue > 0);
            Host.Assert(MaxPValue < 1);
            Host.Assert(MinPValue <= p && p <= MaxPValue);
            Host.Assert(0 < epsilon && epsilon < 1);
 
            return Math.Log(epsilon) + (epsilon - 1) * Math.Log(p);
        }
 
        /// <summary>
        /// Calculates the betting function for the Mixture martingale in the log scale.
        /// For more details, please refer to http://arxiv.org/pdf/1204.3251.pdf.
        /// </summary>
        /// <param name="p">The p-value</param>
        /// <returns>The Mixure (marginalized over epsilon) martingale betting function value in the natural logarithmic scale.</returns>
        internal Double LogMixtureMartigaleBettingFunc(Double p)
        {
            Host.Assert(MinPValue > 0);
            Host.Assert(MaxPValue < 1);
            Host.Assert(MinPValue <= p && p <= MaxPValue);
 
            Double logP = Math.Log(p);
            return Math.Log(p * logP + 1 - p) - 2 * Math.Log(-logP) - logP;
        }
 
        internal override IStatefulRowMapper MakeRowMapper(DataViewSchema schema) => new Mapper(Host, this, schema);
 
        internal sealed class Mapper : IStatefulRowMapper
        {
            private readonly IHost _host;
            private readonly SequentialAnomalyDetectionTransformBase<TInput, TState> _parent;
            private readonly DataViewSchema _parentSchema;
            private readonly int _inputColumnIndex;
            private readonly VBuffer<ReadOnlyMemory<Char>> _slotNames;
            private AnomalyDetectionStateBase State { get; set; }
 
            public Mapper(IHostEnvironment env, SequentialAnomalyDetectionTransformBase<TInput, TState> parent, DataViewSchema inputSchema)
            {
                Contracts.CheckValue(env, nameof(env));
                _host = env.Register(nameof(Mapper));
                _host.CheckValue(inputSchema, nameof(inputSchema));
                _host.CheckValue(parent, nameof(parent));
 
                if (!inputSchema.TryGetColumnIndex(parent.InputColumnName, out _inputColumnIndex))
                    throw _host.ExceptSchemaMismatch(nameof(inputSchema), "input", parent.InputColumnName);
 
                var colType = inputSchema[_inputColumnIndex].Type;
                if (colType != NumberDataViewType.Single)
                    throw _host.ExceptSchemaMismatch(nameof(inputSchema), "input", parent.InputColumnName, "Single", colType.ToString());
 
                _parent = parent;
                _parentSchema = inputSchema;
                _slotNames = new VBuffer<ReadOnlyMemory<char>>(4, new[] { "Alert".AsMemory(), "Raw Score".AsMemory(),
                    "P-Value Score".AsMemory(), "Martingale Score".AsMemory() });
 
                State = (AnomalyDetectionStateBase)_parent.StateRef;
            }
 
            public DataViewSchema.DetachedColumn[] GetOutputColumns()
            {
                var meta = new DataViewSchema.Annotations.Builder();
                meta.AddSlotNames(_parent.OutputLength, GetSlotNames);
                var info = new DataViewSchema.DetachedColumn[1];
                info[0] = new DataViewSchema.DetachedColumn(_parent.OutputColumnName, new VectorDataViewType(NumberDataViewType.Double, _parent.OutputLength), meta.ToAnnotations());
                return info;
            }
 
            public void GetSlotNames(ref VBuffer<ReadOnlyMemory<char>> dst) => _slotNames.CopyTo(ref dst, 0, _parent.OutputLength);
 
            public Func<int, bool> GetDependencies(Func<int, bool> activeOutput)
            {
                if (activeOutput(0))
                    return col => col == _inputColumnIndex;
                else
                    return col => false;
            }
 
            void ICanSaveModel.Save(ModelSaveContext ctx) => _parent.SaveModel(ctx);
 
            public Delegate[] CreateGetters(DataViewRow input, Func<int, bool> activeOutput, out Action disposer)
            {
                disposer = null;
                var getters = new Delegate[1];
                if (activeOutput(0))
                    getters[0] = MakeGetter(input, State);
 
                return getters;
            }
 
            private delegate void ProcessData(ref TInput src, ref VBuffer<double> dst);
 
            private Delegate MakeGetter(DataViewRow input, AnomalyDetectionStateBase state)
            {
                _host.AssertValue(input);
                var srcGetter = input.GetGetter<TInput>(input.Schema[_inputColumnIndex]);
                ProcessData processData = _parent.WindowSize > 0 ?
                    (ProcessData)state.Process : state.ProcessWithoutBuffer;
 
                ValueGetter<VBuffer<double>> valueGetter = (ref VBuffer<double> dst) =>
                {
                    TInput src = default;
                    srcGetter(ref src);
                    processData(ref src, ref dst);
                };
                return valueGetter;
            }
 
            public Action<PingerArgument> CreatePinger(DataViewRow input, Func<int, bool> activeOutput, out Action disposer)
            {
                disposer = null;
                Action<PingerArgument> pinger = null;
                if (activeOutput(0))
                    pinger = MakePinger(input, State);
 
                return pinger;
            }
 
            private Action<PingerArgument> MakePinger(DataViewRow input, AnomalyDetectionStateBase state)
            {
                _host.AssertValue(input);
                var srcGetter = input.GetGetter<TInput>(input.Schema[_inputColumnIndex]);
                Action<PingerArgument> pinger = (PingerArgument args) =>
                {
                    if (args.DontConsumeSource)
                        return;
 
                    TInput src = default;
                    srcGetter(ref src);
                    state.UpdateState(ref src, args.RowPosition, _parent.WindowSize > 0);
                };
                return pinger;
            }
 
            public void CloneState()
            {
                if (Interlocked.Increment(ref _parent.StateRefCount) > 1)
                {
                    State = (AnomalyDetectionStateBase)_parent.StateRef.Clone();
                }
            }
 
            public ITransformer GetTransformer()
            {
                return _parent;
            }
        }
        /// <summary>
        /// The base state class for sequential anomaly detection: this class implements the p-values and martinagle calculations for anomaly detection
        /// given that the raw anomaly score calculation is specified by the derived classes.
        /// </summary>
        internal abstract class AnomalyDetectionStateBase : SequentialTransformerBase<TInput, VBuffer<Double>, TState>.StateBase
        {
            // A reference to the parent transform.
            protected SequentialAnomalyDetectionTransformBase<TInput, TState> Parent;
 
            // A windowed buffer to cache the update values to the martingale score in the log scale.
            private FixedSizeQueue<Double> LogMartingaleUpdateBuffer { get; set; }
 
            // A windowed buffer to cache the raw anomaly scores for p-value calculation.
            private FixedSizeQueue<Single> RawScoreBuffer { get; set; }
 
            // The current martingale score in the log scale.
            private Double _logMartingaleValue;
 
            // Sum of the squared Euclidean distances among the raw socres in the buffer.
            // Used for computing the optimal bandwidth for Kernel Density Estimation of p-values.
            private Double _sumSquaredDist;
 
            private int _martingaleAlertCounter;
 
            protected Double LatestMartingaleScore => Math.Exp(_logMartingaleValue);
 
            private protected AnomalyDetectionStateBase() { }
 
            private protected override void CloneCore(TState state)
            {
                base.CloneCore(state);
                Contracts.Assert(state is AnomalyDetectionStateBase);
                var stateLocal = state as AnomalyDetectionStateBase;
                stateLocal.LogMartingaleUpdateBuffer = LogMartingaleUpdateBuffer.Clone();
                stateLocal.RawScoreBuffer = RawScoreBuffer.Clone();
            }
 
            private protected AnomalyDetectionStateBase(BinaryReader reader) : base(reader)
            {
                LogMartingaleUpdateBuffer = TimeSeriesUtils.DeserializeFixedSizeQueueDouble(reader, Host);
                RawScoreBuffer = TimeSeriesUtils.DeserializeFixedSizeQueueSingle(reader, Host);
                _logMartingaleValue = reader.ReadDouble();
                _sumSquaredDist = reader.ReadDouble();
                _martingaleAlertCounter = reader.ReadInt32();
            }
 
            internal override void Save(BinaryWriter writer)
            {
                base.Save(writer);
                TimeSeriesUtils.SerializeFixedSizeQueue(LogMartingaleUpdateBuffer, writer);
                TimeSeriesUtils.SerializeFixedSizeQueue(RawScoreBuffer, writer);
                writer.Write(_logMartingaleValue);
                writer.Write(_sumSquaredDist);
                writer.Write(_martingaleAlertCounter);
            }
 
            private Double ComputeKernelPValue(Double rawScore)
            {
                int i;
                int n = RawScoreBuffer.Count;
 
                if (n == 0)
                    return 0.5;
 
                Double pValue = 0;
                Double bandWidth = Math.Sqrt(2) * ((n == 1) ? 1 : Math.Sqrt(_sumSquaredDist) / n);
                bandWidth = Math.Max(bandWidth, 1e-6);
 
                Double diff;
                for (i = 0; i < n; ++i)
                {
                    diff = rawScore - RawScoreBuffer[i];
                    pValue -= ProbabilityFunctions.Erf(diff / bandWidth);
                    _sumSquaredDist += diff * diff;
                }
 
                pValue = 0.5 + pValue / (2 * n);
                if (RawScoreBuffer.IsFull)
                {
                    for (i = 1; i < n; ++i)
                    {
                        diff = RawScoreBuffer[0] - RawScoreBuffer[i];
                        _sumSquaredDist -= diff * diff;
                    }
 
                    diff = RawScoreBuffer[0] - rawScore;
                    _sumSquaredDist -= diff * diff;
                }
 
                return pValue;
            }
 
            private protected override void SetNaOutput(ref VBuffer<Double> dst)
            {
                var outputLength = Parent.OutputLength;
                var editor = VBufferEditor.Create(ref dst, outputLength);
 
                for (int i = 0; i < outputLength; ++i)
                    editor.Values[i] = Double.NaN;
 
                dst = editor.Commit();
            }
 
            public sealed override void TransformCore(ref TInput input, FixedSizeQueue<TInput> windowedBuffer, long iteration, ref VBuffer<Double> dst)
            {
                var outputLength = Parent.OutputLength;
                Host.Assert(outputLength >= 2);
 
                var result = VBufferEditor.Create(ref dst, outputLength);
                float rawScore = 0;
 
                for (int i = 0; i < outputLength; ++i)
                    result.Values[i] = Double.NaN;
 
                // Step 1: Computing the raw anomaly score
                result.Values[1] = ComputeRawAnomalyScore(ref input, windowedBuffer, iteration);
 
                if (Double.IsNaN(result.Values[1]))
                    result.Values[0] = 0;
                else
                {
                    if (WindowSize > 0)
                    {
                        // Step 2: Computing the p-value score
                        rawScore = (float)result.Values[1];
                        if (Parent.ThresholdScore == AlertingScore.RawScore)
                        {
                            switch (Parent.Side)
                            {
                                case AnomalySide.Negative:
                                    rawScore = (float)(-result.Values[1]);
                                    break;
 
                                case AnomalySide.Positive:
                                    break;
 
                                default:
                                    rawScore = (float)Math.Abs(result.Values[1]);
                                    break;
                            }
                        }
                        else
                        {
                            result.Values[2] = ComputeKernelPValue(rawScore);
 
                            switch (Parent.Side)
                            {
                                case AnomalySide.Negative:
                                    result.Values[2] = 1 - result.Values[2];
                                    break;
 
                                case AnomalySide.Positive:
                                    break;
 
                                default:
                                    result.Values[2] = Math.Min(result.Values[2], 1 - result.Values[2]);
                                    break;
                            }
 
                            // Keeping the p-value in the safe range
                            if (result.Values[2] < SequentialAnomalyDetectionTransformBase<TInput, TState>.MinPValue)
                                result.Values[2] = SequentialAnomalyDetectionTransformBase<TInput, TState>.MinPValue;
                            else if (result.Values[2] > SequentialAnomalyDetectionTransformBase<TInput, TState>.MaxPValue)
                                result.Values[2] = SequentialAnomalyDetectionTransformBase<TInput, TState>.MaxPValue;
 
                            RawScoreBuffer.AddLast(rawScore);
 
                            // Step 3: Computing the martingale value
                            if (Parent.Martingale != MartingaleType.None && Parent.ThresholdScore == AlertingScore.MartingaleScore)
                            {
                                Double martingaleUpdate = 0;
                                switch (Parent.Martingale)
                                {
                                    case MartingaleType.Power:
                                        martingaleUpdate = Parent.LogPowerMartigaleBettingFunc(result.Values[2], Parent.PowerMartingaleEpsilon);
                                        break;
 
                                    case MartingaleType.Mixture:
                                        martingaleUpdate = Parent.LogMixtureMartigaleBettingFunc(result.Values[2]);
                                        break;
                                }
 
                                if (LogMartingaleUpdateBuffer.Count == 0)
                                {
                                    for (int i = 0; i < LogMartingaleUpdateBuffer.Capacity; ++i)
                                        LogMartingaleUpdateBuffer.AddLast(martingaleUpdate);
                                    _logMartingaleValue += LogMartingaleUpdateBuffer.Capacity * martingaleUpdate;
                                }
                                else
                                {
                                    _logMartingaleValue += martingaleUpdate;
                                    _logMartingaleValue -= LogMartingaleUpdateBuffer.PeekFirst();
                                    LogMartingaleUpdateBuffer.AddLast(martingaleUpdate);
                                }
 
                                result.Values[3] = Math.Exp(_logMartingaleValue);
                            }
                        }
                    }
 
                    // Generating alert
                    bool alert = false;
 
                    if (RawScoreBuffer.IsFull) // No alert until the buffer is completely full.
                    {
                        switch (Parent.ThresholdScore)
                        {
                            case AlertingScore.RawScore:
                                alert = rawScore >= Parent.AlertThreshold;
                                break;
                            case AlertingScore.PValueScore:
                                alert = result.Values[2] <= Parent.AlertThreshold;
                                break;
                            case AlertingScore.MartingaleScore:
                                alert = (Parent.Martingale != MartingaleType.None) && (result.Values[3] >= Parent.AlertThreshold);
 
                                if (alert)
                                {
                                    if (_martingaleAlertCounter > 0)
                                        alert = false;
                                    else
                                        _martingaleAlertCounter = Parent.WindowSize;
                                }
 
                                _martingaleAlertCounter--;
                                _martingaleAlertCounter = _martingaleAlertCounter < 0 ? 0 : _martingaleAlertCounter;
                                break;
                        }
                    }
 
                    result.Values[0] = Convert.ToDouble(alert);
                }
 
                dst = result.Commit();
            }
 
            private protected sealed override void InitializeStateCore(bool disk = false)
            {
                Parent = (SequentialAnomalyDetectionTransformBase<TInput, TState>)ParentTransform;
                Host.Assert(WindowSize >= 0);
 
                if (disk == false)
                {
                    if (Parent.Martingale != MartingaleType.None)
                        LogMartingaleUpdateBuffer = new FixedSizeQueue<Double>(WindowSize == 0 ? 1 : WindowSize);
                    else
                        LogMartingaleUpdateBuffer = new FixedSizeQueue<Double>(1);
 
                    RawScoreBuffer = new FixedSizeQueue<float>(WindowSize == 0 ? 1 : WindowSize);
 
                    _logMartingaleValue = 0;
                }
 
                InitializeAnomalyDetector();
            }
 
            /// <summary>
            /// The abstract method that realizes the initialization functionality for the anomaly detector.
            /// </summary>
            private protected abstract void InitializeAnomalyDetector();
 
            /// <summary>
            /// The abstract method that realizes the main logic for calculating the raw anomaly score bfor the current input given a windowed buffer
            /// </summary>
            /// <param name="input">A reference to the input object.</param>
            /// <param name="windowedBuffer">A reference to the windowed buffer.</param>
            /// <param name="iteration">A long number that indicates the number of times ComputeRawAnomalyScore has been called so far (starting value = 0).</param>
            /// <returns>The raw anomaly score for the input. The Assumption is the higher absolute value of the raw score, the more anomalous the input is.
            /// The sign of the score determines whether it's a positive anomaly or a negative one.</returns>
            private protected abstract Double ComputeRawAnomalyScore(ref TInput input, FixedSizeQueue<TInput> windowedBuffer, long iteration);
        }
    }
}