diff --git a/Microsoft.ML.sln b/Microsoft.ML.sln
index 2c7d3ead98..362347afbc 100644
--- a/Microsoft.ML.sln
+++ b/Microsoft.ML.sln
@@ -115,14 +115,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Analyzer", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.StaticPipelineTesting", "test\Microsoft.ML.StaticPipelineTesting\Microsoft.ML.StaticPipelineTesting.csproj", "{8B38BF24-35F4-4787-A9C5-22D35987106E}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TimeSeries", "src\Microsoft.ML.TimeSeries\Microsoft.ML.TimeSeries.csproj", "{5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxTransform", "src\Microsoft.ML.OnnxTransform\Microsoft.ML.OnnxTransform.csproj", "{8C05642D-C3AA-4972-B02C-93681161A6BC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnAnalyzer", "src\Microsoft.ML.DnnAnalyzer\Microsoft.ML.DnnAnalyzer\Microsoft.ML.DnnAnalyzer.csproj", "{73DAAC82-D308-48CC-8FFE-3B037F8BBCCA}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.ML.OnnxTransformTest", "test\Microsoft.ML.OnnxTransformTest\Microsoft.ML.OnnxTransformTest.csproj", "{49D03292-8AFE-4B82-823C-D047BF8420F7}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxTransformTest", "test\Microsoft.ML.OnnxTransformTest\Microsoft.ML.OnnxTransformTest.csproj", "{49D03292-8AFE-4B82-823C-D047BF8420F7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Benchmarks.Tests", "test\Microsoft.ML.Benchmarks.Tests\Microsoft.ML.Benchmarks.Tests.csproj", "{B6C83F04-A04B-4F00-9E68-1EC411F9317C}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TimeSeries.Tests", "test\Microsoft.ML.TimeSeries.Tests\Microsoft.ML.TimeSeries.Tests.csproj", "{4B101D58-E7E4-4877-A536-A9B41E2E82A3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -427,6 +431,14 @@ Global
{8B38BF24-35F4-4787-A9C5-22D35987106E}.Release|Any CPU.Build.0 = Release|Any CPU
{8B38BF24-35F4-4787-A9C5-22D35987106E}.Release-Intrinsics|Any CPU.ActiveCfg = Release-Intrinsics|Any CPU
{8B38BF24-35F4-4787-A9C5-22D35987106E}.Release-Intrinsics|Any CPU.Build.0 = Release-Intrinsics|Any CPU
+ {5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}.Debug-Intrinsics|Any CPU.ActiveCfg = Debug-Intrinsics|Any CPU
+ {5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}.Debug-Intrinsics|Any CPU.Build.0 = Debug-Intrinsics|Any CPU
+ {5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}.Release-Intrinsics|Any CPU.ActiveCfg = Release-Intrinsics|Any CPU
+ {5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}.Release-Intrinsics|Any CPU.Build.0 = Release-Intrinsics|Any CPU
{8C05642D-C3AA-4972-B02C-93681161A6BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C05642D-C3AA-4972-B02C-93681161A6BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C05642D-C3AA-4972-B02C-93681161A6BC}.Debug-Intrinsics|Any CPU.ActiveCfg = Debug-Intrinsics|Any CPU
@@ -459,6 +471,14 @@ Global
{B6C83F04-A04B-4F00-9E68-1EC411F9317C}.Release|Any CPU.Build.0 = Release|Any CPU
{B6C83F04-A04B-4F00-9E68-1EC411F9317C}.Release-Intrinsics|Any CPU.ActiveCfg = Release-Intrinsics|Any CPU
{B6C83F04-A04B-4F00-9E68-1EC411F9317C}.Release-Intrinsics|Any CPU.Build.0 = Release-Intrinsics|Any CPU
+ {4B101D58-E7E4-4877-A536-A9B41E2E82A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4B101D58-E7E4-4877-A536-A9B41E2E82A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4B101D58-E7E4-4877-A536-A9B41E2E82A3}.Debug-Intrinsics|Any CPU.ActiveCfg = Debug-Intrinsics|Any CPU
+ {4B101D58-E7E4-4877-A536-A9B41E2E82A3}.Debug-Intrinsics|Any CPU.Build.0 = Debug-Intrinsics|Any CPU
+ {4B101D58-E7E4-4877-A536-A9B41E2E82A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4B101D58-E7E4-4877-A536-A9B41E2E82A3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4B101D58-E7E4-4877-A536-A9B41E2E82A3}.Release-Intrinsics|Any CPU.ActiveCfg = Release-Intrinsics|Any CPU
+ {4B101D58-E7E4-4877-A536-A9B41E2E82A3}.Release-Intrinsics|Any CPU.Build.0 = Release-Intrinsics|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -506,10 +526,12 @@ Global
{570A0B8A-5463-44D2-8521-54C0CA4CACA9} = {09EADF06-BE25-4228-AB53-95AE3E15B530}
{6DEF0F40-3853-47B3-8165-5F24BA5E14DF} = {09EADF06-BE25-4228-AB53-95AE3E15B530}
{8B38BF24-35F4-4787-A9C5-22D35987106E} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4}
+ {5A79C7F0-3D99-4123-B0DA-7C9FFCD13132} = {09EADF06-BE25-4228-AB53-95AE3E15B530}
{8C05642D-C3AA-4972-B02C-93681161A6BC} = {09EADF06-BE25-4228-AB53-95AE3E15B530}
{73DAAC82-D308-48CC-8FFE-3B037F8BBCCA} = {09EADF06-BE25-4228-AB53-95AE3E15B530}
{49D03292-8AFE-4B82-823C-D047BF8420F7} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4}
{B6C83F04-A04B-4F00-9E68-1EC411F9317C} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4}
+ {4B101D58-E7E4-4877-A536-A9B41E2E82A3} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {41165AF1-35BB-4832-A189-73060F82B01D}
diff --git a/build/Dependencies.props b/build/Dependencies.props
index 704b323c71..34971cf262 100644
--- a/build/Dependencies.props
+++ b/build/Dependencies.props
@@ -8,7 +8,7 @@
4.3.0
1.0.0-beta-62824-02
2.1.2.2
- 0.0.0.6
+ 0.0.0.7
4.5.0
0.11.1
1.10.0
diff --git a/pkg/Microsoft.ML.HalLearners/Microsoft.ML.HalLearners.nupkgproj b/pkg/Microsoft.ML.HalLearners/Microsoft.ML.HalLearners.nupkgproj
index 132b995a2f..2503b9911a 100644
--- a/pkg/Microsoft.ML.HalLearners/Microsoft.ML.HalLearners.nupkgproj
+++ b/pkg/Microsoft.ML.HalLearners/Microsoft.ML.HalLearners.nupkgproj
@@ -7,10 +7,10 @@
+
-
diff --git a/pkg/Microsoft.ML.Mkl.Redist/Microsoft.ML.Mkl.Redist.nupkgproj b/pkg/Microsoft.ML.Mkl.Redist/Microsoft.ML.Mkl.Redist.nupkgproj
new file mode 100644
index 0000000000..ef139e763b
--- /dev/null
+++ b/pkg/Microsoft.ML.Mkl.Redist/Microsoft.ML.Mkl.Redist.nupkgproj
@@ -0,0 +1,14 @@
+
+
+
+ Intel
+ netstandard2.0
+ $(MSBuildProjectName) contains the MKL library redistributed as a NuGet package.
+ $(PackageTags) MLNET MKL
+
+
+
+
+
+
+
diff --git a/pkg/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.nupkgproj b/pkg/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.nupkgproj
new file mode 100644
index 0000000000..d0325da023
--- /dev/null
+++ b/pkg/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.nupkgproj
@@ -0,0 +1,13 @@
+
+
+
+ netstandard2.0
+ Microsoft.ML.TimeSeries contains ML.NET Time Series prediction algorithms. Uses Intel Mkl.
+
+
+
+
+
+
+
+
diff --git a/pkg/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.symbols.nupkgproj b/pkg/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.symbols.nupkgproj
new file mode 100644
index 0000000000..05f361fa6c
--- /dev/null
+++ b/pkg/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.symbols.nupkgproj
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/Common/AssemblyLoadingUtils.cs b/src/Common/AssemblyLoadingUtils.cs
index bbfa1d8126..9a9dc0c079 100644
--- a/src/Common/AssemblyLoadingUtils.cs
+++ b/src/Common/AssemblyLoadingUtils.cs
@@ -152,6 +152,7 @@ private static bool ShouldSkipPath(string path)
case "mklimports.dll":
case "microsoft.research.controls.decisiontrees.dll":
case "microsoft.ml.neuralnetworks.sse.dll":
+ case "mklproxynative.dll":
case "neuraltreeevaluator.dll":
case "optimizationbuilderdotnet.dll":
case "parallelcommunicator.dll":
diff --git a/src/Microsoft.ML.HalLearners/Microsoft.ML.HalLearners.csproj b/src/Microsoft.ML.HalLearners/Microsoft.ML.HalLearners.csproj
index e23ef26a76..7690a14ec4 100644
--- a/src/Microsoft.ML.HalLearners/Microsoft.ML.HalLearners.csproj
+++ b/src/Microsoft.ML.HalLearners/Microsoft.ML.HalLearners.csproj
@@ -10,7 +10,6 @@
-
diff --git a/src/Microsoft.ML.HalLearners/SymSgdClassificationTrainer.cs b/src/Microsoft.ML.HalLearners/SymSgdClassificationTrainer.cs
index 77d7025800..42a9dc9e26 100644
--- a/src/Microsoft.ML.HalLearners/SymSgdClassificationTrainer.cs
+++ b/src/Microsoft.ML.HalLearners/SymSgdClassificationTrainer.cs
@@ -754,6 +754,9 @@ private void CheckLabel(RoleMappedData examples, out int weightSetCount)
private static unsafe class Native
{
+ //To triger the loading of MKL library since SymSGD native library depends on it.
+ static Native() => ErrorMessage(0);
+
internal const string DllName = "SymSgdNative";
[DllImport(DllName), SuppressUnmanagedCodeSecurity]
@@ -848,6 +851,11 @@ public static void DeallocateSequentially(GCHandle stateGCHandle)
{
DeallocateSequentially((State*)stateGCHandle.AddrOfPinnedObject());
}
+
+ // See: https://software.intel.com/en-us/node/521990
+ [System.Security.SuppressUnmanagedCodeSecurity]
+ [DllImport("MklImports", EntryPoint = "DftiErrorMessage", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
+ private static extern IntPtr ErrorMessage(int status);
}
///
diff --git a/src/Microsoft.ML.Legacy/CSharpApi.cs b/src/Microsoft.ML.Legacy/CSharpApi.cs
index 634e071f4e..a556621c94 100644
--- a/src/Microsoft.ML.Legacy/CSharpApi.cs
+++ b/src/Microsoft.ML.Legacy/CSharpApi.cs
@@ -478,6 +478,102 @@ public void Add(Microsoft.ML.Legacy.Models.TrainTestEvaluator input, Microsoft.M
_jsonNodes.Add(Serialize("Models.TrainTestEvaluator", input, output));
}
+ public Microsoft.ML.Legacy.TimeSeriesProcessing.ExponentialAverage.Output Add(Microsoft.ML.Legacy.TimeSeriesProcessing.ExponentialAverage input)
+ {
+ var output = new Microsoft.ML.Legacy.TimeSeriesProcessing.ExponentialAverage.Output();
+ Add(input, output);
+ return output;
+ }
+
+ public void Add(Microsoft.ML.Legacy.TimeSeriesProcessing.ExponentialAverage input, Microsoft.ML.Legacy.TimeSeriesProcessing.ExponentialAverage.Output output)
+ {
+ _jsonNodes.Add(Serialize("TimeSeriesProcessing.ExponentialAverage", input, output));
+ }
+
+ public Microsoft.ML.Legacy.TimeSeriesProcessing.IidChangePointDetector.Output Add(Microsoft.ML.Legacy.TimeSeriesProcessing.IidChangePointDetector input)
+ {
+ var output = new Microsoft.ML.Legacy.TimeSeriesProcessing.IidChangePointDetector.Output();
+ Add(input, output);
+ return output;
+ }
+
+ public void Add(Microsoft.ML.Legacy.TimeSeriesProcessing.IidChangePointDetector input, Microsoft.ML.Legacy.TimeSeriesProcessing.IidChangePointDetector.Output output)
+ {
+ _jsonNodes.Add(Serialize("TimeSeriesProcessing.IidChangePointDetector", input, output));
+ }
+
+ public Microsoft.ML.Legacy.TimeSeriesProcessing.IidSpikeDetector.Output Add(Microsoft.ML.Legacy.TimeSeriesProcessing.IidSpikeDetector input)
+ {
+ var output = new Microsoft.ML.Legacy.TimeSeriesProcessing.IidSpikeDetector.Output();
+ Add(input, output);
+ return output;
+ }
+
+ public void Add(Microsoft.ML.Legacy.TimeSeriesProcessing.IidSpikeDetector input, Microsoft.ML.Legacy.TimeSeriesProcessing.IidSpikeDetector.Output output)
+ {
+ _jsonNodes.Add(Serialize("TimeSeriesProcessing.IidSpikeDetector", input, output));
+ }
+
+ public Microsoft.ML.Legacy.TimeSeriesProcessing.PercentileThresholdTransform.Output Add(Microsoft.ML.Legacy.TimeSeriesProcessing.PercentileThresholdTransform input)
+ {
+ var output = new Microsoft.ML.Legacy.TimeSeriesProcessing.PercentileThresholdTransform.Output();
+ Add(input, output);
+ return output;
+ }
+
+ public void Add(Microsoft.ML.Legacy.TimeSeriesProcessing.PercentileThresholdTransform input, Microsoft.ML.Legacy.TimeSeriesProcessing.PercentileThresholdTransform.Output output)
+ {
+ _jsonNodes.Add(Serialize("TimeSeriesProcessing.PercentileThresholdTransform", input, output));
+ }
+
+ public Microsoft.ML.Legacy.TimeSeriesProcessing.PValueTransform.Output Add(Microsoft.ML.Legacy.TimeSeriesProcessing.PValueTransform input)
+ {
+ var output = new Microsoft.ML.Legacy.TimeSeriesProcessing.PValueTransform.Output();
+ Add(input, output);
+ return output;
+ }
+
+ public void Add(Microsoft.ML.Legacy.TimeSeriesProcessing.PValueTransform input, Microsoft.ML.Legacy.TimeSeriesProcessing.PValueTransform.Output output)
+ {
+ _jsonNodes.Add(Serialize("TimeSeriesProcessing.PValueTransform", input, output));
+ }
+
+ public Microsoft.ML.Legacy.TimeSeriesProcessing.SlidingWindowTransform.Output Add(Microsoft.ML.Legacy.TimeSeriesProcessing.SlidingWindowTransform input)
+ {
+ var output = new Microsoft.ML.Legacy.TimeSeriesProcessing.SlidingWindowTransform.Output();
+ Add(input, output);
+ return output;
+ }
+
+ public void Add(Microsoft.ML.Legacy.TimeSeriesProcessing.SlidingWindowTransform input, Microsoft.ML.Legacy.TimeSeriesProcessing.SlidingWindowTransform.Output output)
+ {
+ _jsonNodes.Add(Serialize("TimeSeriesProcessing.SlidingWindowTransform", input, output));
+ }
+
+ public Microsoft.ML.Legacy.TimeSeriesProcessing.SsaChangePointDetector.Output Add(Microsoft.ML.Legacy.TimeSeriesProcessing.SsaChangePointDetector input)
+ {
+ var output = new Microsoft.ML.Legacy.TimeSeriesProcessing.SsaChangePointDetector.Output();
+ Add(input, output);
+ return output;
+ }
+
+ public void Add(Microsoft.ML.Legacy.TimeSeriesProcessing.SsaChangePointDetector input, Microsoft.ML.Legacy.TimeSeriesProcessing.SsaChangePointDetector.Output output)
+ {
+ _jsonNodes.Add(Serialize("TimeSeriesProcessing.SsaChangePointDetector", input, output));
+ }
+
+ public Microsoft.ML.Legacy.TimeSeriesProcessing.SsaSpikeDetector.Output Add(Microsoft.ML.Legacy.TimeSeriesProcessing.SsaSpikeDetector input)
+ {
+ var output = new Microsoft.ML.Legacy.TimeSeriesProcessing.SsaSpikeDetector.Output();
+ Add(input, output);
+ return output;
+ }
+
+ public void Add(Microsoft.ML.Legacy.TimeSeriesProcessing.SsaSpikeDetector input, Microsoft.ML.Legacy.TimeSeriesProcessing.SsaSpikeDetector.Output output)
+ {
+ _jsonNodes.Add(Serialize("TimeSeriesProcessing.SsaSpikeDetector", input, output));
+ }
+
public Microsoft.ML.Legacy.Trainers.AveragedPerceptronBinaryClassifier.Output Add(Microsoft.ML.Legacy.Trainers.AveragedPerceptronBinaryClassifier input)
{
var output = new Microsoft.ML.Legacy.Trainers.AveragedPerceptronBinaryClassifier.Output();
@@ -4226,6 +4322,759 @@ public sealed class Output
}
}
+ namespace Legacy.TimeSeriesProcessing
+ {
+
+ ///
+ /// Applies a Exponential average on a time series.
+ ///
+ public sealed partial class ExponentialAverage : Microsoft.ML.Runtime.EntryPoints.CommonInputs.ITransformInput, Microsoft.ML.Legacy.ILearningPipelineItem
+ {
+
+
+ ///
+ /// The name of the source column
+ ///
+ public string Source { get; set; }
+
+ ///
+ /// The name of the new column
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Coefficient d in: d m(y_t) = d * y_t + (1-d) * m(y_(t-1)), it should be in [0, 1].
+ ///
+ public float Decay { get; set; } = 0.9f;
+
+ ///
+ /// Input dataset
+ ///
+ public Var Data { get; set; } = new Var();
+
+
+ public sealed class Output : Microsoft.ML.Runtime.EntryPoints.CommonOutputs.ITransformOutput
+ {
+ ///
+ /// Transformed dataset
+ ///
+ public Var OutputData { get; set; } = new Var();
+
+ ///
+ /// Transform model
+ ///
+ public Var Model { get; set; } = new Var();
+
+ }
+ public Var GetInputData() => Data;
+
+ public ILearningPipelineStep ApplyStep(ILearningPipelineStep previousStep, Experiment experiment)
+ {
+ if (previousStep != null)
+ {
+ if (!(previousStep is ILearningPipelineDataStep dataStep))
+ {
+ throw new InvalidOperationException($"{ nameof(ExponentialAverage)} only supports an { nameof(ILearningPipelineDataStep)} as an input.");
+ }
+
+ Data = dataStep.Data;
+ }
+ Output output = experiment.Add(this);
+ return new ExponentialAveragePipelineStep(output);
+ }
+
+ private class ExponentialAveragePipelineStep : ILearningPipelineDataStep
+ {
+ public ExponentialAveragePipelineStep(Output output)
+ {
+ Data = output.OutputData;
+ Model = output.Model;
+ }
+
+ public Var Data { get; }
+ public Var Model { get; }
+ }
+ }
+ }
+
+ namespace Legacy.TimeSeriesProcessing
+ {
+ public enum SequentialAnomalyDetectionTransformBaseSingleIidAnomalyDetectionBaseStateMartingaleType : byte
+ {
+ None = 0,
+ Power = 1,
+ Mixture = 2
+ }
+
+
+ ///
+ /// This transform detects the change-points in an i.i.d. sequence using adaptive kernel density estimation and martingales.
+ ///
+ public sealed partial class IidChangePointDetector : Microsoft.ML.Runtime.EntryPoints.CommonInputs.ITransformInput, Microsoft.ML.Legacy.ILearningPipelineItem
+ {
+
+
+ ///
+ /// The name of the source column.
+ ///
+ public string Source { get; set; }
+
+ ///
+ /// The name of the new column.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The change history length.
+ ///
+ public int ChangeHistoryLength { get; set; } = 20;
+
+ ///
+ /// The confidence for change point detection in the range [0, 100].
+ ///
+ public double Confidence { get; set; } = 95d;
+
+ ///
+ /// The martingale used for scoring.
+ ///
+ public SequentialAnomalyDetectionTransformBaseSingleIidAnomalyDetectionBaseStateMartingaleType Martingale { get; set; } = SequentialAnomalyDetectionTransformBaseSingleIidAnomalyDetectionBaseStateMartingaleType.Power;
+
+ ///
+ /// The epsilon parameter for the Power martingale.
+ ///
+ public double PowerMartingaleEpsilon { get; set; } = 0.1d;
+
+ ///
+ /// Input dataset
+ ///
+ public Var Data { get; set; } = new Var();
+
+
+ public sealed class Output : Microsoft.ML.Runtime.EntryPoints.CommonOutputs.ITransformOutput
+ {
+ ///
+ /// Transformed dataset
+ ///
+ public Var OutputData { get; set; } = new Var();
+
+ ///
+ /// Transform model
+ ///
+ public Var Model { get; set; } = new Var();
+
+ }
+ public Var GetInputData() => Data;
+
+ public ILearningPipelineStep ApplyStep(ILearningPipelineStep previousStep, Experiment experiment)
+ {
+ if (previousStep != null)
+ {
+ if (!(previousStep is ILearningPipelineDataStep dataStep))
+ {
+ throw new InvalidOperationException($"{ nameof(IidChangePointDetector)} only supports an { nameof(ILearningPipelineDataStep)} as an input.");
+ }
+
+ Data = dataStep.Data;
+ }
+ Output output = experiment.Add(this);
+ return new IidChangePointDetectorPipelineStep(output);
+ }
+
+ private class IidChangePointDetectorPipelineStep : ILearningPipelineDataStep
+ {
+ public IidChangePointDetectorPipelineStep(Output output)
+ {
+ Data = output.OutputData;
+ Model = output.Model;
+ }
+
+ public Var Data { get; }
+ public Var Model { get; }
+ }
+ }
+ }
+
+ namespace Legacy.TimeSeriesProcessing
+ {
+ public enum SequentialAnomalyDetectionTransformBaseSingleIidAnomalyDetectionBaseStateAnomalySide : byte
+ {
+ Positive = 0,
+ Negative = 1,
+ TwoSided = 2
+ }
+
+
+ ///
+ /// This transform detects the spikes in a i.i.d. sequence using adaptive kernel density estimation.
+ ///
+ public sealed partial class IidSpikeDetector : Microsoft.ML.Runtime.EntryPoints.CommonInputs.ITransformInput, Microsoft.ML.Legacy.ILearningPipelineItem
+ {
+
+
+ ///
+ /// The name of the source column.
+ ///
+ public string Source { get; set; }
+
+ ///
+ /// The name of the new column.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The argument that determines whether to detect positive or negative anomalies, or both.
+ ///
+ public SequentialAnomalyDetectionTransformBaseSingleIidAnomalyDetectionBaseStateAnomalySide Side { get; set; } = SequentialAnomalyDetectionTransformBaseSingleIidAnomalyDetectionBaseStateAnomalySide.TwoSided;
+
+ ///
+ /// The size of the sliding window for computing the p-value.
+ ///
+ public int PvalueHistoryLength { get; set; } = 100;
+
+ ///
+ /// The confidence for spike detection in the range [0, 100].
+ ///
+ public double Confidence { get; set; } = 99d;
+
+ ///
+ /// Input dataset
+ ///
+ public Var Data { get; set; } = new Var();
+
+
+ public sealed class Output : Microsoft.ML.Runtime.EntryPoints.CommonOutputs.ITransformOutput
+ {
+ ///
+ /// Transformed dataset
+ ///
+ public Var OutputData { get; set; } = new Var();
+
+ ///
+ /// Transform model
+ ///
+ public Var Model { get; set; } = new Var();
+
+ }
+ public Var GetInputData() => Data;
+
+ public ILearningPipelineStep ApplyStep(ILearningPipelineStep previousStep, Experiment experiment)
+ {
+ if (previousStep != null)
+ {
+ if (!(previousStep is ILearningPipelineDataStep dataStep))
+ {
+ throw new InvalidOperationException($"{ nameof(IidSpikeDetector)} only supports an { nameof(ILearningPipelineDataStep)} as an input.");
+ }
+
+ Data = dataStep.Data;
+ }
+ Output output = experiment.Add(this);
+ return new IidSpikeDetectorPipelineStep(output);
+ }
+
+ private class IidSpikeDetectorPipelineStep : ILearningPipelineDataStep
+ {
+ public IidSpikeDetectorPipelineStep(Output output)
+ {
+ Data = output.OutputData;
+ Model = output.Model;
+ }
+
+ public Var Data { get; }
+ public Var Model { get; }
+ }
+ }
+ }
+
+ namespace Legacy.TimeSeriesProcessing
+ {
+
+ ///
+ /// Detects the values of time-series that are in the top percentile of the sliding window.
+ ///
+ public sealed partial class PercentileThresholdTransform : Microsoft.ML.Runtime.EntryPoints.CommonInputs.ITransformInput, Microsoft.ML.Legacy.ILearningPipelineItem
+ {
+
+
+ ///
+ /// The name of the source column
+ ///
+ public string Source { get; set; }
+
+ ///
+ /// The name of the new column
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The percentile value for thresholding in the range [0, 100]
+ ///
+ public double Percentile { get; set; } = 1d;
+
+ ///
+ /// The size of the sliding window for computing the percentile threshold. The default value is set to 1.
+ ///
+ public int WindowSize { get; set; } = 1;
+
+ ///
+ /// Input dataset
+ ///
+ public Var Data { get; set; } = new Var();
+
+
+ public sealed class Output : Microsoft.ML.Runtime.EntryPoints.CommonOutputs.ITransformOutput
+ {
+ ///
+ /// Transformed dataset
+ ///
+ public Var OutputData { get; set; } = new Var();
+
+ ///
+ /// Transform model
+ ///
+ public Var Model { get; set; } = new Var();
+
+ }
+ public Var GetInputData() => Data;
+
+ public ILearningPipelineStep ApplyStep(ILearningPipelineStep previousStep, Experiment experiment)
+ {
+ if (previousStep != null)
+ {
+ if (!(previousStep is ILearningPipelineDataStep dataStep))
+ {
+ throw new InvalidOperationException($"{ nameof(PercentileThresholdTransform)} only supports an { nameof(ILearningPipelineDataStep)} as an input.");
+ }
+
+ Data = dataStep.Data;
+ }
+ Output output = experiment.Add(this);
+ return new PercentileThresholdTransformPipelineStep(output);
+ }
+
+ private class PercentileThresholdTransformPipelineStep : ILearningPipelineDataStep
+ {
+ public PercentileThresholdTransformPipelineStep(Output output)
+ {
+ Data = output.OutputData;
+ Model = output.Model;
+ }
+
+ public Var Data { get; }
+ public Var Model { get; }
+ }
+ }
+ }
+
+ namespace Legacy.TimeSeriesProcessing
+ {
+
+ ///
+ /// This P-Value transform calculates the p-value of the current input in the sequence with regard to the values in the sliding window.
+ ///
+ public sealed partial class PValueTransform : Microsoft.ML.Runtime.EntryPoints.CommonInputs.ITransformInput, Microsoft.ML.Legacy.ILearningPipelineItem
+ {
+
+
+ ///
+ /// The name of the source column
+ ///
+ public string Source { get; set; }
+
+ ///
+ /// The name of the new column
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The seed value of the random generator
+ ///
+ public int Seed { get; set; }
+
+ ///
+ /// The flag that determines whether the p-values are calculated on the positive side
+ ///
+ public bool PositiveSide { get; set; } = true;
+
+ ///
+ /// The size of the sliding window for computing the p-value
+ ///
+ public int WindowSize { get; set; } = 1;
+
+ ///
+ /// The size of the initial window for computing the p-value. The default value is set to 0, which means there is no initial window considered.
+ ///
+ public int InitialWindowSize { get; set; }
+
+ ///
+ /// Input dataset
+ ///
+ public Var Data { get; set; } = new Var();
+
+
+ public sealed class Output : Microsoft.ML.Runtime.EntryPoints.CommonOutputs.ITransformOutput
+ {
+ ///
+ /// Transformed dataset
+ ///
+ public Var OutputData { get; set; } = new Var();
+
+ ///
+ /// Transform model
+ ///
+ public Var Model { get; set; } = new Var();
+
+ }
+ public Var GetInputData() => Data;
+
+ public ILearningPipelineStep ApplyStep(ILearningPipelineStep previousStep, Experiment experiment)
+ {
+ if (previousStep != null)
+ {
+ if (!(previousStep is ILearningPipelineDataStep dataStep))
+ {
+ throw new InvalidOperationException($"{ nameof(PValueTransform)} only supports an { nameof(ILearningPipelineDataStep)} as an input.");
+ }
+
+ Data = dataStep.Data;
+ }
+ Output output = experiment.Add(this);
+ return new PValueTransformPipelineStep(output);
+ }
+
+ private class PValueTransformPipelineStep : ILearningPipelineDataStep
+ {
+ public PValueTransformPipelineStep(Output output)
+ {
+ Data = output.OutputData;
+ Model = output.Model;
+ }
+
+ public Var Data { get; }
+ public Var Model { get; }
+ }
+ }
+ }
+
+ namespace Legacy.TimeSeriesProcessing
+ {
+ public enum SlidingWindowTransformBaseSingleBeginOptions : byte
+ {
+ NaNValues = 0,
+ FirstValue = 1
+ }
+
+
+ ///
+ /// Returns the last values for a time series [y(t-d-l+1), y(t-d-l+2), ..., y(t-l-1), y(t-l)] where d is the size of the window, l the lag and y is a Float.
+ ///
+ public sealed partial class SlidingWindowTransform : Microsoft.ML.Runtime.EntryPoints.CommonInputs.ITransformInput, Microsoft.ML.Legacy.ILearningPipelineItem
+ {
+
+
+ ///
+ /// The name of the source column
+ ///
+ public string Source { get; set; }
+
+ ///
+ /// The name of the new column
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The size of the sliding window for computing the moving average
+ ///
+ public int WindowSize { get; set; } = 2;
+
+ ///
+ /// Lag between current observation and last observation from the sliding window
+ ///
+ public int Lag { get; set; } = 1;
+
+ ///
+ /// Define how to populate the first rows of the produced series
+ ///
+ public SlidingWindowTransformBaseSingleBeginOptions Begin { get; set; } = SlidingWindowTransformBaseSingleBeginOptions.NaNValues;
+
+ ///
+ /// Input dataset
+ ///
+ public Var Data { get; set; } = new Var();
+
+
+ public sealed class Output : Microsoft.ML.Runtime.EntryPoints.CommonOutputs.ITransformOutput
+ {
+ ///
+ /// Transformed dataset
+ ///
+ public Var OutputData { get; set; } = new Var();
+
+ ///
+ /// Transform model
+ ///
+ public Var Model { get; set; } = new Var();
+
+ }
+ public Var GetInputData() => Data;
+
+ public ILearningPipelineStep ApplyStep(ILearningPipelineStep previousStep, Experiment experiment)
+ {
+ if (previousStep != null)
+ {
+ if (!(previousStep is ILearningPipelineDataStep dataStep))
+ {
+ throw new InvalidOperationException($"{ nameof(SlidingWindowTransform)} only supports an { nameof(ILearningPipelineDataStep)} as an input.");
+ }
+
+ Data = dataStep.Data;
+ }
+ Output output = experiment.Add(this);
+ return new SlidingWindowTransformPipelineStep(output);
+ }
+
+ private class SlidingWindowTransformPipelineStep : ILearningPipelineDataStep
+ {
+ public SlidingWindowTransformPipelineStep(Output output)
+ {
+ Data = output.OutputData;
+ Model = output.Model;
+ }
+
+ public Var Data { get; }
+ public Var Model { get; }
+ }
+ }
+ }
+
+ namespace Legacy.TimeSeriesProcessing
+ {
+ public enum ErrorFunctionUtilsErrorFunction : byte
+ {
+ SignedDifference = 0,
+ AbsoluteDifference = 1,
+ SignedProportion = 2,
+ AbsoluteProportion = 3,
+ SquaredDifference = 4
+ }
+
+ public enum SequentialAnomalyDetectionTransformBaseSingleSsaAnomalyDetectionBaseStateMartingaleType : byte
+ {
+ None = 0,
+ Power = 1,
+ Mixture = 2
+ }
+
+
+ ///
+ /// This transform detects the change-points in a seasonal time-series using Singular Spectrum Analysis (SSA).
+ ///
+ public sealed partial class SsaChangePointDetector : Microsoft.ML.Runtime.EntryPoints.CommonInputs.ITransformInput, Microsoft.ML.Legacy.ILearningPipelineItem
+ {
+
+
+ ///
+ /// The name of the source column.
+ ///
+ public string Source { get; set; }
+
+ ///
+ /// The name of the new column.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The change history length.
+ ///
+ public int ChangeHistoryLength { get; set; } = 20;
+
+ ///
+ /// The number of points from the beginning of the sequence used for training.
+ ///
+ public int TrainingWindowSize { get; set; } = 100;
+
+ ///
+ /// The confidence for change point detection in the range [0, 100].
+ ///
+ public double Confidence { get; set; } = 95d;
+
+ ///
+ /// An upper bound on the largest relevant seasonality in the input time-series.
+ ///
+ public int SeasonalWindowSize { get; set; } = 10;
+
+ ///
+ /// The function used to compute the error between the expected and the observed value.
+ ///
+ public ErrorFunctionUtilsErrorFunction ErrorFunction { get; set; } = ErrorFunctionUtilsErrorFunction.SignedDifference;
+
+ ///
+ /// The martingale used for scoring.
+ ///
+ public SequentialAnomalyDetectionTransformBaseSingleSsaAnomalyDetectionBaseStateMartingaleType Martingale { get; set; } = SequentialAnomalyDetectionTransformBaseSingleSsaAnomalyDetectionBaseStateMartingaleType.Power;
+
+ ///
+ /// The epsilon parameter for the Power martingale.
+ ///
+ public double PowerMartingaleEpsilon { get; set; } = 0.1d;
+
+ ///
+ /// Input dataset
+ ///
+ public Var Data { get; set; } = new Var();
+
+
+ public sealed class Output : Microsoft.ML.Runtime.EntryPoints.CommonOutputs.ITransformOutput
+ {
+ ///
+ /// Transformed dataset
+ ///
+ public Var OutputData { get; set; } = new Var();
+
+ ///
+ /// Transform model
+ ///
+ public Var Model { get; set; } = new Var();
+
+ }
+ public Var GetInputData() => Data;
+
+ public ILearningPipelineStep ApplyStep(ILearningPipelineStep previousStep, Experiment experiment)
+ {
+ if (previousStep != null)
+ {
+ if (!(previousStep is ILearningPipelineDataStep dataStep))
+ {
+ throw new InvalidOperationException($"{ nameof(SsaChangePointDetector)} only supports an { nameof(ILearningPipelineDataStep)} as an input.");
+ }
+
+ Data = dataStep.Data;
+ }
+ Output output = experiment.Add(this);
+ return new SsaChangePointDetectorPipelineStep(output);
+ }
+
+ private class SsaChangePointDetectorPipelineStep : ILearningPipelineDataStep
+ {
+ public SsaChangePointDetectorPipelineStep(Output output)
+ {
+ Data = output.OutputData;
+ Model = output.Model;
+ }
+
+ public Var Data { get; }
+ public Var Model { get; }
+ }
+ }
+ }
+
+ namespace Legacy.TimeSeriesProcessing
+ {
+ public enum SequentialAnomalyDetectionTransformBaseSingleSsaAnomalyDetectionBaseStateAnomalySide : byte
+ {
+ Positive = 0,
+ Negative = 1,
+ TwoSided = 2
+ }
+
+
+ ///
+ /// This transform detects the spikes in a seasonal time-series using Singular Spectrum Analysis (SSA).
+ ///
+ public sealed partial class SsaSpikeDetector : Microsoft.ML.Runtime.EntryPoints.CommonInputs.ITransformInput, Microsoft.ML.Legacy.ILearningPipelineItem
+ {
+
+
+ ///
+ /// The name of the source column.
+ ///
+ public string Source { get; set; }
+
+ ///
+ /// The name of the new column.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The argument that determines whether to detect positive or negative anomalies, or both.
+ ///
+ public SequentialAnomalyDetectionTransformBaseSingleSsaAnomalyDetectionBaseStateAnomalySide Side { get; set; } = SequentialAnomalyDetectionTransformBaseSingleSsaAnomalyDetectionBaseStateAnomalySide.TwoSided;
+
+ ///
+ /// The size of the sliding window for computing the p-value.
+ ///
+ public int PvalueHistoryLength { get; set; } = 100;
+
+ ///
+ /// The number of points from the beginning of the sequence used for training.
+ ///
+ public int TrainingWindowSize { get; set; } = 100;
+
+ ///
+ /// The confidence for spike detection in the range [0, 100].
+ ///
+ public double Confidence { get; set; } = 99d;
+
+ ///
+ /// An upper bound on the largest relevant seasonality in the input time-series.
+ ///
+ public int SeasonalWindowSize { get; set; } = 10;
+
+ ///
+ /// The function used to compute the error between the expected and the observed value.
+ ///
+ public ErrorFunctionUtilsErrorFunction ErrorFunction { get; set; } = ErrorFunctionUtilsErrorFunction.SignedDifference;
+
+ ///
+ /// Input dataset
+ ///
+ public Var Data { get; set; } = new Var();
+
+
+ public sealed class Output : Microsoft.ML.Runtime.EntryPoints.CommonOutputs.ITransformOutput
+ {
+ ///
+ /// Transformed dataset
+ ///
+ public Var OutputData { get; set; } = new Var();
+
+ ///
+ /// Transform model
+ ///
+ public Var Model { get; set; } = new Var();
+
+ }
+ public Var GetInputData() => Data;
+
+ public ILearningPipelineStep ApplyStep(ILearningPipelineStep previousStep, Experiment experiment)
+ {
+ if (previousStep != null)
+ {
+ if (!(previousStep is ILearningPipelineDataStep dataStep))
+ {
+ throw new InvalidOperationException($"{ nameof(SsaSpikeDetector)} only supports an { nameof(ILearningPipelineDataStep)} as an input.");
+ }
+
+ Data = dataStep.Data;
+ }
+ Output output = experiment.Add(this);
+ return new SsaSpikeDetectorPipelineStep(output);
+ }
+
+ private class SsaSpikeDetectorPipelineStep : ILearningPipelineDataStep
+ {
+ public SsaSpikeDetectorPipelineStep(Output output)
+ {
+ Data = output.OutputData;
+ Model = output.Model;
+ }
+
+ public Var Data { get; }
+ public Var Model { get; }
+ }
+ }
+ }
+
namespace Legacy.Trainers
{
diff --git a/src/Microsoft.ML.TimeSeries/AdaptiveSingularSpectrumSequenceModeler.cs b/src/Microsoft.ML.TimeSeries/AdaptiveSingularSpectrumSequenceModeler.cs
new file mode 100644
index 0000000000..7f0fe543f6
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/AdaptiveSingularSpectrumSequenceModeler.cs
@@ -0,0 +1,1535 @@
+// 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.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Numerics;
+using Microsoft.ML.Runtime;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.Internal.CpuMath;
+using Microsoft.ML.Runtime.Internal.Utilities;
+using Microsoft.ML.Runtime.Model;
+using Microsoft.ML.Runtime.TimeSeriesProcessing;
+
+[assembly: LoadableClass(typeof(ISequenceModeler), typeof(AdaptiveSingularSpectrumSequenceModeler), null, typeof(SignatureLoadModel),
+ "SSA Sequence Modeler",
+ AdaptiveSingularSpectrumSequenceModeler.LoaderSignature)]
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// This class implements basic Singular Spectrum Analysis (SSA) model for modeling univariate time-series.
+ /// For the details of the model, refer to http://arxiv.org/pdf/1206.6910.pdf.
+ ///
+ public sealed class AdaptiveSingularSpectrumSequenceModeler : ISequenceModeler
+ {
+ public const string LoaderSignature = "SSAModel";
+
+ public enum RankSelectionMethod
+ {
+ Fixed,
+ Exact,
+ Fast
+ }
+
+ public sealed class SsaForecastResult : ForecastResultBase
+ {
+ public VBuffer ForecastStandardDeviation;
+ public VBuffer UpperBound;
+ public VBuffer LowerBound;
+ public Single ConfidenceLevel;
+
+ internal bool CanComputeForecastIntervals;
+ internal Single BoundOffset;
+
+ public bool IsVarianceValid { get { return CanComputeForecastIntervals; } }
+ }
+
+ public struct GrowthRatio
+ {
+ private int _timeSpan;
+ private Double _growth;
+
+ public int TimeSpan
+ {
+ get
+ {
+ return _timeSpan;
+ }
+ set
+ {
+ Contracts.CheckParam(value > 0, nameof(TimeSpan), "The time span must be strictly positive.");
+ _timeSpan = value;
+ }
+ }
+
+ public Double Growth
+ {
+ get
+ {
+ return _growth;
+ }
+ set
+ {
+ Contracts.CheckParam(value >= 0, nameof(Growth), "The growth must be non-negative.");
+ _growth = value;
+ }
+ }
+
+ public GrowthRatio(int timeSpan = 1, double growth = Double.PositiveInfinity)
+ {
+ Contracts.CheckParam(timeSpan > 0, nameof(TimeSpan), "The time span must be strictly positive.");
+ Contracts.CheckParam(growth >= 0, nameof(Growth), "The growth must be non-negative.");
+
+ _growth = growth;
+ _timeSpan = timeSpan;
+ }
+
+ public Double Ratio { get { return Math.Pow(_growth, 1d / _timeSpan); } }
+ }
+
+ public sealed class ModelInfo
+ {
+ ///
+ /// The singular values of the SSA of the input time-series
+ ///
+ public Single[] Spectrum;
+
+ ///
+ /// The roots of the characteristic polynomial after stabilization (meaningful only if the model is stabilized.)
+ ///
+ public Complex[] RootsAfterStabilization;
+
+ ///
+ /// The roots of the characteristic polynomial before stabilization (meaningful only if the model is stabilized.)
+ ///
+ public Complex[] RootsBeforeStabilization;
+
+ ///
+ /// The rank of the model
+ ///
+ public int Rank;
+
+ ///
+ /// The window size used to compute the SSA model
+ ///
+ public int WindowSize;
+
+ ///
+ /// The auto-regressive coefficients learned by the model
+ ///
+ public Single[] AutoRegressiveCoefficients;
+
+ ///
+ /// The flag indicating whether the model has been trained
+ ///
+ public bool IsTrained;
+
+ ///
+ /// The flag indicating a naive model is trained instead of SSA
+ ///
+ public bool IsNaiveModelTrained;
+
+ ///
+ /// The flag indicating whether the learned model has an exponential trend (meaningful only if the model is stabilized.)
+ ///
+ public bool IsExponentialTrendPresent;
+
+ ///
+ /// The flag indicating whether the learned model has a polynomial trend (meaningful only if the model is stabilized.)
+ ///
+ public bool IsPolynomialTrendPresent;
+
+ ///
+ /// The flag indicating whether the learned model has been stabilized
+ ///
+ public bool IsStabilized;
+
+ ///
+ /// The flag indicating whether any artificial seasonality (a seasonality with period greater than the window size) is removed
+ /// (meaningful only if the model is stabilized.)
+ ///
+ public bool IsArtificialSeasonalityRemoved;
+
+ ///
+ /// The exponential trend magnitude (meaningful only if the model is stabilized.)
+ ///
+ public Double ExponentialTrendFactor;
+ }
+
+ private ModelInfo _info;
+
+ ///
+ /// Returns the meta information about the learned model.
+ ///
+ public ModelInfo Info
+ {
+ get { return _info; }
+ }
+
+ private Single[] _alpha;
+ private Single[] _state;
+ private readonly FixedSizeQueue _buffer;
+ private CpuAlignedVector _x;
+ private CpuAlignedVector _xSmooth;
+ private int _windowSize;
+ private readonly int _seriesLength;
+ private readonly RankSelectionMethod _rankSelectionMethod;
+ private readonly Single _discountFactor;
+ private readonly int _trainSize;
+ private int _maxRank;
+ private readonly Double _maxTrendRatio;
+ private readonly bool _shouldStablize;
+ private readonly bool _shouldMaintainInfo;
+
+ private readonly IHost _host;
+
+ private CpuAlignedMatrixRow _wTrans;
+ private Single _observationNoiseVariance;
+ private Single _observationNoiseMean;
+ private Single _autoregressionNoiseVariance;
+ private Single _autoregressionNoiseMean;
+
+ private int _rank;
+ private CpuAlignedVector _y;
+ private Single _nextPrediction;
+
+ ///
+ /// Determines whether the confidence interval required for forecasting.
+ ///
+ public bool ShouldComputeForecastIntervals { get; set; }
+
+ ///
+ /// Returns the rank of the subspace used for SSA projection (parameter r).
+ ///
+ public int Rank { get { return _rank; } }
+
+ ///
+ /// Returns the smoothed (via SSA projection) version of the last series observation fed to the model.
+ ///
+ public Single LastSmoothedValue { get { return _state[_windowSize - 2]; } }
+
+ ///
+ /// Returns the last series observation fed to the model.
+ ///
+ public Single LastValue { get { return _buffer.Count > 0 ? _buffer[_buffer.Count - 1] : Single.NaN; } }
+
+ private static VersionInfo GetVersionInfo()
+ {
+ return new VersionInfo(
+ modelSignature: "SSAMODLR",
+ verWrittenCur: 0x00010001, // Initial
+ verReadableCur: 0x00010001,
+ verWeCanReadBack: 0x00010001,
+ loaderSignature: LoaderSignature,
+ loaderAssemblyName: typeof(AdaptiveSingularSpectrumSequenceModeler).Assembly.FullName);
+ }
+
+ ///
+ /// The constructor for Adaptive SSA model.
+ ///
+ /// The exception context.
+ /// The length of series from the begining used for training.
+ /// The length of series that is kept in buffer for modeling (parameter N).
+ /// The length of the window on the series for building the trajectory matrix (parameter L).
+ /// The discount factor in [0,1] used for online updates (default = 1).
+ /// The buffer used to keep the series in the memory. If null, an internal buffer is created (default = null).
+ /// The rank selection method (default = Exact).
+ /// The desired rank of the subspace used for SSA projection (parameter r). This parameter should be in the range in [1, windowSize].
+ /// If set to null, the rank is automatically determined based on prediction error minimization. (default = null)
+ /// The maximum rank considered during the rank selection process. If not provided (i.e. set to null), it is set to windowSize - 1.
+ /// The flag determining whether the confidence bounds for the point forecasts should be computed. (default = true)
+ /// The flag determining whether the model should be stabilized.
+ /// The flag determining whether the meta information for the model needs to be maintained.
+ /// The maximum growth on the exponential trend
+ public AdaptiveSingularSpectrumSequenceModeler(IHostEnvironment env, int trainSize, int seriesLength, int windowSize, Single discountFactor = 1,
+ FixedSizeQueue buffer = null, RankSelectionMethod rankSelectionMethod = RankSelectionMethod.Exact, int? rank = null, int? maxRank = null,
+ bool shouldComputeForecastIntervals = true, bool shouldstablize = true, bool shouldMaintainInfo = false, GrowthRatio? maxGrowth = null)
+ {
+ Contracts.CheckValue(env, nameof(env));
+ _host = env.Register(LoaderSignature);
+ _host.CheckParam(windowSize >= 2, nameof(windowSize), "The window size should be at least 2."); // ...because otherwise we have nothing to autoregress on
+ _host.CheckParam(seriesLength > windowSize, nameof(seriesLength), "The series length should be greater than the window size.");
+ _host.Check(trainSize > 2 * windowSize, "The input series length for training should be greater than twice the window size.");
+ _host.CheckParam(0 <= discountFactor && discountFactor <= 1, nameof(discountFactor), "The discount factor should be in [0,1].");
+
+ if (maxRank != null)
+ {
+ _maxRank = (int)maxRank;
+ _host.CheckParam(1 <= _maxRank && _maxRank < windowSize, nameof(maxRank),
+ "The max rank should be in [1, windowSize).");
+ }
+ else
+ _maxRank = windowSize - 1;
+
+ _rankSelectionMethod = rankSelectionMethod;
+ if (_rankSelectionMethod == RankSelectionMethod.Fixed)
+ {
+ if (rank != null)
+ {
+ _rank = (int)rank;
+ _host.CheckParam(1 <= _rank && _rank < windowSize, nameof(rank), "The rank should be in [1, windowSize).");
+ }
+ else
+ _rank = _maxRank;
+ }
+
+ _seriesLength = seriesLength;
+ _windowSize = windowSize;
+ _trainSize = trainSize;
+ _discountFactor = discountFactor;
+
+ if (buffer == null)
+ _buffer = new FixedSizeQueue(seriesLength);
+ else
+ _buffer = buffer;
+
+ _alpha = new Single[windowSize - 1];
+ _state = new Single[windowSize - 1];
+ _x = new CpuAlignedVector(windowSize, SseUtils.CbAlign);
+ _xSmooth = new CpuAlignedVector(windowSize, SseUtils.CbAlign);
+ ShouldComputeForecastIntervals = shouldComputeForecastIntervals;
+
+ _observationNoiseVariance = 0;
+ _autoregressionNoiseVariance = 0;
+ _observationNoiseMean = 0;
+ _autoregressionNoiseMean = 0;
+ _shouldStablize = shouldstablize;
+ _maxTrendRatio = maxGrowth == null ? Double.PositiveInfinity : ((GrowthRatio)maxGrowth).Ratio;
+
+ _shouldMaintainInfo = shouldMaintainInfo;
+ if (_shouldMaintainInfo)
+ {
+ _info = new ModelInfo();
+ _info.WindowSize = _windowSize;
+ }
+ }
+
+ ///
+ /// The copy constructor
+ ///
+ /// An object whose contents are copied to the current object.
+ private AdaptiveSingularSpectrumSequenceModeler(AdaptiveSingularSpectrumSequenceModeler model)
+ {
+ _host = model._host.Register(LoaderSignature);
+ _host.Assert(model._windowSize >= 2);
+ _host.Assert(model._seriesLength > model._windowSize);
+ _host.Assert(model._trainSize > 2 * model._windowSize);
+ _host.Assert(0 <= model._discountFactor && model._discountFactor <= 1);
+ _host.Assert(1 <= model._rank && model._rank < model._windowSize);
+
+ _rank = model._rank;
+ _maxRank = model._maxRank;
+ _rankSelectionMethod = model._rankSelectionMethod;
+ _seriesLength = model._seriesLength;
+ _windowSize = model._windowSize;
+ _trainSize = model._trainSize;
+ _discountFactor = model._discountFactor;
+ ShouldComputeForecastIntervals = model.ShouldComputeForecastIntervals;
+ _observationNoiseVariance = model._observationNoiseVariance;
+ _autoregressionNoiseVariance = model._autoregressionNoiseVariance;
+ _observationNoiseMean = model._observationNoiseMean;
+ _autoregressionNoiseMean = model._autoregressionNoiseMean;
+ _maxTrendRatio = model._maxTrendRatio;
+ _shouldStablize = model._shouldStablize;
+ _shouldMaintainInfo = model._shouldMaintainInfo;
+ _info = model._info;
+ _buffer = new FixedSizeQueue(_seriesLength);
+ _alpha = new Single[_windowSize - 1];
+ Array.Copy(model._alpha, _alpha, _windowSize - 1);
+ _state = new Single[_windowSize - 1];
+ Array.Copy(model._state, _state, _windowSize - 1);
+
+ _x = new CpuAlignedVector(_windowSize, SseUtils.CbAlign);
+ _xSmooth = new CpuAlignedVector(_windowSize, SseUtils.CbAlign);
+
+ if (model._wTrans != null)
+ {
+ _y = new CpuAlignedVector(_rank, SseUtils.CbAlign);
+ _wTrans = new CpuAlignedMatrixRow(_rank, _windowSize, SseUtils.CbAlign);
+ _wTrans.CopyFrom(model._wTrans);
+ }
+ }
+
+ public AdaptiveSingularSpectrumSequenceModeler(IHostEnvironment env, ModelLoadContext ctx)
+ {
+ Contracts.CheckValue(env, nameof(env));
+ _host = env.Register(LoaderSignature);
+
+ // *** Binary format ***
+ // int: _seriesLength
+ // int: _windowSize
+ // int: _trainSize
+ // int: _rank
+ // float: _discountFactor
+ // RankSelectionMethod: _rankSelectionMethod
+ // bool: isWeightSet
+ // float[]: _alpha
+ // bool: ShouldComputeForecastIntervals
+ // float: _observationNoiseVariance
+ // float: _autoregressionNoiseVariance
+ // float: _observationNoiseMean
+ // float: _autoregressionNoiseMean
+ // int: _maxRank
+ // bool: _shouldStablize
+ // bool: _shouldMaintainInfo
+ // double: _maxTrendRatio
+ // float[]: _wTrans (only if _isWeightSet == true)
+
+ _seriesLength = ctx.Reader.ReadInt32();
+ // Do an early check. We'll have the stricter check later.
+ _host.CheckDecode(_seriesLength > 2);
+
+ _windowSize = ctx.Reader.ReadInt32();
+ _host.CheckDecode(_windowSize >= 2);
+ _host.CheckDecode(_seriesLength > _windowSize);
+
+ _trainSize = ctx.Reader.ReadInt32();
+ _host.CheckDecode(_trainSize > 2 * _windowSize);
+
+ _rank = ctx.Reader.ReadInt32();
+ _host.CheckDecode(1 <= _rank && _rank < _windowSize);
+
+ _discountFactor = ctx.Reader.ReadSingle();
+ _host.CheckDecode(0 <= _discountFactor && _discountFactor <= 1);
+
+ byte temp;
+ temp = ctx.Reader.ReadByte();
+ _rankSelectionMethod = (RankSelectionMethod)temp;
+ bool isWeightSet = ctx.Reader.ReadBoolByte();
+
+ _alpha = ctx.Reader.ReadFloatArray();
+ _host.CheckDecode(Utils.Size(_alpha) == _windowSize - 1);
+
+ ShouldComputeForecastIntervals = ctx.Reader.ReadBoolByte();
+
+ _observationNoiseVariance = ctx.Reader.ReadSingle();
+ _host.CheckDecode(_observationNoiseVariance >= 0);
+
+ _autoregressionNoiseVariance = ctx.Reader.ReadSingle();
+ _host.CheckDecode(_autoregressionNoiseVariance >= 0);
+
+ _observationNoiseMean = ctx.Reader.ReadSingle();
+ _autoregressionNoiseMean = ctx.Reader.ReadSingle();
+
+ _maxRank = ctx.Reader.ReadInt32();
+ _host.CheckDecode(1 <= _maxRank && _maxRank <= _windowSize - 1);
+
+ _shouldStablize = ctx.Reader.ReadBoolByte();
+ _shouldMaintainInfo = ctx.Reader.ReadBoolByte();
+ if (_shouldMaintainInfo)
+ {
+ _info = new ModelInfo();
+ _info.AutoRegressiveCoefficients = new Single[_windowSize - 1];
+ Array.Copy(_alpha, _info.AutoRegressiveCoefficients, _windowSize - 1);
+
+ _info.IsStabilized = _shouldStablize;
+ _info.Rank = _rank;
+ _info.WindowSize = _windowSize;
+ }
+
+ _maxTrendRatio = ctx.Reader.ReadDouble();
+ _host.CheckDecode(_maxTrendRatio >= 0);
+
+ if (isWeightSet)
+ {
+ var tempArray = ctx.Reader.ReadFloatArray();
+ _host.CheckDecode(Utils.Size(tempArray) == _rank * _windowSize);
+ _wTrans = new CpuAlignedMatrixRow(_rank, _windowSize, SseUtils.CbAlign);
+ int i = 0;
+ _wTrans.CopyFrom(tempArray, ref i);
+ }
+
+ _buffer = new FixedSizeQueue(_seriesLength);
+ _state = new Single[_windowSize - 1];
+ _x = new CpuAlignedVector(_windowSize, SseUtils.CbAlign);
+ _xSmooth = new CpuAlignedVector(_windowSize, SseUtils.CbAlign);
+ }
+
+ public void Save(ModelSaveContext ctx)
+ {
+ _host.CheckValue(ctx, nameof(ctx));
+ ctx.CheckAtModel();
+ ctx.SetVersionInfo(GetVersionInfo());
+
+ _host.Assert(_windowSize >= 2);
+ _host.Assert(_seriesLength > _windowSize);
+ _host.Assert(_trainSize > 2 * _windowSize);
+ _host.Assert(0 <= _discountFactor && _discountFactor <= 1);
+ _host.Assert(1 <= _rank && _rank < _windowSize);
+ _host.Assert(Utils.Size(_alpha) == _windowSize - 1);
+ _host.Assert(_observationNoiseVariance >= 0);
+ _host.Assert(_autoregressionNoiseVariance >= 0);
+ _host.Assert(1 <= _maxRank && _maxRank <= _windowSize - 1);
+ _host.Assert(_maxTrendRatio >= 0);
+
+ // *** Binary format ***
+ // int: _seriesLength
+ // int: _windowSize
+ // int: _trainSize
+ // int: _rank
+ // float: _discountFactor
+ // RankSelectionMethod: _rankSelectionMethod
+ // bool: _isWeightSet
+ // float[]: _alpha
+ // bool: ShouldComputeForecastIntervals
+ // float: _observationNoiseVariance
+ // float: _autoregressionNoiseVariance
+ // float: _observationNoiseMean
+ // float: _autoregressionNoiseMean
+ // int: _maxRank
+ // bool: _shouldStablize
+ // bool: _shouldMaintainInfo
+ // double: _maxTrendRatio
+ // float[]: _wTrans (only if _isWeightSet == true)
+
+ ctx.Writer.Write(_seriesLength);
+ ctx.Writer.Write(_windowSize);
+ ctx.Writer.Write(_trainSize);
+ ctx.Writer.Write(_rank);
+ ctx.Writer.Write(_discountFactor);
+ ctx.Writer.Write((byte)_rankSelectionMethod);
+ ctx.Writer.WriteBoolByte(_wTrans != null);
+ ctx.Writer.WriteFloatArray(_alpha);
+ ctx.Writer.WriteBoolByte(ShouldComputeForecastIntervals);
+ ctx.Writer.Write(_observationNoiseVariance);
+ ctx.Writer.Write(_autoregressionNoiseVariance);
+ ctx.Writer.Write(_observationNoiseMean);
+ ctx.Writer.Write(_autoregressionNoiseMean);
+ ctx.Writer.Write(_maxRank);
+ ctx.Writer.WriteBoolByte(_shouldStablize);
+ ctx.Writer.WriteBoolByte(_shouldMaintainInfo);
+ ctx.Writer.Write(_maxTrendRatio);
+
+ if (_wTrans != null)
+ {
+ // REVIEW: this may not be the most efficient way for serializing an aligned matrix.
+ var tempArray = new Single[_rank * _windowSize];
+ int iv = 0;
+ _wTrans.CopyTo(tempArray, ref iv);
+ ctx.Writer.WriteFloatArray(tempArray);
+ }
+ }
+
+ private static void ReconstructSignal(TrajectoryMatrix tMat, Single[] singularVectors, int rank, Single[] output)
+ {
+ Contracts.Assert(tMat != null);
+ Contracts.Assert(1 <= rank && rank <= tMat.WindowSize);
+ Contracts.Assert(Utils.Size(singularVectors) >= tMat.WindowSize * rank);
+ Contracts.Assert(Utils.Size(output) >= tMat.SeriesLength);
+
+ var k = tMat.SeriesLength - tMat.WindowSize + 1;
+ Contracts.Assert(k > 0);
+
+ var v = new Single[k];
+ int i;
+
+ for (i = 0; i < tMat.SeriesLength; ++i)
+ output[i] = 0;
+
+ for (i = 0; i < rank; ++i)
+ {
+ // Reconstructing the i-th eigen triple component and adding it to output
+ tMat.MultiplyTranspose(singularVectors, v, false, tMat.WindowSize * i, 0);
+ tMat.RankOneHankelization(singularVectors, v, 1, output, true, tMat.WindowSize * i, 0, 0);
+ }
+ }
+
+ private static void ReconstructSignalTailFast(Single[] series, TrajectoryMatrix tMat, Single[] singularVectors, int rank, Single[] output)
+ {
+ Contracts.Assert(tMat != null);
+ Contracts.Assert(1 <= rank && rank <= tMat.WindowSize);
+ Contracts.Assert(Utils.Size(singularVectors) >= tMat.WindowSize * rank);
+
+ int len = 2 * tMat.WindowSize - 1;
+ Contracts.Assert(Utils.Size(output) >= len);
+
+ Single v;
+ /*var k = tMat.SeriesLength - tMat.WindowSize + 1;
+ Contracts.Assert(k > 0);
+ var v = new Single[k];*/
+
+ Single temp;
+ int i;
+ int j;
+ int offset1 = tMat.SeriesLength - 2 * tMat.WindowSize + 1;
+ int offset2 = tMat.SeriesLength - tMat.WindowSize;
+
+ for (i = 0; i < len; ++i)
+ output[i] = 0;
+
+ for (i = 0; i < rank; ++i)
+ {
+ // Reconstructing the i-th eigen triple component and adding it to output
+ v = 0;
+ for (j = offset1; j < offset1 + tMat.WindowSize; ++j)
+ v += series[j] * singularVectors[tMat.WindowSize * i - offset1 + j];
+
+ for (j = 0; j < tMat.WindowSize - 1; ++j)
+ output[j] += v * singularVectors[tMat.WindowSize * i + j];
+
+ temp = v * singularVectors[tMat.WindowSize * (i + 1) - 1];
+
+ v = 0;
+ for (j = offset2; j < offset2 + tMat.WindowSize; ++j)
+ v += series[j] * singularVectors[tMat.WindowSize * i - offset2 + j];
+
+ for (j = tMat.WindowSize; j < 2 * tMat.WindowSize - 1; ++j)
+ output[j] += v * singularVectors[tMat.WindowSize * (i - 1) + j + 1];
+
+ temp += v * singularVectors[tMat.WindowSize * i];
+ output[tMat.WindowSize - 1] += (temp / 2);
+ }
+ }
+
+ private static void ComputeNoiseMoments(Single[] series, Single[] signal, Single[] alpha, out Single observationNoiseVariance, out Single autoregressionNoiseVariance,
+ out Single observationNoiseMean, out Single autoregressionNoiseMean, int startIndex = 0)
+ {
+ Contracts.Assert(Utils.Size(alpha) > 0);
+ Contracts.Assert(Utils.Size(signal) > 2 * Utils.Size(alpha)); // To assure that the autoregression noise variance is unbiased.
+ Contracts.Assert(Utils.Size(series) >= Utils.Size(signal) + startIndex);
+
+ var signalLength = Utils.Size(signal);
+ var windowSize = Utils.Size(alpha) + 1;
+ var k = signalLength - windowSize + 1;
+ Contracts.Assert(k > 0);
+
+ var y = new Single[k];
+ int i;
+
+ observationNoiseMean = 0;
+ observationNoiseVariance = 0;
+ autoregressionNoiseMean = 0;
+ autoregressionNoiseVariance = 0;
+
+ // Computing the observation noise moments
+ for (i = 0; i < signalLength; ++i)
+ observationNoiseMean += (series[i + startIndex] - signal[i]);
+ observationNoiseMean /= signalLength;
+
+ for (i = 0; i < signalLength; ++i)
+ observationNoiseVariance += (series[i + startIndex] - signal[i]) * (series[i + startIndex] - signal[i]);
+ observationNoiseVariance /= signalLength;
+ observationNoiseVariance -= (observationNoiseMean * observationNoiseMean);
+
+ // Computing the auto-regression noise moments
+ TrajectoryMatrix xTM = new TrajectoryMatrix(null, signal, windowSize - 1, signalLength - 1);
+ xTM.MultiplyTranspose(alpha, y);
+
+ for (i = 0; i < k; ++i)
+ autoregressionNoiseMean += (signal[windowSize - 1 + i] - y[i]);
+ autoregressionNoiseMean /= k;
+
+ for (i = 0; i < k; ++i)
+ {
+ autoregressionNoiseVariance += (signal[windowSize - 1 + i] - y[i] - autoregressionNoiseMean) *
+ (signal[windowSize - 1 + i] - y[i] - autoregressionNoiseMean);
+ }
+
+ autoregressionNoiseVariance /= (k - windowSize + 1);
+ Contracts.Assert(autoregressionNoiseVariance >= 0);
+ }
+
+ private static int DetermineSignalRank(Single[] series, TrajectoryMatrix tMat, Single[] singularVectors, Single[] singularValues,
+ Single[] outputSignal, int maxRank)
+ {
+ Contracts.Assert(tMat != null);
+ Contracts.Assert(Utils.Size(series) >= tMat.SeriesLength);
+ Contracts.Assert(Utils.Size(outputSignal) >= tMat.SeriesLength);
+ Contracts.Assert(Utils.Size(singularVectors) >= tMat.WindowSize * tMat.WindowSize);
+ Contracts.Assert(Utils.Size(singularValues) >= tMat.WindowSize);
+ Contracts.Assert(1 <= maxRank && maxRank <= tMat.WindowSize - 1);
+
+ var inputSeriesLength = tMat.SeriesLength;
+ var k = inputSeriesLength - tMat.WindowSize + 1;
+ Contracts.Assert(k > 0);
+
+ var x = new Single[inputSeriesLength];
+ var y = new Single[k];
+ var alpha = new Single[tMat.WindowSize - 1];
+ var v = new Single[k];
+
+ Single nu = 0;
+ Double minErr = Double.MaxValue;
+ int minIndex = maxRank - 1;
+ int evaluationLength = Math.Min(Math.Max(tMat.WindowSize, 200), k);
+
+ TrajectoryMatrix xTM = new TrajectoryMatrix(null, x, tMat.WindowSize - 1, inputSeriesLength - 1);
+
+ int i;
+ int j;
+ int n;
+ Single temp;
+ Double error;
+ Double sumSingularVals = 0;
+ Single lambda;
+ Single observationNoiseMean;
+
+ FixedSizeQueue window = new FixedSizeQueue(tMat.WindowSize - 1);
+
+ for (i = 0; i < tMat.WindowSize; ++i)
+ sumSingularVals += singularValues[i];
+
+ for (i = 0; i < maxRank; ++i)
+ {
+ // Updating the auto-regressive coefficients
+ lambda = singularVectors[tMat.WindowSize * i + tMat.WindowSize - 1];
+ for (j = 0; j < tMat.WindowSize - 1; ++j)
+ alpha[j] += lambda * singularVectors[tMat.WindowSize * i + j];
+
+ // Updating nu
+ nu += lambda * lambda;
+
+ // Reconstructing the i-th eigen triple component and adding it to x
+ tMat.MultiplyTranspose(singularVectors, v, false, tMat.WindowSize * i, 0);
+ tMat.RankOneHankelization(singularVectors, v, 1, x, true, tMat.WindowSize * i, 0, 0);
+
+ observationNoiseMean = 0;
+ for (j = 0; j < inputSeriesLength; ++j)
+ observationNoiseMean += (series[j] - x[j]);
+ observationNoiseMean /= inputSeriesLength;
+
+ for (j = inputSeriesLength - evaluationLength - tMat.WindowSize + 1; j < inputSeriesLength - evaluationLength; ++j)
+ window.AddLast(x[j]);
+
+ error = 0;
+ for (j = inputSeriesLength - evaluationLength; j < inputSeriesLength; ++j)
+ {
+ temp = 0;
+ for (n = 0; n < tMat.WindowSize - 1; ++n)
+ temp += alpha[n] * window[n];
+
+ temp /= (1 - nu);
+ temp += observationNoiseMean;
+ window.AddLast(temp);
+ error += Math.Abs(series[j] - temp);
+ if (error > minErr)
+ break;
+ }
+
+ if (error < minErr)
+ {
+ minErr = error;
+ minIndex = i;
+ Array.Copy(x, outputSignal, inputSeriesLength);
+ }
+ }
+
+ return minIndex + 1;
+ }
+
+ public void InitState()
+ {
+ for (int i = 0; i < _windowSize - 2; ++i)
+ _state[i] = 0;
+
+ _buffer.Clear();
+ }
+
+ private static int DetermineSignalRankFast(Single[] series, TrajectoryMatrix tMat, Single[] singularVectors, Single[] singularValues, int maxRank)
+ {
+ Contracts.Assert(tMat != null);
+ Contracts.Assert(Utils.Size(series) >= tMat.SeriesLength);
+ Contracts.Assert(Utils.Size(singularVectors) >= tMat.WindowSize * tMat.WindowSize);
+ Contracts.Assert(Utils.Size(singularValues) >= tMat.WindowSize);
+ Contracts.Assert(1 <= maxRank && maxRank <= tMat.WindowSize - 1);
+
+ var inputSeriesLength = tMat.SeriesLength;
+ var k = inputSeriesLength - tMat.WindowSize + 1;
+ Contracts.Assert(k > 0);
+
+ var x = new Single[tMat.WindowSize - 1];
+ var alpha = new Single[tMat.WindowSize - 1];
+ Single v;
+
+ Single nu = 0;
+ Double minErr = Double.MaxValue;
+ int minIndex = maxRank - 1;
+ int evaluationLength = Math.Min(Math.Max(tMat.WindowSize, 200), k);
+
+ int i;
+ int j;
+ int n;
+ int offset;
+ Single temp;
+ Double error;
+ Single lambda;
+ Single observationNoiseMean;
+
+ FixedSizeQueue window = new FixedSizeQueue(tMat.WindowSize - 1);
+
+ for (i = 0; i < maxRank; ++i)
+ {
+ // Updating the auto-regressive coefficients
+ lambda = singularVectors[tMat.WindowSize * i + tMat.WindowSize - 1];
+ for (j = 0; j < tMat.WindowSize - 1; ++j)
+ alpha[j] += lambda * singularVectors[tMat.WindowSize * i + j];
+
+ // Updating nu
+ nu += lambda * lambda;
+
+ // Reconstructing the i-th eigen triple component and adding it to x
+ v = 0;
+ offset = inputSeriesLength - evaluationLength - tMat.WindowSize + 1;
+
+ for (j = offset; j <= inputSeriesLength - evaluationLength; ++j)
+ v += series[j] * singularVectors[tMat.WindowSize * i - offset + j];
+
+ for (j = 0; j < tMat.WindowSize - 1; ++j)
+ x[j] += v * singularVectors[tMat.WindowSize * i + j];
+
+ // Computing the empirical observation noise mean
+ observationNoiseMean = 0;
+ for (j = offset; j < inputSeriesLength - evaluationLength; ++j)
+ observationNoiseMean += (series[j] - x[j - offset]);
+ observationNoiseMean /= (tMat.WindowSize - 1);
+
+ for (j = 0; j < tMat.WindowSize - 1; ++j)
+ window.AddLast(x[j]);
+
+ error = 0;
+ for (j = inputSeriesLength - evaluationLength; j < inputSeriesLength; ++j)
+ {
+ temp = 0;
+ for (n = 0; n < tMat.WindowSize - 1; ++n)
+ temp += alpha[n] * window[n];
+
+ temp /= (1 - nu);
+ temp += observationNoiseMean;
+ window.AddLast(temp);
+ error += Math.Abs(series[j] - temp);
+ if (error > minErr)
+ break;
+ }
+
+ if (error < minErr)
+ {
+ minErr = error;
+ minIndex = i;
+ }
+ }
+
+ return minIndex + 1;
+ }
+
+ private class SignalComponent
+ {
+ public Double Phase;
+ public int Index;
+ public int Cluster;
+
+ public SignalComponent(Double phase, int index)
+ {
+ Phase = phase;
+ Index = index;
+ }
+ }
+
+ private bool Stabilize()
+ {
+ if (Utils.Size(_alpha) == 1)
+ {
+ if (_shouldMaintainInfo)
+ _info.RootsBeforeStabilization = new[] { new Complex(_alpha[0], 0) };
+
+ if (_alpha[0] > 1)
+ _alpha[0] = 1;
+ else if (_alpha[0] < -1)
+ _alpha[0] = -1;
+
+ if (_shouldMaintainInfo)
+ {
+ _info.IsStabilized = true;
+ _info.RootsAfterStabilization = new[] { new Complex(_alpha[0], 0) };
+ _info.IsExponentialTrendPresent = false;
+ _info.IsPolynomialTrendPresent = false;
+ _info.ExponentialTrendFactor = Math.Abs(_alpha[0]);
+ }
+ return true;
+ }
+
+ var coeff = new Double[_windowSize - 1];
+ Complex[] roots = null;
+ bool trendFound = false;
+ bool polynomialTrendFound = false;
+ Double maxTrendMagnitude = Double.NegativeInfinity;
+ Double maxNonTrendMagnitude = Double.NegativeInfinity;
+ Double eps = 1e-9;
+ Double highFrequenceyBoundry = Math.PI / 2;
+ var sortedComponents = new List();
+ var trendComponents = new List();
+ int i;
+
+ // Computing the roots of the characteristic polynomial
+ for (i = 0; i < _windowSize - 1; ++i)
+ coeff[i] = -_alpha[i];
+
+ if (!PolynomialUtils.FindPolynomialRoots(coeff, ref roots))
+ return false;
+
+ if (_shouldMaintainInfo)
+ {
+ _info.RootsBeforeStabilization = new Complex[_windowSize - 1];
+ Array.Copy(roots, _info.RootsBeforeStabilization, _windowSize - 1);
+ }
+
+ // Detecting trend components
+ for (i = 0; i < _windowSize - 1; ++i)
+ {
+ if (roots[i].Magnitude > 1 && (Math.Abs(roots[i].Phase) <= eps || Math.Abs(Math.Abs(roots[i].Phase) - Math.PI) <= eps))
+ trendComponents.Add(i);
+ }
+
+ // Removing unobserved seasonalities and consequently introducing polynomial trend
+ for (i = 0; i < _windowSize - 1; ++i)
+ {
+ if (roots[i].Phase != 0)
+ {
+ if (roots[i].Magnitude >= 1 && 2 * Math.PI / Math.Abs(roots[i].Phase) > _windowSize)
+ {
+ /*if (roots[i].Real > 1)
+ {
+ polynomialTrendFound = true;
+ roots[i] = new Complex(Math.Max(1, roots[i].Magnitude), 0);
+ maxPolynomialTrendMagnitude = Math.Max(maxPolynomialTrendMagnitude, roots[i].Magnitude);
+ }
+ else
+ roots[i] = Complex.FromPolarCoordinates(1, 0);
+ //roots[i] = Complex.FromPolarCoordinates(0.99, roots[i].Phase);*/
+
+ /* if (_maxTrendRatio > 1)
+ {
+ roots[i] = new Complex(roots[i].Real, 0);
+ polynomialTrendFound = true;
+ }
+ else
+ roots[i] = roots[i].Imaginary > 0 ? new Complex(roots[i].Real, 0) : new Complex(1, 0);*/
+
+ roots[i] = new Complex(roots[i].Real, 0);
+ polynomialTrendFound = true;
+
+ if (_shouldMaintainInfo)
+ _info.IsArtificialSeasonalityRemoved = true;
+ }
+ else if (roots[i].Magnitude > 1)
+ sortedComponents.Add(new SignalComponent(roots[i].Phase, i));
+ }
+ }
+
+ if (_maxTrendRatio > 1)
+ {
+ // Combining the close exponential-seasonal components
+ if (sortedComponents.Count > 1 && polynomialTrendFound)
+ {
+ sortedComponents.Sort((a, b) => a.Phase.CompareTo(b.Phase));
+ var clusterNum = 0;
+
+ for (i = 0; i < sortedComponents.Count - 1; ++i)
+ {
+ if ((sortedComponents[i].Phase < 0 && sortedComponents[i + 1].Phase < 0) ||
+ (sortedComponents[i].Phase > 0 && sortedComponents[i + 1].Phase > 0))
+ {
+ sortedComponents[i].Cluster = clusterNum;
+ if (Math.Abs(sortedComponents[i + 1].Phase - sortedComponents[i].Phase) > 0.05)
+ clusterNum++;
+ sortedComponents[i + 1].Cluster = clusterNum;
+ }
+ else
+ clusterNum++;
+ }
+
+ int start = 0;
+ bool polynomialSeasonalityFound = false;
+ Double largestSeasonalityPhase = 0;
+ for (i = 1; i < sortedComponents.Count; ++i)
+ {
+ if (sortedComponents[i].Cluster != sortedComponents[i - 1].Cluster)
+ {
+ if (i - start > 1) // There are more than one point in the cluster
+ {
+ Double avgPhase = 0;
+ Double avgMagnitude = 0;
+
+ for (var j = start; j < i; ++j)
+ {
+ avgPhase += sortedComponents[j].Phase;
+ avgMagnitude += roots[sortedComponents[j].Index].Magnitude;
+ }
+ avgPhase /= (i - start);
+ avgMagnitude /= (i - start);
+
+ for (var j = start; j < i; ++j)
+ roots[sortedComponents[j].Index] = Complex.FromPolarCoordinates(avgMagnitude,
+ avgPhase);
+
+ if (!polynomialSeasonalityFound && avgPhase > 0)
+ {
+ largestSeasonalityPhase = avgPhase;
+ polynomialSeasonalityFound = true;
+ }
+ }
+
+ start = i;
+ }
+ }
+ }
+
+ // Combining multiple exponential trends into polynomial ones
+ if (!polynomialTrendFound)
+ {
+ var ind1 = -1;
+ var ind2 = -1;
+
+ foreach (var ind in trendComponents)
+ {
+ if (Math.Abs(roots[ind].Phase) <= eps)
+ {
+ ind1 = ind;
+ break;
+ }
+ }
+
+ for (i = 0; i < _windowSize - 1; ++i)
+ {
+ if (Math.Abs(roots[i].Phase) <= eps && 0.9 <= roots[i].Magnitude && i != ind1)
+ {
+ ind2 = i;
+ break;
+ }
+ }
+
+ if (ind1 >= 0 && ind2 >= 0 && ind1 != ind2)
+ {
+ roots[ind1] = Complex.FromPolarCoordinates(1, 0);
+ roots[ind2] = Complex.FromPolarCoordinates(1, 0);
+ polynomialTrendFound = true;
+ }
+ }
+ }
+
+ if (polynomialTrendFound) // Suppress the exponential trend
+ {
+ maxTrendMagnitude = Math.Min(1, _maxTrendRatio);
+ foreach (var ind in trendComponents)
+ roots[ind] = Complex.FromPolarCoordinates(0.99, roots[ind].Phase);
+ }
+ else
+ {
+ // Spotting the exponential trend components and finding the max trend magnitude
+ for (i = 0; i < _windowSize - 1; ++i)
+ {
+ if (roots[i].Magnitude > 1 && Math.Abs(roots[i].Phase) <= eps)
+ {
+ trendFound = true;
+ maxTrendMagnitude = Math.Max(maxTrendMagnitude, roots[i].Magnitude);
+ }
+ else
+ maxNonTrendMagnitude = Math.Max(maxNonTrendMagnitude, roots[i].Magnitude);
+ }
+
+ if (!trendFound)
+ maxTrendMagnitude = 1;
+
+ maxTrendMagnitude = Math.Min(maxTrendMagnitude, _maxTrendRatio);
+ }
+
+ // Squeezing all components below the maximum trend magnitude
+ var smallTrendMagnitude = Math.Min(maxTrendMagnitude, (maxTrendMagnitude + 1) / 2);
+ for (i = 0; i < _windowSize - 1; ++i)
+ {
+ if (roots[i].Magnitude >= maxTrendMagnitude)
+ {
+ if ((highFrequenceyBoundry < roots[i].Phase && roots[i].Phase < Math.PI - eps) ||
+ (-Math.PI + eps < roots[i].Phase && roots[i].Phase < -highFrequenceyBoundry))
+ roots[i] = Complex.FromPolarCoordinates(smallTrendMagnitude, roots[i].Phase);
+ else
+ roots[i] = Complex.FromPolarCoordinates(maxTrendMagnitude, roots[i].Phase);
+ }
+ }
+
+ // Correcting all the other trend components
+ for (i = 0; i < _windowSize - 1; ++i)
+ {
+ var phase = roots[i].Phase;
+ if (Math.Abs(phase) <= eps)
+ roots[i] = new Complex(roots[i].Magnitude, 0);
+ else if (Math.Abs(phase - Math.PI) <= eps)
+ roots[i] = new Complex(-roots[i].Magnitude, 0);
+ else if (Math.Abs(phase + Math.PI) <= eps)
+ roots[i] = new Complex(-roots[i].Magnitude, 0);
+ }
+
+ // Computing the characteristic polynomial from the modified roots
+ try
+ {
+ if (!PolynomialUtils.FindPolynomialCoefficients(roots, ref coeff))
+ return false;
+ }
+ catch (OverflowException)
+ {
+ return false;
+ }
+
+ // Updating alpha
+ for (i = 0; i < _windowSize - 1; ++i)
+ _alpha[i] = (Single)(-coeff[i]);
+
+ if (_shouldMaintainInfo)
+ {
+ _info.RootsAfterStabilization = roots;
+ _info.IsStabilized = true;
+ _info.IsPolynomialTrendPresent = polynomialTrendFound;
+ _info.IsExponentialTrendPresent = maxTrendMagnitude > 1;
+ _info.ExponentialTrendFactor = maxTrendMagnitude;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Feeds the next observation on the series to the model and as a result changes the state of the model.
+ ///
+ /// The next observation on the series.
+ /// Determines whether the model parameters also need to be updated upon consuming the new observation (default = false).
+ public void Consume(ref Single input, bool updateModel = false)
+ {
+ if (Single.IsNaN(input))
+ return;
+
+ int i;
+
+ if (_wTrans == null)
+ {
+ _y = new CpuAlignedVector(_rank, SseUtils.CbAlign);
+ _wTrans = new CpuAlignedMatrixRow(_rank, _windowSize, SseUtils.CbAlign);
+ Single[] vecs = new Single[_rank * _windowSize];
+
+ for (i = 0; i < _rank; ++i)
+ vecs[(_windowSize + 1) * i] = 1;
+
+ i = 0;
+ _wTrans.CopyFrom(vecs, ref i);
+ }
+
+ // Forming vector x
+
+ if (_buffer.Count == 0)
+ {
+ for (i = 0; i < _windowSize - 1; ++i)
+ _buffer.AddLast(_state[i]);
+ }
+
+ int len = _buffer.Count;
+ for (i = 0; i < _windowSize - len - 1; ++i)
+ _x[i] = 0;
+ for (i = Math.Max(0, len - _windowSize + 1); i < len; ++i)
+ _x[i - len + _windowSize - 1] = _buffer[i];
+ _x[_windowSize - 1] = input;
+
+ // Computing y: Eq. (11) in https://hal-institut-mines-telecom.archives-ouvertes.fr/hal-00479772/file/twocolumns.pdf
+ CpuAligenedMathUtils.MatTimesSrc(false, _wTrans, _x, _y);
+
+ // Updating the state vector
+ CpuAligenedMathUtils.MatTranTimesSrc(false, _wTrans, _y, _xSmooth);
+
+ _nextPrediction = _autoregressionNoiseMean + _observationNoiseMean;
+ for (i = 0; i < _windowSize - 2; ++i)
+ {
+ _state[i] = ((_windowSize - 2 - i) * _state[i + 1] + _xSmooth[i + 1]) / (_windowSize - 1 - i);
+ _nextPrediction += _state[i] * _alpha[i];
+ }
+ _state[_windowSize - 2] = _xSmooth[_windowSize - 1];
+ _nextPrediction += _state[_windowSize - 2] * _alpha[_windowSize - 2];
+
+ if (updateModel)
+ {
+ // REVIEW: to be implemented in the next version based on the FAPI algorithm
+ // in https://hal-institut-mines-telecom.archives-ouvertes.fr/hal-00479772/file/twocolumns.pdf.
+ }
+
+ _buffer.AddLast(input);
+ }
+
+ ///
+ /// Train the model parameters based on a training series.
+ ///
+ /// The training time-series.
+ public void Train(FixedSizeQueue data)
+ {
+ _host.CheckParam(data != null, nameof(data), "The input series for training cannot be null.");
+ _host.CheckParam(data.Count >= _trainSize, nameof(data), "The input series for training does not have enough points for training.");
+
+ Single[] dataArray = new Single[_trainSize];
+
+ int i;
+ int count;
+ for (i = 0, count = 0; count < _trainSize && i < data.Count; ++i)
+ if (!Single.IsNaN(data[i]))
+ dataArray[count++] = data[i];
+
+ if (_shouldMaintainInfo)
+ {
+ _info = new ModelInfo();
+ _info.WindowSize = _windowSize;
+ }
+
+ if (count <= 2 * _windowSize)
+ {
+#if !TLCSSA
+ using (var ch = _host.Start("Train"))
+ ch.Warning(
+ "Training cannot be completed because the input series for training does not have enough points.");
+#endif
+ }
+ else
+ {
+ if (count != _trainSize)
+ Array.Resize(ref dataArray, count);
+
+ TrainCore(dataArray, count);
+ }
+ }
+
+#if !TLCSSA
+ ///
+ /// Train the model parameters based on a training series.
+ ///
+ /// The training time-series.
+ public void Train(RoleMappedData data)
+ {
+ _host.CheckParam(data != null, nameof(data), "The input series for training cannot be null.");
+ if (data.Schema.Feature.Type != NumberType.Float)
+ throw _host.ExceptUserArg(nameof(data.Schema.Feature.Name), "The feature column has type '{0}', but must be a float.", data.Schema.Feature.Type);
+
+ Single[] dataArray = new Single[_trainSize];
+ int col = data.Schema.Feature.Index;
+
+ int count = 0;
+ using (var cursor = data.Data.GetRowCursor(c => c == col))
+ {
+ var getVal = cursor.GetGetter(col);
+ Single val = default(Single);
+ while (cursor.MoveNext() && count < _trainSize)
+ {
+ getVal(ref val);
+ if (!Single.IsNaN(val))
+ dataArray[count++] = val;
+ }
+ }
+
+ if (_shouldMaintainInfo)
+ {
+ _info = new ModelInfo();
+ _info.WindowSize = _windowSize;
+ }
+
+ if (count <= 2 * _windowSize)
+ {
+ using (var ch = _host.Start("Train"))
+ ch.Warning("Training cannot be completed because the input series for training does not have enough points.");
+ }
+ else
+ {
+ if (count != _trainSize)
+ Array.Resize(ref dataArray, count);
+
+ TrainCore(dataArray, count);
+ }
+ }
+#endif
+
+ private void TrainCore(Single[] dataArray, int originalSeriesLength)
+ {
+ _host.Assert(Utils.Size(dataArray) > 0);
+ Single[] singularVals;
+ Single[] leftSingularVecs;
+ var learnNaiveModel = false;
+
+ var signalLength = _rankSelectionMethod == RankSelectionMethod.Exact ? originalSeriesLength : 2 * _windowSize - 1;//originalSeriesLength;
+ var signal = new Single[signalLength];
+
+ int i;
+ // Creating the trajectory matrix for the series
+ TrajectoryMatrix tMat = new TrajectoryMatrix(_host, dataArray, _windowSize, originalSeriesLength);
+
+ // Computing the SVD of the trajectory matrix
+ if (!tMat.ComputeSvd(out singularVals, out leftSingularVecs))
+ learnNaiveModel = true;
+ else
+ {
+ for (i = 0; i < _windowSize * _maxRank; ++i)
+ {
+ if (Single.IsNaN(leftSingularVecs[i]))
+ {
+ learnNaiveModel = true;
+ break;
+ }
+ }
+ }
+
+ // Checking for standard eigenvectors, if found reduce the window size and reset training.
+ if (!learnNaiveModel)
+ {
+ for (i = 0; i < _windowSize; ++i)
+ {
+ var v = leftSingularVecs[(i + 1) * _windowSize - 1];
+ if (v * v == 1)
+ {
+ if (_windowSize > 2)
+ {
+ _windowSize--;
+ _maxRank = _windowSize / 2;
+ _alpha = new Single[_windowSize - 1];
+ _state = new Single[_windowSize - 1];
+ _x = new CpuAlignedVector(_windowSize, SseUtils.CbAlign);
+ _xSmooth = new CpuAlignedVector(_windowSize, SseUtils.CbAlign);
+
+ TrainCore(dataArray, originalSeriesLength);
+ return;
+ }
+ else
+ {
+ learnNaiveModel = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // Learn the naive (averaging) model in case the eigen decomposition is not possible
+ if (learnNaiveModel)
+ {
+#if !TLCSSA
+ using (var ch = _host.Start("Train"))
+ ch.Warning("The precise SSA model cannot be trained.");
+#endif
+
+ _rank = 1;
+ var temp = (Single)(1f / Math.Sqrt(_windowSize));
+ for (i = 0; i < _windowSize; ++i)
+ leftSingularVecs[i] = temp;
+ }
+ else
+ {
+ // Computing the signal rank
+ if (_rankSelectionMethod == RankSelectionMethod.Exact)
+ _rank = DetermineSignalRank(dataArray, tMat, leftSingularVecs, singularVals, signal, _maxRank);
+ else if (_rankSelectionMethod == RankSelectionMethod.Fast)
+ _rank = DetermineSignalRankFast(dataArray, tMat, leftSingularVecs, singularVals, _maxRank);
+ }
+
+ // Setting the the y vector
+ _y = new CpuAlignedVector(_rank, SseUtils.CbAlign);
+
+ // Setting the weight matrix
+ _wTrans = new CpuAlignedMatrixRow(_rank, _windowSize, SseUtils.CbAlign);
+ i = 0;
+ _wTrans.CopyFrom(leftSingularVecs, ref i);
+
+ // Setting alpha
+ Single nu = 0;
+ for (i = 0; i < _rank; ++i)
+ {
+ _y[i] = leftSingularVecs[_windowSize * (i + 1) - 1];
+ nu += _y[i] * _y[i];
+ }
+
+ CpuAligenedMathUtils.MatTranTimesSrc(false, _wTrans, _y, _xSmooth);
+ for (i = 0; i < _windowSize - 1; ++i)
+ _alpha[i] = _xSmooth[i] / (1 - nu);
+
+ // Stabilizing the model
+ if (_shouldStablize && !learnNaiveModel)
+ {
+ if (!Stabilize())
+ {
+#if !TLCSSA
+ using (var ch = _host.Start("Train"))
+ ch.Warning("The trained model cannot be stablized.");
+#endif
+ }
+ }
+
+ // Computing the noise moments
+ if (ShouldComputeForecastIntervals)
+ {
+ if (_rankSelectionMethod != RankSelectionMethod.Exact)
+ ReconstructSignalTailFast(dataArray, tMat, leftSingularVecs, _rank, signal);
+
+ ComputeNoiseMoments(dataArray, signal, _alpha, out _observationNoiseVariance, out _autoregressionNoiseVariance,
+ out _observationNoiseMean, out _autoregressionNoiseMean, originalSeriesLength - signalLength);
+ _observationNoiseMean = 0;
+ _autoregressionNoiseMean = 0;
+ }
+
+ // Setting the state
+ _nextPrediction = _autoregressionNoiseMean + _observationNoiseMean;
+
+ if (_buffer.Count > 0) // Use the buffer to set the state when there are data points pushed into the buffer using the Consume() method
+ {
+ int len = _buffer.Count;
+ for (i = 0; i < _windowSize - len; ++i)
+ _x[i] = 0;
+ for (i = Math.Max(0, len - _windowSize); i < len; ++i)
+ _x[i - len + _windowSize] = _buffer[i];
+ }
+ else // use the training data points otherwise
+ {
+ for (i = originalSeriesLength - _windowSize; i < originalSeriesLength; ++i)
+ _x[i - originalSeriesLength + _windowSize] = dataArray[i];
+ }
+
+ CpuAligenedMathUtils.MatTimesSrc(false, _wTrans, _x, _y);
+ CpuAligenedMathUtils.MatTranTimesSrc(false, _wTrans, _y, _xSmooth);
+
+ for (i = 1; i < _windowSize; ++i)
+ {
+ _state[i - 1] = _xSmooth[i];
+ _nextPrediction += _state[i - 1] * _alpha[i - 1];
+ }
+
+ if (_shouldMaintainInfo)
+ {
+ _info.IsTrained = true;
+ _info.WindowSize = _windowSize;
+ _info.AutoRegressiveCoefficients = new Single[_windowSize - 1];
+ Array.Copy(_alpha, _info.AutoRegressiveCoefficients, _windowSize - 1);
+ _info.Rank = _rank;
+ _info.IsNaiveModelTrained = learnNaiveModel;
+ _info.Spectrum = singularVals;
+ }
+ }
+
+ ///
+ /// Forecasts the future values of the series up to the given horizon.
+ ///
+ /// The forecast result.
+ /// The forecast horizon.
+ public void Forecast(ref ForecastResultBase result, int horizon = 1)
+ {
+ _host.CheckParam(horizon >= 1, nameof(horizon), "The horizon parameter should be greater than 0.");
+ if (result == null)
+ result = new SsaForecastResult();
+
+ var str = "The result argument must be of type " + typeof(SsaForecastResult).ToString();
+ _host.CheckParam(result is SsaForecastResult, nameof(result), str);
+
+ var output = result as SsaForecastResult;
+
+ var res = result.PointForecast.Values;
+ if (Utils.Size(res) < horizon)
+ res = new Single[horizon];
+
+ int i;
+ int j;
+ int k;
+
+ // Computing the point forecasts
+ res[0] = _nextPrediction;
+ for (i = 1; i < horizon; ++i)
+ {
+ k = 0;
+ res[i] = _autoregressionNoiseMean + _observationNoiseMean;
+ for (j = i; j < _windowSize - 1; ++j, ++k)
+ res[i] += _state[j] * _alpha[k];
+
+ for (j = Math.Max(0, i - _windowSize + 1); j < i; ++j, ++k)
+ res[i] += res[j] * _alpha[k];
+ }
+
+ // Computing the forecast variances
+ if (ShouldComputeForecastIntervals)
+ {
+ var sd = output.ForecastStandardDeviation.Values;
+ if (Utils.Size(sd) < horizon)
+ sd = new Single[horizon];
+
+ var lastCol = new FixedSizeQueue(_windowSize - 1);
+
+ for (i = 0; i < _windowSize - 3; ++i)
+ lastCol.AddLast(0);
+ lastCol.AddLast(1);
+ lastCol.AddLast(_alpha[_windowSize - 2]);
+ sd[0] = _autoregressionNoiseVariance + _observationNoiseVariance;
+
+ for (i = 1; i < horizon; ++i)
+ {
+ Single temp = 0;
+ for (j = 0; j < _windowSize - 1; ++j)
+ temp += _alpha[j] * lastCol[j];
+ lastCol.AddLast(temp);
+
+ sd[i] = sd[i - 1] + _autoregressionNoiseVariance * temp * temp;
+ }
+
+ for (i = 0; i < horizon; ++i)
+ sd[i] = (float)Math.Sqrt(sd[i]);
+
+ output.ForecastStandardDeviation = new VBuffer(horizon, sd, output.ForecastStandardDeviation.Indices);
+ }
+
+ result.PointForecast = new VBuffer(horizon, res, result.PointForecast.Indices);
+ output.CanComputeForecastIntervals = ShouldComputeForecastIntervals;
+ output.BoundOffset = 0;
+ }
+
+ ///
+ /// Predicts the next value on the series.
+ ///
+ /// The prediction result.
+ public void PredictNext(ref Single output)
+ {
+ output = _nextPrediction;
+ }
+
+ public ISequenceModeler Clone()
+ {
+ return new AdaptiveSingularSpectrumSequenceModeler(this);
+ }
+
+ ///
+ /// Computes the forecast intervals for the input forecast object at the given confidence level. The results are stored in the forecast object.
+ ///
+ /// The input forecast object
+ /// The confidence level in [0, 1)
+ public static void ComputeForecastIntervals(ref SsaForecastResult forecast, Single confidenceLevel = 0.95f)
+ {
+ Contracts.CheckParam(0 <= confidenceLevel && confidenceLevel < 1, nameof(confidenceLevel), "The confidence level must be in [0, 1).");
+ Contracts.CheckValue(forecast, nameof(forecast));
+ Contracts.Check(forecast.CanComputeForecastIntervals, "The forecast intervals cannot be computed for this forecast object.");
+
+ var horizon = Utils.Size(forecast.PointForecast.Values);
+ Contracts.Check(Utils.Size(forecast.ForecastStandardDeviation.Values) >= horizon, "The forecast standard deviation values are not available.");
+
+ forecast.ConfidenceLevel = confidenceLevel;
+ if (horizon == 0)
+ return;
+
+ var upper = forecast.UpperBound.Values;
+ if (Utils.Size(upper) < horizon)
+ upper = new Single[horizon];
+
+ var lower = forecast.LowerBound.Values;
+ if (Utils.Size(lower) < horizon)
+ lower = new Single[horizon];
+
+ var z = ProbabilityFunctions.Probit(0.5 + confidenceLevel / 2.0);
+ var meanForecast = forecast.PointForecast.Values;
+ var sdForecast = forecast.ForecastStandardDeviation.Values;
+ double temp;
+
+ for (int i = 0; i < horizon; ++i)
+ {
+ temp = z * sdForecast[i];
+ upper[i] = (Single)(meanForecast[i] + forecast.BoundOffset + temp);
+ lower[i] = (Single)(meanForecast[i] + forecast.BoundOffset - temp);
+ }
+
+ forecast.UpperBound = new VBuffer(horizon, upper, forecast.UpperBound.Indices);
+ forecast.LowerBound = new VBuffer(horizon, lower, forecast.LowerBound.Indices);
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/EigenUtils.cs b/src/Microsoft.ML.TimeSeries/EigenUtils.cs
new file mode 100644
index 0000000000..42392dd026
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/EigenUtils.cs
@@ -0,0 +1,556 @@
+// 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.Runtime.InteropServices;
+using Microsoft.ML.Runtime.Internal.Utilities;
+using Float = System.Single;
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ //REVIEW: improve perf with SSE and Multithreading
+ public static class EigenUtils
+ {
+ //Compute the Eigen-decomposition of a symmetric matrix
+ //REVIEW: use matrix/vector operations, not Array Math
+ public static void EigenDecomposition(Float[] a, out Float[] eigenvalues, out Float[] eigenvectors)
+ {
+ var count = a.Length;
+ var n = (int)Math.Sqrt(count);
+ Contracts.Assert(n * n == count);
+
+ eigenvectors = new Float[count];
+ eigenvalues = new Float[n];
+
+ //Reduce A to tridiagonal form
+ //REVIEW: it's not ideal to keep using the same variable name for different purposes
+ // - After the operation, "eigenvalues" means the diagonal elements of the reduced matrix
+ //and "eigenvectors" means the orthogonal similarity transformation matrix
+ // - Consider aliasing variables
+ var w = new Float[n];
+ Tred(a, eigenvalues, w, eigenvectors, n);
+
+ //Eigen-decomposition of the tridiagonal matrix
+ //After this operation, "eigenvalues" means eigenvalues^2
+ Imtql(eigenvalues, w, eigenvectors, n);
+
+ for (int i = 0; i < n; i++)
+ eigenvalues[i] = eigenvalues[i] <= 0 ? (Float)(0.0) : (Float)Math.Sqrt(eigenvalues[i]);
+ }
+
+ private static Float Hypot(Float x, Float y)
+ {
+ x = Math.Abs(x);
+ y = Math.Abs(y);
+
+ if (x == 0 || y == 0)
+ return x + y;
+
+ if (x < y)
+ {
+ double t = x / y;
+ return y * (Float)Math.Sqrt(1 + t * t);
+ }
+ else
+ {
+ double t = y / x;
+ return x * (Float)Math.Sqrt(1 + t * t);
+ }
+ }
+
+ private static Float CopySign(Float x, Float y)
+ {
+ Float xx = Math.Abs(x);
+ return y < 0 ? -xx : xx;
+ }
+
+ private static void Tred(Float[] a, Float[] d, Float[] e, Float[] z, int n)
+ {
+ float g;
+ float h;
+ int i;
+ int j;
+ int k;
+ int l;
+
+ /* this subroutine reduces a Float symmetric matrix to a */
+ /* symmetric tridiagonal matrix using and accumulating */
+ /* orthogonal similarity transformations. */
+
+ /* on input */
+
+ /* n is the order of the matrix. */
+
+ /* a contains the Float symmetric input matrix. only the */
+ /* lower triangle of the matrix need be supplied. */
+
+ /* on output */
+
+ /* d contains the diagonal elements of the tridiagonal matrix. */
+
+ /* e contains the sub-diagonal elements of the tridiagonal */
+ /* matrix in its last n-1 positions. e(1) is set to zero. */
+ /* z contains the orthogonal similarity transformation */
+
+ /* ------------------------------------------------------------------ */
+
+ /* Function Body */
+
+ for (i = 0; i < n; ++i)
+ {
+ for (j = i; j < n; ++j)
+ {
+ z[j + i * n] = a[j + i * n];
+ }
+
+ d[i] = a[n - 1 + i * n];
+ }
+
+ if (n == 1)
+ {
+ d[0] = z[0];
+ z[0] = 1;
+ e[0] = 0;
+ return;
+ }
+ // .......... for i=n step -1 until 2 do -- ..........
+ for (i = n; i-- > 1;)
+ {
+ l = i - 1;
+ h = 0;
+ Float scale = 0;
+ if (l == 0)
+ {
+ e[1] = d[0];
+ d[0] = z[0];
+ z[1] = 0;
+ z[n] = 0;
+ d[1] = h;
+ continue;
+ }
+ // .......... scale row ..........
+ for (k = 0; k < i; ++k)
+ {
+ scale += Math.Abs(d[k]);
+ }
+
+ if (scale == 0)
+ {
+ e[i] = d[l];
+
+ for (j = 0; j < i; ++j)
+ {
+ d[j] = z[l + j * n];
+ z[i + j * n] = 0;
+ z[j + i * n] = 0;
+ }
+ d[i] = h;
+ continue;
+
+ }
+ for (k = 0; k < i; ++k)
+ {
+ d[k] /= scale;
+ h += d[k] * d[k];
+ }
+
+ Float f = d[l];
+ g = CopySign((Float)Math.Sqrt(h), f);
+ e[i] = scale * g;
+ h -= f * g;
+ d[l] = f - g;
+ // .......... form a*u ..........
+ for (j = 0; j < i; ++j)
+ {
+ e[j] = 0;
+ }
+
+ for (j = 0; j < i; ++j)
+ {
+ f = d[j];
+ z[j + i * n] = f;
+ g = e[j] + z[j + j * n] * f;
+ if (j + 1 == i)
+ {
+ e[j] = g;
+ continue;
+ }
+
+ for (k = j + 1; k < i; ++k)
+ {
+ g += z[k + j * n] * d[k];
+ e[k] += z[k + j * n] * f;
+ }
+
+ e[j] = g;
+ }
+ // .......... form p ..........
+ f = 0;
+
+ for (j = 0; j < i; ++j)
+ {
+ e[j] /= h;
+ f += e[j] * d[j];
+ }
+
+ Float hh = f / (h + h);
+ // .......... form q ..........
+ for (j = 0; j < i; ++j)
+ {
+ e[j] -= hh * d[j];
+ }
+ // .......... form reduced a ..........
+ for (j = 0; j < i; ++j)
+ {
+ f = d[j];
+ g = e[j];
+
+ for (k = j; k < i; ++k)
+ {
+ z[k + j * n] = (float)((double)z[k + j * n] - (double)f * e[k] - (double)g * d[k]);
+ }
+
+ d[j] = z[l + j * n];
+ z[i + j * n] = 0;
+ }
+
+ d[i] = h;
+ }
+
+ // .......... accumulation of transformation matrices ..........
+
+ for (i = 1; i < n; ++i)
+ {
+ l = i - 1;
+ z[n - 1 + l * n] = z[l + l * n];
+ z[l + l * n] = 1;
+ h = d[i];
+ if (h != 0)
+ {
+ for (k = 0; k < i; ++k)
+ {
+ d[k] = z[k + i * n] / h;
+ }
+
+ for (j = 0; j < i; ++j)
+ {
+ g = 0;
+
+ for (k = 0; k < i; ++k)
+ {
+ g += z[k + i * n] * z[k + j * n];
+ }
+
+ for (k = 0; k < i; ++k)
+ {
+ z[k + j * n] -= g * d[k];
+ }
+ }
+ }
+
+ for (k = 0; k < i; ++k)
+ {
+ z[k + i * n] = 0;
+ }
+ }
+
+ for (i = 0; i < n; ++i)
+ {
+ d[i] = z[n - 1 + i * n];
+ z[n - 1 + i * n] = 0;
+ }
+ z[n * n - 1] = 1;
+ e[0] = 0;
+ } /* Tred */
+
+ /* Subroutine */
+ private static int Imtql(Float[] d, Float[] e, Float[] z, int n)
+ {
+ /* Local variables */
+ double b;
+ double c;
+ double f;
+ double g;
+ int i;
+ int j;
+ int k;
+ int l;
+ int m;
+ double p;
+ double r;
+ double s;
+ double tst1;
+ double tst2;
+
+ /* this subroutine is a translation of the algol procedure imtql2, */
+ /* num. math. 12, 377-383(1968) by martin and wilkinson, */
+ /* as modified in num. math. 15, 450(1970) by dubrulle. */
+ /* handbook for auto. comp., vol.ii-linear algebra, 241-248(1971). */
+
+ /* this subroutine finds the eigenvalues and eigenvectors */
+ /* of a symmetric tridiagonal matrix by the implicit ql method. */
+ /* the eigenvectors of a full symmetric matrix can also */
+ /* be found if tred2 has been used to reduce this */
+ /* full matrix to tridiagonal form. */
+
+ /* on input */
+
+ /* nm must be set to the row dimension of two-dimensional */
+ /* array parameters as declared in the calling program */
+ /* dimension statement. */
+
+ /* n is the order of the matrix. */
+
+ /* d contains the diagonal elements of the input matrix. */
+
+ /* e contains the subdiagonal elements of the input matrix */
+ /* in its last n-1 positions. e(1) is arbitrary. */
+
+ /* z contains the transformation matrix produced in the */
+ /* reduction by tred2, if performed. if the eigenvectors */
+ /* of the tridiagonal matrix are desired, z must contain */
+ /* the identity matrix. */
+
+ /* on output */
+
+ /* d contains the eigenvalues in ascending order. if an */
+ /* error exit is made, the eigenvalues are correct but */
+ /* unordered for indices 1,2,...,ierr-1. */
+
+ /* e has been destroyed. */
+
+ /* z contains orthonormal eigenvectors of the symmetric */
+ /* tridiagonal (or full) matrix. if an error exit is made, */
+ /* z contains the eigenvectors associated with the stored */
+ /* eigenvalues. */
+
+ /* ierr is set to */
+ /* zero for normal return, */
+ /* j if the j-th eigenvalue has not been */
+ /* determined after 30 iterations. */
+
+ /* calls pythag for dsqrt(a*a + b*b) . */
+
+ /* questions and comments should be directed to burton s. garbow, */
+ /* mathematics and computer science div, argonne national laboratory */
+
+ /* this version dated august 1983. */
+
+ /* ------------------------------------------------------------------ */
+
+ /* Function Body */
+ if (n == 1)
+ return 0;
+ for (i = 1; i < n; ++i)
+ {
+ e[i - 1] = e[i];
+ }
+ e[n - 1] = (Float)(0.0);
+
+ for (l = 0; l < n; ++l)
+ {
+ j = 0;
+ do
+ {
+ /* .......... look for small sub-diagonal element .......... */
+ for (m = l; m + 1 < n; ++m)
+ {
+ tst1 = Math.Abs(d[m]) + Math.Abs(d[m + 1]);
+ tst2 = tst1 + Math.Abs(e[m]);
+ if (tst2 == tst1)
+ break;
+ }
+ p = d[l];
+ if (m != l)
+ {
+ if (j++ >= 30)
+ {
+ return l;
+ }
+ /* .......... form shift .......... */
+ g = (d[l + 1] - p) / (e[l] * (Float)(2.0));
+ r = Hypot((float)g, (Float)(1.0));
+ g = d[m] - p + e[l] / (g + CopySign((float)r, (float)g));
+ s = (Float)(1.0);
+ c = (Float)(1.0);
+ p = (Float)(0.0);
+ /* .......... for i=m-1 step -1 until l do -- .......... */
+ for (i = m - 1; i >= l; i--)
+ {
+ f = s * e[i];
+ b = c * e[i];
+ r = Hypot((float)f, (float)g);
+ e[i + 1] = (float)r;
+ if (r == (Float)(0.0))
+ {
+ /* .......... recover from underflow .......... */
+ d[i + 1] -= (float)p;
+ e[m] = 0;
+ break;
+ }
+ s = f / r;
+ c = g / r;
+ g = d[i + 1] - p;
+ r = (d[i] - g) * s + c * (Float)(2.0) * b;
+ p = s * r;
+ d[i + 1] = (float)(g + p);
+ g = c * r - b;
+ /* .......... form vector .......... */
+ for (k = 0; k < n; ++k)
+ {
+ f = z[k + (i + 1) * n];
+ z[k + (i + 1) * n] = (float)(s * z[k + i * n] + c * f);
+ z[k + i * n] = (float)(c * z[k + i * n] - s * f);
+ }
+ }
+ if (r == (Float)(0.0) && i >= l)
+ continue;
+ d[l] -= (float)p;
+ e[l] = (float)g;
+ e[m] = (Float)(0.0);
+ }
+ } while (m != l);
+ }
+ /* .......... order eigenvalues and eigenvectors .......... */
+ for (i = 0; i < n; ++i)
+ {
+ k = i;
+ p = d[i];
+
+ for (j = i + 1; j < n; ++j)
+ {
+ if (d[j] <= p)
+ continue;
+ k = j;
+ p = d[j];
+ }
+
+ if (k == i)
+ continue;
+ d[k] = d[i];
+ d[i] = (float)p;
+
+ for (j = 0; j < n; ++j)
+ {
+ p = z[j + i * n];
+ z[j + i * n] = z[j + k * n];
+ z[j + k * n] = (float)p;
+ }
+ }
+
+ return 0;
+ }
+
+ private const string DllName = "MklImports";
+
+ public enum Layout
+ {
+ RowMajor = 101,
+ ColMajor = 102
+ }
+
+ public enum Job : byte
+ {
+ EigenValues = (byte)'E',
+ Schur = (byte)'S'
+ }
+
+ public enum Compz : byte
+ {
+ None = (byte)'N',
+ SchurH = (byte)'I',
+ SchurA = (byte)'V'
+ }
+
+ public enum Uplo : byte
+ {
+ UpperTriangular = (byte)'U',
+ LowerTriangular = (byte)'L'
+ }
+
+ // See: https://software.intel.com/en-us/node/521087#4C9F4214-70BC-4483-A814-1E7F927B30CF
+ [DllImport(DllName, EntryPoint = "LAPACKE_shseqr", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int Shseqr(Layout matrixLayout, Job job, Compz compz, int n, int ilo, int ihi,
+ [In] float[] h, int idh, [Out] float[] wr, [Out] float[] wi, [Out] float[] z, int ldz);
+
+ // See: https://software.intel.com/en-us/node/521087#4C9F4214-70BC-4483-A814-1E7F927B30CF
+ [DllImport(DllName, EntryPoint = "LAPACKE_dhseqr", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int Dhseqr(Layout matrixLayout, Job job, Compz compz, int n, int ilo, int ihi,
+ [In] double[] h, int idh, [Out] double[] wr, [Out] double[] wi, [Out] double[] z, int ldz);
+
+ // See: https://software.intel.com/en-us/node/521046#7EF85A82-423A-4ABC-A208-88326CD0B887
+ [DllImport(DllName, EntryPoint = "LAPACKE_ssytrd", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int Ssytrd(Layout matrixLayout, Uplo uplo, int n, float[] a, int lda, float[] d,
+ float[] e, float[] tau);
+
+ // See: https://software.intel.com/en-us/node/521046#7EF85A82-423A-4ABC-A208-88326CD0B887
+ [DllImport(DllName, EntryPoint = "LAPACKE_dsytrd", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int Dsytrd(Layout matrixLayout, Uplo uplo, int n, double[] a, int lda, double[] d,
+ double[] e, double[] tau);
+
+ // See: https://software.intel.com/en-us/node/521067#E2C5B8B3-D275-4000-821D-1ABF245D2E30
+ [DllImport(DllName, EntryPoint = "LAPACKE_ssteqr", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int Ssteqr(Layout matrixLayout, Compz compz, int n, float[] d, float[] e, float[] z,
+ int ldz);
+
+ // See: https://software.intel.com/en-us/node/521067#E2C5B8B3-D275-4000-821D-1ABF245D2E30
+ [DllImport(DllName, EntryPoint = "LAPACKE_dsteqr", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int Dsteqr(Layout matrixLayout, Compz compz, int n, double[] d, double[] e, double[] z,
+ int ldz);
+
+ // See: https://software.intel.com/en-us/node/521049#106F8646-1C99-4A9D-8604-D60DAAF7BE0C
+ [DllImport(DllName, EntryPoint = "LAPACKE_sorgtr", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int Sorgtr(Layout matrixLayout, Uplo uplo, int n, float[] a, int lda, float[] tau);
+
+ // See: https://software.intel.com/en-us/node/521049#106F8646-1C99-4A9D-8604-D60DAAF7BE0C
+ [DllImport(DllName, EntryPoint = "LAPACKE_dorgtr", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int Dorgtr(Layout matrixLayout, Uplo uplo, int n, double[] a, int lda, double[] tau);
+
+ public static bool MklSymmetricEigenDecomposition(Single[] input, int size, out Single[] eigenValues, out Single[] eigenVectors)
+ {
+ Contracts.CheckParam(size > 0, nameof(size), "The input matrix size must be strictly positive.");
+ var n2 = size * size;
+ Contracts.Check(Utils.Size(input) >= n2, "The input matrix must at least have " + n2 + " elements");
+
+ eigenValues = null;
+ eigenVectors = null;
+ if (size == 1)
+ {
+ eigenValues = new[] { input[0] };
+ eigenVectors = new[] { 1f };
+ return true;
+ }
+
+ Double[] a = new Double[n2];
+ Array.Copy(input, 0, a, 0, n2);
+ Double[] d = new Double[size];
+ Double[] e = new Double[size - 1];
+ Double[] tau = new Double[size];
+ int info;
+
+ info = Dsytrd(Layout.ColMajor, Uplo.UpperTriangular, size, a, size, d, e, tau);
+ if (info != 0)
+ return false;
+
+ info = Dorgtr(Layout.ColMajor, Uplo.UpperTriangular, size, a, size, tau);
+ if (info != 0)
+ return false;
+
+ info = Dsteqr(Layout.ColMajor, Compz.SchurA, size, d, e, a, size);
+ if (info != 0)
+ return false;
+
+ eigenValues = new Single[size];
+ for (var i = 0; i < size; ++i)
+ eigenValues[i] = (Single)d[i];
+
+ eigenVectors = new Single[n2];
+ for (var i = 0; i < n2; ++i)
+ eigenVectors[i] = (Single)a[i];
+
+ return true;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.ML.TimeSeries/ExponentialAverageTransform.cs b/src/Microsoft.ML.TimeSeries/ExponentialAverageTransform.cs
new file mode 100644
index 0000000000..a6a7c20986
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/ExponentialAverageTransform.cs
@@ -0,0 +1,142 @@
+// 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.Collections.Generic;
+using Microsoft.ML.Runtime;
+using Microsoft.ML.Runtime.CommandLine;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.EntryPoints;
+using Microsoft.ML.Runtime.Internal.Utilities;
+using Microsoft.ML.Runtime.Model;
+using Microsoft.ML.Runtime.TimeSeriesProcessing;
+
+[assembly: LoadableClass(ExponentialAverageTransform.Summary, typeof(ExponentialAverageTransform), typeof(ExponentialAverageTransform.Arguments), typeof(SignatureDataTransform),
+ ExponentialAverageTransform.UserName, ExponentialAverageTransform.LoaderSignature, ExponentialAverageTransform.ShortName)]
+[assembly: LoadableClass(ExponentialAverageTransform.Summary, typeof(ExponentialAverageTransform), null, typeof(SignatureLoadDataTransform),
+ ExponentialAverageTransform.UserName, ExponentialAverageTransform.LoaderSignature)]
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// ExponentialAverageTransform is a weighted average of the values: ExpAvg(y_t) = a * y_t + (1-a) * ExpAvg(y_(t-1)).
+ ///
+ public sealed class ExponentialAverageTransform : SequentialTransformBase
+ {
+ public const string Summary = "Applies a Exponential average on a time series.";
+ public const string LoaderSignature = "ExpAverageTransform";
+ public const string UserName = "Exponential Average Transform";
+ public const string ShortName = "ExpAvg";
+
+ public sealed class Arguments : TransformInputBase
+ {
+ [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 = "Coefficient d in: d m(y_t) = d * y_t + (1-d) * m(y_(t-1)), it should be in [0, 1].",
+ ShortName = "d", SortOrder = 4)]
+ public Single Decay = 0.9f;
+ }
+
+ private static VersionInfo GetVersionInfo()
+ {
+ return new VersionInfo(
+ modelSignature: "EXAVTRNS",
+ verWrittenCur: 0x00010001,
+ verReadableCur: 0x00010001,
+ verWeCanReadBack: 0x00010001,
+ loaderSignature: LoaderSignature,
+ loaderAssemblyName: typeof(ExponentialAverageTransform).Assembly.FullName);
+ }
+
+ private readonly Single _decay;
+
+ public ExponentialAverageTransform(IHostEnvironment env, Arguments args, IDataView input)
+ : base(1, 1, args.Source, args.Name, LoaderSignature, env, input)
+ {
+ Host.CheckUserArg(0 <= args.Decay && args.Decay <= 1, nameof(args.Decay), "Should be in [0, 1].");
+ _decay = args.Decay;
+ }
+
+ public ExponentialAverageTransform(IHostEnvironment env, ModelLoadContext ctx, IDataView input)
+ : base(env, ctx, LoaderSignature, input)
+ {
+ // *** Binary format ***
+ //
+ // Single _decay
+
+ _decay = ctx.Reader.ReadSingle();
+
+ Host.CheckDecode(0 <= _decay && _decay <= 1);
+ Host.CheckDecode(WindowSize == 1);
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+ Host.Assert(WindowSize >= 1);
+ Host.Assert(0 <= _decay && _decay <= 1);
+ ctx.CheckAtModel();
+ ctx.SetVersionInfo(GetVersionInfo());
+
+ // *** Binary format ***
+ //
+ // Single _decay
+
+ base.Save(ctx);
+ ctx.Writer.Write(_decay);
+ }
+
+ public static Single ComputeExponentialAverage(Single input, Single decay, Single previousAverage)
+ {
+ return decay * input + (1 - decay) * previousAverage;
+ }
+
+ public sealed class State : StateBase
+ {
+ private Single _previousAverage;
+ private bool _firstIteration;
+ private Single _decay;
+
+ public State()
+ {
+ _firstIteration = true;
+ }
+
+ protected override void SetNaOutput(ref Single output)
+ {
+ output = Single.NaN;
+ }
+
+ protected override void TransformCore(ref Single input, FixedSizeQueue windowedBuffer, long iteration, ref Single output)
+ {
+ if (_firstIteration)
+ {
+ // we only need the buffer at the first iteration
+ _previousAverage = windowedBuffer[0];
+ _firstIteration = false;
+ }
+ output = ComputeExponentialAverage(input, _decay, _previousAverage);
+ // we keep the previous average in memory
+ _previousAverage = output;
+ }
+
+ protected override void InitializeStateCore()
+ {
+ _firstIteration = true;
+ _decay = ((ExponentialAverageTransform)ParentTransform)._decay;
+ }
+
+ protected override void LearnStateFromDataCore(FixedSizeQueue data)
+ {
+ // This method is empty because there is no need for parameter learning from the initial windowed buffer for this transform.
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/FftUtils.cs b/src/Microsoft.ML.TimeSeries/FftUtils.cs
new file mode 100644
index 0000000000..85f40013c2
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/FftUtils.cs
@@ -0,0 +1,415 @@
+// 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.Reflection;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// The utility functions that wrap the native Discrete Fast Fourier Transform functionality from Intel MKL.
+ ///
+ internal static class FftUtils
+ {
+ //To triger the loading of MKL library since MKL proxy native library depends on it.
+ static FftUtils() => ErrorMessage(0);
+
+ private enum ConfigParam
+ {
+ /* Domain for forward transform. No default value */
+ ForwardDomain = 0,
+
+ /* Dimensionality, or rank. No default value */
+ Dimension = 1,
+
+ /* Length(s) of transform. No default value */
+ Lengths = 2,
+
+ /* Floating point precision. No default value */
+ Precision = 3,
+
+ /* Scale factor for forward transform [1.0] */
+ ForwardScale = 4,
+
+ /* Scale factor for backward transform [1.0] */
+ BackwardScale = 5,
+
+ /* Exponent sign for forward transform [Negative] */
+ /* ForwardSign = 6, ## NOT IMPLEMENTED */
+
+ /* Number of data sets to be transformed [1] */
+ NumberOfTransforms = 7,
+
+ /* Storage of finite complex-valued sequences in complex domain
+ [ComplexComplex] */
+ ComplexStorage = 8,
+
+ /* Storage of finite real-valued sequences in real domain
+ [RealReal] */
+ RealStorage = 9,
+
+ /* Storage of finite complex-valued sequences in conjugate-even
+ domain [ComplexReal] */
+ ConjugateEvenStorage = 10,
+
+ /* Placement of result [InPlace] */
+ Placement = 11,
+
+ /* Generalized strides for input data layout [tigth, row-major for
+ C] */
+ InputStrides = 12,
+
+ /* Generalized strides for output data layout [tight, row-major
+ for C] */
+ OutputStrides = 13,
+
+ /* Distance between first input elements for multiple transforms
+ [0] */
+ InputDistance = 14,
+
+ /* Distance between first output elements for multiple transforms
+ [0] */
+ OutputDistance = 15,
+
+ /* Effort spent in initialization [Medium] */
+ /* InitializationEffort = 16, ## NOT IMPLEMENTED */
+
+ /* Use of workspace during computation [Allow] */
+ /* Workspace = 17, ## NOT IMPLEMENTED */
+
+ /* Ordering of the result [Ordered] */
+ Ordering = 18,
+
+ /* Possible transposition of result [None] */
+ Transpose = 19,
+
+ /* User-settable descriptor name [""] */
+ DescriptorName = 20, /* DEPRECATED */
+
+ /* Packing format for ComplexReal storage of finite
+ conjugate-even sequences [CcsFormat] */
+ PackedFormat = 21,
+
+ /* Commit status of the descriptor - R/O parameter */
+ CommitStatus = 22,
+
+ /* Version string for this DFTI implementation - R/O parameter */
+ Version = 23,
+
+ /* Ordering of the forward transform - R/O parameter */
+ /* ForwardOrdering = 24, ## NOT IMPLEMENTED */
+
+ /* Ordering of the backward transform - R/O parameter */
+ /* BackwardOrdering = 25, ## NOT IMPLEMENTED */
+
+ /* Number of user threads that share the descriptor [1] */
+ NumberOfUserThreads = 26
+ }
+
+ private enum ConfigValue
+ {
+ /* CommitStatus */
+ Committed = 30,
+ Uncommitted = 31,
+
+ /* ForwardDomain */
+ Complex = 32,
+ Real = 33,
+ /* ConjugateEven = 34, ## NOT IMPLEMENTED */
+
+ /* Precision */
+ Single = 35,
+ Double = 36,
+
+ /* ForwardSign */
+ /* Negative = 37, ## NOT IMPLEMENTED */
+ /* Positive = 38, ## NOT IMPLEMENTED */
+
+ /* ComplexStorage and ConjugateEvenStorage */
+ ComplexComplex = 39,
+ ComplexReal = 40,
+
+ /* RealStorage */
+ RealComplex = 41,
+ RealReal = 42,
+
+ /* Placement */
+ InPlace = 43, /* Result overwrites input */
+ NotInPlace = 44, /* Have another place for result */
+
+ /* InitializationEffort */
+ /* Low = 45, ## NOT IMPLEMENTED */
+ /* Medium = 46, ## NOT IMPLEMENTED */
+ /* High = 47, ## NOT IMPLEMENTED */
+
+ /* Ordering */
+ Ordered = 48,
+ BackwardScrambled = 49,
+ /* ForwardScrambled = 50, ## NOT IMPLEMENTED */
+
+ /* Allow/avoid certain usages */
+ Allow = 51, /* Allow transposition or workspace */
+ /* Avoid = 52, ## NOT IMPLEMENTED */
+ None = 53,
+
+ /* PackedFormat (for storing congugate-even finite sequence
+ in real array) */
+ CcsFormat = 54, /* Complex conjugate-symmetric */
+ PackFormat = 55, /* Pack format for real DFT */
+ PermFormat = 56, /* Perm format for real DFT */
+ CceFormat = 57 /* Complex conjugate-even */
+ }
+
+ private const string DllName = "MklImports";
+ private const string DllProxyName = "MklProxyNative";
+
+ // See: https://software.intel.com/en-us/node/521976#8CD904AB-244B-42E4-820A-CC2376E776B8
+ [DllImport(DllProxyName, EntryPoint = "MKLDftiCreateDescriptor", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
+ private static extern int CreateDescriptor(out IntPtr desc, ConfigValue precision, ConfigValue domain, int dimension, int length);
+
+ // See: https://software.intel.com/en-us/node/521977
+ [DllImport(DllName, EntryPoint = "DftiCommitDescriptor")]
+ private static extern int CommitDescriptor(IntPtr desc);
+
+ // See: https://software.intel.com/en-us/node/521978
+ [DllImport(DllName, EntryPoint = "DftiFreeDescriptor")]
+ private static extern int FreeDescriptor(ref IntPtr desc);
+
+ // See: https://software.intel.com/en-us/node/521981
+ [DllImport(DllProxyName, EntryPoint = "MKLDftiSetValue", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
+ private static extern int SetValue(IntPtr desc, ConfigParam configParam, ConfigValue configValue);
+
+ // See: https://software.intel.com/en-us/node/521984
+ [DllImport(DllProxyName, EntryPoint = "MKLDftiComputeForward", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
+ private static extern int ComputeForward(IntPtr desc, [In] double[] inputRe, [In] double[] inputIm, [Out] double[] outputRe, [Out] double[] outputIm);
+
+ // See: https://software.intel.com/en-us/node/521985
+ [DllImport(DllProxyName, EntryPoint = "MKLDftiComputeBackward", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
+ private static extern int ComputeBackward(IntPtr desc, [In] double[] inputRe, [In] double[] inputIm, [Out] double[] outputRe, [Out] double[] outputIm);
+
+ // See: https://software.intel.com/en-us/node/521984
+ [DllImport(DllProxyName, EntryPoint = "MKLDftiComputeForward", CallingConvention = CallingConvention.Cdecl)]
+ private static extern int ComputeForward(IntPtr desc, [In] float[] inputRe, [In] float[] inputIm, [Out] float[] outputRe, [Out] float[] outputIm);
+
+ // See: https://software.intel.com/en-us/node/521985
+ [DllImport(DllProxyName, EntryPoint = "MKLDftiComputeBackward", CallingConvention = CallingConvention.Cdecl)]
+ private static extern int ComputeBackward(IntPtr desc, [In] float[] inputRe, [In] float[] inputIm, [Out] float[] outputRe, [Out] float[] outputIm);
+
+ // See: https://software.intel.com/en-us/node/521990
+ [System.Security.SuppressUnmanagedCodeSecurity]
+ [DllImport(DllName, EntryPoint = "DftiErrorMessage", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
+ private static extern IntPtr ErrorMessage(int status);
+
+ private static void CheckStatus(int status)
+ {
+ if (status != 0)
+ throw Contracts.Except(Marshal.PtrToStringAnsi(ErrorMessage(status)));
+ }
+
+ ///
+ /// Computes the forward Fast Fourier Transform of the input series in single precision.
+ ///
+ /// The real part of the input series
+ /// The imaginary part of the input series
+ /// The real part of the output series
+ /// The imaginary part of the output series
+ ///
+ public static void ComputeForwardFft(float[] inputRe, float[] inputIm, float[] outputRe, float[] outputIm, int length)
+ {
+ Contracts.CheckValue(inputRe, nameof(inputRe));
+ Contracts.CheckValue(inputIm, nameof(inputIm));
+ Contracts.CheckValue(outputRe, nameof(outputRe));
+ Contracts.CheckValue(outputIm, nameof(outputIm));
+ Contracts.CheckParam(length > 0, nameof(length), "The length parameter must be greater than 0.");
+ Contracts.Check(inputRe.Length >= length && inputIm.Length >= length && outputRe.Length >= length && outputIm.Length >= length,
+ "The lengths of inputRe, inputIm, outputRe and outputIm need to be at least equal to the length parameter.");
+
+ int status = 0; // DFTI_NO_ERROR
+ IntPtr descriptor = default(IntPtr);
+
+ try
+ {
+ status = CreateDescriptor(out descriptor, ConfigValue.Single, ConfigValue.Complex, 1, length);
+ CheckStatus(status);
+
+ status = SetValue(descriptor, ConfigParam.Placement, ConfigValue.NotInPlace);
+ CheckStatus(status);
+
+ status = SetValue(descriptor, ConfigParam.ComplexStorage, ConfigValue.RealReal);
+ CheckStatus(status);
+
+ status = CommitDescriptor(descriptor);
+ CheckStatus(status);
+
+ status = ComputeForward(descriptor, inputRe, inputIm, outputRe, outputIm);
+ CheckStatus(status);
+ }
+ finally
+ {
+ if (descriptor != null)
+ FreeDescriptor(ref descriptor);
+ }
+ }
+
+ ///
+ /// Computes the backward (inverse) Fast Fourier Transform of the input series in single precision.
+ ///
+ /// The real part of the input series
+ /// The imaginary part of the input series
+ /// The real part of the output series
+ /// The imaginary part of the output series
+ ///
+ public static void ComputeBackwardFft(float[] inputRe, float[] inputIm, float[] outputRe, float[] outputIm, int length)
+ {
+ Contracts.CheckValue(inputRe, nameof(inputRe));
+ Contracts.CheckValue(inputIm, nameof(inputIm));
+ Contracts.CheckValue(outputRe, nameof(outputRe));
+ Contracts.CheckValue(outputIm, nameof(outputIm));
+ Contracts.CheckParam(length > 0, nameof(length), "The length parameter must be greater than 0.");
+ Contracts.Check(inputRe.Length >= length && inputIm.Length >= length && outputRe.Length >= length && outputIm.Length >= length,
+ "The lengths of inputRe, inputIm, outputRe and outputIm need to be at least equal to the length parameter.");
+
+ int status = 0; // DFTI_NO_ERROR
+ IntPtr descriptor = default(IntPtr);
+ float scale = 1f / length;
+
+ try
+ {
+ status = CreateDescriptor(out descriptor, ConfigValue.Single, ConfigValue.Complex, 1, length);
+ CheckStatus(status);
+
+ status = SetValue(descriptor, ConfigParam.Placement, ConfigValue.NotInPlace);
+ CheckStatus(status);
+
+ status = SetValue(descriptor, ConfigParam.ComplexStorage, ConfigValue.RealReal);
+ CheckStatus(status);
+
+ status = CommitDescriptor(descriptor);
+ CheckStatus(status);
+
+ status = ComputeBackward(descriptor, inputRe, inputIm, outputRe, outputIm);
+ CheckStatus(status);
+ }
+ finally
+ {
+ if (descriptor != null)
+ FreeDescriptor(ref descriptor);
+ }
+
+ // REVIEW: for some reason the native backward scaling for DFTI in MKL does not work.
+ // Therefore here, we manually re-scale the output.
+ // Ideally, the command
+ // status = SetValue(descriptor, ConfigParam.BackwardScale, __arglist(scale));
+ // should do the backward rescaling but for some reason it does not work and needs further investigation.
+ for (int i = 0; i < length; ++i)
+ {
+ outputRe[i] *= scale;
+ outputIm[i] *= scale;
+ }
+ }
+
+ ///
+ /// Computes the forward Fast Fourier Transform of the input series in double precision.
+ ///
+ /// The real part of the input series
+ /// The imaginary part of the input series
+ /// The real part of the output series
+ /// The imaginary part of the output series
+ ///
+ public static void ComputeForwardFft(double[] inputRe, double[] inputIm, double[] outputRe, double[] outputIm, int length)
+ {
+ Contracts.CheckValue(inputRe, nameof(inputRe));
+ Contracts.CheckValue(inputIm, nameof(inputIm));
+ Contracts.CheckValue(outputRe, nameof(outputRe));
+ Contracts.CheckValue(outputIm, nameof(outputIm));
+ Contracts.CheckParam(length > 0, nameof(length), "The length parameter must be greater than 0.");
+ Contracts.Check(inputRe.Length >= length && inputIm.Length >= length && outputRe.Length >= length && outputIm.Length >= length,
+ "The lengths of inputRe, inputIm, outputRe and outputIm need to be at least equal to the length parameter.");
+
+ int status = 0; // DFTI_NO_ERROR
+ IntPtr descriptor = default(IntPtr);
+
+ try
+ {
+ status = CreateDescriptor(out descriptor, ConfigValue.Double, ConfigValue.Complex, 1, length);
+ CheckStatus(status);
+
+ status = SetValue(descriptor, ConfigParam.Placement, ConfigValue.NotInPlace);
+ CheckStatus(status);
+
+ status = SetValue(descriptor, ConfigParam.ComplexStorage, ConfigValue.RealReal);
+ CheckStatus(status);
+
+ status = CommitDescriptor(descriptor);
+ CheckStatus(status);
+
+ status = ComputeForward(descriptor, inputRe, inputIm, outputRe, outputIm);
+ CheckStatus(status);
+ }
+ finally
+ {
+ if (descriptor != null)
+ FreeDescriptor(ref descriptor);
+ }
+ }
+
+ ///
+ /// Computes the backward (inverse) Fast Fourier Transform of the input series in double precision.
+ ///
+ /// The real part of the input series
+ /// The imaginary part of the input series
+ /// The real part of the output series
+ /// The imaginary part of the output series
+ ///
+ public static void ComputeBackwardFft(double[] inputRe, double[] inputIm, double[] outputRe, double[] outputIm, int length)
+ {
+ Contracts.CheckValue(inputRe, nameof(inputRe));
+ Contracts.CheckValue(inputIm, nameof(inputIm));
+ Contracts.CheckValue(outputRe, nameof(outputRe));
+ Contracts.CheckValue(outputIm, nameof(outputIm));
+ Contracts.CheckParam(length > 0, nameof(length), "The length parameter must be greater than 0.");
+ Contracts.Check(inputRe.Length >= length && inputIm.Length >= length && outputRe.Length >= length && outputIm.Length >= length,
+ "The lengths of inputRe, inputIm, outputRe and outputIm need to be at least equal to the length parameter.");
+
+ int status = 0; // DFTI_NO_ERROR
+ IntPtr descriptor = default(IntPtr);
+ double scale = 1.0 / length;
+
+ try
+ {
+ status = CreateDescriptor(out descriptor, ConfigValue.Double, ConfigValue.Complex, 1, length);
+ CheckStatus(status);
+
+ status = SetValue(descriptor, ConfigParam.Placement, ConfigValue.NotInPlace);
+ CheckStatus(status);
+
+ status = SetValue(descriptor, ConfigParam.ComplexStorage, ConfigValue.RealReal);
+ CheckStatus(status);
+
+ status = CommitDescriptor(descriptor);
+ CheckStatus(status);
+
+ status = ComputeBackward(descriptor, inputRe, inputIm, outputRe, outputIm);
+ CheckStatus(status);
+ }
+ finally
+ {
+ if (descriptor != null)
+ FreeDescriptor(ref descriptor);
+ }
+
+ // REVIEW: for some reason the native backward scaling for DFTI in MKL does not work.
+ // Therefore here, we manually re-scale the output.
+ // Ideally, the command
+ // status = SetValue(descriptor, ConfigParam.BackwardScale, __arglist(scale));
+ // should do the backward rescaling but for some reason it does not work and needs further investigation.
+ for (int i = 0; i < length; ++i)
+ {
+ outputRe[i] *= scale;
+ outputIm[i] *= scale;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/ISequenceModeler.cs b/src/Microsoft.ML.TimeSeries/ISequenceModeler.cs
new file mode 100644
index 0000000000..5ee0c01711
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/ISequenceModeler.cs
@@ -0,0 +1,71 @@
+// 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 Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.Internal.Utilities;
+using Microsoft.ML.Runtime.Model;
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// The base container class for the forecast result on a sequence of type .
+ ///
+ /// The type of the elements in the sequence
+ public abstract class ForecastResultBase
+ {
+ public VBuffer PointForecast;
+ }
+
+ ///
+ /// The standard interface for modeling a sequence.
+ ///
+ /// The type of the elements in the input sequence
+ /// The type of the elements in the output sequence
+ public interface ISequenceModeler : ICanSaveModel
+ {
+ ///
+ /// Initializes the state of the modeler
+ ///
+ void InitState();
+
+ ///
+ /// Consumes one element from the input sequence.
+ ///
+ /// An element in the sequence
+ /// determines whether the sequence model should be updated according to the input
+ void Consume(ref TInput input, bool updateModel = false);
+
+ ///
+ /// Trains the sequence model on a given sequence.
+ ///
+ /// The input sequence used for training
+ void Train(FixedSizeQueue data);
+
+ ///
+ /// Trains the sequence model on a given sequence. The method accepts an object of RoleMappedData,
+ /// and assumes the input column is the 'Feature' column of type TInput.
+ ///
+ /// The input sequence used for training
+ void Train(RoleMappedData data);
+
+ ///
+ /// Forecasts the next 'horizon' elements in the output sequence.
+ ///
+ /// The forecast result for the given horizon along with optional information depending on the algorithm
+ /// The forecast horizon
+ void Forecast(ref ForecastResultBase result, int horizon = 1);
+
+ ///
+ /// Predicts the next element in the output sequence.
+ ///
+ /// The output ref parameter the will contain the prediction result
+ void PredictNext(ref TOutput output);
+
+ ///
+ /// Creates a clone of the model.
+ ///
+ /// A clone of the object
+ ISequenceModeler Clone();
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/IidAnomalyDetectionBase.cs b/src/Microsoft.ML.TimeSeries/IidAnomalyDetectionBase.cs
new file mode 100644
index 0000000000..bb14178827
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/IidAnomalyDetectionBase.cs
@@ -0,0 +1,60 @@
+// 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 Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.Internal.Utilities;
+using Microsoft.ML.Runtime.Model;
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// This transform computes the p-values and martingale scores for a supposedly i.i.d input sequence of floats. In other words, it assumes
+ /// the input sequence represents the raw anomaly score which might have been computed via another process.
+ ///
+ public abstract class IidAnomalyDetectionBase : SequentialAnomalyDetectionTransformBase
+ {
+ public IidAnomalyDetectionBase(ArgumentsBase args, string name, IHostEnvironment env, IDataView input)
+ : base(args, name, env, input)
+ {
+ InitialWindowSize = 0;
+ }
+
+ public IidAnomalyDetectionBase(IHostEnvironment env, ModelLoadContext ctx, string name, IDataView input)
+ : base(env, ctx, name, input)
+ {
+ Host.CheckDecode(InitialWindowSize == 0);
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ ctx.CheckAtModel();
+ Host.Assert(InitialWindowSize == 0);
+
+ // *** Binary format ***
+ //
+
+ base.Save(ctx);
+ }
+
+ public sealed class State : AnomalyDetectionStateBase
+ {
+ protected override void LearnStateFromDataCore(FixedSizeQueue data)
+ {
+ // This method is empty because there is no need for initial tuning for this transform.
+ }
+
+ protected override void InitializeAnomalyDetector()
+ {
+ // This method is empty because there is no need for any extra initialization for this transform.
+ }
+
+ protected override double ComputeRawAnomalyScore(ref Single input, FixedSizeQueue windowedBuffer, long iteration)
+ {
+ // This transform treats the input sequenence as the raw anomaly score.
+ return (double)input;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/IidChangePointDetector.cs b/src/Microsoft.ML.TimeSeries/IidChangePointDetector.cs
new file mode 100644
index 0000000000..909bbc390c
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/IidChangePointDetector.cs
@@ -0,0 +1,147 @@
+// 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 Microsoft.ML.Runtime;
+using Microsoft.ML.Runtime.CommandLine;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.EntryPoints;
+using Microsoft.ML.Runtime.Model;
+using Microsoft.ML.Runtime.TimeSeriesProcessing;
+
+[assembly: LoadableClass(IidChangePointDetector.Summary, typeof(IidChangePointDetector), typeof(IidChangePointDetector.Arguments), typeof(SignatureDataTransform),
+ IidChangePointDetector.UserName, IidChangePointDetector.LoaderSignature, IidChangePointDetector.ShortName)]
+[assembly: LoadableClass(IidChangePointDetector.Summary, typeof(IidChangePointDetector), null, typeof(SignatureLoadDataTransform),
+ IidChangePointDetector.UserName, IidChangePointDetector.LoaderSignature)]
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// This class implements the change point detector transform for an i.i.d. sequence based on adaptive kernel density estimation and martingales.
+ ///
+ public sealed class IidChangePointDetector : IidAnomalyDetectionBase, ITransformTemplate
+ {
+ internal const string Summary = "This transform detects the change-points in an i.i.d. sequence using adaptive kernel density estimation and martingales.";
+ public const string LoaderSignature = "IidChangePointDetector";
+ public const string UserName = "IID Change Point Detection";
+ public const string ShortName = "ichgpnt";
+
+ public sealed class Arguments : TransformInputBase
+ {
+ [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.",
+ SortOrder = 2)]
+ public string Name;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The change history length.", ShortName = "wnd",
+ SortOrder = 102)]
+ public int ChangeHistoryLength = 20;
+
+ [Argument(ArgumentType.Required, HelpText = "The confidence for change point detection in the range [0, 100].",
+ ShortName = "cnf", SortOrder = 3)]
+ public double Confidence = 95;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The martingale used for scoring.", ShortName = "mart", SortOrder = 103)]
+ public MartingaleType Martingale = SequentialAnomalyDetectionTransformBase.MartingaleType.Power;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The epsilon parameter for the Power martingale.",
+ ShortName = "eps", SortOrder = 104)]
+ public double PowerMartingaleEpsilon = 0.1;
+ }
+
+ private sealed class BaseArguments : ArgumentsBase
+ {
+ public BaseArguments(Arguments args)
+ {
+ Source = args.Source;
+ Name = args.Name;
+ Side = SequentialAnomalyDetectionTransformBase.AnomalySide.TwoSided;
+ WindowSize = args.ChangeHistoryLength;
+ Martingale = args.Martingale;
+ PowerMartingaleEpsilon = args.PowerMartingaleEpsilon;
+ AlertOn = SequentialAnomalyDetectionTransformBase.AlertingScore.MartingaleScore;
+ }
+
+ public BaseArguments(IidChangePointDetector transform)
+ {
+ Source = transform.InputColumnName;
+ Name = transform.OutputColumnName;
+ Side = AnomalySide.TwoSided;
+ WindowSize = transform.WindowSize;
+ Martingale = transform.Martingale;
+ PowerMartingaleEpsilon = transform.PowerMartingaleEpsilon;
+ AlertOn = AlertingScore.MartingaleScore;
+ AlertThreshold = transform.AlertThreshold;
+ }
+ }
+
+ private static VersionInfo GetVersionInfo()
+ {
+ return new VersionInfo(modelSignature: "ICHGTRNS",
+ verWrittenCur: 0x00010001, // Initial
+ verReadableCur: 0x00010001,
+ verWeCanReadBack: 0x00010001,
+ loaderSignature: LoaderSignature,
+ loaderAssemblyName: typeof(IidChangePointDetector).Assembly.FullName);
+ }
+
+ public IidChangePointDetector(IHostEnvironment env, Arguments args, IDataView input)
+ : base(new BaseArguments(args), LoaderSignature, env, input)
+ {
+ switch (Martingale)
+ {
+ case MartingaleType.None:
+ AlertThreshold = Double.MaxValue;
+ break;
+ case MartingaleType.Power:
+ AlertThreshold = Math.Exp(WindowSize * LogPowerMartigaleBettingFunc(1 - args.Confidence / 100, PowerMartingaleEpsilon));
+ break;
+ case MartingaleType.Mixture:
+ AlertThreshold = Math.Exp(WindowSize * LogMixtureMartigaleBettingFunc(1 - args.Confidence / 100));
+ break;
+ default:
+ throw Host.ExceptParam(nameof(args.Martingale),
+ "The martingale type can be only (0) None, (1) Power or (2) Mixture.");
+ }
+ }
+
+ public IidChangePointDetector(IHostEnvironment env, ModelLoadContext ctx, IDataView input)
+ : base(env, ctx, LoaderSignature, input)
+ {
+ // *** Binary format ***
+ //
+
+ Host.CheckDecode(ThresholdScore == AlertingScore.MartingaleScore);
+ Host.CheckDecode(Side == AnomalySide.TwoSided);
+ }
+
+ private IidChangePointDetector(IHostEnvironment env, IidChangePointDetector transform, IDataView newSource)
+ : base(new BaseArguments(transform), LoaderSignature, env, newSource)
+ {
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+ ctx.CheckAtModel();
+ ctx.SetVersionInfo(GetVersionInfo());
+
+ Host.Assert(ThresholdScore == AlertingScore.MartingaleScore);
+ Host.Assert(Side == AnomalySide.TwoSided);
+
+ // *** Binary format ***
+ //
+
+ base.Save(ctx);
+ }
+
+ public IDataTransform ApplyToData(IHostEnvironment env, IDataView newSource)
+ {
+ return new IidChangePointDetector(env, this, newSource);
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/IidSpikeDetector.cs b/src/Microsoft.ML.TimeSeries/IidSpikeDetector.cs
new file mode 100644
index 0000000000..f4d30c8696
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/IidSpikeDetector.cs
@@ -0,0 +1,126 @@
+// 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 Microsoft.ML.Runtime;
+using Microsoft.ML.Runtime.CommandLine;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.EntryPoints;
+using Microsoft.ML.Runtime.Model;
+using Microsoft.ML.Runtime.TimeSeriesProcessing;
+
+[assembly: LoadableClass(IidSpikeDetector.Summary, typeof(IidSpikeDetector), typeof(IidSpikeDetector.Arguments), typeof(SignatureDataTransform),
+ IidSpikeDetector.UserName, IidSpikeDetector.LoaderSignature, IidSpikeDetector.ShortName)]
+[assembly: LoadableClass(IidSpikeDetector.Summary, typeof(IidSpikeDetector), null, typeof(SignatureLoadDataTransform),
+ IidSpikeDetector.UserName, IidSpikeDetector.LoaderSignature)]
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// This class implements the spike detector transform for an i.i.d. sequence based on adaptive kernel density estimation.
+ ///
+ public sealed class IidSpikeDetector : IidAnomalyDetectionBase, ITransformTemplate
+ {
+ internal const string Summary = "This transform detects the spikes in a i.i.d. sequence using adaptive kernel density estimation.";
+ public const string LoaderSignature = "IidSpikeDetector";
+ public const string UserName = "IID Spike Detection";
+ public const string ShortName = "ispike";
+
+ public sealed class Arguments : TransformInputBase
+ {
+ [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.",
+ 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 = 101)]
+ public AnomalySide Side = AnomalySide.TwoSided;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the sliding window for computing the p-value.", ShortName = "wnd",
+ SortOrder = 102)]
+ public int PvalueHistoryLength = 100;
+
+ [Argument(ArgumentType.Required, HelpText = "The confidence for spike detection in the range [0, 100].",
+ ShortName = "cnf", SortOrder = 3)]
+ public double Confidence = 99;
+ }
+
+ private sealed class BaseArguments : ArgumentsBase
+ {
+ public BaseArguments(Arguments args)
+ {
+ Source = args.Source;
+ Name = args.Name;
+ Side = args.Side;
+ WindowSize = args.PvalueHistoryLength;
+ AlertThreshold = 1 - args.Confidence / 100;
+ AlertOn = SequentialAnomalyDetectionTransformBase.AlertingScore.PValueScore;
+ Martingale = MartingaleType.None;
+ }
+
+ public BaseArguments(IidSpikeDetector transform)
+ {
+ Source = transform.InputColumnName;
+ Name = transform.OutputColumnName;
+ Side = transform.Side;
+ WindowSize = transform.WindowSize;
+ AlertThreshold = transform.AlertThreshold;
+ AlertOn = AlertingScore.PValueScore;
+ Martingale = MartingaleType.None;
+ }
+ }
+
+ private static VersionInfo GetVersionInfo()
+ {
+ return new VersionInfo(
+ modelSignature: "ISPKTRNS",
+ verWrittenCur: 0x00010001, // Initial
+ verReadableCur: 0x00010001,
+ verWeCanReadBack: 0x00010001,
+ loaderSignature: LoaderSignature,
+ loaderAssemblyName: typeof(IidSpikeDetector).Assembly.FullName);
+ }
+
+ public IidSpikeDetector(IHostEnvironment env, Arguments args, IDataView input)
+ : base(new BaseArguments(args), LoaderSignature, env, input)
+ {
+ // This constructor is empty.
+ }
+
+ public IidSpikeDetector(IHostEnvironment env, ModelLoadContext ctx, IDataView input)
+ : base(env, ctx, LoaderSignature, input)
+ {
+ // *** Binary format ***
+ //
+
+ Host.CheckDecode(ThresholdScore == AlertingScore.PValueScore);
+ }
+ private IidSpikeDetector(IHostEnvironment env, IidSpikeDetector transform, IDataView newSource)
+ : base(new BaseArguments(transform), LoaderSignature, env, newSource)
+ {
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+ ctx.CheckAtModel();
+ ctx.SetVersionInfo(GetVersionInfo());
+
+ Host.Assert(ThresholdScore == AlertingScore.PValueScore);
+
+ // *** Binary format ***
+ //
+
+ base.Save(ctx);
+ }
+
+ public IDataTransform ApplyToData(IHostEnvironment env, IDataView newSource)
+ {
+ return new IidSpikeDetector(env, this, newSource);
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj b/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj
new file mode 100644
index 0000000000..09ce1d5758
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj
@@ -0,0 +1,14 @@
+
+
+
+ netstandard2.0
+ Microsoft.ML.TimeSeries
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.ML.TimeSeries/MovingAverageTransform.cs b/src/Microsoft.ML.TimeSeries/MovingAverageTransform.cs
new file mode 100644
index 0000000000..c326492a02
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/MovingAverageTransform.cs
@@ -0,0 +1,299 @@
+// 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.Linq;
+using System.Collections.Generic;
+using Microsoft.ML.Runtime;
+using Microsoft.ML.Runtime.CommandLine;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.Internal.Utilities;
+using Microsoft.ML.Runtime.Model;
+using Microsoft.ML.Runtime.TimeSeriesProcessing;
+
+[assembly: LoadableClass(MovingAverageTransform.Summary, typeof(MovingAverageTransform), typeof(MovingAverageTransform.Arguments), typeof(SignatureDataTransform),
+ "Moving Average Transform", MovingAverageTransform.LoaderSignature, "MoAv")]
+[assembly: LoadableClass(MovingAverageTransform.Summary, typeof(MovingAverageTransform), null, typeof(SignatureLoadDataTransform),
+ "Moving Average Transform", MovingAverageTransform.LoaderSignature)]
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// MovingAverageTransform is a weighted average of the values in
+ /// the sliding window.
+ ///
+ public sealed class MovingAverageTransform : SequentialTransformBase
+ {
+ public const string Summary = "Applies a moving average on a time series. Only finite values are taken into account.";
+ public const string LoaderSignature = "MovingAverageTransform";
+
+ public sealed class Arguments
+ {
+ [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 size of the sliding window for computing the moving average", ShortName = "wnd", SortOrder = 3)]
+ public int WindowSize = 2;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "Lag between current observation and last observation from the sliding window", ShortName = "l", SortOrder = 4)]
+ public int Lag = 1;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "(optional) Comma separated list of weights, the first weight is applied to the oldest value. " +
+ "An empty value will be replaced by uniform weights.",
+ ShortName = "w", SortOrder = 5)]
+ public string Weights = null;
+ }
+
+ private int _lag;
+
+ private static VersionInfo GetVersionInfo()
+ {
+ return new VersionInfo(
+ modelSignature: "MOAVTRNS",
+ verWrittenCur: 0x00010001,
+ verReadableCur: 0x00010001,
+ verWeCanReadBack: 0x00010001,
+ loaderSignature: LoaderSignature,
+ loaderAssemblyName: typeof(MovingAverageTransform).Assembly.FullName);
+ }
+
+ // _weights is null means a uniform moving average is computed.
+ private readonly Single[] _weights;
+
+ public MovingAverageTransform(IHostEnvironment env, Arguments args, IDataView input)
+ : base(args.WindowSize + args.Lag - 1, args.WindowSize + args.Lag - 1, args.Source, args.Name, LoaderSignature, env, input)
+ {
+ Host.CheckUserArg(args.WindowSize >= 1, nameof(args.WindowSize), "Should be at least 1.");
+ Host.CheckUserArg(args.Lag >= 0, nameof(args.Lag), "Should be positive.");
+ Host.CheckUserArg(args.Lag != 0 || args.WindowSize > 1, nameof(args.Lag),
+ "If lag=0 and wnd=1, the transform just copies the column. Use CopyColumn instead.");
+ _weights = string.IsNullOrWhiteSpace(args.Weights) ? null : args.Weights.Split(',').Select(c => Convert.ToSingle(c)).ToArray();
+ if (_weights != null && _weights.Length != args.WindowSize)
+ throw Host.ExceptUserArg(nameof(args.Weights), string.Format("{0} weights are provided, but {1} are expected (or none)'", Utils.Size(_weights), args.WindowSize));
+ _lag = args.Lag;
+ }
+
+ public MovingAverageTransform(IHostEnvironment env, ModelLoadContext ctx, IDataView input)
+ : base(env, ctx, LoaderSignature, input)
+ {
+ // *** Binary format ***
+ //
+ // int: lag
+ // Single[]: _weights
+
+ _lag = ctx.Reader.ReadInt32();
+ _weights = ctx.Reader.ReadFloatArray();
+
+ Host.CheckDecode(WindowSize >= 1);
+ Host.CheckDecode(_weights == null || Utils.Size(_weights) == WindowSize + 1 - _lag);
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+ Host.Assert(WindowSize >= 1);
+ Host.Assert(_lag >= 0);
+ ctx.CheckAtModel();
+ ctx.SetVersionInfo(GetVersionInfo());
+
+ // *** Binary format ***
+ //
+ // int: _lag
+ // Single[]: _weights
+
+ base.Save(ctx);
+ ctx.Writer.Write(_lag);
+ Host.Assert(_weights == null || Utils.Size(_weights) == WindowSize + 1 - _lag);
+ ctx.Writer.WriteFloatArray(_weights);
+ }
+
+ private static Single ComputeMovingAverageUniformInitialisation(FixedSizeQueue others, Single input, int lag,
+ Single lastDropped, ref Single currentSum,
+ ref int nbNanValues)
+ {
+ Single sumValues = 0;
+ nbNanValues = 0;
+ int n;
+ if (lag == 0)
+ {
+ if (Single.IsNaN(input))
+ nbNanValues = 1;
+ else
+ sumValues = input;
+ n = others.Count;
+ }
+ else
+ n = others.Count - lag + 1;
+
+ for (int i = 0; i < n; ++i)
+ {
+ if (Single.IsNaN(others[i]))
+ ++nbNanValues;
+ else
+ sumValues += others[i];
+ }
+ int nb = others.Count + 1 - nbNanValues;
+ currentSum = sumValues;
+ return nb == 0 ? Single.NaN : sumValues / nb;
+ }
+
+ public static Single ComputeMovingAverageNonUniform(FixedSizeQueue others, Single input, Single[] weights, int lag)
+ {
+ Single sumWeights = 0;
+ Single sumValues = 0;
+ int n;
+ if (lag == 0)
+ {
+ if (!Single.IsNaN(input))
+ {
+ sumWeights = weights[weights.Length - 1];
+ sumValues = sumWeights * input;
+ }
+ n = others.Count;
+ }
+ else
+ n = others.Count - lag + 1;
+
+ for (int i = 0; i < n; ++i)
+ {
+ if (!Single.IsNaN(others[i]))
+ {
+ sumWeights += weights[i];
+ sumValues += weights[i] * others[i];
+ }
+ }
+ return sumWeights != 0 ? sumValues / sumWeights : Single.NaN;
+ }
+
+ ///
+ /// Possible returns:
+ ///
+ /// Finite Value: no infinite value in the sliding window and at least a non NaN value
+ /// NaN value: only NaN values in the sliding window or +/- Infinite
+ /// Inifinite value: one infinite value in the sliding window (sign is no relevant)
+ ///
+ public static Single ComputeMovingAverageUniform(FixedSizeQueue others, Single input, int lag,
+ Single lastDropped, ref Single currentSum,
+ ref bool initUniformMovingAverage,
+ ref int nbNanValues)
+ {
+ if (initUniformMovingAverage)
+ {
+ initUniformMovingAverage = false;
+ return ComputeMovingAverageUniformInitialisation(others, input, lag,
+ lastDropped, ref currentSum, ref nbNanValues);
+ }
+ else
+ {
+ if (Single.IsNaN(lastDropped))
+ --nbNanValues;
+ else if (!FloatUtils.IsFinite(lastDropped))
+ // One infinite value left,
+ // we need to recompute everything as we don't know how many infinite values are in the sliding window.
+ return ComputeMovingAverageUniformInitialisation(others, input, lag,
+ lastDropped, ref currentSum, ref nbNanValues);
+ else
+ currentSum -= lastDropped;
+
+ // lastDropped is finite
+ Contracts.Assert(FloatUtils.IsFinite(lastDropped) || Single.IsNaN(lastDropped));
+
+ var newValue = lag == 0 ? input : others[others.Count - lag];
+ if (!Single.IsNaN(newValue) && !FloatUtils.IsFinite(newValue))
+ // One infinite value entered,
+ // we need to recompute everything as we don't know how many infinite values are in the sliding window.
+ return ComputeMovingAverageUniformInitialisation(others, input, lag,
+ lastDropped, ref currentSum, ref nbNanValues);
+
+ // lastDropped is finite and input is finite or NaN
+ Contracts.Assert(FloatUtils.IsFinite(newValue) || Single.IsNaN(newValue));
+
+ if (!Single.IsNaN(currentSum) && !FloatUtils.IsFinite(currentSum))
+ {
+ if (Single.IsNaN(newValue))
+ {
+ ++nbNanValues;
+ return currentSum;
+ }
+ else
+ return FloatUtils.IsFinite(newValue) ? currentSum : (currentSum + newValue);
+ }
+
+ // lastDropped is finite, input is finite or NaN, currentSum is finite or NaN
+ Contracts.Assert(FloatUtils.IsFinite(currentSum) || Single.IsNaN(currentSum));
+
+ if (Single.IsNaN(newValue))
+ {
+ ++nbNanValues;
+ int nb = (lag == 0 ? others.Count + 1 : others.Count - lag + 1) - nbNanValues;
+ return nb == 0 ? Single.NaN : currentSum / nb;
+ }
+ else
+ {
+ int nb = lag == 0 ? others.Count + 1 - nbNanValues : others.Count + 1 - nbNanValues - lag;
+ currentSum += input;
+ return nb == 0 ? Single.NaN : currentSum / nb;
+ }
+ }
+ }
+
+ public sealed class State : StateBase
+ {
+ private Single[] _weights;
+ private int _lag;
+
+ // This is only needed when we compute a uniform moving average.
+ // A temptation could be to extend the buffer size but then the moving average would
+ // start producing values 1 iteration later than expected.
+ private Single _lastDroppedValue;
+ private Single _currentSum;
+
+ // When the moving average is uniform, the computational is incremental,
+ // except for the first iteration or after encountering infinities.
+ private bool _initUniformMovingAverage;
+
+ // When the moving aveage is uniform, we need to remember how many NA values
+ // take part of the computation.
+ private int _nbNanValues;
+
+ protected override void SetNaOutput(ref Single output)
+ {
+ output = Single.NaN;
+ }
+
+ ///
+ /// input is not included
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected override void TransformCore(ref Single input, FixedSizeQueue windowedBuffer, long iteration, ref Single output)
+ {
+ if (_weights == null)
+ output = ComputeMovingAverageUniform(windowedBuffer, input, _lag, _lastDroppedValue, ref _currentSum, ref _initUniformMovingAverage, ref _nbNanValues);
+ else
+ output = ComputeMovingAverageNonUniform(windowedBuffer, input, _weights, _lag);
+ _lastDroppedValue = windowedBuffer[0];
+ }
+
+ protected override void InitializeStateCore()
+ {
+ _weights = ((MovingAverageTransform)ParentTransform)._weights;
+ _lag = ((MovingAverageTransform)ParentTransform)._lag;
+ _initUniformMovingAverage = true;
+ }
+
+ protected override void LearnStateFromDataCore(FixedSizeQueue data)
+ {
+ // This method is empty because there is no need for parameter learning from the initial windowed buffer for this transform.
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/PValueTransform.cs b/src/Microsoft.ML.TimeSeries/PValueTransform.cs
new file mode 100644
index 0000000000..b6490de3c7
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/PValueTransform.cs
@@ -0,0 +1,146 @@
+// 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 Microsoft.ML.Runtime;
+using Microsoft.ML.Runtime.CommandLine;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.EntryPoints;
+using Microsoft.ML.Runtime.Internal.Utilities;
+using Microsoft.ML.Runtime.Model;
+using Microsoft.ML.Runtime.TimeSeriesProcessing;
+
+[assembly: LoadableClass(PValueTransform.Summary, typeof(PValueTransform), typeof(PValueTransform.Arguments), typeof(SignatureDataTransform),
+ PValueTransform.UserName, PValueTransform.LoaderSignature, PValueTransform.ShortName)]
+[assembly: LoadableClass(PValueTransform.Summary, typeof(PValueTransform), null, typeof(SignatureLoadDataTransform),
+ PValueTransform.UserName, PValueTransform.LoaderSignature)]
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// PValueTransform is a sequential transform that computes the empirical p-value of the current value in the series based on the other values in
+ /// the sliding window.
+ ///
+ public sealed class PValueTransform : SequentialTransformBase
+ {
+ internal const string Summary = "This P-Value transform calculates the p-value of the current input in the sequence with regard to the values in the sliding window.";
+ public const string LoaderSignature = "PValueTransform";
+ public const string UserName = "p-Value Transform";
+ public const string ShortName = "PVal";
+
+ public sealed class Arguments : TransformInputBase
+ {
+ [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 seed value of the random generator", ShortName = "seed",
+ SortOrder = 3)]
+ public int Seed = 0;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The flag that determines whether the p-values are calculated on the positive side", ShortName = "pos",
+ SortOrder = 4)]
+ public bool PositiveSide = true;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the sliding window for computing the p-value", ShortName = "wnd",
+ SortOrder = 5)]
+ public int WindowSize = 1;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the initial window for computing the p-value. The default value is set to 0, which means there is no initial window considered.",
+ ShortName = "initwnd", SortOrder = 6)]
+ public int InitialWindowSize = 0;
+ }
+
+ private static VersionInfo GetVersionInfo()
+ {
+ return new VersionInfo(
+ modelSignature: "PVALTRNS",
+ verWrittenCur: 0x00010001, // Initial
+ verReadableCur: 0x00010001,
+ verWeCanReadBack: 0x00010001,
+ loaderSignature: LoaderSignature,
+ loaderAssemblyName: typeof(PValueTransform).Assembly.FullName);
+ }
+
+ private readonly int _seed;
+ private readonly bool _isPositiveSide;
+
+ public PValueTransform(IHostEnvironment env, Arguments args, IDataView input)
+ : base(args.WindowSize, args.InitialWindowSize, args.Source, args.Name, LoaderSignature, env, input)
+ {
+ Host.CheckUserArg(args.WindowSize >= 1, nameof(args.WindowSize), "The size of the sliding window should be at least 1.");
+ _seed = args.Seed;
+ _isPositiveSide = args.PositiveSide;
+ }
+
+ public PValueTransform(IHostEnvironment env, ModelLoadContext ctx, IDataView input)
+ : base(env, ctx, LoaderSignature, input)
+ {
+ // *** Binary format ***
+ // int: _percentile
+ // byte: _isPositiveSide
+
+ _seed = ctx.Reader.ReadInt32();
+ _isPositiveSide = ctx.Reader.ReadBoolByte();
+ Host.CheckDecode(WindowSize >= 1);
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+ Host.Assert(WindowSize >= 1);
+ ctx.CheckAtModel();
+ ctx.SetVersionInfo(GetVersionInfo());
+
+ // *** Binary format ***
+ //
+ // int: _percentile
+ // byte: _isPositiveSide
+
+ base.Save(ctx);
+ ctx.Writer.Write(_seed);
+ ctx.Writer.WriteBoolByte(_isPositiveSide);
+ }
+
+ public sealed class State : StateBase
+ {
+ private IRandom _randomGen;
+
+ private PValueTransform _parent;
+
+ protected override void SetNaOutput(ref Single dst)
+ {
+ dst = Single.NaN;
+ }
+
+ protected override void TransformCore(ref Single input, FixedSizeQueue windowedBuffer, long iteration, ref Single dst)
+ {
+ int count;
+ int equalCount;
+ int totalCount;
+
+ PercentileThresholdTransform.CountGreaterOrEqualValues(windowedBuffer, input, out count, out equalCount, out totalCount);
+ count = (_parent._isPositiveSide) ? count : totalCount - count - equalCount;
+
+ dst = (Single)((count + _randomGen.NextDouble() * equalCount) / (totalCount + 1));
+ // Based on the equation in http://arxiv.org/pdf/1204.3251.pdf
+ }
+
+ protected override void InitializeStateCore()
+ {
+ _parent = (PValueTransform)ParentTransform;
+ _randomGen = RandomUtils.Create(_parent._seed);
+ }
+
+ protected override void LearnStateFromDataCore(FixedSizeQueue data)
+ {
+ // This method is empty because there is no need for parameter learning from the initial windowed buffer for this transform.
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/PercentileThresholdTransform.cs b/src/Microsoft.ML.TimeSeries/PercentileThresholdTransform.cs
new file mode 100644
index 0000000000..9d771ef21a
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/PercentileThresholdTransform.cs
@@ -0,0 +1,159 @@
+// 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 Microsoft.ML.Runtime;
+using Microsoft.ML.Runtime.CommandLine;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.EntryPoints;
+using Microsoft.ML.Runtime.Internal.Utilities;
+using Microsoft.ML.Runtime.Model;
+using Microsoft.ML.Runtime.TimeSeriesProcessing;
+
+[assembly: LoadableClass(PercentileThresholdTransform.Summary, typeof(PercentileThresholdTransform), typeof(PercentileThresholdTransform.Arguments), typeof(SignatureDataTransform),
+ PercentileThresholdTransform.UserName, PercentileThresholdTransform.LoaderSignature, PercentileThresholdTransform.ShortName)]
+[assembly: LoadableClass(PercentileThresholdTransform.Summary, typeof(PercentileThresholdTransform), null, typeof(SignatureLoadDataTransform),
+ PercentileThresholdTransform.UserName, PercentileThresholdTransform.LoaderSignature)]
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// PercentileThresholdTransform is a sequential transform that decides whether the current value of the time-series belongs to the 'percentile' % of the top values in
+ /// the sliding window. The output of the transform will be a boolean flag.
+ ///
+ public sealed class PercentileThresholdTransform : SequentialTransformBase
+ {
+ public const string Summary = "Detects the values of time-series that are in the top percentile of the sliding window.";
+ public const string LoaderSignature = "PercentThrTransform";
+ public const string UserName = "Percentile Threshold Transform";
+ public const string ShortName = "TopPcnt";
+
+ public sealed class Arguments : TransformInputBase
+ {
+ [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 percentile value for thresholding in the range [0, 100]", ShortName = "pcnt",
+ SortOrder = 3)]
+ public Double Percentile = 1;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the sliding window for computing the percentile threshold. " +
+ "The default value is set to 1.", ShortName = "wnd",
+ SortOrder = 4)]
+ public int WindowSize = 1;
+ }
+
+ public const Double MinPercentile = 0;
+ public const Double MaxPercentile = 100;
+
+ private static VersionInfo GetVersionInfo()
+ {
+ return new VersionInfo(
+ modelSignature: "PCNTTRNS",
+ verWrittenCur: 0x00010001, // Initial
+ verReadableCur: 0x00010001,
+ verWeCanReadBack: 0x00010001,
+ loaderSignature: LoaderSignature,
+ loaderAssemblyName: typeof(PercentileThresholdTransform).Assembly.FullName);
+ }
+
+ private readonly Double _percentile;
+
+ public PercentileThresholdTransform(IHostEnvironment env, Arguments args, IDataView input)
+ : base(args.WindowSize, args.WindowSize, args.Source, args.Name, LoaderSignature, env, input)
+ {
+ Host.CheckUserArg(args.WindowSize >= 1, nameof(args.WindowSize), "The size of the sliding window should be at least 1.");
+ Host.CheckUserArg(MinPercentile <= args.Percentile && args.Percentile <= MaxPercentile, nameof(args.Percentile), "The percentile value should be in [0, 100].");
+ _percentile = args.Percentile;
+ }
+
+ public PercentileThresholdTransform(IHostEnvironment env, ModelLoadContext ctx, IDataView input)
+ : base(env, ctx, LoaderSignature,input)
+ {
+ // *** Binary format ***
+ // Double: _percentile
+
+ _percentile = ctx.Reader.ReadDouble();
+
+ Host.CheckDecode(WindowSize >= 1);
+ Host.CheckDecode(MinPercentile <= _percentile && _percentile <= MaxPercentile);
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+ Host.Assert(MinPercentile <= _percentile && _percentile <= MaxPercentile);
+ Host.Assert(WindowSize >= 1);
+ ctx.CheckAtModel();
+ ctx.SetVersionInfo(GetVersionInfo());
+
+ // *** Binary format ***
+ //
+ // Double: _percentile
+
+ base.Save(ctx);
+ ctx.Writer.Write(_percentile);
+ }
+
+ public static void CountGreaterOrEqualValues(FixedSizeQueue others, Single theValue, out int greaterVals, out int equalVals, out int totalVals)
+ {
+ // The current linear algorithm for counting greater and equal elements takes O(n),
+ // but it can be improved to O(log n) if a separate Binary Search Tree data structure is used.
+
+ greaterVals = 1;
+ equalVals = 0;
+ totalVals = 0;
+
+ var n = others.Count;
+
+ for (int i = 0; i < n; ++i)
+ {
+ if (!Single.IsNaN(others[i]))
+ {
+ greaterVals += (others[i] > theValue) ? 1 : 0;
+ equalVals += (others[i] == theValue) ? 1 : 0;
+ totalVals++;
+ }
+ }
+ }
+
+ public sealed class State : StateBase
+ {
+ ///
+ /// The number of elements in the top 'percentile' % of the top values.
+ ///
+ private PercentileThresholdTransform _parent;
+
+ protected override void SetNaOutput(ref bool dst)
+ {
+ dst = false;
+ }
+
+ protected override void TransformCore(ref Single input, FixedSizeQueue windowedBuffer, long iteration, ref bool dst)
+ {
+ int greaterCount;
+ int equalCount;
+ int totalCount;
+
+ CountGreaterOrEqualValues(windowedBuffer, input, out greaterCount, out equalCount, out totalCount);
+ dst = greaterCount < (int)(_parent._percentile * totalCount / 100);
+ }
+
+ protected override void InitializeStateCore()
+ {
+ _parent = (PercentileThresholdTransform)ParentTransform;
+ }
+
+ protected override void LearnStateFromDataCore(FixedSizeQueue data)
+ {
+ // This method is empty because there is no need for parameter learning from the initial windowed buffer for this transform.
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/PolynomialUtils.cs b/src/Microsoft.ML.TimeSeries/PolynomialUtils.cs
new file mode 100644
index 0000000000..6c3f27907d
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/PolynomialUtils.cs
@@ -0,0 +1,392 @@
+// 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.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using Microsoft.ML.Runtime.Internal.Utilities;
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ public static class PolynomialUtils
+ {
+ // Part 1: Computing the polynomial real and complex roots from its real coefficients
+
+ private static Double _tol;
+
+ private static bool IsZero(double x)
+ {
+ return Math.Abs(x) <= _tol;
+ }
+
+ internal static void FindQuadraticRoots(Double b, Double c, out Complex root1, out Complex root2)
+ {
+ var delta = b * b - 4 * c;
+ var sqrtDelta = Math.Sqrt(Math.Abs(delta));
+
+ if (delta >= 0)
+ {
+ root1 = new Complex((-b + sqrtDelta) / 2, 0);
+ root2 = new Complex((-b - sqrtDelta) / 2, 0);
+ }
+ else
+ {
+ root1 = new Complex(-b / 2, sqrtDelta / 2);
+ root2 = new Complex(-b / 2, -sqrtDelta / 2);
+ }
+ }
+
+ private static void CreateFullCompanionMatrix(Double[] coefficients, ref Double[] companionMatrix)
+ {
+ Contracts.Assert(Utils.Size(coefficients) > 1);
+
+ var n = coefficients.Length;
+ var n2 = n * n;
+ if (Utils.Size(companionMatrix) < n2)
+ companionMatrix = new Double[n2];
+
+ int i;
+ for (i = 1; i <= n - 1; ++i)
+ companionMatrix[n * (i - 1) + i] = 1;
+
+ for (i = 0; i < n; ++i)
+ companionMatrix[n2 - n + i] = -coefficients[i];
+ }
+
+ ///
+ /// Computes the real and the complex roots of a real monic polynomial represented as:
+ /// coefficients[0] + coefficients[1] * X + coefficients[2] * X^2 + ... + coefficients[n-1] * X^(n-1) + X^n
+ /// by computing the eigenvalues of the Companion matrix. (https://en.wikipedia.org/wiki/Companion_matrix)
+ ///
+ /// The monic polynomial coefficients in the ascending order
+ /// The computed (complex) roots
+ /// The number decimal digits to keep after round-off
+ /// The machine precision
+ /// A boolean flag indicating whether the algorithm was successful.
+ public static bool FindPolynomialRoots(Double[] coefficients, ref Complex[] roots,
+ int roundOffDigits = 6, Double doublePrecision = 2.22 * 1e-100)
+ {
+ Contracts.CheckParam(doublePrecision > 0, nameof(doublePrecision), "The double precision must be positive.");
+ Contracts.CheckParam(Utils.Size(coefficients) >= 1, nameof(coefficients), "There must be at least one input coefficient.");
+
+ int i;
+ int n = coefficients.Length;
+ bool result = true;
+
+ _tol = doublePrecision;
+
+ if (Utils.Size(roots) < n)
+ roots = new Complex[n];
+
+ // Extracting the zero roots
+ for (i = 0; i < n; ++i)
+ {
+ if (IsZero(coefficients[i]))
+ roots[n - i - 1] = Complex.Zero;
+ else
+ break;
+ }
+
+ if (i == n) // All zero roots
+ return true;
+
+ if (i == n - 1) // Handling the linear case
+ roots[0] = new Complex(-coefficients[i], 0);
+ else if (i == n - 2) // Handling the quadratic case
+ FindQuadraticRoots(coefficients[i + 1], coefficients[i], out roots[0], out roots[1]);
+ else // Handling higher-order cases by computing the eigenvalues of the Companion matrix
+ {
+ var coeff = coefficients;
+ if (i > 0)
+ {
+ coeff = new Double[n - i];
+ Array.Copy(coefficients, i, coeff, 0, n - i);
+ }
+
+ // REVIEW: the eigen decomposition of the companion matrix should be done using the FactorizedCompanionMatrix class
+ // instead of MKL.
+ //FactorizedCompanionMatrix companionMatrix = new FactorizedCompanionMatrix(coeff);
+ //result = companionMatrix.ComputeEigenValues(ref roots);
+
+ Double[] companionMatrix = null;
+ var realPart = new Double[n - i];
+ var imaginaryPart = new Double[n - i];
+ var dummy = new Double[1];
+
+ CreateFullCompanionMatrix(coeff, ref companionMatrix);
+ var info = EigenUtils.Dhseqr(EigenUtils.Layout.ColMajor, EigenUtils.Job.EigenValues, EigenUtils.Compz.None,
+ n - i, 1, n - i, companionMatrix, n - i, realPart, imaginaryPart, dummy, n - i);
+
+ if (info != 0)
+ return false;
+
+ for (var j = 0; j < n - i; ++j)
+ roots[j] = new Complex(realPart[j], imaginaryPart[j]);
+ }
+
+ return result;
+ }
+
+ // Part 2: Computing the polynomial coefficients from its real and complex roots
+ private sealed class FactorMultiplicity
+ {
+ public int Multiplicity;
+
+ public FactorMultiplicity(int multiplicity = 1)
+ {
+ Contracts.Assert(multiplicity > 0);
+ Multiplicity = multiplicity;
+ }
+ }
+
+ private sealed class PolynomialFactor
+ {
+ public List Coefficients;
+ public static decimal[] Destination;
+
+ private decimal _key;
+ public decimal Key { get { return _key; } }
+
+ private void SetKey()
+ {
+ decimal absVal = -1;
+ for (var i = 0; i < Coefficients.Count; ++i)
+ {
+ var temp = Math.Abs(Coefficients[i]);
+ if (temp > absVal)
+ {
+ absVal = temp;
+ _key = Coefficients[i];
+ }
+ }
+ }
+
+ public PolynomialFactor(decimal[] coefficients)
+ {
+ Coefficients = new List(coefficients);
+ SetKey();
+ }
+
+ internal PolynomialFactor(decimal key)
+ {
+ _key = key;
+ }
+
+ public void Multiply(PolynomialFactor factor)
+ {
+ var len = Coefficients.Count;
+ Coefficients.AddRange(factor.Coefficients);
+
+ PolynomialMultiplication(0, len, len, factor.Coefficients.Count, 0, 1, 1);
+
+ for (var i = 0; i < Coefficients.Count; ++i)
+ Coefficients[i] = Destination[i];
+
+ SetKey();
+ }
+
+ private void PolynomialMultiplication(int uIndex, int uLen, int vIndex, int vLen, int dstIndex, decimal uCoeff, decimal vCoeff)
+ {
+ Contracts.Assert(uIndex >= 0);
+ Contracts.Assert(uLen >= 1);
+ Contracts.Assert(uIndex + uLen <= Utils.Size(Coefficients));
+ Contracts.Assert(vIndex >= 0);
+ Contracts.Assert(vLen >= 1);
+ Contracts.Assert(vIndex + vLen <= Utils.Size(Coefficients));
+ Contracts.Assert(uIndex + uLen <= vIndex || vIndex + vLen <= uIndex); // makes sure the input ranges are non-overlapping.
+ Contracts.Assert(dstIndex >= 0);
+ Contracts.Assert(dstIndex + uLen + vLen <= Utils.Size(Destination));
+
+ if (uLen == 1 && vLen == 1)
+ {
+ Destination[dstIndex] = Coefficients[uIndex] * Coefficients[vIndex];
+ Destination[dstIndex + 1] = Coefficients[uIndex] + Coefficients[vIndex];
+ }
+ else
+ NaivePolynomialMultiplication(uIndex, uLen, vIndex, vLen, dstIndex, uCoeff, vCoeff);
+ }
+
+ private void NaivePolynomialMultiplication(int uIndex, int uLen, int vIndex, int vLen, int dstIndex, decimal uCoeff, decimal vCoeff)
+ {
+ int i;
+ int j;
+ int a;
+ int b;
+ int c;
+ var len = uLen + vLen - 1;
+ decimal temp;
+
+ if (vLen < uLen)
+ {
+ var t = vLen;
+ vLen = uLen;
+ uLen = t;
+
+ t = vIndex;
+ vIndex = uIndex;
+ uIndex = t;
+ }
+
+ for (i = 0; i <= len; ++i)
+ {
+ b = Math.Min(uLen, i + 1) - 1;
+ a = i >= Math.Max(uLen, vLen) ? len - i : b + 1;
+ c = Math.Max(0, i - uLen + 1);
+ temp = 0;
+
+ if (i >= uLen)
+ temp = uCoeff * Coefficients[i - uLen + vIndex];
+
+ if (i >= vLen)
+ temp += (vCoeff * Coefficients[i - vLen + uIndex]);
+
+ for (j = 0; j < a; ++j)
+ temp += (Coefficients[b - j + uIndex] * Coefficients[c + j + vIndex]);
+
+ Destination[i + dstIndex] = temp;
+ }
+ }
+ }
+
+ private sealed class ByMaximumCoefficient : IComparer
+ {
+ public int Compare(PolynomialFactor x, PolynomialFactor y)
+ {
+ if (x.Key > y.Key)
+ return 1;
+
+ if (x.Key < y.Key)
+ return -1;
+
+ return 0;
+ }
+ }
+
+ ///
+ /// Computes the coefficients of a real monic polynomial given its real and complex roots.
+ /// The final monic polynomial is represented as:
+ /// coefficients[0] + coefficients[1] * X + coefficients[2] * X^2 + ... + coefficients[n-1] * X^(n-1) + X^n
+ ///
+ /// Note: the constant 1 coefficient of the highest degree term is implicit and not included in the output of the method.
+ ///
+ /// The input (complex) roots
+ /// The output real coefficients
+ /// A boolean flag indicating whether the algorithm was successful.
+ public static bool FindPolynomialCoefficients(Complex[] roots, ref Double[] coefficients)
+ {
+ Contracts.CheckParam(Utils.Size(roots) > 0, nameof(roots), "There must be at least 1 input root.");
+
+ int i;
+ int n = roots.Length;
+ var hash = new Dictionary();
+ int destinationOffset = 0;
+
+ var factors = new List();
+
+ for (i = 0; i < n; ++i)
+ {
+ if (Double.IsNaN(roots[i].Real) || Double.IsNaN(roots[i].Imaginary))
+ return false;
+
+ if (roots[i].Equals(Complex.Zero)) // Zero roots
+ destinationOffset++;
+ else if (roots[i].Imaginary == 0) // Real roots
+ {
+ var f = new PolynomialFactor(new[] { (decimal)-roots[i].Real });
+ factors.Add(f);
+ }
+ else // Complex roots
+ {
+ var conj = Complex.Conjugate(roots[i]);
+ FactorMultiplicity temp;
+ if (hash.TryGetValue(conj, out temp))
+ {
+ temp.Multiplicity--;
+
+ var f = new PolynomialFactor(new[]
+ {
+ (decimal) (roots[i].Real*roots[i].Real + roots[i].Imaginary*roots[i].Imaginary),
+ (decimal) (-2*roots[i].Real)
+ });
+
+ factors.Add(f);
+
+ if (temp.Multiplicity <= 0)
+ hash.Remove(conj);
+ }
+ else
+ {
+ if (hash.TryGetValue(roots[i], out temp))
+ temp.Multiplicity++;
+ else
+ hash.Add(roots[i], new FactorMultiplicity());
+ }
+ }
+ }
+
+ if (hash.Count > 0)
+ return false;
+
+ var comparer = new ByMaximumCoefficient();
+
+ factors.Sort(comparer);
+
+ if (destinationOffset < n - 1)
+ {
+ if (Utils.Size(PolynomialFactor.Destination) < n)
+ PolynomialFactor.Destination = new decimal[n];
+
+ while (factors.Count > 1)
+ {
+ var k1 = Math.Abs(factors.ElementAt(0).Key);
+ var k2 = Math.Abs(factors.ElementAt(factors.Count - 1).Key);
+
+ PolynomialFactor f1;
+ if (k1 < k2)
+ {
+ f1 = factors.ElementAt(0);
+ factors.RemoveAt(0);
+ }
+ else
+ {
+ f1 = factors.ElementAt(factors.Count - 1);
+ factors.RemoveAt(factors.Count - 1);
+ }
+
+ var ind = factors.BinarySearch(new PolynomialFactor(-f1.Key), comparer);
+ if (ind < 0)
+ ind = ~ind;
+
+ ind = Math.Min(factors.Count - 1, ind);
+ var f2 = factors.ElementAt(ind);
+ factors.RemoveAt(ind);
+
+ f1.Multiply(f2);
+
+ ind = factors.BinarySearch(f1, comparer);
+ if (ind >= 0)
+ factors.Insert(ind, f1);
+ else
+ factors.Insert(~ind, f1);
+ }
+ }
+
+ if (Utils.Size(coefficients) < n)
+ coefficients = new Double[n];
+
+ for (i = 0; i < destinationOffset; ++i)
+ coefficients[i] = 0;
+
+ if (destinationOffset < n)
+ {
+ var coeff = factors.ElementAt(0).Coefficients;
+ for (i = destinationOffset; i < n; ++i)
+ coefficients[i] = Decimal.ToDouble(coeff[i - destinationOffset]);
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/SequentialAnomalyDetectionTransformBase.cs b/src/Microsoft.ML.TimeSeries/SequentialAnomalyDetectionTransformBase.cs
new file mode 100644
index 0000000000..a6eb78165c
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/SequentialAnomalyDetectionTransformBase.cs
@@ -0,0 +1,655 @@
+// 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.Collections.Generic;
+using Microsoft.ML.Runtime.CommandLine;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.Internal.CpuMath;
+using Microsoft.ML.Runtime.Internal.Utilities;
+using Microsoft.ML.Runtime.Model;
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ // REVIEW: This base class and its children classes generate one output column of type VBuffer 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.
+
+ ///
+ /// 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
+ ///
+ /// The type of the input sequence
+ /// The type of the state object for sequential anomaly detection. Must be a class inherited from AnomalyDetectionStateBase
+ public abstract class SequentialAnomalyDetectionTransformBase : SequentialTransformBase, TState>
+ where TState : SequentialAnomalyDetectionTransformBase.AnomalyDetectionStateBase, new()
+ {
+ ///
+ /// The type of the martingale.
+ ///
+ public enum MartingaleType : byte
+ {
+ ///
+ /// (None) No martingale is used.
+ ///
+ None,
+ ///
+ /// (Power) The Power martingale is used.
+ ///
+ Power,
+ ///
+ /// (Mixture) The Mixture martingale is used.
+ ///
+ Mixture
+ }
+
+ ///
+ /// The side of anomaly detection.
+ ///
+ public enum AnomalySide : byte
+ {
+ ///
+ /// (Positive) Only positive anomalies are detected.
+ ///
+ Positive,
+ ///
+ /// (Negative) Only negative anomalies are detected.
+ ///
+ Negative,
+ ///
+ /// (TwoSided) Both positive and negative anomalies are detected.
+ ///
+ TwoSided
+ }
+
+ ///
+ /// The score that should be thresholded to generate alerts.
+ ///
+ public enum AlertingScore : byte
+ {
+ ///
+ /// (RawScore) The raw anomaly score is thresholded.
+ ///
+ RawScore,
+ ///
+ /// (PValueScore) The p-value score is thresholded.
+ ///
+ PValueScore,
+ ///
+ /// (MartingaleScore) The martingale score is thresholded.
+ ///
+ MartingaleScore
+ }
+
+ ///
+ /// The base class that can be inherited by the 'Argument' classes in the derived classes containing the shared input parameters.
+ ///
+ public 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;
+ }
+
+ // Determines the side of anomaly detection for this transform.
+ protected AnomalySide Side;
+
+ // Determines the type of martingale used by this transform.
+ protected MartingaleType Martingale;
+
+ // The epsilon parameter used by the Power martingale.
+ protected Double PowerMartingaleEpsilon;
+
+ // Determines the score that should be thresholded to generate alerts by this transform.
+ protected AlertingScore ThresholdScore;
+
+ // Determines the threshold for generating alerts.
+ protected Double AlertThreshold;
+
+ // The size of the VBuffer in the dst column.
+ private int _outputLength;
+
+ private readonly SchemaImpl _wrappedSchema;
+
+ public override ISchema Schema => _wrappedSchema;
+
+ 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 static SchemaImpl CreateSchema(ISchema parentSchema, string colName, int length)
+ {
+ Contracts.AssertValue(parentSchema);
+ Contracts.Assert(2 <= length && length <= 4);
+
+ string[] names = { "Alert", "Raw Score", "P-Value Score", "Martingale Score" };
+ int col;
+ bool result = parentSchema.TryGetColumnIndex(colName, out col);
+ Contracts.Assert(result);
+
+ return new SchemaImpl(parentSchema, col, names, length);
+ }
+
+ protected SequentialAnomalyDetectionTransformBase(int windowSize, int initialWindowSize, string inputColumnName, string outputColumnName, string name, IHostEnvironment env, IDataView input,
+ AnomalySide anomalySide, MartingaleType martingale, AlertingScore alertingScore, Double powerMartingaleEpsilon,
+ Double alertThreshold)
+ : base(windowSize, initialWindowSize, inputColumnName, outputColumnName, name, env, input, new VectorType(NumberType.R8, 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);
+ _wrappedSchema = CreateSchema(base.Schema, outputColumnName, _outputLength);
+ }
+
+ protected SequentialAnomalyDetectionTransformBase(ArgumentsBase args, string name, IHostEnvironment env, IDataView input)
+ : this(args.WindowSize, args.InitialWindowSize, args.Source, args.Name, name, env, input, args.Side, args.Martingale,
+ args.AlertOn, args.PowerMartingaleEpsilon, args.AlertThreshold)
+ {
+ }
+
+ protected SequentialAnomalyDetectionTransformBase(IHostEnvironment env, ModelLoadContext ctx, string name, IDataView input)
+ : base(env, ctx, name, input)
+ {
+ // *** Binary format ***
+ //
+ // 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);
+ _wrappedSchema = CreateSchema(base.Schema, OutputColumnName, _outputLength);
+ }
+
+ public override void Save(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 ***
+ //
+ // byte: _martingale
+ // byte: _alertingScore
+ // byte: _anomalySide
+ // Double: _powerMartingaleEpsilon
+ // Double: _alertThreshold
+
+ base.Save(ctx);
+ ctx.Writer.Write((byte)Martingale);
+ ctx.Writer.Write((byte)ThresholdScore);
+ ctx.Writer.Write((byte)Side);
+ ctx.Writer.Write(PowerMartingaleEpsilon);
+ ctx.Writer.Write(AlertThreshold);
+ }
+
+ // The minimum value for p-values. The smaller p-values are ceiled to this value.
+ private const Double MinPValue = 1e-8;
+
+ // The maximun value for p-values. The larger p-values are floored to this value.
+ private const Double MaxPValue = 1 - MinPValue;
+
+ ///
+ /// 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.
+ ///
+ /// The p-value
+ /// The epsilon
+ /// The Power martingale betting function value in the natural logarithmic scale.
+ protected 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);
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The p-value
+ /// The Mixure (marginalized over epsilon) martingale betting function value in the natural logarithmic scale.
+ protected 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;
+ }
+
+ ///
+ /// 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.
+ ///
+ public abstract class AnomalyDetectionStateBase : StateBase
+ {
+ // A reference to the parent transform.
+ protected SequentialAnomalyDetectionTransformBase Parent;
+
+ // A windowed buffer to cache the update values to the martingale score in the log scale.
+ private FixedSizeQueue _logMartingaleUpdateBuffer;
+
+ // A windowed buffer to cache the raw anomaly scores for p-value calculation.
+ private FixedSizeQueue _rawScoreBuffer;
+
+ // 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 {
+ get { return Math.Exp(_logMartingaleValue); }
+ }
+
+ 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;
+ }
+
+ protected override void SetNaOutput(ref VBuffer dst)
+ {
+ var values = dst.Values;
+ var outputLength = Parent._outputLength;
+ if (Utils.Size(values) < outputLength)
+ values = new Double[outputLength];
+
+ for (int i = 0; i < outputLength; ++i)
+ values[i] = Double.NaN;
+
+ dst = new VBuffer(Utils.Size(values), values, dst.Indices);
+ }
+
+ protected override sealed void TransformCore(ref TInput input, FixedSizeQueue windowedBuffer, long iteration, ref VBuffer dst)
+ {
+ var outputLength = Parent._outputLength;
+ Host.Assert(outputLength >= 2);
+
+ var result = dst.Values;
+ if (Utils.Size(result) < outputLength)
+ result = new Double[outputLength];
+
+ float rawScore = 0;
+
+ for (int i = 0; i < outputLength; ++i)
+ result[i] = Double.NaN;
+
+ // Step 1: Computing the raw anomaly score
+ result[1] = ComputeRawAnomalyScore(ref input, windowedBuffer, iteration);
+
+ if (Double.IsNaN(result[1]))
+ result[0] = 0;
+ else
+ {
+ if (WindowSize > 0)
+ {
+ // Step 2: Computing the p-value score
+ rawScore = (float)result[1];
+ if (Parent.ThresholdScore == AlertingScore.RawScore)
+ {
+ switch (Parent.Side)
+ {
+ case AnomalySide.Negative:
+ rawScore = (float)(-result[1]);
+ break;
+
+ case AnomalySide.Positive:
+ break;
+
+ default:
+ rawScore = (float)Math.Abs(result[1]);
+ break;
+ }
+ }
+ else
+ {
+ result[2] = ComputeKernelPValue(rawScore);
+
+ switch (Parent.Side)
+ {
+ case AnomalySide.Negative:
+ result[2] = 1 - result[2];
+ break;
+
+ case AnomalySide.Positive:
+ break;
+
+ default:
+ result[2] = Math.Min(result[2], 1 - result[2]);
+ break;
+ }
+
+ // Keeping the p-value in the safe range
+ if (result[2] < MinPValue)
+ result[2] = MinPValue;
+ else if (result[2] > MaxPValue)
+ result[2] = 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[2], Parent.PowerMartingaleEpsilon);
+ break;
+
+ case MartingaleType.Mixture:
+ martingaleUpdate = Parent.LogMixtureMartigaleBettingFunc(result[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[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[2] <= Parent.AlertThreshold;
+ break;
+ case AlertingScore.MartingaleScore:
+ alert = (Parent.Martingale != MartingaleType.None) && (result[3] >= Parent.AlertThreshold);
+
+ if (alert)
+ {
+ if (_martingaleAlertCounter > 0)
+ alert = false;
+ else
+ _martingaleAlertCounter = Parent.WindowSize;
+ }
+
+ _martingaleAlertCounter--;
+ _martingaleAlertCounter = _martingaleAlertCounter < 0 ? 0 : _martingaleAlertCounter;
+ break;
+ }
+ }
+
+ result[0] = Convert.ToDouble(alert);
+ }
+
+ dst = new VBuffer(outputLength, result, dst.Indices);
+ }
+
+ protected override sealed void InitializeStateCore()
+ {
+ Parent = (SequentialAnomalyDetectionTransformBase)ParentTransform;
+ Host.Assert(WindowSize >= 0);
+
+ if (Parent.Martingale != MartingaleType.None)
+ _logMartingaleUpdateBuffer = new FixedSizeQueue(WindowSize == 0 ? 1 : WindowSize);
+
+ _rawScoreBuffer = new FixedSizeQueue(WindowSize == 0 ? 1 : WindowSize);
+
+ _logMartingaleValue = 0;
+ InitializeAnomalyDetector();
+ }
+
+ ///
+ /// The abstract method that realizes the initialization functionality for the anomaly detector.
+ ///
+ protected abstract void InitializeAnomalyDetector();
+
+ ///
+ /// The abstract method that realizes the main logic for calculating the raw anomaly score bfor the current input given a windowed buffer
+ ///
+ /// A reference to the input object.
+ /// A reference to the windowed buffer.
+ /// A long number that indicates the number of times ComputeRawAnomalyScore has been called so far (starting value = 0).
+ /// 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.
+ protected abstract Double ComputeRawAnomalyScore(ref TInput input, FixedSizeQueue windowedBuffer, long iteration);
+ }
+
+ ///
+ /// Schema implementation to add slot name metadata to the produced output column.
+ ///
+ private sealed class SchemaImpl : ISchema
+ {
+ private readonly ISchema _parent;
+ private readonly int _col;
+ private readonly ColumnType _type;
+ private readonly string[] _names;
+ private readonly int _namesLength;
+ private readonly MetadataUtils.MetadataGetter>> _getter;
+
+ public int ColumnCount { get { return _parent.ColumnCount; } }
+
+ ///
+ /// Constructs the schema.
+ ///
+ /// The schema we will wrap.
+ /// Aside from presenting that additional piece of metadata, the constructed schema
+ /// will appear identical to this input schema.
+ /// The column in that has the metadata.
+ ///
+ ///
+ public SchemaImpl(ISchema schema, int col, string[] names, int length)
+ {
+ Contracts.Assert(length > 0);
+ Contracts.Assert(Utils.Size(names) >= length);
+ Contracts.AssertValue(schema);
+ Contracts.Assert(0 <= col && col < schema.ColumnCount);
+ _parent = schema;
+ _col = col;
+
+ _names = names;
+ _namesLength = length;
+
+ _type = new VectorType(TextType.Instance, _namesLength);
+ Contracts.AssertValue(_type);
+ _getter = GetSlotNames;
+ }
+
+ public bool TryGetColumnIndex(string name, out int col)
+ {
+ return _parent.TryGetColumnIndex(name, out col);
+ }
+
+ public string GetColumnName(int col)
+ {
+ return _parent.GetColumnName(col);
+ }
+
+ public ColumnType GetColumnType(int col)
+ {
+ return _parent.GetColumnType(col);
+ }
+
+ public IEnumerable> GetMetadataTypes(int col)
+ {
+ var result = _parent.GetMetadataTypes(col);
+ if (col == _col)
+ return result.Prepend(_type.GetPair(MetadataUtils.Kinds.SlotNames));
+ return result;
+ }
+
+ public ColumnType GetMetadataTypeOrNull(string kind, int col)
+ {
+ if (col == _col && kind == MetadataUtils.Kinds.SlotNames)
+ return _type;
+ return _parent.GetMetadataTypeOrNull(kind, col);
+ }
+
+ public void GetSlotNames(int col, ref VBuffer> slotNames)
+ {
+ Contracts.Assert(col == _col);
+
+ var result = slotNames.Values;
+ if (Utils.Size(result) < _namesLength)
+ result = new ReadOnlyMemory[_namesLength];
+
+ for (int i = 0; i < _namesLength; ++i)
+ result[i] = _names[i].AsMemory();
+
+ slotNames = new VBuffer>(_namesLength, result, slotNames.Indices);
+ }
+
+ public void GetMetadata(string kind, int col, ref TValue value)
+ {
+ if (col == _col && kind == MetadataUtils.Kinds.SlotNames)
+ {
+ _getter.Marshal(col, ref value);
+ return;
+ }
+ _parent.GetMetadata(kind, col, ref value);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/SequentialTransformBase.cs b/src/Microsoft.ML.TimeSeries/SequentialTransformBase.cs
new file mode 100644
index 0000000000..8cccefda85
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/SequentialTransformBase.cs
@@ -0,0 +1,405 @@
+// 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 Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.Data.IO;
+using Microsoft.ML.Runtime.Internal.Utilities;
+using Microsoft.ML.Runtime.Model;
+using Microsoft.ML.Runtime.Api;
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// The box class that is used to box the TInput and TOutput for the LambdaTransform.
+ ///
+ /// The type to be boxed, e.g. TInput or TOutput
+ internal sealed class DataBox
+ {
+ public T Value;
+
+ public DataBox()
+ {
+ }
+
+ public DataBox(T value)
+ {
+ Value = value;
+ }
+ }
+
+ ///
+ /// The base class for sequential processing transforms. This class implements the basic sliding window buffering. The derived classes need to specify the transform logic,
+ /// the initialization logic and the learning logic via implementing the abstract methods TransformCore(), InitializeStateCore() and LearnStateFromDataCore(), respectively
+ ///
+ /// The input type of the sequential processing.
+ /// The dst type of the sequential processing.
+ /// The state type of the sequential processing. Must be a class inherited from StateBase
+ public abstract class SequentialTransformBase : TransformBase
+ where TState : SequentialTransformBase.StateBase, new()
+ {
+ ///
+ /// The base class for encapsulating the State object for sequential processing. This class implements a windowed buffer.
+ ///
+ public abstract class StateBase
+ {
+ // Ideally this class should be private. However, due to the current constraints with the LambdaTransform, we need to have
+ // access to the state class when inheriting from SequentialTransformBase.
+ protected IHost Host;
+
+ ///
+ /// A reference to the parent transform that operates on the state object.
+ ///
+ protected SequentialTransformBase ParentTransform;
+
+ ///
+ /// The internal windowed buffer for buffering the values in the input sequence.
+ ///
+ protected FixedSizeQueue WindowedBuffer;
+
+ ///
+ /// The buffer used to buffer the training data points.
+ ///
+ protected FixedSizeQueue InitialWindowedBuffer;
+
+ protected int WindowSize { get; private set; }
+
+ protected int InitialWindowSize { get; private set; }
+
+ ///
+ /// Counts the number of rows observed by the transform so far.
+ ///
+ protected int RowCounter { get; private set; }
+
+ protected int IncrementRowCounter()
+ {
+ RowCounter++;
+ return RowCounter;
+ }
+
+ private bool _isIniatilized;
+
+ ///
+ /// This method sets the window size and initializes the buffer only once.
+ /// Since the class needs to implement a default constructor, this methods provides a mechanism to initialize the window size and buffer.
+ ///
+ /// The size of the windowed buffer
+ /// The size of the windowed initial buffer used for training
+ /// The parent transform of this state object
+ /// The host
+ public void InitState(int windowSize, int initialWindowSize, SequentialTransformBase parentTransform, IHost host)
+ {
+ Contracts.CheckValue(host, nameof(host), "The host cannot be null.");
+ host.Check(!_isIniatilized, "The window size can be set only once.");
+ host.CheckValue(parentTransform, nameof(parentTransform));
+ host.CheckParam(windowSize >= 0, nameof(windowSize), "Must be non-negative.");
+ host.CheckParam(initialWindowSize >= 0, nameof(initialWindowSize), "Must be non-negative.");
+
+ Host = host;
+ WindowSize = windowSize;
+ InitialWindowSize = initialWindowSize;
+ ParentTransform = parentTransform;
+ WindowedBuffer = (WindowSize > 0) ? new FixedSizeQueue(WindowSize) : new FixedSizeQueue(1);
+ InitialWindowedBuffer = (InitialWindowSize > 0) ? new FixedSizeQueue(InitialWindowSize) : new FixedSizeQueue(1);
+ RowCounter = 0;
+
+ InitializeStateCore();
+ _isIniatilized = true;
+ }
+
+ ///
+ /// This method implements the basic resetting mechanism for a state object and clears the buffer.
+ ///
+ public virtual void Reset()
+ {
+ Host.Assert(_isIniatilized);
+ Host.Assert(WindowedBuffer != null);
+ Host.Assert(InitialWindowedBuffer != null);
+
+ RowCounter = 0;
+ WindowedBuffer.Clear();
+ InitialWindowedBuffer.Clear();
+ }
+
+ protected StateBase()
+ {
+ // Default constructor is required by the LambdaTransform.
+ }
+
+ public void Process(ref TInput input, ref TOutput output)
+ {
+ if (InitialWindowedBuffer.Count < InitialWindowSize)
+ {
+ InitialWindowedBuffer.AddLast(input);
+ SetNaOutput(ref output);
+
+ if (InitialWindowedBuffer.Count >= InitialWindowSize - WindowSize)
+ WindowedBuffer.AddLast(input);
+
+ if (InitialWindowedBuffer.Count == InitialWindowSize)
+ LearnStateFromDataCore(InitialWindowedBuffer);
+ }
+ else
+ {
+ TransformCore(ref input, WindowedBuffer, RowCounter - InitialWindowSize, ref output);
+ WindowedBuffer.AddLast(input);
+ IncrementRowCounter();
+ }
+ }
+
+ public void ProcessWithoutBuffer(ref TInput input, ref TOutput output)
+ {
+ if (InitialWindowedBuffer.Count < InitialWindowSize)
+ {
+ InitialWindowedBuffer.AddLast(input);
+ SetNaOutput(ref output);
+
+ if (InitialWindowedBuffer.Count == InitialWindowSize)
+ LearnStateFromDataCore(InitialWindowedBuffer);
+ }
+ else
+ {
+ TransformCore(ref input, WindowedBuffer, RowCounter - InitialWindowSize, ref output);
+ IncrementRowCounter();
+ }
+ }
+
+ ///
+ /// The abstract method that specifies the NA value for the dst type.
+ ///
+ ///
+ protected abstract void SetNaOutput(ref TOutput dst);
+
+ ///
+ /// The abstract method that realizes the main logic for the transform.
+ ///
+ /// A reference to the input object.
+ /// A reference to the dst object.
+ /// A reference to the windowed buffer.
+ /// A long number that indicates the number of times TransformCore has been called so far (starting value = 0).
+ protected abstract void TransformCore(ref TInput input, FixedSizeQueue windowedBuffer, long iteration, ref TOutput dst);
+
+ ///
+ /// The abstract method that realizes the logic for initializing the state object.
+ ///
+ protected abstract void InitializeStateCore();
+
+ ///
+ /// The abstract method that realizes the logic for learning the parameters and the initial state object from data.
+ ///
+ /// A queue of data points used for training
+ protected abstract void LearnStateFromDataCore(FixedSizeQueue data);
+ }
+
+ ///
+ /// The inner stateful Lambda Transform object.
+ ///
+ private readonly IDataTransform _transform;
+
+ ///
+ /// The window size for buffering.
+ ///
+ protected readonly int WindowSize;
+
+ ///
+ /// The number of datapoints from the beginning of the sequence that are used for learning the initial state.
+ ///
+ protected int InitialWindowSize;
+
+ protected string InputColumnName;
+ protected string OutputColumnName;
+
+ private static IDataTransform CreateLambdaTransform(IHost host, IDataView input, string inputColumnName, string outputColumnName,
+ Action initFunction, bool hasBuffer, ColumnType outputColTypeOverride)
+ {
+ var inputSchema = SchemaDefinition.Create(typeof(DataBox));
+ inputSchema[0].ColumnName = inputColumnName;
+
+ var outputSchema = SchemaDefinition.Create(typeof(DataBox));
+ outputSchema[0].ColumnName = outputColumnName;
+
+ if (outputColTypeOverride != null)
+ outputSchema[0].ColumnType = outputColTypeOverride;
+
+ Action, DataBox, TState> lambda;
+ if (hasBuffer)
+ lambda = MapFunction;
+ else
+ lambda = MapFunctionWithoutBuffer;
+
+ return LambdaTransform.CreateMap(host, input, lambda, initFunction, inputSchema, outputSchema);
+ }
+
+ ///
+ /// The main constructor for the sequential transform
+ ///
+ /// The size of buffer used for windowed buffering.
+ /// The number of datapoints picked from the beginning of the series for training the transform parameters if needed.
+ /// The name of the input column.
+ /// The name of the dst column.
+ ///
+ /// A reference to the environment variable.
+ /// A reference to the input data view.
+ ///
+ protected SequentialTransformBase(int windowSize, int initialWindowSize, string inputColumnName, string outputColumnName,
+ string name, IHostEnvironment env, IDataView input, ColumnType outputColTypeOverride = null)
+ : this(windowSize, initialWindowSize, inputColumnName, outputColumnName, Contracts.CheckRef(env, nameof(env)).Register(name), input, outputColTypeOverride)
+ {
+ }
+
+ protected SequentialTransformBase(int windowSize, int initialWindowSize, string inputColumnName, string outputColumnName,
+ IHost host, IDataView input, ColumnType outputColTypeOverride = null)
+ : base(host, input)
+ {
+ Contracts.AssertValue(Host);
+ Host.CheckParam(initialWindowSize >= 0, nameof(initialWindowSize), "Must be non-negative.");
+ Host.CheckParam(windowSize >= 0, nameof(windowSize), "Must be non-negative.");
+ // REVIEW: Very bad design. This base class is responsible for reporting errors on
+ // the arguments, but the arguments themselves are not derived form any base class.
+ Host.CheckNonEmpty(inputColumnName, nameof(PercentileThresholdTransform.Arguments.Source));
+ Host.CheckNonEmpty(outputColumnName, nameof(PercentileThresholdTransform.Arguments.Source));
+
+ InputColumnName = inputColumnName;
+ OutputColumnName = outputColumnName;
+ InitialWindowSize = initialWindowSize;
+ WindowSize = windowSize;
+
+ _transform = CreateLambdaTransform(Host, input, InputColumnName, OutputColumnName, InitFunction, WindowSize > 0, outputColTypeOverride);
+ }
+
+ protected SequentialTransformBase(IHostEnvironment env, ModelLoadContext ctx, string name, IDataView input)
+ : base(env, name, input)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+
+ // *** Binary format ***
+ // int: _windowSize
+ // int: _initialWindowSize
+ // int (string ID): _inputColumnName
+ // int (string ID): _outputColumnName
+ // ColumnType: _transform.Schema.GetColumnType(0)
+
+ var windowSize = ctx.Reader.ReadInt32();
+ Host.CheckDecode(windowSize >= 0);
+
+ var initialWindowSize = ctx.Reader.ReadInt32();
+ Host.CheckDecode(initialWindowSize >= 0);
+
+ var inputColumnName = ctx.LoadNonEmptyString();
+ var outputColumnName = ctx.LoadNonEmptyString();
+
+ InputColumnName = inputColumnName;
+ OutputColumnName = outputColumnName;
+ InitialWindowSize = initialWindowSize;
+ WindowSize = windowSize;
+
+ BinarySaver bs = new BinarySaver(Host, new BinarySaver.Arguments());
+ ColumnType ct = bs.LoadTypeDescriptionOrNull(ctx.Reader.BaseStream);
+
+ _transform = CreateLambdaTransform(Host, input, InputColumnName, OutputColumnName, InitFunction, WindowSize > 0, ct);
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+ Host.Assert(InitialWindowSize >= 0);
+ Host.Assert(WindowSize >= 0);
+
+ // *** Binary format ***
+ // int: _windowSize
+ // int: _initialWindowSize
+ // int (string ID): _inputColumnName
+ // int (string ID): _outputColumnName
+ // ColumnType: _transform.Schema.GetColumnType(0)
+
+ ctx.Writer.Write(WindowSize);
+ ctx.Writer.Write(InitialWindowSize);
+ ctx.SaveNonEmptyString(InputColumnName);
+ ctx.SaveNonEmptyString(OutputColumnName);
+
+ int byteWritten;
+ BinarySaver bs = new BinarySaver(Host, new BinarySaver.Arguments());
+
+ int colIndex;
+ if (!_transform.Schema.TryGetColumnIndex(OutputColumnName, out colIndex))
+ throw Host.Except(String.Format("The column {0} does not exist in the schema.", OutputColumnName));
+
+ bs.TryWriteTypeDescription(ctx.Writer.BaseStream, _transform.Schema.GetColumnType(colIndex), out byteWritten);
+ }
+
+ private static void MapFunction(DataBox input, DataBox output, TState state)
+ {
+ state.Process(ref input.Value, ref output.Value);
+ }
+
+ private static void MapFunctionWithoutBuffer(DataBox input, DataBox output, TState state)
+ {
+ state.ProcessWithoutBuffer(ref input.Value, ref output.Value);
+ }
+
+ private void InitFunction(TState state)
+ {
+ state.InitState(WindowSize, InitialWindowSize, this, Host);
+ }
+
+ public override bool CanShuffle { get { return false; } }
+
+ protected override bool? ShouldUseParallelCursors(Func predicate)
+ {
+ Host.AssertValue(predicate);
+ return false;
+ }
+
+ protected override IRowCursor GetRowCursorCore(Func predicate, IRandom rand = null)
+ {
+ var srcCursor = _transform.GetRowCursor(predicate, rand);
+ return new Cursor(this, srcCursor);
+ }
+
+ public override ISchema Schema
+ {
+ get { return _transform.Schema; }
+ }
+
+ public override long? GetRowCount(bool lazy = true)
+ {
+ return _transform.GetRowCount(lazy);
+ }
+
+ public override IRowCursor[] GetRowCursorSet(out IRowCursorConsolidator consolidator, Func predicate, int n, IRandom rand = null)
+ {
+ consolidator = null;
+ return new IRowCursor[] { GetRowCursorCore(predicate, rand) };
+ }
+
+ ///
+ /// A wrapper around the cursor which replaces the schema.
+ ///
+ private sealed class Cursor : SynchronizedCursorBase, IRowCursor
+ {
+ private readonly SequentialTransformBase _parent;
+
+ public Cursor(SequentialTransformBase parent, IRowCursor input)
+ : base(parent.Host, input)
+ {
+ Ch.Assert(input.Schema.ColumnCount == parent.Schema.ColumnCount);
+ _parent = parent;
+ }
+
+ public ISchema Schema { get { return _parent.Schema; } }
+
+ public bool IsColumnActive(int col)
+ {
+ Ch.Check(0 <= col && col < Schema.ColumnCount, "col");
+ return Input.IsColumnActive(col);
+ }
+
+ public ValueGetter GetGetter(int col)
+ {
+ Ch.Check(IsColumnActive(col), "col");
+ return Input.GetGetter(col);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/SlidingWindowTransform.cs b/src/Microsoft.ML.TimeSeries/SlidingWindowTransform.cs
new file mode 100644
index 0000000000..ce3bbf0092
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/SlidingWindowTransform.cs
@@ -0,0 +1,62 @@
+// 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 Microsoft.ML.Runtime;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.Model;
+using Microsoft.ML.Runtime.TimeSeriesProcessing;
+
+[assembly: LoadableClass(SlidingWindowTransform.Summary, typeof(SlidingWindowTransform), typeof(SlidingWindowTransform.Arguments), typeof(SignatureDataTransform),
+ SlidingWindowTransform.UserName, SlidingWindowTransform.LoaderSignature, SlidingWindowTransform.ShortName)]
+[assembly: LoadableClass(SlidingWindowTransform.Summary, typeof(SlidingWindowTransform), null, typeof(SignatureLoadDataTransform),
+ SlidingWindowTransform.UserName, SlidingWindowTransform.LoaderSignature)]
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// Outputs a sliding window on a time series of type Single.
+ ///
+ public sealed class SlidingWindowTransform : SlidingWindowTransformBase
+ {
+ public const string Summary = "Returns the last values for a time series [y(t-d-l+1), y(t-d-l+2), ..., y(t-l-1), y(t-l)] where d is the size of the window, l the lag and y is a Float.";
+ public const string LoaderSignature = "SlideWinTransform";
+ public const string UserName = "Sliding Window Transform";
+ public const string ShortName = "SlideWin";
+
+ private static VersionInfo GetVersionInfo()
+ {
+ return new VersionInfo(
+ modelSignature: "SWINTRNS",
+ verWrittenCur: 0x00010001, // Initial
+ verReadableCur: 0x00010001,
+ verWeCanReadBack: 0x00010001,
+ loaderSignature: LoaderSignature,
+ loaderAssemblyName: typeof(SlidingWindowTransform).Assembly.FullName);
+ }
+
+ public SlidingWindowTransform(IHostEnvironment env, Arguments args, IDataView input)
+ : base(args, LoaderSignature, env, input)
+ {
+ }
+
+ public SlidingWindowTransform(IHostEnvironment env, ModelLoadContext ctx, IDataView input)
+ : base(env, ctx, LoaderSignature, input)
+ {
+ // *** Binary format ***
+ //
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+ ctx.CheckAtModel();
+ ctx.SetVersionInfo(GetVersionInfo());
+
+ // *** Binary format ***
+ //
+ base.Save(ctx);
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/SlidingWindowTransformBase.cs b/src/Microsoft.ML.TimeSeries/SlidingWindowTransformBase.cs
new file mode 100644
index 0000000000..c0d0ec0aed
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/SlidingWindowTransformBase.cs
@@ -0,0 +1,194 @@
+// 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.Linq;
+using System.Collections.Generic;
+using Microsoft.ML.Runtime;
+using Microsoft.ML.Runtime.CommandLine;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.Internal.Utilities;
+using Microsoft.ML.Runtime.Model;
+using Microsoft.ML.Runtime.TimeSeriesProcessing;
+using Microsoft.ML.Runtime.Data.Conversion;
+using Microsoft.ML.Runtime.EntryPoints;
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// SlidingWindowTransformBase outputs a sliding window as a VBuffer from a series of any type.
+ /// The VBuffer contains n consecutives observations delayed or not from the current one.
+ /// Let's denote y(t) a timeseries, the transform returns a vector of values for each time t
+ /// which corresponds to [y(t-d-l+1), y(t-d-l+2), ..., y(t-l-1), y(t-l)] where d is the size of the window
+ /// and l is the delay.
+ ///
+
+ public abstract class SlidingWindowTransformBase : SequentialTransformBase, SlidingWindowTransformBase.StateSlide>
+ {
+ ///
+ /// Defines what should be done about the first rows.
+ ///
+ public enum BeginOptions : byte
+ {
+ ///
+ /// Fill first rows with NaN values.
+ ///
+ NaNValues = 0,
+
+ ///
+ /// Copy the first value of the series.
+ ///
+ FirstValue = 1
+ }
+
+ public sealed class Arguments : TransformInputBase
+ {
+ [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",
+ SortOrder = 2)]
+ public string Name;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the sliding window for computing the moving average", ShortName = "wnd", SortOrder = 3)]
+ public int WindowSize = 2;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "Lag between current observation and last observation from the sliding window", ShortName = "l", SortOrder = 4)]
+ public int Lag = 1;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "Define how to populate the first rows of the produced series", SortOrder = 5)]
+ public BeginOptions Begin = BeginOptions.NaNValues;
+ }
+
+ private readonly int _lag;
+ private BeginOptions _begin;
+ private TInput _nanValue;
+
+ protected SlidingWindowTransformBase(Arguments args, string loaderSignature, IHostEnvironment env, IDataView input)
+ : base(args.WindowSize + args.Lag - 1, args.WindowSize + args.Lag - 1, args.Source, args.Name, loaderSignature, env, input)
+ {
+ Host.CheckUserArg(args.WindowSize >= 1, nameof(args.WindowSize), "Must be at least 1.");
+ Host.CheckUserArg(args.Lag >= 0, nameof(args.Lag), "Must be positive.");
+ if (args.Lag == 0 && args.WindowSize <= 1)
+ {
+ Host.Assert(args.WindowSize == 1);
+ throw Host.ExceptUserArg(nameof(args.Lag),
+ $"If {args.Lag}=0 and {args.WindowSize}=1, the transform just copies the column. Use {CopyColumnsTransform.LoaderSignature} transform instead.");
+ }
+ Host.CheckUserArg(Enum.IsDefined(typeof(BeginOptions), args.Begin), nameof(args.Begin), "Undefined value.");
+ _lag = args.Lag;
+ _begin = args.Begin;
+ _nanValue = GetNaValue();
+ }
+
+ protected SlidingWindowTransformBase(IHostEnvironment env, ModelLoadContext ctx, string loaderSignature, IDataView input)
+ : base(env, ctx, loaderSignature, input)
+ {
+ // *** Binary format ***
+ //
+ // Int32 lag
+ // byte begin
+
+ Host.CheckDecode(WindowSize >= 1);
+ _lag = ctx.Reader.ReadInt32();
+ Host.CheckDecode(_lag >= 0);
+ byte r = ctx.Reader.ReadByte();
+ Host.CheckDecode(Enum.IsDefined(typeof(BeginOptions), r));
+ _begin = (BeginOptions)r;
+ _nanValue = GetNaValue();
+ }
+
+ private TInput GetNaValue()
+ {
+ var sch = Schema;
+ int index;
+ sch.TryGetColumnIndex(InputColumnName, out index);
+ ColumnType col = sch.GetColumnType(index);
+ TInput nanValue = Conversions.Instance.GetNAOrDefault(col);
+
+ // We store the nan_value here to avoid getting it each time a state is instanciated.
+ return nanValue;
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+ Host.Assert(WindowSize >= 1);
+ Host.Assert(_lag >= 0);
+ Host.Assert(Enum.IsDefined(typeof(BeginOptions), _begin));
+ ctx.CheckAtModel();
+
+ // *** Binary format ***
+ //
+ // Int32 lag
+ // byte begin
+
+ base.Save(ctx);
+ ctx.Writer.Write(_lag);
+ ctx.Writer.Write((byte)_begin);
+ }
+
+ public sealed class StateSlide : StateBase
+ {
+ private SlidingWindowTransformBase _parentSliding;
+
+ protected override void SetNaOutput(ref VBuffer output)
+ {
+
+ int size = _parentSliding.WindowSize - _parentSliding._lag + 1;
+ var result = output.Values;
+ if (Utils.Size(result) < size)
+ result = new TInput[size];
+
+ TInput value = _parentSliding._nanValue;
+ switch (_parentSliding._begin)
+ {
+ case BeginOptions.NaNValues:
+ value = _parentSliding._nanValue;
+ break;
+ case BeginOptions.FirstValue:
+ // REVIEW: will complete the implementation
+ // if the design looks good
+ throw new NotImplementedException();
+ }
+
+ for (int i = 0; i < size; ++i)
+ result[i] = value;
+ output = new VBuffer(size, result, output.Indices);
+ }
+
+ protected override void TransformCore(ref TInput input, FixedSizeQueue windowedBuffer, long iteration, ref VBuffer output)
+ {
+ int size = _parentSliding.WindowSize - _parentSliding._lag + 1;
+ var result = output.Values;
+ if (Utils.Size(result) < size)
+ result = new TInput[size];
+
+ if (_parentSliding._lag == 0)
+ {
+ for (int i = 0; i < _parentSliding.WindowSize; ++i)
+ result[i] = windowedBuffer[i];
+ result[_parentSliding.WindowSize] = input;
+ }
+ else
+ {
+ for (int i = 0; i < size; ++i)
+ result[i] = windowedBuffer[i];
+ }
+ output = new VBuffer(size, result, output.Indices);
+ }
+
+ protected override void InitializeStateCore()
+ {
+ _parentSliding = (SlidingWindowTransformBase)base.ParentTransform;
+ }
+
+ protected override void LearnStateFromDataCore(FixedSizeQueue data)
+ {
+ // This method is empty because there is no need for parameter learning from the initial windowed buffer for this transform.
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/SsaAnomalyDetectionBase.cs b/src/Microsoft.ML.TimeSeries/SsaAnomalyDetectionBase.cs
new file mode 100644
index 0000000000..f4f7023b81
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/SsaAnomalyDetectionBase.cs
@@ -0,0 +1,220 @@
+// 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 Microsoft.ML.Runtime.CommandLine;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.Internal.Utilities;
+using Microsoft.ML.Runtime.Model;
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// Provides the utility functions for different error functions for computing deviation.
+ ///
+ public static class ErrorFunctionUtils
+ {
+ public const string ErrorFunctionHelpText = "The error function should be either (0) SignedDifference, (1) AbsoluteDifference, (2) SignedProportion" +
+ " (3) AbsoluteProportion or (4) SquaredDifference.";
+
+ public enum ErrorFunction : byte
+ {
+ SignedDifference,
+ AbsoluteDifference,
+ SignedProportion,
+ AbsoluteProportion,
+ SquaredDifference
+ }
+
+ public static Double SignedDifference(Double actual, Double predicted)
+ {
+ return actual - predicted;
+ }
+
+ public static Double AbsoluteDifference(Double actual, Double predicted)
+ {
+ return Math.Abs(actual - predicted);
+ }
+
+ public static Double SignedProportion(Double actual, Double predicted)
+ {
+ return predicted == 0 ? 0 : (actual - predicted) / predicted;
+ }
+
+ public static Double AbsoluteProportion(Double actual, Double predicted)
+ {
+ return predicted == 0 ? 0 : Math.Abs((actual - predicted) / predicted);
+ }
+
+ public static Double SquaredDifference(Double actual, Double predicted)
+ {
+ Double temp = actual - predicted;
+ return temp * temp;
+ }
+
+ public static Func GetErrorFunction(ErrorFunction errorFunction)
+ {
+ switch (errorFunction)
+ {
+ case ErrorFunction.SignedDifference:
+ return SignedDifference;
+
+ case ErrorFunction.AbsoluteDifference:
+ return AbsoluteDifference;
+
+ case ErrorFunction.SignedProportion:
+ return SignedProportion;
+
+ case ErrorFunction.AbsoluteProportion:
+ return AbsoluteProportion;
+
+ case ErrorFunction.SquaredDifference:
+ return SquaredDifference;
+
+ default:
+ throw Contracts.Except(ErrorFunctionHelpText);
+ }
+ }
+ }
+
+ ///
+ /// This base class that implements the general anomaly detection transform based on Singular Spectrum modeling of the time-series.
+ /// For the details of the Singular Spectrum Analysis (SSA), refer to http://arxiv.org/pdf/1206.6910.pdf.
+ ///
+ public abstract class SsaAnomalyDetectionBase : SequentialAnomalyDetectionTransformBase
+ {
+ public abstract class SsaArguments : ArgumentsBase
+ {
+ [Argument(ArgumentType.Required, HelpText = "The inner window size for SSA in [2, windowSize]", ShortName = "swnd", SortOrder = 11)]
+ public int SeasonalWindowSize;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The discount factor in [0, 1]", ShortName = "disc", SortOrder = 12)]
+ public Single DiscountFactor = 1;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The function used to compute the error between the expected and the observed value", ShortName = "err", SortOrder = 13)]
+ public ErrorFunctionUtils.ErrorFunction ErrorFunction = ErrorFunctionUtils.ErrorFunction.SignedDifference;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The flag determing whether the model is adaptive", ShortName = "adp", SortOrder = 14)]
+ public bool IsAdaptive = false;
+ }
+
+ protected readonly int SeasonalWindowSize;
+ protected readonly Single DiscountFactor;
+ protected readonly bool IsAdaptive;
+ protected readonly ErrorFunctionUtils.ErrorFunction ErrorFunction;
+ protected readonly Func ErrorFunc;
+ protected readonly ISequenceModeler Model;
+
+ public SsaAnomalyDetectionBase(SsaArguments args, string name, IHostEnvironment env, IDataView input)
+ : base(args.WindowSize, 0, args.Source, args.Name, name, env, input, args.Side, args.Martingale, args.AlertOn, args.PowerMartingaleEpsilon, args.AlertThreshold)
+ {
+ Host.CheckUserArg(2 <= args.SeasonalWindowSize, nameof(args.SeasonalWindowSize), "Must be at least 2.");
+ Host.CheckUserArg(0 <= args.DiscountFactor && args.DiscountFactor <= 1, nameof(args.DiscountFactor), "Must be in the range [0, 1].");
+ Host.CheckUserArg(Enum.IsDefined(typeof(ErrorFunctionUtils.ErrorFunction), args.ErrorFunction), nameof(args.ErrorFunction), ErrorFunctionUtils.ErrorFunctionHelpText);
+
+ SeasonalWindowSize = args.SeasonalWindowSize;
+ DiscountFactor = args.DiscountFactor;
+ ErrorFunction = args.ErrorFunction;
+ ErrorFunc = ErrorFunctionUtils.GetErrorFunction(ErrorFunction);
+ IsAdaptive = args.IsAdaptive;
+
+ // Creating the master SSA model
+ Model = new AdaptiveSingularSpectrumSequenceModeler(Host, args.InitialWindowSize, SeasonalWindowSize + 1, SeasonalWindowSize,
+ DiscountFactor, null, AdaptiveSingularSpectrumSequenceModeler.RankSelectionMethod.Exact, null, SeasonalWindowSize / 2, false, false);
+
+ // Training the master SSA model
+ var data = new RoleMappedData(input, null, InputColumnName);
+ Model.Train(data);
+ }
+
+ public SsaAnomalyDetectionBase(IHostEnvironment env, ModelLoadContext ctx, string name, IDataView input)
+ : base(env, ctx, name, input)
+ {
+ // *** Binary format ***
+ //
+ // int: _seasonalWindowSize
+ // float: _discountFactor
+ // byte: _errorFunction
+ // bool: _isAdaptive
+ // AdaptiveSingularSpectrumSequenceModeler: _model
+
+ Host.CheckDecode(InitialWindowSize == 0);
+
+ SeasonalWindowSize = ctx.Reader.ReadInt32();
+ Host.CheckDecode(2 <= SeasonalWindowSize);
+
+ DiscountFactor = ctx.Reader.ReadSingle();
+ Host.CheckDecode(0 <= DiscountFactor && DiscountFactor <= 1);
+
+ byte temp;
+ temp = ctx.Reader.ReadByte();
+ Host.CheckDecode(Enum.IsDefined(typeof(ErrorFunctionUtils.ErrorFunction), temp));
+ ErrorFunction = (ErrorFunctionUtils.ErrorFunction)temp;
+ ErrorFunc = ErrorFunctionUtils.GetErrorFunction(ErrorFunction);
+
+ IsAdaptive = ctx.Reader.ReadBoolean();
+
+ ctx.LoadModel, SignatureLoadModel>(env, out Model, "SSA");
+ Host.CheckDecode(Model != null);
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+ ctx.CheckAtModel();
+
+ Host.Assert(InitialWindowSize == 0);
+ Host.Assert(2 <= SeasonalWindowSize);
+ Host.Assert(0 <= DiscountFactor && DiscountFactor <= 1);
+ Host.Assert(Enum.IsDefined(typeof(ErrorFunctionUtils.ErrorFunction), ErrorFunction));
+ Host.Assert(Model != null);
+
+ // *** Binary format ***
+ //
+ // int: _seasonalWindowSize
+ // float: _discountFactor
+ // byte: _errorFunction
+ // bool: _isAdaptive
+ // AdaptiveSingularSpectrumSequenceModeler: _model
+
+ base.Save(ctx);
+ ctx.Writer.Write(SeasonalWindowSize);
+ ctx.Writer.Write(DiscountFactor);
+ ctx.Writer.Write((byte)ErrorFunction);
+ ctx.Writer.Write(IsAdaptive);
+ ctx.SaveModel(Model, "SSA");
+ }
+
+ public sealed class State : AnomalyDetectionStateBase
+ {
+ private ISequenceModeler _model;
+ private SsaAnomalyDetectionBase _parentAnomalyDetector;
+
+ protected override void LearnStateFromDataCore(FixedSizeQueue data)
+ {
+ // This method is empty because there is no need to implement a training logic here.
+ }
+
+ protected override void InitializeAnomalyDetector()
+ {
+ _parentAnomalyDetector = (SsaAnomalyDetectionBase)Parent;
+ _model = _parentAnomalyDetector.Model.Clone();
+ _model.InitState();
+ }
+
+ protected override double ComputeRawAnomalyScore(ref Single input, FixedSizeQueue windowedBuffer, long iteration)
+ {
+ // Get the prediction for the next point opn the series
+ Single expectedValue = 0;
+ _model.PredictNext(ref expectedValue);
+
+ // Feed the current point to the model
+ _model.Consume(ref input, _parentAnomalyDetector.IsAdaptive);
+
+ // Return the error as the raw anomaly score
+ return _parentAnomalyDetector.ErrorFunc(input, expectedValue);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/SsaChangePointDetector.cs b/src/Microsoft.ML.TimeSeries/SsaChangePointDetector.cs
new file mode 100644
index 0000000000..8eca18a034
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/SsaChangePointDetector.cs
@@ -0,0 +1,145 @@
+// 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 Microsoft.ML.Runtime;
+using Microsoft.ML.Runtime.CommandLine;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.EntryPoints;
+using Microsoft.ML.Runtime.Model;
+using Microsoft.ML.Runtime.TimeSeriesProcessing;
+
+[assembly: LoadableClass(SsaChangePointDetector.Summary, typeof(SsaChangePointDetector), typeof(SsaChangePointDetector.Arguments), typeof(SignatureDataTransform),
+ SsaChangePointDetector.UserName, SsaChangePointDetector.LoaderSignature, SsaChangePointDetector.ShortName)]
+[assembly: LoadableClass(SsaChangePointDetector.Summary, typeof(SsaChangePointDetector), null, typeof(SignatureLoadDataTransform),
+ SsaChangePointDetector.UserName, SsaChangePointDetector.LoaderSignature)]
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// This class implements the change point detector transform based on Singular Spectrum modeling of the time-series.
+ /// For the details of the Singular Spectrum Analysis (SSA), refer to http://arxiv.org/pdf/1206.6910.pdf.
+ ///
+ public sealed class SsaChangePointDetector : SsaAnomalyDetectionBase
+ {
+ internal const string Summary = "This transform detects the change-points in a seasonal time-series using Singular Spectrum Analysis (SSA).";
+ public const string LoaderSignature = "SsaChangePointDetector";
+ public const string UserName = "SSA Change Point Detection";
+ public const string ShortName = "chgpnt";
+
+ public sealed class Arguments : TransformInputBase
+ {
+ [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.",
+ SortOrder = 2)]
+ public string Name;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The change history length.", ShortName = "wnd",
+ SortOrder = 102)]
+ public int ChangeHistoryLength = 20;
+
+ [Argument(ArgumentType.Required, HelpText = "The number of points from the beginning of the sequence used for training.",
+ ShortName = "twnd", SortOrder = 3)]
+ public int TrainingWindowSize = 100;
+
+ [Argument(ArgumentType.Required, HelpText = "The confidence for change point detection in the range [0, 100].",
+ ShortName = "cnf", SortOrder = 4)]
+ public double Confidence = 95;
+
+ [Argument(ArgumentType.Required, HelpText = "An upper bound on the largest relevant seasonality in the input time-series.", ShortName = "swnd", SortOrder = 5)]
+ public int SeasonalWindowSize = 10;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The function used to compute the error between the expected and the observed value.", ShortName = "err", SortOrder = 103)]
+ public ErrorFunctionUtils.ErrorFunction ErrorFunction = ErrorFunctionUtils.ErrorFunction.SignedDifference;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The martingale used for scoring.", ShortName = "mart", SortOrder = 104)]
+ public MartingaleType Martingale = SequentialAnomalyDetectionTransformBase.MartingaleType.Power;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The epsilon parameter for the Power martingale.",
+ ShortName = "eps", SortOrder = 105)]
+ public double PowerMartingaleEpsilon = 0.1;
+ }
+
+ private sealed class BaseArguments : SsaArguments
+ {
+ public BaseArguments(Arguments args)
+ {
+ Source = args.Source;
+ Name = args.Name;
+ Side = SequentialAnomalyDetectionTransformBase.AnomalySide.TwoSided;
+ WindowSize = args.ChangeHistoryLength;
+ InitialWindowSize = args.TrainingWindowSize;
+ SeasonalWindowSize = args.SeasonalWindowSize;
+ Martingale = args.Martingale;
+ PowerMartingaleEpsilon = args.PowerMartingaleEpsilon;
+ AlertOn = SequentialAnomalyDetectionTransformBase.AlertingScore.MartingaleScore;
+ DiscountFactor = 1;
+ IsAdaptive = false;
+ ErrorFunction = args.ErrorFunction;
+ }
+ }
+
+ private static VersionInfo GetVersionInfo()
+ {
+ return new VersionInfo(modelSignature: "SCHGTRNS",
+ verWrittenCur: 0x00010001, // Initial
+ verReadableCur: 0x00010001,
+ verWeCanReadBack: 0x00010001,
+ loaderSignature: LoaderSignature,
+ loaderAssemblyName: typeof(SsaChangePointDetector).Assembly.FullName);
+ }
+
+ public SsaChangePointDetector(IHostEnvironment env, Arguments args, IDataView input)
+ : base(new BaseArguments(args), LoaderSignature, env, input)
+ {
+ switch (Martingale)
+ {
+ case MartingaleType.None:
+ AlertThreshold = Double.MaxValue;
+ break;
+ case MartingaleType.Power:
+ AlertThreshold = Math.Exp(WindowSize * LogPowerMartigaleBettingFunc(1 - args.Confidence / 100, PowerMartingaleEpsilon));
+ break;
+ case MartingaleType.Mixture:
+ AlertThreshold = Math.Exp(WindowSize * LogMixtureMartigaleBettingFunc(1 - args.Confidence / 100));
+ break;
+ default:
+ Host.Assert(!Enum.IsDefined(typeof(MartingaleType), Martingale));
+ throw Host.ExceptUserArg(nameof(args.Martingale), "Value not defined.");
+ }
+ }
+
+ public SsaChangePointDetector(IHostEnvironment env, ModelLoadContext ctx, IDataView input)
+ : base(env, ctx, LoaderSignature, input)
+ {
+ // *** Binary format ***
+ //
+
+ Host.CheckDecode(ThresholdScore == AlertingScore.MartingaleScore);
+ Host.CheckDecode(Side == AnomalySide.TwoSided);
+ Host.CheckDecode(DiscountFactor == 1);
+ Host.CheckDecode(IsAdaptive == false);
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+ ctx.CheckAtModel();
+ ctx.SetVersionInfo(GetVersionInfo());
+
+ Host.Assert(ThresholdScore == AlertingScore.MartingaleScore);
+ Host.Assert(Side == AnomalySide.TwoSided);
+ Host.Assert(DiscountFactor == 1);
+ Host.Assert(IsAdaptive == false);
+
+ // *** Binary format ***
+ //
+
+ base.Save(ctx);
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/SsaSpikeDetector.cs b/src/Microsoft.ML.TimeSeries/SsaSpikeDetector.cs
new file mode 100644
index 0000000000..79d6622ff5
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/SsaSpikeDetector.cs
@@ -0,0 +1,126 @@
+// 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 Microsoft.ML.Runtime;
+using Microsoft.ML.Runtime.CommandLine;
+using Microsoft.ML.Runtime.Data;
+using Microsoft.ML.Runtime.EntryPoints;
+using Microsoft.ML.Runtime.Model;
+using Microsoft.ML.Runtime.TimeSeriesProcessing;
+
+[assembly: LoadableClass(SsaSpikeDetector.Summary, typeof(SsaSpikeDetector), typeof(SsaSpikeDetector.Arguments), typeof(SignatureDataTransform),
+ SsaSpikeDetector.UserName, SsaSpikeDetector.LoaderSignature, SsaSpikeDetector.ShortName)]
+[assembly: LoadableClass(SsaSpikeDetector.Summary, typeof(SsaSpikeDetector), null, typeof(SignatureLoadDataTransform),
+ SsaSpikeDetector.UserName, SsaSpikeDetector.LoaderSignature)]
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// This class implements the spike detector transform based on Singular Spectrum modeling of the time-series.
+ /// For the details of the Singular Spectrum Analysis (SSA), refer to http://arxiv.org/pdf/1206.6910.pdf.
+ ///
+ public sealed class SsaSpikeDetector : SsaAnomalyDetectionBase
+ {
+ internal const string Summary = "This transform detects the spikes in a seasonal time-series using Singular Spectrum Analysis (SSA).";
+ public const string LoaderSignature = "SsaSpikeDetector";
+ public const string UserName = "SSA Spike Detection";
+ public const string ShortName = "spike";
+
+ public sealed class Arguments : TransformInputBase
+ {
+ [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.",
+ 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 = 101)]
+ public AnomalySide Side = AnomalySide.TwoSided;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the sliding window for computing the p-value.", ShortName = "wnd",
+ SortOrder = 102)]
+ public int PvalueHistoryLength = 100;
+
+ [Argument(ArgumentType.Required, HelpText = "The number of points from the beginning of the sequence used for training.",
+ ShortName = "twnd", SortOrder = 3)]
+ public int TrainingWindowSize = 100;
+
+ [Argument(ArgumentType.Required, HelpText = "The confidence for spike detection in the range [0, 100].",
+ ShortName = "cnf", SortOrder = 4)]
+ public double Confidence = 99;
+
+ [Argument(ArgumentType.Required, HelpText = "An upper bound on the largest relevant seasonality in the input time-series.", ShortName = "swnd", SortOrder = 5)]
+ public int SeasonalWindowSize = 10;
+
+ [Argument(ArgumentType.AtMostOnce, HelpText = "The function used to compute the error between the expected and the observed value.", ShortName = "err", SortOrder = 103)]
+ public ErrorFunctionUtils.ErrorFunction ErrorFunction = ErrorFunctionUtils.ErrorFunction.SignedDifference;
+ }
+
+ private sealed class BaseArguments : SsaArguments
+ {
+ public BaseArguments(Arguments args)
+ {
+ Source = args.Source;
+ Name = args.Name;
+ Side = args.Side;
+ WindowSize = args.PvalueHistoryLength;
+ InitialWindowSize = args.TrainingWindowSize;
+ SeasonalWindowSize = args.SeasonalWindowSize;
+ AlertThreshold = 1 - args.Confidence / 100;
+ AlertOn = SequentialAnomalyDetectionTransformBase.AlertingScore.PValueScore;
+ DiscountFactor = 1;
+ IsAdaptive = false;
+ ErrorFunction = args.ErrorFunction;
+ Martingale = MartingaleType.None;
+ }
+ }
+
+ private static VersionInfo GetVersionInfo()
+ {
+ return new VersionInfo(
+ modelSignature: "SSPKTRNS",
+ verWrittenCur: 0x00010001, // Initial
+ verReadableCur: 0x00010001,
+ verWeCanReadBack: 0x00010001,
+ loaderSignature: LoaderSignature,
+ loaderAssemblyName: typeof(SsaSpikeDetector).Assembly.FullName);
+ }
+
+ public SsaSpikeDetector(IHostEnvironment env, Arguments args, IDataView input)
+ : base(new BaseArguments(args), LoaderSignature, env, input)
+ {
+ // This constructor is empty.
+ }
+
+ public SsaSpikeDetector(IHostEnvironment env, ModelLoadContext ctx, IDataView input)
+ : base(env, ctx, LoaderSignature, input)
+ {
+ // *** Binary format ***
+ //
+
+ Host.CheckDecode(ThresholdScore == AlertingScore.PValueScore);
+ Host.CheckDecode(DiscountFactor == 1);
+ Host.CheckDecode(IsAdaptive == false);
+ }
+
+ public override void Save(ModelSaveContext ctx)
+ {
+ Host.CheckValue(ctx, nameof(ctx));
+ ctx.CheckAtModel();
+ ctx.SetVersionInfo(GetVersionInfo());
+
+ Host.Assert(ThresholdScore == AlertingScore.PValueScore);
+ Host.Assert(DiscountFactor == 1);
+ Host.Assert(IsAdaptive == false);
+
+ // *** Binary format ***
+ //
+
+ base.Save(ctx);
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/TimeSeriesProcessing.cs b/src/Microsoft.ML.TimeSeries/TimeSeriesProcessing.cs
new file mode 100644
index 0000000000..6b4275ffa3
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/TimeSeriesProcessing.cs
@@ -0,0 +1,113 @@
+// 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 Microsoft.ML.Runtime.EntryPoints;
+using Microsoft.ML.Runtime.TimeSeriesProcessing;
+
+[assembly: EntryPointModule(typeof(TimeSeriesProcessing))]
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// Entry points for text anylytics transforms.
+ ///
+ public static class TimeSeriesProcessing
+ {
+ [TlcModule.EntryPoint(Desc = ExponentialAverageTransform.Summary, UserName = ExponentialAverageTransform.UserName, ShortName = ExponentialAverageTransform.ShortName)]
+ public static CommonOutputs.TransformOutput ExponentialAverage(IHostEnvironment env, ExponentialAverageTransform.Arguments input)
+ {
+ var h = EntryPointUtils.CheckArgsAndCreateHost(env, "ExponentialAverageTransform", input);
+ var xf = new ExponentialAverageTransform(h, input, input.Data);
+ return new CommonOutputs.TransformOutput()
+ {
+ Model = new TransformModel(h, xf, input.Data),
+ OutputData = xf
+ };
+ }
+
+ [TlcModule.EntryPoint(Desc = Runtime.TimeSeriesProcessing.IidChangePointDetector.Summary, UserName = Runtime.TimeSeriesProcessing.IidChangePointDetector.UserName, ShortName = Runtime.TimeSeriesProcessing.IidChangePointDetector.ShortName)]
+ public static CommonOutputs.TransformOutput IidChangePointDetector(IHostEnvironment env, IidChangePointDetector.Arguments input)
+ {
+ var h = EntryPointUtils.CheckArgsAndCreateHost(env, "IidChangePointDetector", input);
+ var view = new IidChangePointDetector(h, input, input.Data);
+ return new CommonOutputs.TransformOutput()
+ {
+ Model = new TransformModel(h, view, input.Data),
+ OutputData = view
+ };
+ }
+
+ [TlcModule.EntryPoint(Desc = Runtime.TimeSeriesProcessing.IidSpikeDetector.Summary, UserName = Runtime.TimeSeriesProcessing.IidSpikeDetector.UserName, ShortName = Runtime.TimeSeriesProcessing.IidSpikeDetector.ShortName)]
+ public static CommonOutputs.TransformOutput IidSpikeDetector(IHostEnvironment env, IidSpikeDetector.Arguments input)
+ {
+ var h = EntryPointUtils.CheckArgsAndCreateHost(env, "IidSpikeDetector", input);
+ var view = new IidSpikeDetector(h, input, input.Data);
+ return new CommonOutputs.TransformOutput()
+ {
+ Model = new TransformModel(h, view, input.Data),
+ OutputData = view
+ };
+ }
+
+ [TlcModule.EntryPoint(Desc = Runtime.TimeSeriesProcessing.PercentileThresholdTransform.Summary, UserName = Runtime.TimeSeriesProcessing.PercentileThresholdTransform.UserName, ShortName = Runtime.TimeSeriesProcessing.PercentileThresholdTransform.ShortName)]
+ public static CommonOutputs.TransformOutput PercentileThresholdTransform(IHostEnvironment env, PercentileThresholdTransform.Arguments input)
+ {
+ var h = EntryPointUtils.CheckArgsAndCreateHost(env, "PercentileThresholdTransform", input);
+ var view = new PercentileThresholdTransform(h, input, input.Data);
+ return new CommonOutputs.TransformOutput()
+ {
+ Model = new TransformModel(h, view, input.Data),
+ OutputData = view
+ };
+ }
+
+ [TlcModule.EntryPoint(Desc = Runtime.TimeSeriesProcessing.PValueTransform.Summary, UserName = Runtime.TimeSeriesProcessing.PValueTransform.UserName, ShortName = Runtime.TimeSeriesProcessing.PValueTransform.ShortName)]
+ public static CommonOutputs.TransformOutput PValueTransform(IHostEnvironment env, PValueTransform.Arguments input)
+ {
+ var h = EntryPointUtils.CheckArgsAndCreateHost(env, "PValueTransform", input);
+ var view = new PValueTransform(h, input, input.Data);
+ return new CommonOutputs.TransformOutput()
+ {
+ Model = new TransformModel(h, view, input.Data),
+ OutputData = view
+ };
+ }
+
+ [TlcModule.EntryPoint(Desc = Runtime.TimeSeriesProcessing.SlidingWindowTransform.Summary, UserName = Runtime.TimeSeriesProcessing.SlidingWindowTransform.UserName, ShortName = Runtime.TimeSeriesProcessing.SlidingWindowTransform.ShortName)]
+ public static CommonOutputs.TransformOutput SlidingWindowTransform(IHostEnvironment env, SlidingWindowTransform.Arguments input)
+ {
+ var h = EntryPointUtils.CheckArgsAndCreateHost(env, "SlidingWindowTransform", input);
+ var view = new SlidingWindowTransform(h, input, input.Data);
+ return new CommonOutputs.TransformOutput()
+ {
+ Model = new TransformModel(h, view, input.Data),
+ OutputData = view
+ };
+ }
+
+ [TlcModule.EntryPoint(Desc = Runtime.TimeSeriesProcessing.SsaChangePointDetector.Summary, UserName = Runtime.TimeSeriesProcessing.SsaChangePointDetector.UserName, ShortName = Runtime.TimeSeriesProcessing.SsaChangePointDetector.ShortName)]
+ public static CommonOutputs.TransformOutput SsaChangePointDetector(IHostEnvironment env, SsaChangePointDetector.Arguments input)
+ {
+ var h = EntryPointUtils.CheckArgsAndCreateHost(env, "SsaChangePointDetector", input);
+ var view = new SsaChangePointDetector(h, input, input.Data);
+ return new CommonOutputs.TransformOutput()
+ {
+ Model = new TransformModel(h, view, input.Data),
+ OutputData = view
+ };
+ }
+
+ [TlcModule.EntryPoint(Desc = Runtime.TimeSeriesProcessing.SsaSpikeDetector.Summary, UserName = Runtime.TimeSeriesProcessing.SsaSpikeDetector.UserName, ShortName = Runtime.TimeSeriesProcessing.SsaSpikeDetector.ShortName)]
+ public static CommonOutputs.TransformOutput SsaSpikeDetector(IHostEnvironment env, SsaSpikeDetector.Arguments input)
+ {
+ var h = EntryPointUtils.CheckArgsAndCreateHost(env, "SsaSpikeDetector", input);
+ var view = new SsaSpikeDetector(h, input, input.Data);
+ return new CommonOutputs.TransformOutput()
+ {
+ Model = new TransformModel(h, view, input.Data),
+ OutputData = view
+ };
+ }
+ }
+}
diff --git a/src/Microsoft.ML.TimeSeries/TrajectoryMatrix.cs b/src/Microsoft.ML.TimeSeries/TrajectoryMatrix.cs
new file mode 100644
index 0000000000..229e3e9a3e
--- /dev/null
+++ b/src/Microsoft.ML.TimeSeries/TrajectoryMatrix.cs
@@ -0,0 +1,666 @@
+// 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 Microsoft.ML.Runtime.Internal.Utilities;
+
+namespace Microsoft.ML.Runtime.TimeSeriesProcessing
+{
+ ///
+ /// This class encapsulates the trajectory matrix of a time-series used in Singular Spectrum Analysis (SSA).
+ /// In particular, for a given series of length N and the window size of L, (such that N > L):
+ ///
+ /// x(1), x(2), x(3), ... , x(N)
+ ///
+ /// The trajectory matrix H is defined in the explicit form as:
+ ///
+ /// [x(1) x(2) x(3) ... x(N - L + 1)]
+ /// [x(2) x(3) x(4) ... x(N - L + 2)]
+ /// H = [x(3) x(4) x(5) ... x(N - L + 3)]
+ /// [ . . . . ]
+ /// [ . . . . ]
+ /// [x(L) x(L+1) x(L+2) ... x(N) ]
+ ///
+ /// of size L * K, where K = N - L + 1.
+ ///
+ /// This class does not explicitly store the trajectory matrix though. Furthermore, since the trajectory matrix is
+ /// a Hankel matrix, its multiplication by an arbitrary vector is implemented efficiently using the Discrete Fast Fourier Transform.
+ ///
+ public sealed class TrajectoryMatrix
+ {
+ ///
+ /// The time series data
+ ///
+ private Single[] _data;
+
+ ///
+ /// The window length L
+ ///
+ private readonly int _windowSize;
+
+ ///
+ /// The series length N
+ ///
+ private readonly int _seriesLength;
+
+ ///
+ /// The real part of the Fourier transform of the input series.
+ ///
+ private Double[] _cachedSeriesFftRe;
+
+ ///
+ /// The imaginary part of the Fourier transform of the input series.
+ ///
+ private Double[] _cachedSeriesFftIm;
+
+ private Double[] _allZerosIm;
+ private Double[] _inputRe;
+ private Double[] _outputRe;
+ private Double[] _outputIm;
+
+ private bool _isSeriesFftCached;
+ private readonly bool _shouldFftUsed;
+ private IExceptionContext _ectx;
+ private readonly int _k;
+
+ private void ComputeBoundryIndices(int start, int end, out int us, out int ue, out int vs, out int ve)
+ {
+ _ectx.Assert(0 <= end && end < _seriesLength, "The end index must be in [0, seriesLength).");
+ _ectx.Assert(0 <= start && start <= end, "The start index must be in [0, end index].");
+
+ if (start < _k)
+ {
+ us = 0;
+ vs = start;
+ }
+ else
+ {
+ us = start - _k + 1;
+ vs = _k - 1;
+ }
+
+ if (end < _windowSize)
+ {
+ ue = end;
+ ve = 0;
+ }
+ else
+ {
+ ue = _windowSize - 1;
+ ve = end - _windowSize + 1;
+ }
+ }
+
+ ///
+ /// Returns the length of the time-series represented by this trajectory matrix.
+ ///
+ public int SeriesLength { get { return _seriesLength; } }
+
+ ///
+ /// Returns the window size (L) used for building this trajectory matrix.
+ ///
+ public int WindowSize { get { return _windowSize; } }
+
+ ///
+ /// Constructs a trajectory matrix from the input series given the window length (L)
+ ///
+ /// The exception context
+ /// The input series
+ /// The window size L
+ /// The number of elements from the beginning of the input array to be used for building the trajectory matrix
+ public TrajectoryMatrix(IExceptionContext ectx, Single[] data, int windowSize, int seriesLength)
+ {
+ Contracts.CheckValueOrNull(ectx);
+ _ectx = ectx;
+
+ _ectx.Check(windowSize > 0, "The window length should be greater than 0.");
+ _ectx.CheckValue(data, nameof(data), "The input data cannot be null.");
+ _ectx.Check(data.Length >= seriesLength, "The series length cannot be greater than the data length.");
+
+ _seriesLength = seriesLength;
+ _ectx.Check(windowSize <= _seriesLength, "The length of the window should be less than or equal to the length of the data.");
+
+ _data = data;
+ _windowSize = windowSize;
+ _k = _seriesLength - _windowSize + 1;
+ _shouldFftUsed = _windowSize * _k > (3 + 3 * Math.Log(_seriesLength)) * _seriesLength;
+ }
+
+ ///
+ /// Sets the value of the underlying series to new values.
+ ///
+ /// The new series
+ public void SetSeries(Single[] data)
+ {
+ _ectx.Check(Utils.Size(data) >= _seriesLength, "The length of the input series cannot be less than that of the original series.");
+
+ _data = data;
+ if (_isSeriesFftCached)
+ {
+ int i;
+
+ for (i = _k - 1; i < _seriesLength; ++i)
+ _inputRe[i - _k + 1] = _data[i];
+
+ for (i = 0; i < _k - 1; ++i)
+ _inputRe[_windowSize + i] = _data[i];
+
+ FftUtils.ComputeForwardFft(_inputRe, _allZerosIm, _cachedSeriesFftRe, _cachedSeriesFftIm, _inputRe.Length);
+ }
+ }
+
+ private static Single RoundUpToReal(Double re, Double im, Double coeff = 1)
+ {
+ return (Single)(coeff * Math.Sign(re) * Math.Sqrt(re * re + im * im));
+ }
+
+ private void CacheInputSeriesFft()
+ {
+ int i;
+
+ _cachedSeriesFftRe = new Double[_seriesLength];
+ _cachedSeriesFftIm = new Double[_seriesLength];
+ _allZerosIm = new Double[_seriesLength];
+ _inputRe = new Double[_seriesLength];
+ _outputIm = new Double[_seriesLength];
+ _outputRe = new Double[_seriesLength];
+
+ for (i = _k - 1; i < _seriesLength; ++i)
+ _inputRe[i - _k + 1] = _data[i];
+
+ for (i = 0; i < _k - 1; ++i)
+ _inputRe[_windowSize + i] = _data[i];
+
+ FftUtils.ComputeForwardFft(_inputRe, _allZerosIm, _cachedSeriesFftRe, _cachedSeriesFftIm, _inputRe.Length);
+ _isSeriesFftCached = true;
+ }
+
+ ///
+ /// This function computes the unnormalized covariance of the trajectory matrix (which is a Hankel matrix of size L*K).
+ /// In particular, if H is the trajectory matrix of size L*K on the input series, this method computes H * H' (of size L*L).
+ /// This function does not form the trajectory matrix H explicitly.
+ /// Let k = N - L + 1 be the number of columns of the trajectory matrix.
+ /// In most applications, we have L smaller than K, though this is not a strict constraint.
+ /// The naive computational complexity for computing H * H' is O(L*L*K) while the naive memory complexity is O(K*L + L*L).
+ /// However, this function computes H * H' in O(L*L + M) time, where M = min(L*K, (L + K)*Log(L + K)) and O(L*L) memory.
+ ///
+ /// The output row-major vectorized covariance matrix of size L*L
+ public void ComputeUnnormalizedTrajectoryCovarianceMat(Single[] cov)
+ {
+ _ectx.Assert(Utils.Size(cov) >= _windowSize * _windowSize);
+
+ int i;
+ int j;
+
+ // Computing the first row of the covariance matrix
+ var temp = new Single[_k];
+
+ for (i = 0; i < _k; ++i)
+ temp[i] = _data[i];
+ Multiply(temp, cov);
+
+ // Computing the rest of the rows
+ for (i = 1; i < _windowSize; ++i)
+ {
+ // Copying the symmetric part
+ for (j = 0; j < i; ++j)
+ cov[i * _windowSize + j] = cov[j * _windowSize + i];
+
+ // Computing the novel part
+ for (j = i; j < _windowSize; ++j)
+ cov[i * _windowSize + j] = (float)((double)cov[(i - 1) * _windowSize + j - 1] - (double)_data[i - 1] * _data[j - 1] + (double)_data[i + _k - 1] * _data[j + _k - 1]);
+ }
+ }
+
+ ///
+ /// This function computes the singular value decomposition of the trajectory matrix.
+ /// This function only computes the singular values and the left singular vectors.
+ ///
+ /// The output singular values of size L
+ /// The output singular vectors of size L*L
+ public bool ComputeSvd(out Single[] singularValues, out Single[] leftSingularvectors)
+ {
+ Single[] covariance = new Single[_windowSize * _windowSize];
+ Single[] sVal;
+ Single[] sVec;
+ singularValues = new Single[_windowSize];
+ leftSingularvectors = new Single[_windowSize * _windowSize];
+
+ // Computing the covariance matrix of the trajectory matrix on the input series
+ ComputeUnnormalizedTrajectoryCovarianceMat(covariance);
+
+ // Computing the eigen decomposition of the covariance matrix
+ //EigenUtils.EigenDecomposition(covariance, out sVal, out sVec);
+ EigenUtils.MklSymmetricEigenDecomposition(covariance, _windowSize, out sVal, out sVec);
+
+ var ind = new int[_windowSize];
+ int i;
+
+ for (i = 0; i < _windowSize; ++i)
+ ind[i] = i;
+
+ Array.Sort(ind, (a, b) => sVal[b].CompareTo(sVal[a]));
+ for (i = 0; i < _windowSize; ++i)
+ {
+ singularValues[i] = sVal[ind[i]];
+ Array.Copy(sVec, _windowSize * ind[i], leftSingularvectors, _windowSize * i, _windowSize);
+ }
+
+ return true;
+ }
+
+ ///
+ /// This function computes the naive multiplication of the trajectory matrix H by an arbitrary vector v, i.e. H * v.
+ ///
+ /// The input vector
+ /// The output vector allocated by the caller
+ /// Whether the multiplication result should be added to the current value in result
+ /// The starting index for the vector argument
+ /// The starting index for the result
+ private void NaiveMultiply(Single[] vector, Single[] result, bool add = false, int srcIndex = 0, int dstIndex = 0)
+ {
+ _ectx.Assert(srcIndex >= 0);
+ _ectx.Assert(dstIndex >= 0);
+ _ectx.Assert(Utils.Size(vector) >= _k + srcIndex);
+ _ectx.Assert(Utils.Size(result) >= _windowSize + dstIndex);
+
+ int i;
+ int j;
+
+ for (j = 0; j < _windowSize; ++j)
+ {
+ if (!add)
+ result[j + dstIndex] = 0;
+ for (i = 0; i < _k; ++i)
+ result[j + dstIndex] += (vector[i + srcIndex] * _data[i + j]);
+ }
+ }
+
+ ///
+ /// This function computes the efficient multiplication of the trajectory matrix H by an arbitrary vector v, i.e. H * v.
+ /// Since the trajectory matrix is a Hankel matrix, using the Discrete Fourier Transform,
+ /// the multiplication is carried out in O(N.log(N)) instead of O(N^2), wheere N is the series length.
+ /// For details, refer to Algorithm 2 in http://arxiv.org/pdf/0911.4498.pdf.
+ ///
+ /// The input vector
+ /// The output vector allocated by the caller
+ /// Whether the multiplication result should be added to the current value in result
+ /// The starting index for the vector argument
+ /// The starting index for the result
+ private void FftMultiply(Single[] vector, Single[] result, bool add = false, int srcIndex = 0, int dstIndex = 0)
+ {
+ _ectx.Assert(srcIndex >= 0);
+ _ectx.Assert(dstIndex >= 0);
+ _ectx.Assert(Utils.Size(vector) >= _k + srcIndex);
+ _ectx.Assert(Utils.Size(result) >= _windowSize + dstIndex);
+
+ int i;
+
+ // Computing the FFT of the trajectory matrix
+ if (!_isSeriesFftCached)
+ CacheInputSeriesFft();
+
+ // Computing the FFT of the input vector
+ for (i = 0; i < _k; ++i)
+ _inputRe[i] = vector[_k - i - 1 + srcIndex];
+
+ for (i = _k; i < _seriesLength; ++i)
+ _inputRe[i] = 0;
+
+ FftUtils.ComputeForwardFft(_inputRe, _allZerosIm, _outputRe, _outputIm, _inputRe.Length);
+
+ // Computing the element-by-element product in the Fourier space
+ double re;
+ double im;
+ for (i = 0; i < _seriesLength; ++i)
+ {
+ re = _outputRe[i];
+ im = _outputIm[i];
+
+ _outputRe[i] = _cachedSeriesFftRe[i] * re - _cachedSeriesFftIm[i] * im;
+ _outputIm[i] = _cachedSeriesFftRe[i] * im + _cachedSeriesFftIm[i] * re;
+ }
+
+ // Computing the inverse FFT of the result
+ FftUtils.ComputeBackwardFft(_outputRe, _outputIm, _outputRe, _outputIm, _inputRe.Length);
+
+ // Generating the output
+ if (add)
+ {
+ for (i = 0; i < _windowSize; ++i)
+ result[i + dstIndex] += RoundUpToReal(_outputRe[i], _outputIm[i]);
+ }
+ else
+ {
+ for (i = 0; i < _windowSize; ++i)
+ result[i + dstIndex] = RoundUpToReal(_outputRe[i], _outputIm[i]);
+ }
+ }
+
+ ///
+ /// This function efficiently computes the multiplication of the trajectory matrix H by an arbitrary vector v, i.e. H * v.
+ ///
+ /// The input vector
+ /// The output vector allocated by the caller
+ /// Whether the multiplication result should be added to the current value in result
+ /// The starting index for the vector argument
+ /// The starting index for the result
+ public void Multiply(Single[] vector, Single[] result, bool add = false, int srcIndex = 0, int dstIndex = 0)
+ {
+ if (_shouldFftUsed)
+ FftMultiply(vector, result, add, srcIndex, dstIndex);
+ else
+ NaiveMultiply(vector, result, add, srcIndex, dstIndex);
+ }
+
+ ///
+ /// This function computes the naive multiplication of the transpose of the trajectory matrix H by an arbitrary vector v, i.e. H' * v.
+ ///
+ /// The input vector
+ /// The output vector allocated by the caller
+ /// Whether the multiplication result should be added to the current value in result
+ /// The starting index for the vector argument
+ /// The starting index for the result
+ private void NaiveMultiplyTranspose(Single[] vector, Single[] result, bool add = false, int srcIndex = 0, int dstIndex = 0)
+ {
+ _ectx.Assert(srcIndex >= 0);
+ _ectx.Assert(dstIndex >= 0);
+ _ectx.Assert(Utils.Size(vector) >= _windowSize + srcIndex);
+ _ectx.Assert(Utils.Size(result) >= _k + dstIndex);
+
+ int i;
+ int j;
+
+ for (j = 0; j < _k; ++j)
+ {
+ if (!add)
+ result[j + dstIndex] = 0;
+ for (i = 0; i < _windowSize; ++i)
+ result[j + dstIndex] += (vector[i + srcIndex] * _data[i + j]);
+ }
+ }
+
+ ///
+ /// This function computes the the multiplication of the transpose of the trajectory matrix H by an arbitrary vector v, i.e. H' * v.
+ /// Since the trajectory matrix is a Hankel matrix, using the Discrete Fourier Transform,
+ /// the multiplication is carried out in O(N.log(N)) instead of O(N^2), wheere N is the series length.
+ /// For details, refer to Algorithm 3 in http://arxiv.org/pdf/0911.4498.pdf.
+ ///
+ /// The input vector
+ /// The output vector allocated by the caller
+ /// Whether the multiplication result should be added to the current value in result
+ /// The starting index for the vector argument
+ /// The starting index for the result
+ private void FftMultiplyTranspose(Single[] vector, Single[] result, bool add = false, int srcIndex = 0, int dstIndex = 0)
+ {
+ _ectx.Assert(srcIndex >= 0);
+ _ectx.Assert(dstIndex >= 0);
+ _ectx.Assert(Utils.Size(vector) >= _windowSize + srcIndex);
+ _ectx.Assert(Utils.Size(result) >= _k + dstIndex);
+
+ int i;
+
+ // Computing the FFT of the trajectory matrix
+ if (!_isSeriesFftCached)
+ CacheInputSeriesFft();
+
+ // Computing the FFT of the input vector
+ for (i = 0; i < _k - 1; ++i)
+ _inputRe[i] = 0;
+
+ for (i = _k - 1; i < _seriesLength; ++i)
+ _inputRe[i] = vector[_seriesLength - i - 1 + srcIndex];
+
+ FftUtils.ComputeForwardFft(_inputRe, _allZerosIm, _outputRe, _outputIm, _inputRe.Length);
+
+ // Computing the element-by-element product in the Fourier space
+ double re;
+ double im;
+ for (i = 0; i < _seriesLength; ++i)
+ {
+ re = _outputRe[i];
+ im = _outputIm[i];
+
+ _outputRe[i] = _cachedSeriesFftRe[i] * re - _cachedSeriesFftIm[i] * im;
+ _outputIm[i] = _cachedSeriesFftRe[i] * im + _cachedSeriesFftIm[i] * re;
+ }
+
+ // Computing the inverse FFT of the result
+ FftUtils.ComputeBackwardFft(_outputRe, _outputIm, _outputRe, _outputIm, _inputRe.Length);
+
+ // Generating the output
+ if (add)
+ {
+ for (i = 0; i < _k; ++i)
+ result[i + dstIndex] += RoundUpToReal(_outputRe[_windowSize - 1 + i], _outputIm[_windowSize - 1 + i]);
+ }
+ else
+ {
+ for (i = 0; i < _k; ++i)
+ result[i + dstIndex] = RoundUpToReal(_outputRe[_windowSize - 1 + i], _outputIm[_windowSize - 1 + i]);
+ }
+ }
+
+ ///
+ /// This function efficiently computes the multiplication of the transpose of the trajectory matrix H by an arbitrary vector v, i.e. H' * v.
+ ///
+ /// The input vector
+ /// The output vector allocated by the caller
+ /// Whether the multiplication result should be added to the current value in result
+ /// The starting index for the vector argument
+ /// The starting index for the result
+ public void MultiplyTranspose(Single[] vector, Single[] result, bool add = false, int srcIndex = 0, int dstIndex = 0)
+ {
+ if (_shouldFftUsed)
+ FftMultiplyTranspose(vector, result, add, srcIndex, dstIndex);
+ else
+ NaiveMultiplyTranspose(vector, result, add, srcIndex, dstIndex);
+ }
+
+ ///
+ /// This function computes the naive Hankelization of the matrix sigma * u * v' in O(L * K).
+ ///
+ /// The u vector
+ /// The v vector
+ /// The scalar coefficient
+ /// The output series
+ /// Whether the hankelization result should be added to the current value in result
+ /// The starting index for the u vector argument
+ /// The starting index for the v vector argument
+ /// The starting index for the result
+ /// The staring index of the series to be reconstructed (by default zero)
+ /// The ending index of the series to be reconstructed (by default series length)
+ private void NaiveRankOneHankelization(Single[] u, Single[] v, Single sigma, Single[] result, bool add = false,
+ int uIndex = 0, int vIndex = 0, int dstIndex = 0, int? start = null, int? end = null)
+ {
+ int s;
+ int e;
+ int us;
+ int ue;
+ int vs;
+ int ve;
+
+ s = start ?? 0;
+ e = end ?? _seriesLength - 1;
+
+ ComputeBoundryIndices(s, e, out us, out ue, out vs, out ve);
+ _ectx.Assert(0 <= ue && ue < _windowSize);
+ _ectx.Assert(0 <= us && us <= ue);
+ _ectx.Assert(0 <= ve && ve < _k);
+ _ectx.Assert(0 <= vs && vs <= ve);
+
+ var len = e - s + 1;
+ var uLen = ue - us + 1;
+ var vLen = ve - vs + 1;
+
+ _ectx.Assert(uIndex >= 0);
+ _ectx.Assert(vIndex >= 0);
+ _ectx.Assert(dstIndex >= 0);
+ _ectx.Assert(Utils.Size(u) >= _windowSize + uIndex);
+ _ectx.Assert(Utils.Size(v) >= _k + vIndex);
+ _ectx.Assert(Utils.Size(result) >= len + dstIndex);
+ _ectx.Assert(!Single.IsNaN(sigma));
+ _ectx.Assert(!Single.IsInfinity(sigma));
+
+ int i;
+ int j;
+ int a;
+ int b;
+ int c;
+ Single temp;
+
+ if (!add)
+ {
+ for (i = 0; i < len; ++i)
+ result[i + dstIndex] = 0;
+ }
+
+ for (i = 0; i < len; ++i)
+ {
+ b = Math.Min(uLen, i + 1) - 1;
+ a = i >= Math.Max(uLen, vLen) ? len - i : b + 1;
+ c = Math.Max(0, i - uLen + 1);
+ temp = 0;
+ for (j = 0; j < a; ++j)
+ temp += u[us + b - j + uIndex] * v[vs + c + j + vIndex];
+
+ result[i + dstIndex] += (temp * sigma / a);
+ }
+ }
+
+ ///
+ /// This function computes the efficient Hankelization of the matrix sigma * u * v' using Fast Fourier Transform in in O((L + K) * log(L + K)).
+ /// For details, refer to Algorithm 4 in http://arxiv.org/pdf/0911.4498.pdf.
+ ///
+ /// The u vector
+ /// The v vector
+ /// The scalar coefficient
+ /// The output series
+ /// Whether the hankelization result should be added to the current value in result
+ /// The starting index for the u vector argument
+ /// The starting index for the v vector argument
+ /// The starting index for the result
+ /// The staring index of the series to be reconstructed (by default zero)
+ /// The ending index of the series to be reconstructed (by default series length)
+ private void FftRankOneHankelization(Single[] u, Single[] v, Single sigma, Single[] result, bool add = false,
+ int uIndex = 0, int vIndex = 0, int dstIndex = 0, int? start = null, int? end = null)
+ {
+ int s;
+ int e;
+ int us;
+ int ue;
+ int vs;
+ int ve;
+ int i;
+
+ s = start ?? 0;
+ e = end ?? _seriesLength - 1;
+
+ ComputeBoundryIndices(s, e, out us, out ue, out vs, out ve);
+ _ectx.Assert(0 <= ue && ue < _windowSize);
+ _ectx.Assert(0 <= us && us <= ue);
+ _ectx.Assert(0 <= ve && ve < _k);
+ _ectx.Assert(0 <= vs && vs <= ve);
+
+ var len = e - s + 1;
+
+ _ectx.Assert(uIndex >= 0);
+ _ectx.Assert(vIndex >= 0);
+ _ectx.Assert(dstIndex >= 0);
+ _ectx.Assert(Utils.Size(u) >= _windowSize + uIndex);
+ _ectx.Assert(Utils.Size(v) >= _k + vIndex);
+ _ectx.Assert(Utils.Size(result) >= len + dstIndex);
+ _ectx.Assert(!Single.IsNaN(sigma));
+ _ectx.Assert(!Single.IsInfinity(sigma));
+
+ if (!_isSeriesFftCached)
+ CacheInputSeriesFft();
+
+ // Computing the FFT of u
+ for (i = us; i <= ue; ++i)
+ _inputRe[i - us] = u[i + uIndex];
+
+ for (i = ue + 1; i < len + us; ++i)
+ _inputRe[i - us] = 0;
+
+ FftUtils.ComputeForwardFft(_inputRe, _allZerosIm, _outputRe, _outputIm, len);
+
+ // Computing the FFT of v
+ for (i = vs; i <= ve; ++i)
+ _inputRe[i - vs] = v[i + vIndex];
+
+ for (i = ve + 1; i < len + vs; ++i)
+ _inputRe[i - vs] = 0;
+
+ FftUtils.ComputeForwardFft(_inputRe, _allZerosIm, _inputRe, _allZerosIm, len);
+
+ // Computing the element-by-element product in the Fourier space
+ double re;
+ double im;
+ for (i = 0; i < len; ++i)
+ {
+ re = _outputRe[i];
+ im = _outputIm[i];
+
+ _outputRe[i] = _inputRe[i] * re - _allZerosIm[i] * im;
+ _outputIm[i] = _inputRe[i] * im + _allZerosIm[i] * re;
+ }
+
+ // Setting _allZerosIm to 0's again
+ for (i = 0; i < _seriesLength; ++i)
+ _allZerosIm[i] = 0;
+
+ // Computing the inverse FFT of the result
+ FftUtils.ComputeBackwardFft(_outputRe, _outputIm, _outputRe, _outputIm, len);
+
+ // Generating the output
+ int a = Math.Min(ue - us + 1, ve - vs + 1);
+
+ if (add)
+ {
+ for (i = 0; i < a; ++i)
+ result[i + dstIndex] += RoundUpToReal(_outputRe[i], _outputIm[i], sigma / (i + 1));
+
+ for (i = a; i < len - a + 1; ++i)
+ result[i + dstIndex] += RoundUpToReal(_outputRe[i], _outputIm[i], sigma / a);
+
+ for (i = len - a + 1; i < len; ++i)
+ result[i + dstIndex] += RoundUpToReal(_outputRe[i], _outputIm[i], sigma / (len - i));
+ }
+ else
+ {
+ for (i = 0; i < a; ++i)
+ result[i + dstIndex] = RoundUpToReal(_outputRe[i], _outputIm[i], sigma / (i + 1));
+
+ for (i = a; i < len - a + 1; ++i)
+ result[i + dstIndex] = RoundUpToReal(_outputRe[i], _outputIm[i], sigma / a);
+
+ for (i = len - a + 1; i < len; ++i)
+ result[i + dstIndex] = RoundUpToReal(_outputRe[i], _outputIm[i], sigma / (len - i));
+ }
+ }
+
+ ///
+ /// This function efficiently computes the Hankelization of the matrix sigma * u * v'.
+ ///
+ /// The u vector
+ /// The v vector
+ /// The scalar coefficient
+ /// The output series
+ /// Whether the hankelization result should be added to the current value in result
+ /// The starting index for the u vector argument
+ /// The starting index for the v vector argument
+ /// The starting index for the result
+ /// The staring index of the series to be reconstructed (by default zero)
+ /// The ending index of the series to be reconstructed (by default series length)
+ public void RankOneHankelization(Single[] u, Single[] v, Single sigma, Single[] result, bool add = false,
+ int uIndex = 0, int vIndex = 0, int dstIndex = 0, int? start = null, int? end = null)
+ {
+ if (_shouldFftUsed)
+ FftRankOneHankelization(u, v, sigma, result, add, uIndex, vIndex, dstIndex, start, end);
+ else
+ NaiveRankOneHankelization(u, v, sigma, result, add, uIndex, vIndex, dstIndex, start, end);
+ }
+ }
+}
diff --git a/src/Native/CMakeLists.txt b/src/Native/CMakeLists.txt
index a0b06f864f..471b9aeff4 100644
--- a/src/Native/CMakeLists.txt
+++ b/src/Native/CMakeLists.txt
@@ -182,4 +182,5 @@ add_subdirectory(CpuMathNative)
add_subdirectory(FastTreeNative)
add_subdirectory(LdaNative)
add_subdirectory(FactorizationMachineNative)
-add_subdirectory(SymSgdNative)
\ No newline at end of file
+add_subdirectory(SymSgdNative)
+add_subdirectory(MklProxyNative)
\ No newline at end of file
diff --git a/src/Native/MklProxyNative/CMakeLists.txt b/src/Native/MklProxyNative/CMakeLists.txt
new file mode 100644
index 0000000000..caea9450cb
--- /dev/null
+++ b/src/Native/MklProxyNative/CMakeLists.txt
@@ -0,0 +1,25 @@
+project (MklProxyNative)
+
+set(SOURCES
+ MklProxyNative.cpp
+)
+
+find_library(MKL_LIBRARY MklImports HINTS ${MKL_LIB_PATH})
+if(NOT WIN32)
+ list(APPEND SOURCES ${VERSION_FILE_PATH})
+ if(NOT APPLE)
+ SET(CMAKE_SKIP_BUILD_RPATH FALSE)
+ SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
+ SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
+ SET(CMAKE_INSTALL_RPATH "$ORIGIN/")
+ endif()
+endif()
+
+add_library(MklProxyNative SHARED ${SOURCES} ${RESOURCES})
+target_link_libraries(MklProxyNative PUBLIC ${MKL_LIBRARY})
+
+if(APPLE)
+ set_target_properties(MklProxyNative PROPERTIES INSTALL_RPATH "@loader_path")
+endif()
+
+install_library_and_symbols (MklProxyNative)
\ No newline at end of file
diff --git a/src/Native/MklProxyNative/MklProxyNative.cpp b/src/Native/MklProxyNative/MklProxyNative.cpp
new file mode 100644
index 0000000000..28819c8494
--- /dev/null
+++ b/src/Native/MklProxyNative/MklProxyNative.cpp
@@ -0,0 +1,177 @@
+// 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.
+
+#include "../Stdafx.h"
+
+enum ConfigParam
+{
+ /* Domain for forward transform. No default value */
+ ForwardDomain = 0,
+
+ /* Dimensionality, or rank. No default value */
+ Dimension = 1,
+
+ /* Length(s) of transform. No default value */
+ Lengths = 2,
+
+ /* Floating point precision. No default value */
+ Precision = 3,
+
+ /* Scale factor for forward transform [1.0] */
+ ForwardScale = 4,
+
+ /* Scale factor for backward transform [1.0] */
+ BackwardScale = 5,
+
+ /* Exponent sign for forward transform [Negative] */
+ /* ForwardSign = 6, ## NOT IMPLEMENTED */
+
+ /* Number of data sets to be transformed [1] */
+ NumberOfTransforms = 7,
+
+ /* Storage of finite complex-valued sequences in complex domain
+ [ComplexComplex] */
+ ComplexStorage = 8,
+
+ /* Storage of finite real-valued sequences in real domain
+ [RealReal] */
+ RealStorage = 9,
+
+ /* Storage of finite complex-valued sequences in conjugate-even
+ domain [ComplexReal] */
+ ConjugateEvenStorage = 10,
+
+ /* Placement of result [InPlace] */
+ Placement = 11,
+
+ /* Generalized strides for input data layout [tigth, row-major for
+ C] */
+ InputStrides = 12,
+
+ /* Generalized strides for output data layout [tight, row-major
+ for C] */
+ OutputStrides = 13,
+
+ /* Distance between first input elements for multiple transforms
+ [0] */
+ InputDistance = 14,
+
+ /* Distance between first output elements for multiple transforms
+ [0] */
+ OutputDistance = 15,
+
+ /* Effort spent in initialization [Medium] */
+ /* InitializationEffort = 16, ## NOT IMPLEMENTED */
+
+ /* Use of workspace during computation [Allow] */
+ /* Workspace = 17, ## NOT IMPLEMENTED */
+
+ /* Ordering of the result [Ordered] */
+ Ordering = 18,
+
+ /* Possible transposition of result [None] */
+ Transpose = 19,
+
+ /* User-settable descriptor name [""] */
+ DescriptorName = 20, /* DEPRECATED */
+
+ /* Packing format for ComplexReal storage of finite
+ conjugate-even sequences [CcsFormat] */
+ PackedFormat = 21,
+
+ /* Commit status of the descriptor - R/O parameter */
+ CommitStatus = 22,
+
+ /* Version string for this DFTI implementation - R/O parameter */
+ Version = 23,
+
+ /* Ordering of the forward transform - R/O parameter */
+ /* ForwardOrdering = 24, ## NOT IMPLEMENTED */
+
+ /* Ordering of the backward transform - R/O parameter */
+ /* BackwardOrdering = 25, ## NOT IMPLEMENTED */
+
+ /* Number of user threads that share the descriptor [1] */
+ NumberOfUserThreads = 26
+};
+
+enum ConfigValue
+{
+ /* CommitStatus */
+ Committed = 30,
+ Uncommitted = 31,
+
+ /* ForwardDomain */
+ Complex = 32,
+ Real = 33,
+ /* ConjugateEven = 34, ## NOT IMPLEMENTED */
+
+ /* Precision */
+ Single = 35,
+ Double = 36,
+
+ /* ForwardSign */
+ /* Negative = 37, ## NOT IMPLEMENTED */
+ /* Positive = 38, ## NOT IMPLEMENTED */
+
+ /* ComplexStorage and ConjugateEvenStorage */
+ ComplexComplex = 39,
+ ComplexReal = 40,
+
+ /* RealStorage */
+ RealComplex = 41,
+ RealReal = 42,
+
+ /* Placement */
+ InPlace = 43, /* Result overwrites input */
+ NotInPlace = 44, /* Have another place for result */
+
+ /* InitializationEffort */
+ /* Low = 45, ## NOT IMPLEMENTED */
+ /* Medium = 46, ## NOT IMPLEMENTED */
+ /* High = 47, ## NOT IMPLEMENTED */
+
+ /* Ordering */
+ Ordered = 48,
+ BackwardScrambled = 49,
+ /* ForwardScrambled = 50, ## NOT IMPLEMENTED */
+
+ /* Allow/avoid certain usages */
+ Allow = 51, /* Allow transposition or workspace */
+ /* Avoid = 52, ## NOT IMPLEMENTED */
+ None = 53,
+
+ /* PackedFormat (for storing congugate-even finite sequence
+ in real array) */
+ CcsFormat = 54, /* Complex conjugate-symmetric */
+ PackFormat = 55, /* Pack format for real DFT */
+ PermFormat = 56, /* Perm format for real DFT */
+ CceFormat = 57 /* Complex conjugate-even */
+};
+
+//Proxy functions to handle variable length arguments in MKL functions.
+EXPORT_API(int) DftiSetValue(void *handle, ConfigParam config_param, ...);
+EXPORT_API(int) DftiCreateDescriptor(void **handle, ConfigValue precision, ConfigValue domain, int dim, ...);
+EXPORT_API(int) DftiComputeForward(void *handle, ...);
+EXPORT_API(int) DftiComputeBackward(void *handle, ...);
+
+EXPORT_API(int) MKLDftiSetValue(void *handle, ConfigParam config_param, int config_val)
+{
+ return DftiSetValue(handle, config_param, config_val);
+}
+
+EXPORT_API(int) MKLDftiCreateDescriptor(void **handle, ConfigValue precision, ConfigValue domain, int dim, int sizes)
+{
+ return DftiCreateDescriptor(handle, precision,domain, dim, sizes);
+}
+
+EXPORT_API(int) MKLDftiComputeForward(void *handle, double *inputRe, double * inputIm, double * outputRe, double * outputIm)
+{
+ return DftiComputeForward(handle, inputRe, inputIm, outputRe, outputIm);
+}
+
+EXPORT_API(int) MKLDftiComputeBackward(void *handle, double *inputRe, double * inputIm, double * outputRe, double * outputIm)
+{
+ return DftiComputeBackward(handle, inputRe, inputIm, outputRe, outputIm);
+}
\ No newline at end of file
diff --git a/src/Native/SymSgdNative/CMakeLists.txt b/src/Native/SymSgdNative/CMakeLists.txt
index 56baa7138d..4bbf9f69d6 100644
--- a/src/Native/SymSgdNative/CMakeLists.txt
+++ b/src/Native/SymSgdNative/CMakeLists.txt
@@ -18,4 +18,8 @@ endif()
add_library(SymSgdNative SHARED ${SOURCES} ${RESOURCES})
target_link_libraries(SymSgdNative PUBLIC ${MKL_LIBRARY})
+if(APPLE)
+ set_target_properties(SymSgdNative PROPERTIES INSTALL_RPATH "@loader_path;@loader_path/${MKL_LIB_RPATH}}")
+endif()
+
install_library_and_symbols (SymSgdNative)
\ No newline at end of file
diff --git a/src/Native/build.cmd b/src/Native/build.cmd
index 11684ac9d2..c5e0c9b9f7 100644
--- a/src/Native/build.cmd
+++ b/src/Native/build.cmd
@@ -27,7 +27,6 @@ if /i [%1] == [x64] ( set __BuildArch=x64&&set __VCBuildArch=x86_amd64&&
if /i [%1] == [amd64] ( set __BuildArch=x64&&set __VCBuildArch=x86_amd64&&shift&goto Arg_Loop)
if /i [%1] == [--mkllibpath] ( set MKL_LIB_PATH=%2&&shift&goto Arg_Loop)
-
shift
goto :Arg_Loop
diff --git a/src/Native/build.proj b/src/Native/build.proj
index 19864816c5..d9d0b9b650 100644
--- a/src/Native/build.proj
+++ b/src/Native/build.proj
@@ -40,7 +40,11 @@
DependsOnTargets="GenerateVersionSourceFile">
--stripSymbols
- --configuration $(Configuration) --arch $(TargetArchitecture) $(StripArgs) --mkllibpath $(PackagesDir)mlnetmkldeps/$(MlNetMklDepsPackageVersion)/runtimes/$(PackageRid)/native
+
+ --configuration $(Configuration) --arch $(TargetArchitecture) $(StripArgs) --mkllibpath $(PackagesDir)mlnetmkldeps/$(MlNetMklDepsPackageVersion)/runtimes/$(PackageRid)/native --mkllibrpath ../../../../../microsoft.ml.mkl.redist/$(Version)/runtimes/$(PackageRid)/native
@@ -67,7 +71,7 @@
+ DestinationFolder="$(NativeAssetsBuiltPath)" />
+
@@ -96,10 +102,11 @@
AND '%(NativePackageAsset.Identity)' != '$(PlaceholderFile)'"
Include="@(NativePackageAsset->'%(RootDir)%(Directory)%(Filename)$(NativeLibSymbolExtension)')" />
-
+
+ RelativePath="Microsoft.ML.Mkl.Redist\runtimes\$(PackageRid)\native" />
+
+ Metadata 'SlotNames': Vec: Length=4, Count=4
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score', [3] 'Martingale Score'
+---- ConvertTransform ----
+3 columns:
+ Features: R4
+ Anomaly: Vec
+ Metadata 'SlotNames': Vec: Length=4, Count=4
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score', [3] 'Martingale Score'
+ fAnomaly: Vec
+ Metadata 'SlotNames': Vec: Length=4, Count=4
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score', [3] 'Martingale Score'
+---- IidChangePointDetector ----
+4 columns:
+ Features: R4
+ Anomaly: Vec
+ Metadata 'SlotNames': Vec: Length=4, Count=4
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score', [3] 'Martingale Score'
+ fAnomaly: Vec
+ Metadata 'SlotNames': Vec: Length=4, Count=4
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score', [3] 'Martingale Score'
+ Anomaly2: Vec
+ Metadata 'SlotNames': Vec: Length=4, Count=4
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score', [3] 'Martingale Score'
+---- ConvertTransform ----
+5 columns:
+ Features: R4
+ Anomaly: Vec
+ Metadata 'SlotNames': Vec: Length=4, Count=4
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score', [3] 'Martingale Score'
+ fAnomaly: Vec
+ Metadata 'SlotNames': Vec: Length=4, Count=4
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score', [3] 'Martingale Score'
+ Anomaly2: Vec
+ Metadata 'SlotNames': Vec: Length=4, Count=4
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score', [3] 'Martingale Score'
+ fAnomaly2: Vec
+ Metadata 'SlotNames': Vec: Length=4, Count=4
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score', [3] 'Martingale Score'
+---- ChooseColumnsTransform ----
+3 columns:
+ Features: R4
+ fAnomaly: Vec
+ Metadata 'SlotNames': Vec: Length=4, Count=4
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score', [3] 'Martingale Score'
+ fAnomaly2: Vec
+ Metadata 'SlotNames': Vec: Length=4, Count=4
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score', [3] 'Martingale Score'
diff --git a/test/BaselineOutput/SingleDebug/SavePipe/SavePipeIidSpike-Data.txt b/test/BaselineOutput/SingleDebug/SavePipe/SavePipeIidSpike-Data.txt
new file mode 100644
index 0000000000..5c33867c9d
--- /dev/null
+++ b/test/BaselineOutput/SingleDebug/SavePipe/SavePipeIidSpike-Data.txt
@@ -0,0 +1,1428 @@
+#@ TextLoader{
+#@ header+
+#@ sep=tab
+#@ col=Features:R4:0
+#@ col=fAnomaly:R4:1-3
+#@ col=fAnomaly2:R4:4-6
+#@ }
+Features Alert Raw Score P-Value Score Alert Raw Score P-Value Score
+7 3:0.5 6:0.5
+0.11 0 0.11 0.456204623 0 0.11 0.543795347
+0.111 0 0.111 0.257266462 0 0.111 0.742733538
+0.3333 0 0.3333 6.32245929E-06 0 0.3333 0.9999937
+0.4444 0 0.4444 0.04637583 0 0.4444 0.9536242
+0.111 0 0.111 0.627704442 0 0.111 0.3722956
+0.363 0 0.363 0.213761881 0 0.363 0.786238134
+0.413871437 0 0.413871437 0.189836681 0 0.413871437 0.8101633
+0.464742869 0 0.464742869 0.166525915 0 0.464742869 0.8334741
+0.5156143 0 0.5156143 0.147354573 0 0.5156143 0.852645457
+0.5664857 0 0.5664857 0.133305743 0 0.5664857 0.8666943
+0.617357135 0 0.617357135 0.1236946 0 0.617357135 0.8763054
+0.668228567 0 0.668228567 0.11735633 0 0.668228567 0.8826437
+0.7191 0 0.7191 0.113250025 0 0.7191 0.88675
+0.76997143 0 0.76997143 0.110613458 0 0.76997143 0.889386535
+0.820842862 0 0.820842862 0.108933933 0 0.820842862 0.8910661
+0.8717143 0 0.8717143 0.107878543 0 0.8717143 0.892121434
+0.9225857 0 0.9225857 0.107233316 0 0.9225857 0.892766654
+0.973457158 0 0.973457158 0.10686028 0 0.973457158 0.8931397
+1.02432859 0 1.02432859 0.106669575 0 1.02432859 0.893330455
+1.0752 0 1.0752 0.106601924 0 1.0752 0.8933981
+1.12607145 0 1.12607145 0.106617555 0 1.12607145 0.89338243
+1.17694283 0 1.17694283 0.106689692 0 1.17694283 0.8933103
+1.22781432 0 1.22781432 0.106799714 0 1.22781432 0.8932003
+1.27868569 0 1.27868569 0.106934905 0 1.27868569 0.8930651
+1.32955718 0 1.32955718 0.107086174 0 1.32955718 0.8929138
+1.38042855 0 1.38042855 0.107247278 0 1.38042855 0.8927527
+1.4313 0 1.4313 0.10741362 0 1.4313 0.8925864
+1.48217142 0 1.48217142 0.1075821 0 1.48217142 0.8924179
+1.53304291 0 1.53304291 0.107750386 0 1.53304291 0.892249644
+1.58391428 0 1.58391428 0.107916959 0 1.58391428 0.892083049
+1.63478577 0 1.63478577 0.108080626 0 1.63478577 0.8919194
+1.68565714 0 1.68565714 0.108240716 0 1.68565714 0.8917593
+1.73652852 0 1.73652852 0.108396679 0 1.73652852 0.8916033
+1.7874 0 1.7874 0.108548179 0 1.7874 0.891451836
+1.83827138 0 1.83827138 0.1086951 0 1.83827138 0.8913049
+1.88914287 0 1.88914287 0.108837292 0 1.88914287 0.8911627
+1.94001424 0 1.94001424 0.108974837 0 1.94001424 0.8910252
+1.99088573 0 1.99088573 0.1091077 0 1.99088573 0.8908923
+2.041757 0 2.041757 0.109236039 0 2.041757 0.890763938
+2.09262848 0 2.09262848 0.109359942 0 2.09262848 0.8906401
+2.1435 0 2.1435 0.109479472 0 2.1435 0.8905205
+2.19437146 0 2.19437146 0.109594889 0 2.19437146 0.8904051
+2.24524283 0 2.24524283 0.10970629 0 2.24524283 0.8902937
+2.29611421 0 2.29611421 0.109813809 0 2.29611421 0.8901862
+2.34698582 0 2.34698582 0.109917566 0 2.34698582 0.8900824
+2.39785719 0 2.39785719 0.1100178 0 2.39785719 0.8899822
+2.44872856 0 2.44872856 0.110114619 0 2.44872856 0.889885366
+2.4996 0 2.4996 0.110208154 0 2.4996 0.889791846
+2.55047154 0 2.55047154 0.110298507 0 2.55047154 0.8897015
+2.601343 0 2.601343 0.1103859 0 2.601343 0.8896141
+2.65221429 0 2.65221429 0.110470422 0 2.65221429 0.8895296
+2.70308566 0 2.70308566 0.110552184 0 2.70308566 0.8894478
+2.753957 0 2.753957 0.110631317 0 2.753957 0.8893687
+2.80482864 0 2.80482864 0.110707887 0 2.80482864 0.8892921
+2.8557 0 2.8557 0.110782087 0 2.8557 0.8892179
+2.90657139 0 2.90657139 0.110853978 0 2.90657139 0.88914603
+2.95744276 0 2.95744276 0.110923655 0 2.95744276 0.889076352
+3.00831437 0 3.00831437 0.110991172 0 3.00831437 0.8890088
+3.05918574 0 3.05918574 0.1110567 0 3.05918574 0.8889433
+3.110057 0 3.110057 0.111120284 0 3.110057 0.8888797
+3.16092849 0 3.16092849 0.111182004 0 3.16092849 0.888818
+3.2118 0 3.2118 0.111241892 0 3.2118 0.8887581
+3.26267147 0 3.26267147 0.1113001 0 3.26267147 0.8886999
+3.31354284 0 3.31354284 0.111356668 0 3.31354284 0.8886433
+3.36441422 0 3.36441422 0.111411653 0 3.36441422 0.888588369
+3.41528583 0 3.41528583 0.111465082 0 3.41528583 0.8885349
+3.4661572 0 3.4661572 0.111517087 0 3.4661572 0.8884829
+3.51702857 0 3.51702857 0.111567684 0 3.51702857 0.8884323
+3.5679 0 3.5679 0.111616932 0 3.5679 0.8883831
+3.61877131 0 3.61877131 0.111664884 0 3.61877131 0.8883351
+3.669643 0 3.669643 0.111711554 0 3.669643 0.888288438
+3.7205143 0 3.7205143 0.111757055 0 3.7205143 0.88824296
+3.77138567 0 3.77138567 0.111801393 0 3.77138567 0.8881986
+3.822257 0 3.822257 0.111844614 0 3.822257 0.8881554
+3.87312865 0 3.87312865 0.111886732 0 3.87312865 0.88811326
+3.924 0 3.924 0.111927837 0 3.924 0.888072133
+3.9748714 0 3.9748714 0.111967951 0 3.9748714 0.8880321
+4.025743 0 4.025743 0.112007059 0 4.025743 0.8879929
+4.07661438 0 4.07661438 0.112045273 0 4.07661438 0.8879547
+4.12748575 0 4.12748575 0.112082586 0 4.12748575 0.8879174
+4.178357 0 4.178357 0.112119026 0 4.178357 0.887881
+4.2292285 0 4.2292285 0.112154633 0 4.2292285 0.8878454
+4.2801 0 4.2801 0.11218942 0 4.2801 0.8878106
+4.33097124 0 4.33097124 0.112223424 0 4.33097124 0.887776554
+4.381843 0 4.381843 0.112256609 0 4.381843 0.8877434
+4.43271446 0 4.43271446 0.112289123 0 4.43271446 0.887710869
+4.483586 0 4.483586 0.112320922 0 4.483586 0.8876791
+4.534457 0 4.534457 0.112352036 0 4.534457 0.887648
+4.58532858 0 4.58532858 0.112382479 0 4.58532858 0.8876175
+4.6362 0 4.6362 0.112412281 0 4.6362 0.8875877
+4.68707132 0 4.68707132 0.11244145 0 4.68707132 0.8875586
+4.7379427 0 4.7379427 0.112470016 0 4.7379427 0.887529969
+4.788814 0 4.788814 0.112497985 0 4.788814 0.887502
+4.839686 0 4.839686 0.112525344 0 4.839686 0.887474656
+4.89055729 0 4.89055729 0.112552196 0 4.89055729 0.8874478
+4.94142866 0 4.94142866 0.112578511 0 4.94142866 0.8874215
+4.9923 0 4.9923 0.112604305 0 4.9923 0.8873957
+5.04317141 0 5.04317141 0.112629592 0 5.04317141 0.8873704
+5.094043 0 5.094043 0.112654388 0 5.094043 0.8873456
+5.144914 0 5.144914 0.112678707 0 5.144914 0.8873213
+5.19578552 0 5.19578552 0.112702556 0 5.19578552 0.887297451
+5.24665737 0 5.24665737 0.112725914 0 5.24665737 0.8872741
+5.29752874 0 5.29752874 0.112748876 0 5.29752874 0.887251139
+5.3484 0 5.3484 0.112771407 0 5.3484 0.8872286
+5.39927149 0 5.39927149 0.112793528 0 5.39927149 0.8872065
+5.450143 0 5.450143 0.112815246 0 5.450143 0.887184739
+5.501014 0 5.501014 0.11283657 0 5.501014 0.8871634
+5.5518856 0 5.5518856 0.112857513 0 5.5518856 0.8871425
+5.602757 0 5.602757 0.112878077 0 5.602757 0.8871219
+5.65362835 0 5.65362835 0.112898283 0 5.65362835 0.8871017
+5.7045 0 5.7045 0.112918094 0 5.7045 0.8870819
+5.75537157 0 5.75537157 0.1129376 0 5.75537157 0.8870624
+5.806243 0 5.806243 0.11295677 0 5.806243 0.887043238
+5.85711432 0 5.85711432 0.11297562 0 5.85711432 0.8870244
+5.90798569 0 5.90798569 0.112994142 0 5.90798569 0.887005866
+5.958857 0 5.958857 0.113012359 0 5.958857 0.8869876
+6.00972843 0 6.00972843 0.11303027 0 6.00972843 0.886969745
+6.0606 0 6.0606 0.11304789 0 6.0606 0.8869521
+6.11147165 0 6.11147165 0.113065176 0 6.11147165 0.8869348
+6.162343 0 6.162343 0.113082223 0 6.162343 0.88691777
+6.2132144 0 6.2132144 0.113098994 0 6.2132144 0.886901
+6.264086 0 6.264086 0.113115504 0 6.264086 0.8868845
+6.314957 0 6.314957 0.113131739 0 6.314957 0.886868238
+6.36582851 0 6.36582851 0.113147728 0 6.36582851 0.886852264
+6.4167 0 6.4167 0.113163464 0 6.4167 0.8868365
+6.46757126 0 6.46757126 0.113178954 0 6.46757126 0.886821032
+6.51844263 0 6.51844263 0.113194205 0 6.51844263 0.8868058
+6.56931448 0 6.56931448 0.113209188 0 6.56931448 0.8867908
+6.620186 0 6.620186 0.113223985 0 6.620186 0.88677603
+6.671057 0 6.671057 0.113238558 0 6.671057 0.8867614
+6.7219286 0 6.7219286 0.113252908 0 6.7219286 0.8867471
+6.7728 0 6.7728 0.113267049 0 6.7728 0.886732936
+6.82367134 0 6.82367134 0.113280974 0 6.82367134 0.886719048
+6.87454271 0 6.87454271 0.113294706 0 6.87454271 0.8867053
+6.925414 0 6.925414 0.113308229 0 6.925414 0.886691749
+6.976286 0 6.976286 0.113321528 0 6.976286 0.886678457
+7.02715731 0 7.02715731 0.113334671 0 7.02715731 0.886665344
+7.07802868 0 7.07802868 0.113347627 0 7.07802868 0.88665235
+7.1289 0 7.1289 0.1133604 0 7.1289 0.8866396
+7.17977142 0 7.17977142 0.113372989 0 7.17977142 0.886627
+7.230643 0 7.230643 0.113385409 0 7.230643 0.886614561
+7.281514 0 7.281514 0.113397658 0 7.281514 0.886602342
+7.33238554 0 7.33238554 0.113409735 0 7.33238554 0.886590242
+7.383257 0 7.383257 0.113421649 0 7.383257 0.8865784
+7.43412876 0 7.43412876 0.113433361 0 7.43412876 0.886566639
+7.485 0 7.485 0.113444962 0 7.485 0.886555
+7.53587151 0 7.53587151 0.1134564 0 7.53587151 0.886543632
+7.586743 0 7.586743 0.113467686 0 7.586743 0.8865323
+7.63761425 0 7.63761425 0.113478817 0 7.63761425 0.886521161
+7.68848562 0 7.68848562 0.113489814 0 7.68848562 0.8865102
+7.739357 0 7.739357 0.113500662 0 7.739357 0.886499345
+7.79022837 0 7.79022837 0.113511369 0 7.79022837 0.8864886
+7.8411 0 7.8411 0.113521904 0 7.8411 0.886478066
+7.89197159 0 7.89197159 0.113532342 0 7.89197159 0.886467636
+7.942843 0 7.942843 0.113542639 0 7.942843 0.8864574
+7.99371433 0 7.99371433 0.113552816 0 7.99371433 0.8864472
+8.044586 0 8.044586 0.11356283 0 8.044586 0.8864372
+8.095457 0 8.095457 0.113572776 0 8.095457 0.8864272
+8.146329 0 8.146329 0.113582544 0 8.146329 0.886417449
+8.1972 0 8.1972 0.113592245 0 8.1972 0.886407733
+8.248072 0 8.248072 0.113601774 0 8.248072 0.8863982
+8.298943 0 8.298943 0.113611244 0 8.298943 0.8863888
+8.349814 0 8.349814 0.113620542 0 8.349814 0.8863795
+8.400685 0 8.400685 0.113629781 0 8.400685 0.886370242
+8.451557 0 8.451557 0.113638856 0 8.451557 0.8863611
+8.502429 0 8.502429 0.113647826 0 8.502429 0.8863522
+8.5533 0 8.5533 0.113656744 0 8.5533 0.886343241
+8.604172 0 8.604172 0.1136655 0 8.604172 0.8863345
+8.655043 0 8.655043 0.113674209 0 8.655043 0.8863258
+8.7059145 0 8.7059145 0.113682762 0 8.7059145 0.886317253
+8.756785 0 8.756785 0.113691278 0 8.756785 0.8863087
+8.807657 0 8.807657 0.11369963 0 8.807657 0.8863004
+8.858528 0 8.858528 0.113707945 0 8.858528 0.88629204
+8.9094 0 8.9094 0.113716118 0 8.9094 0.8862839
+8.960272 0 8.960272 0.113724194 0 8.960272 0.8862758
+9.011143 0 9.011143 0.113732226 0 9.011143 0.8862678
+9.062015 0 9.062015 0.113740124 0 9.062015 0.886259854
+9.112885 0 9.112885 0.113747984 0 9.112885 0.886252
+9.163757 0 9.163757 0.11375571 0 9.163757 0.8862443
+9.214628 0 9.214628 0.113763392 0 9.214628 0.8862366
+9.2655 0 9.2655 0.113770947 0 9.2655 0.886229038
+9.316371 0 9.316371 0.113778472 0 9.316371 0.8862215
+9.367243 0 9.367243 0.113785855 0 9.367243 0.886214137
+9.418115 0 9.418115 0.113793172 0 9.418115 0.8862068
+9.468986 0 9.468986 0.113800451 0 9.468986 0.886199534
+9.519857 0 9.519857 0.1138076 0 9.519857 0.8861924
+9.570728 0 9.570728 0.113814734 0 9.570728 0.8861853
+9.6216 0 9.6216 0.113821737 0 9.6216 0.886178255
+9.672471 0 9.672471 0.113828719 0 9.672471 0.8861713
+9.723343 0 9.723343 0.113835581 0 9.723343 0.8861644
+9.774215 0 9.774215 0.113842368 0 9.774215 0.886157632
+9.825086 0 9.825086 0.113849133 0 9.825086 0.8861509
+9.87595749 0 9.87595749 0.113855779 0 9.87595749 0.8861442
+9.926828 0 9.926828 0.11386241 0 9.926828 0.8861376
+9.9777 0 9.9777 0.113868922 0 9.9777 0.886131048
+10.0285711 0 10.0285711 0.113875419 0 10.0285711 0.8861246
+10.079443 0 10.079443 0.1138818 0 10.079443 0.8861182
+10.1303139 0 10.1303139 0.113888167 0 10.1303139 0.886111856
+10.1811857 0 10.1811857 0.113894418 0 10.1811857 0.8861056
+10.2320576 0 10.2320576 0.113900617 0 10.2320576 0.8860994
+10.2829285 0 10.2829285 0.11388094 0 10.2829285 0.886119068
+10.3338 0 10.3338 0.113881171 0 10.3338 0.8861188
+10.3846712 0 10.3846712 0.113864474 0 10.3846712 0.8861355
+10.4355431 0 10.4355431 0.113905221 0 10.4355431 0.886094749
+10.486414 0 10.486414 0.113966189 0 10.486414 0.886033833
+10.5372858 0 10.5372858 0.113897361 0 10.5372858 0.8861026
+10.5881567 0 10.5881567 0.113897383 0 10.5881567 0.8861026
+10.6390285 0 10.6390285 0.113897368 0 10.6390285 0.8861026
+10.6899 0 10.6899 0.113897346 0 10.6899 0.8861027
+10.7407713 0 10.7407713 0.113897368 0 10.7407713 0.8861026
+10.7916431 0 10.7916431 0.113897353 0 10.7916431 0.8861027
+10.842514 0 10.842514 0.113897376 0 10.842514 0.8861026
+10.8933859 0 10.8933859 0.113897353 0 10.8933859 0.8861026
+10.9442568 0 10.9442568 0.113897383 0 10.9442568 0.8861026
+10.9951286 0 10.9951286 0.113897361 0 10.9951286 0.8861026
+11.046 0 11.046 0.113897391 0 11.046 0.8861026
+11.0968714 0 11.0968714 0.113897368 0 11.0968714 0.8861026
+11.1477432 0 11.1477432 0.113897346 0 11.1477432 0.8861027
+11.1986141 0 11.1986141 0.113897376 0 11.1986141 0.8861026
+11.249486 0 11.249486 0.113897353 0 11.249486 0.8861027
+11.3003569 0 11.3003569 0.113897376 0 11.3003569 0.8861026
+11.3512287 0 11.3512287 0.113897353 0 11.3512287 0.8861026
+11.4021 0 11.4021 0.113897383 0 11.4021 0.8861026
+11.4529715 0 11.4529715 0.113897361 0 11.4529715 0.8861026
+11.5038433 0 11.5038433 0.113897339 0 11.5038433 0.8861027
+11.5547142 0 11.5547142 0.113897368 0 11.5547142 0.8861026
+11.6055861 0 11.6055861 0.113897346 0 11.6055861 0.8861027
+11.6564569 0 11.6564569 0.113897376 0 11.6564569 0.8861026
+11.7073288 0 11.7073288 0.113897353 0 11.7073288 0.8861027
+11.7582 0 11.7582 0.113897383 0 11.7582 0.8861026
+11.8090715 0 11.8090715 0.113897361 0 11.8090715 0.8861026
+11.8599424 0 11.8599424 0.113897383 0 11.8599424 0.8861026
+11.9108143 0 11.9108143 0.113897361 0 11.9108143 0.8861026
+11.9616861 0 11.9616861 0.113897346 0 11.9616861 0.8861027
+12.012557 0 12.012557 0.113897368 0 12.012557 0.8861026
+12.0634289 0 12.0634289 0.113897346 0 12.0634289 0.8861027
+12.1143 0 12.1143 0.113897376 0 12.1143 0.8861026
+12.1651716 0 12.1651716 0.113897353 0 12.1651716 0.8861026
+12.2160425 0 12.2160425 0.113897383 0 12.2160425 0.8861026
+12.2669144 0 12.2669144 0.113897361 0 12.2669144 0.8861026
+12.3177853 0 12.3177853 0.113897383 0 12.3177853 0.8861026
+12.3686571 0 12.3686571 0.113897368 0 12.3686571 0.8861026
+12.419529 0 12.419529 0.113897346 0 12.419529 0.8861027
+12.4704 0 12.4704 0.113897368 0 12.4704 0.8861026
+12.5212717 0 12.5212717 0.113897353 0 12.5212717 0.8861027
+12.5721426 0 12.5721426 0.113897376 0 12.5721426 0.8861026
+12.6230145 0 12.6230145 0.113897353 0 12.6230145 0.8861026
+12.6738853 0 12.6738853 0.113897383 0 12.6738853 0.8861026
+12.7247572 0 12.7247572 0.113897361 0 12.7247572 0.8861026
+12.775629 0 12.775629 0.113897339 0 12.775629 0.8861027
+12.8265 0 12.8265 0.113897368 0 12.8265 0.8861026
+12.8773718 0 12.8773718 0.113897346 0 12.8773718 0.8861027
+12.9282427 0 12.9282427 0.113897376 0 12.9282427 0.8861026
+12.9791145 0 12.9791145 0.113897353 0 12.9791145 0.8861027
+13.0299854 0 13.0299854 0.113897376 0 13.0299854 0.8861026
+13.0808573 0 13.0808573 0.113897361 0 13.0808573 0.8861026
+13.1317282 0 13.1317282 0.113897383 0 13.1317282 0.8861026
+13.1826 0 13.1826 0.113897361 0 13.1826 0.8861026
+13.2334719 0 13.2334719 0.113897346 0 13.2334719 0.8861027
+13.2843428 0 13.2843428 0.113897368 0 13.2843428 0.8861026
+13.3352146 0 13.3352146 0.113897346 0 13.3352146 0.8861027
+13.3860855 0 13.3860855 0.113897376 0 13.3860855 0.8861026
+13.4369574 0 13.4369574 0.113897353 0 13.4369574 0.8861026
+13.4878283 0 13.4878283 0.113897383 0 13.4878283 0.8861026
+13.5387 0 13.5387 0.113897361 0 13.5387 0.8861026
+13.589571 0 13.589571 0.113897383 0 13.589571 0.8861026
+13.6404428 0 13.6404428 0.113897368 0 13.6404428 0.8861026
+13.6913147 0 13.6913147 0.113897346 0 13.6913147 0.8861027
+13.7421856 0 13.7421856 0.113897368 0 13.7421856 0.8861026
+13.7930574 0 13.7930574 0.113897353 0 13.7930574 0.8861027
+13.8439283 0 13.8439283 0.113897376 0 13.8439283 0.8861026
+13.8948 0 13.8948 0.113897353 0 13.8948 0.8861026
+13.9456711 0 13.9456711 0.113897383 0 13.9456711 0.8861026
+13.9965429 0 13.9965429 0.113897361 0 13.9965429 0.8861026
+14.0474138 0 14.0474138 0.113897383 0 14.0474138 0.8861026
+14.0982857 0 14.0982857 0.113897368 0 14.0982857 0.8861026
+14.1491575 0 14.1491575 0.113897346 0 14.1491575 0.8861027
+14.2000284 0 14.2000284 0.113897368 0 14.2000284 0.8861026
+14.2509 0 14.2509 0.113897353 0 14.2509 0.8861027
+14.3017712 0 14.3017712 0.113897376 0 14.3017712 0.8861026
+14.352643 0 14.352643 0.113897353 0 14.352643 0.8861026
+14.4035139 0 14.4035139 0.113897383 0 14.4035139 0.8861026
+14.4543858 0 14.4543858 0.113897361 0 14.4543858 0.8861026
+14.5052576 0 14.5052576 0.113897339 0 14.5052576 0.8861027
+14.5561285 0 14.5561285 0.113897368 0 14.5561285 0.8861026
+14.607 0 14.607 0.113897346 0 14.607 0.8861027
+14.6578712 0 14.6578712 0.113897376 0 14.6578712 0.8861026
+14.7087431 0 14.7087431 0.113897353 0 14.7087431 0.8861027
+14.759614 0 14.759614 0.113897376 0 14.759614 0.8861026
+14.8104858 0 14.8104858 0.113897361 0 14.8104858 0.8861026
+14.8613567 0 14.8613567 0.113897383 0 14.8613567 0.8861026
+14.9122286 0 14.9122286 0.113897361 0 14.9122286 0.8861026
+14.9631 0 14.9631 0.113897346 0 14.9631 0.8861027
+15.0139713 0 15.0139713 0.113897368 0 15.0139713 0.8861026
+15.0648432 0 15.0648432 0.113897346 0 15.0648432 0.8861027
+15.1157141 0 15.1157141 0.113897376 0 15.1157141 0.8861026
+15.1665859 0 15.1665859 0.113897353 0 15.1665859 0.8861026
+15.2174568 0 15.2174568 0.113897383 0 15.2174568 0.8861026
+15.2683287 0 15.2683287 0.113897361 0 15.2683287 0.8861026
+15.3192 0 15.3192 0.113897383 0 15.3192 0.8861026
+15.3700714 0 15.3700714 0.113897368 0 15.3700714 0.8861026
+15.4209433 0 15.4209433 0.113897346 0 15.4209433 0.8861027
+15.4718142 0 15.4718142 0.113897368 0 15.4718142 0.8861026
+15.522686 0 15.522686 0.113897353 0 15.522686 0.8861027
+15.5735569 0 15.5735569 0.113897376 0 15.5735569 0.8861026
+15.6244287 0 15.6244287 0.113897353 0 15.6244287 0.8861026
+15.6753 0 15.6753 0.113897383 0 15.6753 0.8861026
+15.7261715 0 15.7261715 0.113897361 0 15.7261715 0.8861026
+15.7770424 0 15.7770424 0.113897391 0 15.7770424 0.8861026
+15.8279142 0 15.8279142 0.113897368 0 15.8279142 0.8861026
+15.8787861 0 15.8787861 0.113897346 0 15.8787861 0.8861027
+15.929657 0 15.929657 0.113897376 0 15.929657 0.8861026
+15.9805288 0 15.9805288 0.113897353 0 15.9805288 0.8861027
+16.0314 0 16.0314 0.113897331 0 16.0314 0.8861027
+16.0822716 0 16.0822716 0.113897361 0 16.0822716 0.8861026
+16.1331425 0 16.1331425 0.113897383 0 16.1331425 0.8861026
+16.1840134 0 16.1840134 0.113897413 0 16.1840134 0.8861026
+16.2348862 0 16.2348862 0.113897339 0 16.2348862 0.8861027
+16.2857571 0 16.2857571 0.113897368 0 16.2857571 0.8861026
+16.336628 0 16.336628 0.113897391 0 16.336628 0.8861026
+16.3875 0 16.3875 0.113897324 0 16.3875 0.8861027
+16.4383717 0 16.4383717 0.113897353 0 16.4383717 0.8861027
+16.4892426 0 16.4892426 0.113897383 0 16.4892426 0.8861026
+16.5401134 0 16.5401134 0.113897406 0 16.5401134 0.8861026
+16.5909863 0 16.5909863 0.113897339 0 16.5909863 0.8861027
+16.6418571 0 16.6418571 0.113897361 0 16.6418571 0.8861026
+16.692728 0 16.692728 0.113897391 0 16.692728 0.8861026
+16.7436 0 16.7436 0.113897324 0 16.7436 0.8861027
+16.7944717 0 16.7944717 0.113897346 0 16.7944717 0.8861027
+16.8453426 0 16.8453426 0.113897376 0 16.8453426 0.8861026
+16.8962135 0 16.8962135 0.1138974 0 16.8962135 0.8861026
+16.9470863 0 16.9470863 0.113897331 0 16.9470863 0.8861027
+16.9979572 0 16.9979572 0.113897361 0 16.9979572 0.8861026
+17.0488281 0 17.0488281 0.113897383 0 17.0488281 0.8861026
+17.0997 0 17.0997 0.113897316 0 17.0997 0.8861027
+17.1505718 0 17.1505718 0.113897346 0 17.1505718 0.8861027
+17.2014427 0 17.2014427 0.113897368 0 17.2014427 0.8861026
+17.2523136 0 17.2523136 0.1138974 0 17.2523136 0.8861026
+17.3031864 0 17.3031864 0.113897331 0 17.3031864 0.8861027
+17.3540573 0 17.3540573 0.113897353 0 17.3540573 0.8861026
+17.4049282 0 17.4049282 0.113897383 0 17.4049282 0.8861026
+17.4558 0 17.4558 0.113897406 0 17.4558 0.8861026
+17.5066719 0 17.5066719 0.113897339 0 17.5066719 0.8861027
+17.5575428 0 17.5575428 0.113897368 0 17.5575428 0.8861026
+17.6084137 0 17.6084137 0.113897391 0 17.6084137 0.8861026
+17.6592865 0 17.6592865 0.113897324 0 17.6592865 0.8861027
+17.7101574 0 17.7101574 0.113897353 0 17.7101574 0.8861027
+17.7610283 0 17.7610283 0.113897376 0 17.7610283 0.8861026
+17.8119 0 17.8119 0.113897406 0 17.8119 0.8861026
+17.862772 0 17.862772 0.113897339 0 17.862772 0.8861027
+17.9136429 0 17.9136429 0.113897361 0 17.9136429 0.8861026
+17.9645138 0 17.9645138 0.113897391 0 17.9645138 0.8861026
+18.0153866 0 18.0153866 0.113897324 0 18.0153866 0.8861027
+18.0662575 0 18.0662575 0.113897346 0 18.0662575 0.8861027
+18.1171284 0 18.1171284 0.113897376 0 18.1171284 0.8861026
+18.168 0 18.168 0.1138974 0 18.168 0.8861026
+18.2188721 0 18.2188721 0.113897331 0 18.2188721 0.8861027
+18.269743 0 18.269743 0.113897361 0 18.269743 0.8861026
+18.3206139 0 18.3206139 0.113897383 0 18.3206139 0.8861026
+18.3714848 0 18.3714848 0.113897413 0 18.3714848 0.8861026
+18.4223576 0 18.4223576 0.113897346 0 18.4223576 0.8861027
+18.4732285 0 18.4732285 0.113897368 0 18.4732285 0.8861026
+18.5241 0 18.5241 0.1138974 0 18.5241 0.8861026
+18.5749722 0 18.5749722 0.113897331 0 18.5749722 0.8861027
+18.625843 0 18.625843 0.113897353 0 18.625843 0.8861026
+18.6767139 0 18.6767139 0.113897383 0 18.6767139 0.8861026
+18.7275848 0 18.7275848 0.113897406 0 18.7275848 0.8861026
+18.7784576 0 18.7784576 0.113897339 0 18.7784576 0.8861027
+18.8293285 0 18.8293285 0.113897368 0 18.8293285 0.8861026
+18.8802 0 18.8802 0.113897391 0 18.8802 0.8861026
+18.9310722 0 18.9310722 0.113897324 0 18.9310722 0.8861027
+18.9819431 0 18.9819431 0.113897353 0 18.9819431 0.8861027
+19.032814 0 19.032814 0.113897376 0 19.032814 0.8861026
+19.0836849 0 19.0836849 0.113897406 0 19.0836849 0.8861026
+19.1345577 0 19.1345577 0.113897331 0 19.1345577 0.8861027
+19.1854286 0 19.1854286 0.113897361 0 19.1854286 0.8861026
+19.2363 0 19.2363 0.113897391 0 19.2363 0.8861026
+19.2871723 0 19.2871723 0.113897316 0 19.2871723 0.8861027
+19.3380432 0 19.3380432 0.113897346 0 19.3380432 0.8861027
+19.3889141 0 19.3889141 0.113897376 0 19.3889141 0.8861026
+19.439785 0 19.439785 0.1138974 0 19.439785 0.8861026
+19.4906578 0 19.4906578 0.113897331 0 19.4906578 0.8861027
+19.5415287 0 19.5415287 0.113897361 0 19.5415287 0.8861026
+19.5924 0 19.5924 0.113897383 0 19.5924 0.8861026
+19.64327 0 19.64327 0.113897413 0 19.64327 0.8861026
+19.6941433 0 19.6941433 0.113897339 0 19.6941433 0.8861027
+19.7450142 0 19.7450142 0.113897368 0 19.7450142 0.8861026
+19.7958851 0 19.7958851 0.113897391 0 19.7958851 0.8861026
+19.8467579 0 19.8467579 0.113897324 0 19.8467579 0.8861027
+19.8976288 0 19.8976288 0.113897353 0 19.8976288 0.8861027
+19.9485 0 19.9485 0.113897376 0 19.9485 0.8861026
+19.99937 0 19.99937 0.113897406 0 19.99937 0.8861026
+20.0502434 0 20.0502434 0.113897339 0 20.0502434 0.8861027
+20.1011143 0 20.1011143 0.113897361 0 20.1011143 0.8861026
+20.1519852 0 20.1519852 0.113897391 0 20.1519852 0.8861026
+20.202858 0 20.202858 0.113897324 0 20.202858 0.8861027
+20.2537289 0 20.2537289 0.113897346 0 20.2537289 0.8861027
+20.3046 0 20.3046 0.113897376 0 20.3046 0.8861026
+20.35547 0 20.35547 0.1138974 0 20.35547 0.8861026
+20.4063435 0 20.4063435 0.113897331 0 20.4063435 0.8861027
+20.4572144 0 20.4572144 0.113897361 0 20.4572144 0.8861026
+20.5080853 0 20.5080853 0.113897383 0 20.5080853 0.8861026
+20.5589581 0 20.5589581 0.113897316 0 20.5589581 0.8861027
+20.6098289 0 20.6098289 0.113897346 0 20.6098289 0.8861027
+20.6607 0 20.6607 0.113897368 0 20.6607 0.8861026
+20.71157 0 20.71157 0.1138974 0 20.71157 0.8861026
+20.7624435 0 20.7624435 0.113897331 0 20.7624435 0.8861027
+20.8133144 0 20.8133144 0.113897353 0 20.8133144 0.8861026
+20.8641853 0 20.8641853 0.113897383 0 20.8641853 0.8861026
+20.9150562 0 20.9150562 0.113897406 0 20.9150562 0.8861026
+20.965929 0 20.965929 0.113897339 0 20.965929 0.8861027
+21.0168 0 21.0168 0.113897368 0 21.0168 0.8861026
+21.06767 0 21.06767 0.113897391 0 21.06767 0.8861026
+21.1185436 0 21.1185436 0.113897324 0 21.1185436 0.8861027
+21.1694145 0 21.1694145 0.113897353 0 21.1694145 0.8861027
+21.2202854 0 21.2202854 0.113897376 0 21.2202854 0.8861026
+21.2711563 0 21.2711563 0.113897406 0 21.2711563 0.8861026
+21.32203 0 21.32203 0.113897339 0 21.32203 0.8861027
+21.3729 0 21.3729 0.113897361 0 21.3729 0.8861026
+21.42377 0 21.42377 0.113897391 0 21.42377 0.8861026
+21.4746437 0 21.4746437 0.113897324 0 21.4746437 0.8861027
+21.5255146 0 21.5255146 0.113897346 0 21.5255146 0.8861027
+21.5763855 0 21.5763855 0.113897376 0 21.5763855 0.8861026
+21.6272564 0 21.6272564 0.1138974 0 21.6272564 0.8861026
+21.67813 0 21.67813 0.113897331 0 21.67813 0.8861027
+21.729 0 21.729 0.113897361 0 21.729 0.8861026
+21.779871 0 21.779871 0.113897383 0 21.779871 0.8861026
+21.8307438 0 21.8307438 0.113897316 0 21.8307438 0.8861027
+21.8816147 0 21.8816147 0.113897346 0 21.8816147 0.8861027
+21.9324856 0 21.9324856 0.113897368 0 21.9324856 0.8861026
+21.9833565 0 21.9833565 0.1138974 0 21.9833565 0.8861026
+22.03423 0 22.03423 0.113897331 0 22.03423 0.8861027
+22.0851 0 22.0851 0.113897353 0 22.0851 0.8861026
+22.1359711 0 22.1359711 0.113897383 0 22.1359711 0.8861026
+22.186842 0 22.186842 0.113897406 0 22.186842 0.8861026
+22.2377148 0 22.2377148 0.113897339 0 22.2377148 0.8861027
+22.2885857 0 22.2885857 0.113897368 0 22.2885857 0.8861026
+22.3394566 0 22.3394566 0.113897391 0 22.3394566 0.8861026
+22.39033 0 22.39033 0.113897324 0 22.39033 0.8861027
+22.4412 0 22.4412 0.113897353 0 22.4412 0.8861027
+22.4920712 0 22.4920712 0.113897376 0 22.4920712 0.8861026
+22.542942 0 22.542942 0.113897406 0 22.542942 0.8861026
+22.5938148 0 22.5938148 0.113897339 0 22.5938148 0.8861027
+22.6446857 0 22.6446857 0.113897361 0 22.6446857 0.8861026
+22.6955566 0 22.6955566 0.113897391 0 22.6955566 0.8861026
+22.74643 0 22.74643 0.113897324 0 22.74643 0.8861027
+22.7973 0 22.7973 0.113897346 0 22.7973 0.8861027
+22.8481712 0 22.8481712 0.113897376 0 22.8481712 0.8861026
+22.8990421 0 22.8990421 0.1138974 0 22.8990421 0.8861026
+22.9499149 0 22.9499149 0.113897331 0 22.9499149 0.8861027
+23.0007858 0 23.0007858 0.113897361 0 23.0007858 0.8861026
+23.0516567 0 23.0516567 0.113897383 0 23.0516567 0.8861026
+23.1025276 0 23.1025276 0.113897413 0 23.1025276 0.8861026
+23.1534 0 23.1534 0.113897346 0 23.1534 0.8861027
+23.2042713 0 23.2042713 0.113897368 0 23.2042713 0.8861026
+23.2551422 0 23.2551422 0.1138974 0 23.2551422 0.8861026
+23.306015 0 23.306015 0.113897331 0 23.306015 0.8861027
+23.3568859 0 23.3568859 0.113897353 0 23.3568859 0.8861026
+23.4077568 0 23.4077568 0.113897383 0 23.4077568 0.8861026
+23.4586277 0 23.4586277 0.113897406 0 23.4586277 0.8861026
+23.5095 0 23.5095 0.113897339 0 23.5095 0.8861027
+23.5603714 0 23.5603714 0.113897368 0 23.5603714 0.8861026
+23.6112423 0 23.6112423 0.113897391 0 23.6112423 0.8861026
+23.6621151 0 23.6621151 0.113897324 0 23.6621151 0.8861027
+23.712986 0 23.712986 0.113897353 0 23.712986 0.8861027
+23.7638569 0 23.7638569 0.113897376 0 23.7638569 0.8861026
+23.8147278 0 23.8147278 0.113897406 0 23.8147278 0.8861026
+23.8656 0 23.8656 0.113897331 0 23.8656 0.8861027
+23.9164715 0 23.9164715 0.113897361 0 23.9164715 0.8861026
+23.9673424 0 23.9673424 0.113897391 0 23.9673424 0.8861026
+24.0182152 0 24.0182152 0.113897316 0 24.0182152 0.8861027
+24.0690861 0 24.0690861 0.113897346 0 24.0690861 0.8861027
+24.119957 0 24.119957 0.113897376 0 24.119957 0.8861026
+24.1708279 0 24.1708279 0.1138974 0 24.1708279 0.8861026
+24.2217 0 24.2217 0.113897331 0 24.2217 0.8861027
+24.2725716 0 24.2725716 0.113897361 0 24.2725716 0.8861026
+24.3234425 0 24.3234425 0.113897383 0 24.3234425 0.8861026
+24.3743134 0 24.3743134 0.113897413 0 24.3743134 0.8861026
+24.4251862 0 24.4251862 0.113897339 0 24.4251862 0.8861027
+24.4760571 0 24.4760571 0.113897368 0 24.4760571 0.8861026
+24.5269279 0 24.5269279 0.113897391 0 24.5269279 0.8861026
+24.5778 0 24.5778 0.113897324 0 24.5778 0.8861027
+24.6286716 0 24.6286716 0.113897353 0 24.6286716 0.8861027
+24.6795425 0 24.6795425 0.113897376 0 24.6795425 0.8861026
+24.7304134 0 24.7304134 0.113897406 0 24.7304134 0.8861026
+24.7812862 0 24.7812862 0.113897339 0 24.7812862 0.8861027
+24.8321571 0 24.8321571 0.113897361 0 24.8321571 0.8861026
+24.883028 0 24.883028 0.113897391 0 24.883028 0.8861026
+24.9339 0 24.9339 0.113897324 0 24.9339 0.8861027
+24.9847717 0 24.9847717 0.113897346 0 24.9847717 0.8861027
+25.0356426 0 25.0356426 0.113897376 0 25.0356426 0.8861026
+25.0865135 0 25.0865135 0.1138974 0 25.0865135 0.8861026
+25.1373863 0 25.1373863 0.113897331 0 25.1373863 0.8861027
+25.1882572 0 25.1882572 0.113897361 0 25.1882572 0.8861026
+25.2391281 0 25.2391281 0.113897383 0 25.2391281 0.8861026
+25.29 0 25.29 0.113897316 0 25.29 0.8861027
+25.3408718 0 25.3408718 0.113897346 0 25.3408718 0.8861027
+25.3917427 0 25.3917427 0.113897368 0 25.3917427 0.8861026
+25.4426136 0 25.4426136 0.1138974 0 25.4426136 0.8861026
+25.4934864 0 25.4934864 0.113897331 0 25.4934864 0.8861027
+25.5443573 0 25.5443573 0.113897353 0 25.5443573 0.8861026
+25.5952282 0 25.5952282 0.113897383 0 25.5952282 0.8861026
+25.6461 0 25.6461 0.113897406 0 25.6461 0.8861026
+25.6969719 0 25.6969719 0.113897339 0 25.6969719 0.8861027
+25.7478428 0 25.7478428 0.113897368 0 25.7478428 0.8861026
+25.7987137 0 25.7987137 0.113897391 0 25.7987137 0.8861026
+25.8495865 0 25.8495865 0.113897324 0 25.8495865 0.8861027
+25.9004574 0 25.9004574 0.113897353 0 25.9004574 0.8861027
+25.9513283 0 25.9513283 0.113897376 0 25.9513283 0.8861026
+26.0022 0 26.0022 0.113897406 0 26.0022 0.8861026
+26.053072 0 26.053072 0.113897339 0 26.053072 0.8861027
+26.1039429 0 26.1039429 0.113897361 0 26.1039429 0.8861026
+26.1548138 0 26.1548138 0.113897391 0 26.1548138 0.8861026
+26.2056866 0 26.2056866 0.113897324 0 26.2056866 0.8861027
+26.2565575 0 26.2565575 0.113897346 0 26.2565575 0.8861027
+26.3074284 0 26.3074284 0.113897376 0 26.3074284 0.8861026
+26.3583 0 26.3583 0.1138974 0 26.3583 0.8861026
+26.4091721 0 26.4091721 0.113897331 0 26.4091721 0.8861027
+26.460043 0 26.460043 0.113897361 0 26.460043 0.8861026
+26.5109138 0 26.5109138 0.113897383 0 26.5109138 0.8861026
+26.5617867 0 26.5617867 0.113897316 0 26.5617867 0.8861027
+26.6126575 0 26.6126575 0.113897346 0 26.6126575 0.8861027
+26.6635284 0 26.6635284 0.113897368 0 26.6635284 0.8861026
+26.7144 0 26.7144 0.1138974 0 26.7144 0.8861026
+26.7652721 0 26.7652721 0.113897331 0 26.7652721 0.8861027
+26.816143 0 26.816143 0.113897353 0 26.816143 0.8861026
+26.8670139 0 26.8670139 0.113897383 0 26.8670139 0.8861026
+26.9178848 0 26.9178848 0.113897406 0 26.9178848 0.8861026
+26.9687576 0 26.9687576 0.113897339 0 26.9687576 0.8861027
+27.0196285 0 27.0196285 0.113897368 0 27.0196285 0.8861026
+27.0705 0 27.0705 0.113897391 0 27.0705 0.8861026
+27.1213722 0 27.1213722 0.113897324 0 27.1213722 0.8861027
+27.1722431 0 27.1722431 0.113897353 0 27.1722431 0.8861027
+27.223114 0 27.223114 0.113897376 0 27.223114 0.8861026
+27.2739849 0 27.2739849 0.113897406 0 27.2739849 0.8861026
+27.3248577 0 27.3248577 0.113897339 0 27.3248577 0.8861027
+27.3757286 0 27.3757286 0.113897361 0 27.3757286 0.8861026
+27.4266 0 27.4266 0.113897391 0 27.4266 0.8861026
+27.4774723 0 27.4774723 0.113897324 0 27.4774723 0.8861027
+27.5283432 0 27.5283432 0.113897346 0 27.5283432 0.8861027
+27.5792141 0 27.5792141 0.113897376 0 27.5792141 0.8861026
+27.630085 0 27.630085 0.1138974 0 27.630085 0.8861026
+27.6809578 0 27.6809578 0.113897331 0 27.6809578 0.8861027
+27.7318287 0 27.7318287 0.113897361 0 27.7318287 0.8861026
+27.7827 0 27.7827 0.113897383 0 27.7827 0.8861026
+27.83357 0 27.83357 0.113897413 0 27.83357 0.8861026
+27.8844433 0 27.8844433 0.113897346 0 27.8844433 0.8861027
+27.9353142 0 27.9353142 0.113897368 0 27.9353142 0.8861026
+27.9861851 0 27.9861851 0.1138974 0 27.9861851 0.8861026
+28.0370579 0 28.0370579 0.113897331 0 28.0370579 0.8861027
+28.0879288 0 28.0879288 0.113897353 0 28.0879288 0.8861026
+28.1388 0 28.1388 0.113897383 0 28.1388 0.8861026
+28.18967 0 28.18967 0.113897406 0 28.18967 0.8861026
+28.2405434 0 28.2405434 0.113897339 0 28.2405434 0.8861027
+28.2914143 0 28.2914143 0.113897368 0 28.2914143 0.8861026
+28.3422852 0 28.3422852 0.113897391 0 28.3422852 0.8861026
+28.393158 0 28.393158 0.113897324 0 28.393158 0.8861027
+28.4440289 0 28.4440289 0.113897353 0 28.4440289 0.8861027
+28.4949 0 28.4949 0.113897376 0 28.4949 0.8861026
+28.54577 0 28.54577 0.113897406 0 28.54577 0.8861026
+28.5966434 0 28.5966434 0.113897331 0 28.5966434 0.8861027
+28.6475143 0 28.6475143 0.113897361 0 28.6475143 0.8861026
+28.6983852 0 28.6983852 0.113897391 0 28.6983852 0.8861026
+28.749258 0 28.749258 0.113897316 0 28.749258 0.8861027
+28.8001289 0 28.8001289 0.113897346 0 28.8001289 0.8861027
+28.851 0 28.851 0.113897376 0 28.851 0.8861026
+28.90187 0 28.90187 0.1138974 0 28.90187 0.8861026
+28.9527435 0 28.9527435 0.113897331 0 28.9527435 0.8861027
+29.0036144 0 29.0036144 0.113897361 0 29.0036144 0.8861026
+29.0544853 0 29.0544853 0.113897383 0 29.0544853 0.8861026
+29.1053562 0 29.1053562 0.113897413 0 29.1053562 0.8861026
+29.156229 0 29.156229 0.113897339 0 29.156229 0.8861027
+29.2071 0 29.2071 0.113897368 0 29.2071 0.8861026
+29.25797 0 29.25797 0.113897391 0 29.25797 0.8861026
+29.3088436 0 29.3088436 0.113897324 0 29.3088436 0.8861027
+29.3597145 0 29.3597145 0.113897353 0 29.3597145 0.8861027
+29.4105854 0 29.4105854 0.113897376 0 29.4105854 0.8861026
+29.4614563 0 29.4614563 0.113897406 0 29.4614563 0.8861026
+29.51233 0 29.51233 0.113897339 0 29.51233 0.8861027
+29.5632 0 29.5632 0.113897361 0 29.5632 0.8861026
+29.61407 0 29.61407 0.113897391 0 29.61407 0.8861026
+29.6649437 0 29.6649437 0.113897324 0 29.6649437 0.8861027
+29.7158146 0 29.7158146 0.113897346 0 29.7158146 0.8861027
+29.7666855 0 29.7666855 0.113897376 0 29.7666855 0.8861026
+29.8175564 0 29.8175564 0.1138974 0 29.8175564 0.8861026
+29.86843 0 29.86843 0.113897331 0 29.86843 0.8861027
+29.9193 0 29.9193 0.113897361 0 29.9193 0.8861026
+29.970171 0 29.970171 0.113897383 0 29.970171 0.8861026
+30.0210438 0 30.0210438 0.113897316 0 30.0210438 0.8861027
+30.0719147 0 30.0719147 0.113897346 0 30.0719147 0.8861027
+30.1227856 0 30.1227856 0.113897368 0 30.1227856 0.8861026
+30.1736565 0 30.1736565 0.1138974 0 30.1736565 0.8861026
+30.22453 0 30.22453 0.113897331 0 30.22453 0.8861027
+30.2754 0 30.2754 0.113897353 0 30.2754 0.8861026
+30.3262711 0 30.3262711 0.113897383 0 30.3262711 0.8861026
+30.377142 0 30.377142 0.113897406 0 30.377142 0.8861026
+30.4280148 0 30.4280148 0.113897339 0 30.4280148 0.8861027
+30.4788857 0 30.4788857 0.113897368 0 30.4788857 0.8861026
+30.5297565 0 30.5297565 0.113897391 0 30.5297565 0.8861026
+30.58063 0 30.58063 0.113897324 0 30.58063 0.8861027
+30.6315 0 30.6315 0.113897353 0 30.6315 0.8861027
+30.6823711 0 30.6823711 0.113897376 0 30.6823711 0.8861026
+30.733242 0 30.733242 0.113897406 0 30.733242 0.8861026
+30.7841148 0 30.7841148 0.113897339 0 30.7841148 0.8861027
+30.8349857 0 30.8349857 0.113897361 0 30.8349857 0.8861026
+30.8858566 0 30.8858566 0.113897391 0 30.8858566 0.8861026
+30.93673 0 30.93673 0.113897324 0 30.93673 0.8861027
+30.9876 0 30.9876 0.113897346 0 30.9876 0.8861027
+31.0384712 0 31.0384712 0.113897376 0 31.0384712 0.8861026
+31.0893421 0 31.0893421 0.1138974 0 31.0893421 0.8861026
+31.1402149 0 31.1402149 0.113897331 0 31.1402149 0.8861027
+31.1910858 0 31.1910858 0.113897361 0 31.1910858 0.8861026
+31.2419567 0 31.2419567 0.113897383 0 31.2419567 0.8861026
+31.29283 0 31.29283 0.113897316 0 31.29283 0.8861027
+31.3437 0 31.3437 0.113897346 0 31.3437 0.8861027
+31.3945713 0 31.3945713 0.113897368 0 31.3945713 0.8861026
+31.4454422 0 31.4454422 0.1138974 0 31.4454422 0.8861026
+31.496315 0 31.496315 0.113897331 0 31.496315 0.8861027
+31.5471859 0 31.5471859 0.113897353 0 31.5471859 0.8861026
+31.5980568 0 31.5980568 0.113897383 0 31.5980568 0.8861026
+31.6489277 0 31.6489277 0.113897406 0 31.6489277 0.8861026
+31.6998 0 31.6998 0.113897339 0 31.6998 0.8861027
+31.7506714 0 31.7506714 0.113897368 0 31.7506714 0.8861026
+31.8015423 0 31.8015423 0.113897391 0 31.8015423 0.8861026
+31.8524151 0 31.8524151 0.113897324 0 31.8524151 0.8861027
+31.903286 0 31.903286 0.113897353 0 31.903286 0.8861027
+31.9541569 0 31.9541569 0.113897376 0 31.9541569 0.8861026
+32.0050278 0 32.0050278 0.113897406 0 32.0050278 0.8861026
+32.0559 0 32.0559 0.113897339 0 32.0559 0.8861027
+32.10677 0 32.10677 0.113897458 0 32.10677 0.886102557
+32.1576424 0 32.1576424 0.113897391 0 32.1576424 0.8861026
+32.2085152 0 32.2085152 0.113897316 0 32.2085152 0.8861027
+32.2593842 0 32.2593842 0.113897443 0 32.2593842 0.886102557
+32.310257 0 32.310257 0.113897368 0 32.310257 0.8861026
+32.36113 0 32.36113 0.1138973 0 32.36113 0.8861027
+32.412 0 32.412 0.11389742 0 32.412 0.886102557
+32.46287 0 32.46287 0.113897353 0 32.46287 0.8861026
+32.5137444 0 32.5137444 0.113897286 0 32.5137444 0.886102736
+32.5646133 0 32.5646133 0.113897406 0 32.5646133 0.8861026
+32.6154861 0 32.6154861 0.113897339 0 32.6154861 0.8861027
+32.66636 0 32.66636 0.113897271 0 32.66636 0.886102736
+32.7172279 0 32.7172279 0.1138974 0 32.7172279 0.8861026
+32.7681 0 32.7681 0.113897331 0 32.7681 0.8861027
+32.81897 0 32.81897 0.11389745 0 32.81897 0.886102557
+32.8698425 0 32.8698425 0.113897376 0 32.8698425 0.8861026
+32.9207153 0 32.9207153 0.113897309 0 32.9207153 0.8861027
+32.9715843 0 32.9715843 0.113897435 0 32.9715843 0.886102557
+33.0224571 0 33.0224571 0.113897361 0 33.0224571 0.8861026
+33.07333 0 33.07333 0.113897294 0 33.07333 0.8861027
+33.1242 0 33.1242 0.11389742 0 33.1242 0.886102557
+33.17507 0 33.17507 0.113897346 0 33.17507 0.8861027
+33.2259445 0 33.2259445 0.113897279 0 33.2259445 0.886102736
+33.2768135 0 33.2768135 0.113897406 0 33.2768135 0.8861026
+33.3276863 0 33.3276863 0.113897331 0 33.3276863 0.8861027
+33.3785553 0 33.3785553 0.113897458 0 33.3785553 0.886102557
+33.42943 0 33.42943 0.113897383 0 33.42943 0.8861026
+33.4803 0 33.4803 0.113897316 0 33.4803 0.8861027
+33.53117 0 33.53117 0.113897435 0 33.53117 0.886102557
+33.5820427 0 33.5820427 0.113897368 0 33.5820427 0.8861026
+33.6329155 0 33.6329155 0.1138973 0 33.6329155 0.8861027
+33.6837845 0 33.6837845 0.11389742 0 33.6837845 0.886102557
+33.7346573 0 33.7346573 0.113897353 0 33.7346573 0.8861026
+33.78553 0 33.78553 0.113897286 0 33.78553 0.886102736
+33.8364 0 33.8364 0.113897406 0 33.8364 0.8861026
+33.88727 0 33.88727 0.113897339 0 33.88727 0.8861027
+33.9381447 0 33.9381447 0.113897271 0 33.9381447 0.886102736
+33.9890137 0 33.9890137 0.1138974 0 33.9890137 0.8861026
+34.0398865 0 34.0398865 0.113897324 0 34.0398865 0.8861027
+34.0907555 0 34.0907555 0.11389745 0 34.0907555 0.886102557
+34.14163 0 34.14163 0.113897376 0 34.14163 0.8861026
+34.1925 0 34.1925 0.113897309 0 34.1925 0.8861027
+34.24337 0 34.24337 0.113897428 0 34.24337 0.886102557
+34.2942429 0 34.2942429 0.113897361 0 34.2942429 0.8861026
+34.3451157 0 34.3451157 0.113897294 0 34.3451157 0.8861027
+34.3959846 0 34.3959846 0.113897413 0 34.3959846 0.886102557
+34.4468575 0 34.4468575 0.113897346 0 34.4468575 0.8861027
+34.49773 0 34.49773 0.113897279 0 34.49773 0.886102736
+34.5486 0 34.5486 0.113897406 0 34.5486 0.8861026
+34.599472 0 34.599472 0.113897331 0 34.599472 0.8861027
+34.65034 0 34.65034 0.11389745 0 34.65034 0.886102557
+34.7012138 0 34.7012138 0.113897383 0 34.7012138 0.8861026
+34.7520866 0 34.7520866 0.113897316 0 34.7520866 0.8861027
+34.8029556 0 34.8029556 0.113897435 0 34.8029556 0.886102557
+34.85383 0 34.85383 0.113897368 0 34.85383 0.8861026
+34.9047 0 34.9047 0.1138973 0 34.9047 0.8861027
+34.95557 0 34.95557 0.11389742 0 34.95557 0.886102557
+35.006443 0 35.006443 0.113897353 0 35.006443 0.8861026
+35.0573158 0 35.0573158 0.113897286 0 35.0573158 0.886102736
+35.1081848 0 35.1081848 0.113897406 0 35.1081848 0.8861026
+35.1590576 0 35.1590576 0.113897339 0 35.1590576 0.8861027
+35.20993 0 35.20993 0.113897271 0 35.20993 0.886102736
+35.2608 0 35.2608 0.113897391 0 35.2608 0.8861026
+35.3116722 0 35.3116722 0.113897324 0 35.3116722 0.8861027
+35.36254 0 35.36254 0.113897443 0 35.36254 0.886102557
+35.413414 0 35.413414 0.113897376 0 35.413414 0.8861026
+35.4642868 0 35.4642868 0.113897309 0 35.4642868 0.8861027
+35.5151558 0 35.5151558 0.113897428 0 35.5151558 0.886102557
+35.56603 0 35.56603 0.113897361 0 35.56603 0.8861026
+35.6169 0 35.6169 0.113897294 0 35.6169 0.8861027
+35.66777 0 35.66777 0.113897413 0 35.66777 0.886102557
+35.7186432 0 35.7186432 0.113897346 0 35.7186432 0.8861027
+35.769516 0 35.769516 0.113897279 0 35.769516 0.886102736
+35.820385 0 35.820385 0.1138974 0 35.820385 0.8861026
+35.8712578 0 35.8712578 0.113897331 0 35.8712578 0.8861027
+35.9221268 0 35.9221268 0.11389745 0 35.9221268 0.886102557
+35.973 0 35.973 0.113897383 0 35.973 0.8861026
+36.0238724 0 36.0238724 0.113897316 0 36.0238724 0.8861027
+36.07474 0 36.07474 0.113897435 0 36.07474 0.886102557
+36.1256142 0 36.1256142 0.113897368 0 36.1256142 0.8861026
+36.176487 0 36.176487 0.1138973 0 36.176487 0.8861027
+36.227356 0 36.227356 0.11389742 0 36.227356 0.886102557
+36.27823 0 36.27823 0.113897353 0 36.27823 0.8861026
+36.3291 0 36.3291 0.113897286 0 36.3291 0.886102736
+36.37997 0 36.37997 0.113897406 0 36.37997 0.8861026
+36.4308434 0 36.4308434 0.113897339 0 36.4308434 0.8861027
+36.4817162 0 36.4817162 0.113897271 0 36.4817162 0.886102736
+36.5325851 0 36.5325851 0.113897391 0 36.5325851 0.8861026
+36.5834579 0 36.5834579 0.113897324 0 36.5834579 0.8861027
+36.6343269 0 36.6343269 0.113897443 0 36.6343269 0.886102557
+36.6852 0 36.6852 0.113897376 0 36.6852 0.8861026
+36.7360725 0 36.7360725 0.113897309 0 36.7360725 0.8861027
+36.78694 0 36.78694 0.113897428 0 36.78694 0.886102557
+36.8378143 0 36.8378143 0.113897361 0 36.8378143 0.8861026
+36.8886871 0 36.8886871 0.113897294 0 36.8886871 0.8861027
+36.9395561 0 36.9395561 0.113897413 0 36.9395561 0.886102557
+36.99043 0 36.99043 0.113897346 0 36.99043 0.8861027
+37.0413 0 37.0413 0.113897279 0 37.0413 0.886102736
+37.09217 0 37.09217 0.113897406 0 37.09217 0.8861026
+37.1430435 0 37.1430435 0.113897331 0 37.1430435 0.8861027
+37.1939125 0 37.1939125 0.11389745 0 37.1939125 0.886102557
+37.2447853 0 37.2447853 0.113897383 0 37.2447853 0.8861026
+37.29566 0 37.29566 0.113897316 0 37.29566 0.8861027
+37.3465271 0 37.3465271 0.113897435 0 37.3465271 0.886102557
+37.3974 0 37.3974 0.113897368 0 37.3974 0.8861026
+37.4482727 0 37.4482727 0.1138973 0 37.4482727 0.8861027
+37.49914 0 37.49914 0.11389742 0 37.49914 0.886102557
+37.5500145 0 37.5500145 0.113897353 0 37.5500145 0.8861026
+37.6008873 0 37.6008873 0.113897286 0 37.6008873 0.886102736
+37.6517563 0 37.6517563 0.113897406 0 37.6517563 0.8861026
+37.70263 0 37.70263 0.113897339 0 37.70263 0.8861027
+37.7535 0 37.7535 0.113897271 0 37.7535 0.886102736
+37.80437 0 37.80437 0.1138974 0 37.80437 0.8861026
+37.8552437 0 37.8552437 0.113897324 0 37.8552437 0.8861027
+37.9061127 0 37.9061127 0.11389745 0 37.9061127 0.886102557
+37.9569855 0 37.9569855 0.113897376 0 37.9569855 0.8861026
+38.00786 0 38.00786 0.113897309 0 38.00786 0.8861027
+38.0587273 0 38.0587273 0.113897428 0 38.0587273 0.886102557
+38.1096 0 38.1096 0.113897361 0 38.1096 0.8861026
+38.1604729 0 38.1604729 0.113897294 0 38.1604729 0.8861027
+38.21134 0 38.21134 0.113897413 0 38.21134 0.886102557
+38.2622147 0 38.2622147 0.113897346 0 38.2622147 0.8861027
+38.3130875 0 38.3130875 0.113897279 0 38.3130875 0.886102736
+38.3639565 0 38.3639565 0.113897406 0 38.3639565 0.8861026
+38.41483 0 38.41483 0.113897331 0 38.41483 0.8861027
+38.4657 0 38.4657 0.113897458 0 38.4657 0.886102557
+38.51657 0 38.51657 0.113897383 0 38.51657 0.8861026
+38.5674438 0 38.5674438 0.113897316 0 38.5674438 0.8861027
+38.6183128 0 38.6183128 0.113897435 0 38.6183128 0.886102557
+38.6691856 0 38.6691856 0.113897368 0 38.6691856 0.8861026
+38.72006 0 38.72006 0.1138973 0 38.72006 0.8861027
+38.7709274 0 38.7709274 0.11389742 0 38.7709274 0.886102557
+38.8218 0 38.8218 0.113897353 0 38.8218 0.8861026
+38.872673 0 38.872673 0.113897286 0 38.872673 0.886102736
+38.923542 0 38.923542 0.113897406 0 38.923542 0.8861026
+38.9744148 0 38.9744148 0.113897339 0 38.9744148 0.8861027
+39.0252838 0 39.0252838 0.113897458 0 39.0252838 0.886102557
+39.0761566 0 39.0761566 0.113897391 0 39.0761566 0.8861026
+39.12703 0 39.12703 0.113897324 0 39.12703 0.8861027
+39.1779 0 39.1779 0.113897443 0 39.1779 0.886102557
+39.22877 0 39.22877 0.113897376 0 39.22877 0.8861026
+39.279644 0 39.279644 0.113897309 0 39.279644 0.8861027
+39.330513 0 39.330513 0.113897428 0 39.330513 0.886102557
+39.3813858 0 39.3813858 0.113897361 0 39.3813858 0.8861026
+39.43226 0 39.43226 0.113897294 0 39.43226 0.886102736
+39.4831276 0 39.4831276 0.113897413 0 39.4831276 0.8861026
+39.534 0 39.534 0.113897346 0 39.534 0.8861027
+39.5848732 0 39.5848732 0.113897279 0 39.5848732 0.886102736
+39.6357422 0 39.6357422 0.1138974 0 39.6357422 0.8861026
+39.686615 0 39.686615 0.113897331 0 39.686615 0.8861027
+39.737484 0 39.737484 0.11389745 0 39.737484 0.886102557
+39.7883568 0 39.7883568 0.113897383 0 39.7883568 0.8861026
+39.83923 0 39.83923 0.113897316 0 39.83923 0.8861027
+39.8901 0 39.8901 0.113897435 0 39.8901 0.886102557
+39.94097 0 39.94097 0.113897368 0 39.94097 0.8861026
+39.9918442 0 39.9918442 0.1138973 0 39.9918442 0.8861027
+40.0427132 0 40.0427132 0.11389742 0 40.0427132 0.886102557
+40.093586 0 40.093586 0.113897353 0 40.093586 0.8861027
+40.14446 0 40.14446 0.113897286 0 40.14446 0.886102736
+40.1953278 0 40.1953278 0.113897406 0 40.1953278 0.8861026
+40.2462 0 40.2462 0.113897339 0 40.2462 0.8861027
+40.29707 0 40.29707 0.113897458 0 40.29707 0.886102557
+40.3479424 0 40.3479424 0.113897391 0 40.3479424 0.8861026
+40.3988152 0 40.3988152 0.113897324 0 40.3988152 0.8861027
+40.4496841 0 40.4496841 0.113897443 0 40.4496841 0.886102557
+40.5005569 0 40.5005569 0.113897376 0 40.5005569 0.8861026
+40.55143 0 40.55143 0.1138973 0 40.55143 0.8861027
+40.6023 0 40.6023 0.113897428 0 40.6023 0.886102557
+40.65317 0 40.65317 0.113897353 0 40.65317 0.8861026
+40.7040443 0 40.7040443 0.113897286 0 40.7040443 0.886102736
+40.7549133 0 40.7549133 0.113897413 0 40.7549133 0.8861026
+40.8057861 0 40.8057861 0.113897346 0 40.8057861 0.8861027
+40.85666 0 40.85666 0.113897279 0 40.85666 0.886102736
+40.9075279 0 40.9075279 0.1138974 0 40.9075279 0.8861026
+40.9584 0 40.9584 0.113897331 0 40.9584 0.8861027
+41.00927 0 41.00927 0.11389745 0 41.00927 0.886102557
+41.0601425 0 41.0601425 0.113897383 0 41.0601425 0.8861026
+41.1110153 0 41.1110153 0.113897309 0 41.1110153 0.8861027
+41.1618843 0 41.1618843 0.113897435 0 41.1618843 0.886102557
+41.2127571 0 41.2127571 0.113897361 0 41.2127571 0.8861026
+41.26363 0 41.26363 0.113897294 0 41.26363 0.8861027
+41.3145 0 41.3145 0.11389742 0 41.3145 0.886102557
+41.36537 0 41.36537 0.113897346 0 41.36537 0.8861027
+41.4162445 0 41.4162445 0.113897286 0 41.4162445 0.886102736
+41.4671135 0 41.4671135 0.113897406 0 41.4671135 0.8861026
+41.5179863 0 41.5179863 0.113897339 0 41.5179863 0.8861027
+41.5688553 0 41.5688553 0.113897458 0 41.5688553 0.886102557
+41.6197281 0 41.6197281 0.113897383 0 41.6197281 0.8861026
+41.6706 0 41.6706 0.113897316 0 41.6706 0.8861027
+41.72147 0 41.72147 0.113897443 0 41.72147 0.886102557
+41.7723427 0 41.7723427 0.113897368 0 41.7723427 0.8861026
+41.8232155 0 41.8232155 0.1138973 0 41.8232155 0.8861027
+41.8740845 0 41.8740845 0.11389742 0 41.8740845 0.886102557
+41.9249573 0 41.9249573 0.113897353 0 41.9249573 0.8861026
+41.97583 0 41.97583 0.113897286 0 41.97583 0.886102736
+42.0267 0 42.0267 0.113897406 0 42.0267 0.8861026
+42.07757 0 42.07757 0.113897339 0 42.07757 0.8861027
+42.1284447 0 42.1284447 0.113897271 0 42.1284447 0.886102736
+42.1793137 0 42.1793137 0.1138974 0 42.1793137 0.8861026
+42.2301865 0 42.2301865 0.113897331 0 42.2301865 0.8861027
+42.2810555 0 42.2810555 0.11389745 0 42.2810555 0.886102557
+42.33193 0 42.33193 0.113897376 0 42.33193 0.8861026
+42.3828 0 42.3828 0.113897309 0 42.3828 0.8861027
+42.43367 0 42.43367 0.113897428 0 42.43367 0.886102557
+42.4845428 0 42.4845428 0.113897361 0 42.4845428 0.8861026
+42.5354156 0 42.5354156 0.113897294 0 42.5354156 0.8861027
+42.5862846 0 42.5862846 0.113897413 0 42.5862846 0.886102557
+42.6371574 0 42.6371574 0.113897346 0 42.6371574 0.8861027
+42.68803 0 42.68803 0.113897279 0 42.68803 0.886102736
+42.7389 0 42.7389 0.113897406 0 42.7389 0.8861026
+42.789772 0 42.789772 0.113897331 0 42.789772 0.8861027
+42.84064 0 42.84064 0.11389745 0 42.84064 0.886102557
+42.8915138 0 42.8915138 0.113897383 0 42.8915138 0.8861026
+42.9423866 0 42.9423866 0.113897316 0 42.9423866 0.8861027
+42.9932556 0 42.9932556 0.113897435 0 42.9932556 0.886102557
+43.04413 0 43.04413 0.113897368 0 43.04413 0.8861026
+43.095 0 43.095 0.1138973 0 43.095 0.8861027
+43.14587 0 43.14587 0.11389742 0 43.14587 0.886102557
+43.196743 0 43.196743 0.113897353 0 43.196743 0.8861027
+43.2476158 0 43.2476158 0.113897286 0 43.2476158 0.886102736
+43.2984848 0 43.2984848 0.113897406 0 43.2984848 0.8861026
+43.3493576 0 43.3493576 0.113897339 0 43.3493576 0.8861027
+43.40023 0 43.40023 0.113897271 0 43.40023 0.886102736
+43.4511 0 43.4511 0.113897391 0 43.4511 0.8861026
+43.5019722 0 43.5019722 0.113897324 0 43.5019722 0.8861027
+43.55284 0 43.55284 0.113897443 0 43.55284 0.886102557
+43.603714 0 43.603714 0.113897376 0 43.603714 0.8861026
+43.6545868 0 43.6545868 0.113897309 0 43.6545868 0.8861027
+43.7054558 0 43.7054558 0.113897428 0 43.7054558 0.886102557
+43.75633 0 43.75633 0.113897361 0 43.75633 0.8861026
+43.8072 0 43.8072 0.113897294 0 43.8072 0.8861027
+43.85807 0 43.85807 0.113897413 0 43.85807 0.886102557
+43.9089432 0 43.9089432 0.113897346 0 43.9089432 0.8861027
+43.959816 0 43.959816 0.113897279 0 43.959816 0.886102736
+44.010685 0 44.010685 0.1138974 0 44.010685 0.8861026
+44.0615578 0 44.0615578 0.113897331 0 44.0615578 0.8861027
+44.1124268 0 44.1124268 0.11389745 0 44.1124268 0.886102557
+44.1633 0 44.1633 0.113897383 0 44.1633 0.8861026
+44.2141724 0 44.2141724 0.113897316 0 44.2141724 0.8861027
+44.26504 0 44.26504 0.113897435 0 44.26504 0.886102557
+44.3159142 0 44.3159142 0.113897368 0 44.3159142 0.8861026
+44.366787 0 44.366787 0.1138973 0 44.366787 0.8861027
+44.4176559 0 44.4176559 0.11389742 0 44.4176559 0.886102557
+44.46853 0 44.46853 0.113897353 0 44.46853 0.8861027
+44.5194 0 44.5194 0.113897286 0 44.5194 0.886102736
+44.57027 0 44.57027 0.113897406 0 44.57027 0.8861026
+44.6211433 0 44.6211433 0.113897339 0 44.6211433 0.8861027
+44.6720161 0 44.6720161 0.113897271 0 44.6720161 0.886102736
+44.7228851 0 44.7228851 0.113897391 0 44.7228851 0.8861026
+44.7737579 0 44.7737579 0.113897324 0 44.7737579 0.8861027
+44.8246269 0 44.8246269 0.113897443 0 44.8246269 0.886102557
+44.8755 0 44.8755 0.113897376 0 44.8755 0.8861026
+44.9263725 0 44.9263725 0.113897309 0 44.9263725 0.8861027
+44.97724 0 44.97724 0.113897428 0 44.97724 0.886102557
+45.0281143 0 45.0281143 0.113897361 0 45.0281143 0.8861026
+45.0789871 0 45.0789871 0.113897294 0 45.0789871 0.8861027
+45.1298561 0 45.1298561 0.113897413 0 45.1298561 0.886102557
+45.18073 0 45.18073 0.113897346 0 45.18073 0.8861027
+45.2316 0 45.2316 0.113897279 0 45.2316 0.886102736
+45.28247 0 45.28247 0.1138974 0 45.28247 0.8861026
+45.3333435 0 45.3333435 0.113897331 0 45.3333435 0.8861027
+45.3842125 0 45.3842125 0.11389745 0 45.3842125 0.886102557
+45.4350853 0 45.4350853 0.113897383 0 45.4350853 0.8861026
+45.48596 0 45.48596 0.113897316 0 45.48596 0.8861027
+45.5368271 0 45.5368271 0.113897435 0 45.5368271 0.886102557
+45.5877 0 45.5877 0.113897368 0 45.5877 0.8861026
+45.6385727 0 45.6385727 0.1138973 0 45.6385727 0.8861027
+45.68944 0 45.68944 0.11389742 0 45.68944 0.886102557
+45.7403145 0 45.7403145 0.113897353 0 45.7403145 0.8861027
+45.7911873 0 45.7911873 0.113897286 0 45.7911873 0.886102736
+45.8420563 0 45.8420563 0.113897406 0 45.8420563 0.8861026
+45.89293 0 45.89293 0.113897339 0 45.89293 0.8861027
+45.9438 0 45.9438 0.113897271 0 45.9438 0.886102736
+45.99467 0 45.99467 0.1138974 0 45.99467 0.8861026
+46.0455437 0 46.0455437 0.113897324 0 46.0455437 0.8861027
+46.0964127 0 46.0964127 0.11389745 0 46.0964127 0.886102557
+46.1472855 0 46.1472855 0.113897376 0 46.1472855 0.8861026
+46.19816 0 46.19816 0.113897309 0 46.19816 0.8861027
+46.2490273 0 46.2490273 0.113897428 0 46.2490273 0.886102557
+46.2999 0 46.2999 0.113897361 0 46.2999 0.8861026
+46.3507729 0 46.3507729 0.113897294 0 46.3507729 0.8861027
+46.40164 0 46.40164 0.113897413 0 46.40164 0.886102557
+46.4525146 0 46.4525146 0.113897346 0 46.4525146 0.8861027
+46.5033875 0 46.5033875 0.113897279 0 46.5033875 0.886102736
+46.5542564 0 46.5542564 0.1138974 0 46.5542564 0.8861026
+46.60513 0 46.60513 0.113897331 0 46.60513 0.8861027
+46.656 0 46.656 0.11389745 0 46.656 0.886102557
+46.70687 0 46.70687 0.113897383 0 46.70687 0.8861026
+46.7577438 0 46.7577438 0.113897316 0 46.7577438 0.8861027
+46.8086128 0 46.8086128 0.113897435 0 46.8086128 0.886102557
+46.8594856 0 46.8594856 0.113897368 0 46.8594856 0.8861026
+46.91036 0 46.91036 0.1138973 0 46.91036 0.8861027
+46.9612274 0 46.9612274 0.11389742 0 46.9612274 0.886102557
+47.0121 0 47.0121 0.113897353 0 47.0121 0.8861026
+47.062973 0 47.062973 0.113897286 0 47.062973 0.886102736
+47.113842 0 47.113842 0.113897406 0 47.113842 0.8861026
+47.1647148 0 47.1647148 0.113897339 0 47.1647148 0.8861027
+47.2155876 0 47.2155876 0.113897271 0 47.2155876 0.886102736
+47.2664566 0 47.2664566 0.1138974 0 47.2664566 0.8861026
+47.31733 0 47.31733 0.113897331 0 47.31733 0.8861027
+47.3682 0 47.3682 0.11389745 0 47.3682 0.886102557
+47.41907 0 47.41907 0.113897376 0 47.41907 0.8861026
+47.469944 0 47.469944 0.113897309 0 47.469944 0.8861027
+47.520813 0 47.520813 0.113897428 0 47.520813 0.886102557
+47.5716858 0 47.5716858 0.113897361 0 47.5716858 0.8861026
+47.62256 0 47.62256 0.113897294 0 47.62256 0.8861027
+47.6734276 0 47.6734276 0.113897413 0 47.6734276 0.886102557
+47.7243 0 47.7243 0.113897346 0 47.7243 0.8861027
+47.7751732 0 47.7751732 0.113897279 0 47.7751732 0.886102736
+47.8260422 0 47.8260422 0.113897406 0 47.8260422 0.8861026
+47.876915 0 47.876915 0.113897331 0 47.876915 0.8861027
+47.927784 0 47.927784 0.113897458 0 47.927784 0.886102557
+47.9786568 0 47.9786568 0.113897383 0 47.9786568 0.8861026
+48.02953 0 48.02953 0.113897316 0 48.02953 0.8861027
+48.0804 0 48.0804 0.113897435 0 48.0804 0.886102557
+48.13127 0 48.13127 0.113897368 0 48.13127 0.8861026
+48.1821442 0 48.1821442 0.1138973 0 48.1821442 0.8861027
+48.2330132 0 48.2330132 0.11389742 0 48.2330132 0.886102557
+48.283886 0 48.283886 0.113897353 0 48.283886 0.8861026
+48.33476 0 48.33476 0.113897286 0 48.33476 0.886102736
+48.3856277 0 48.3856277 0.113897413 0 48.3856277 0.8861026
+48.4365 0 48.4365 0.113897339 0 48.4365 0.8861027
+48.48737 0 48.48737 0.113897458 0 48.48737 0.886102557
+48.5382423 0 48.5382423 0.113897391 0 48.5382423 0.8861026
+48.5891151 0 48.5891151 0.113897324 0 48.5891151 0.8861027
+48.6399841 0 48.6399841 0.113897443 0 48.6399841 0.886102557
+48.6908569 0 48.6908569 0.113897376 0 48.6908569 0.8861026
+48.74173 0 48.74173 0.113897309 0 48.74173 0.8861027
+48.7926 0 48.7926 0.113897428 0 48.7926 0.886102557
+48.84347 0 48.84347 0.113897361 0 48.84347 0.8861026
+48.8943443 0 48.8943443 0.113897294 0 48.8943443 0.886102736
+48.9452133 0 48.9452133 0.113897413 0 48.9452133 0.886102557
+48.9960861 0 48.9960861 0.113897346 0 48.9960861 0.8861027
+49.04696 0 49.04696 0.113897279 0 49.04696 0.886102736
+49.0978279 0 49.0978279 0.1138974 0 49.0978279 0.8861026
+49.1487 0 49.1487 0.113897331 0 49.1487 0.8861027
+49.19957 0 49.19957 0.11389745 0 49.19957 0.886102557
+49.2504425 0 49.2504425 0.113897383 0 49.2504425 0.8861026
+49.3013153 0 49.3013153 0.113897316 0 49.3013153 0.8861027
+49.3521843 0 49.3521843 0.113897435 0 49.3521843 0.886102557
+49.4030571 0 49.4030571 0.113897368 0 49.4030571 0.8861026
+49.45393 0 49.45393 0.1138973 0 49.45393 0.8861027
+49.5048 0 49.5048 0.11389742 0 49.5048 0.886102557
+49.55567 0 49.55567 0.113897353 0 49.55567 0.8861027
+49.6065445 0 49.6065445 0.113897286 0 49.6065445 0.886102736
+49.6574135 0 49.6574135 0.113897406 0 49.6574135 0.8861026
+49.7082863 0 49.7082863 0.113897339 0 49.7082863 0.8861027
+49.7591553 0 49.7591553 0.113897458 0 49.7591553 0.886102557
+49.8100281 0 49.8100281 0.113897391 0 49.8100281 0.8861026
+49.8609 0 49.8609 0.113897324 0 49.8609 0.8861027
+49.91177 0 49.91177 0.113897443 0 49.91177 0.886102557
+49.9626427 0 49.9626427 0.113897376 0 49.9626427 0.8861026
+50.0135155 0 50.0135155 0.1138973 0 50.0135155 0.8861027
+50.0643845 0 50.0643845 0.113897428 0 50.0643845 0.886102557
+50.1152573 0 50.1152573 0.113897353 0 50.1152573 0.8861026
+50.16613 0 50.16613 0.113897286 0 50.16613 0.886102736
+50.217 0 50.217 0.113897413 0 50.217 0.8861026
+50.26787 0 50.26787 0.113897346 0 50.26787 0.8861027
+50.3187447 0 50.3187447 0.113897279 0 50.3187447 0.886102736
+50.3696136 0 50.3696136 0.1138974 0 50.3696136 0.8861026
+50.4204865 0 50.4204865 0.113897331 0 50.4204865 0.8861027
+50.4713554 0 50.4713554 0.11389745 0 50.4713554 0.886102557
+50.52223 0 50.52223 0.113897383 0 50.52223 0.8861026
+50.5731 0 50.5731 0.113897309 0 50.5731 0.8861027
+50.62397 0 50.62397 0.113897435 0 50.62397 0.886102557
+50.6748428 0 50.6748428 0.113897361 0 50.6748428 0.8861026
+50.7257156 0 50.7257156 0.113897294 0 50.7257156 0.8861027
+50.7765846 0 50.7765846 0.11389742 0 50.7765846 0.886102557
+50.8274574 0 50.8274574 0.113897346 0 50.8274574 0.8861027
+50.87833 0 50.87833 0.113897279 0 50.87833 0.886102736
+50.9292 0 50.9292 0.113897406 0 50.9292 0.8861026
+50.980072 0 50.980072 0.113897339 0 50.980072 0.8861027
+51.03094 0 51.03094 0.113897458 0 51.03094 0.886102557
+51.0818138 0 51.0818138 0.113897383 0 51.0818138 0.8861026
+51.1326866 0 51.1326866 0.113897316 0 51.1326866 0.8861027
+51.1835556 0 51.1835556 0.113897443 0 51.1835556 0.886102557
+51.23443 0 51.23443 0.113897368 0 51.23443 0.8861026
+51.2853 0 51.2853 0.1138973 0 51.2853 0.8861027
+51.33617 0 51.33617 0.11389742 0 51.33617 0.886102557
+51.387043 0 51.387043 0.113897353 0 51.387043 0.8861026
+51.4379158 0 51.4379158 0.113897286 0 51.4379158 0.886102736
+51.4887848 0 51.4887848 0.113897406 0 51.4887848 0.8861026
+51.5396576 0 51.5396576 0.113897339 0 51.5396576 0.8861027
+51.59053 0 51.59053 0.113897271 0 51.59053 0.886102736
+51.6414 0 51.6414 0.1138974 0 51.6414 0.8861026
+51.6922722 0 51.6922722 0.113897331 0 51.6922722 0.8861027
+51.74314 0 51.74314 0.11389745 0 51.74314 0.886102557
+51.794014 0 51.794014 0.113897376 0 51.794014 0.8861026
+51.8448868 0 51.8448868 0.113897309 0 51.8448868 0.8861027
+51.8957558 0 51.8957558 0.113897428 0 51.8957558 0.886102557
+51.94663 0 51.94663 0.113897361 0 51.94663 0.8861026
+51.9975 0 51.9975 0.113897294 0 51.9975 0.8861027
+52.04837 0 52.04837 0.113897413 0 52.04837 0.886102557
+52.0992432 0 52.0992432 0.113897346 0 52.0992432 0.8861027
+52.150116 0 52.150116 0.113897279 0 52.150116 0.886102736
+52.200985 0 52.200985 0.113897406 0 52.200985 0.8861026
+52.2518578 0 52.2518578 0.113897331 0 52.2518578 0.8861027
+52.3027267 0 52.3027267 0.11389745 0 52.3027267 0.886102557
+52.3536 0 52.3536 0.113897383 0 52.3536 0.8861026
+52.4044724 0 52.4044724 0.113897316 0 52.4044724 0.8861027
+52.45534 0 52.45534 0.113897435 0 52.45534 0.886102557
+52.5062141 0 52.5062141 0.113897368 0 52.5062141 0.8861026
+52.5570869 0 52.5570869 0.1138973 0 52.5570869 0.8861027
+52.6079559 0 52.6079559 0.11389742 0 52.6079559 0.886102557
+52.65883 0 52.65883 0.113897353 0 52.65883 0.8861027
+52.7097 0 52.7097 0.113897286 0 52.7097 0.886102736
+52.76057 0 52.76057 0.113897406 0 52.76057 0.8861026
+52.8114433 0 52.8114433 0.113897339 0 52.8114433 0.8861027
+52.8623161 0 52.8623161 0.113897271 0 52.8623161 0.886102736
+52.9131851 0 52.9131851 0.113897391 0 52.9131851 0.8861026
+52.9640579 0 52.9640579 0.113897324 0 52.9640579 0.8861027
+53.0149269 0 53.0149269 0.113897443 0 53.0149269 0.886102557
+53.0658 0 53.0658 0.113897376 0 53.0658 0.8861026
+53.1166725 0 53.1166725 0.113897309 0 53.1166725 0.8861027
+53.16754 0 53.16754 0.113897428 0 53.16754 0.886102557
+53.2184143 0 53.2184143 0.113897361 0 53.2184143 0.8861026
+53.2692871 0 53.2692871 0.113897294 0 53.2692871 0.8861027
+53.3201561 0 53.3201561 0.113897413 0 53.3201561 0.886102557
+53.37103 0 53.37103 0.113897346 0 53.37103 0.8861027
+53.4219 0 53.4219 0.113897279 0 53.4219 0.886102736
+53.47277 0 53.47277 0.1138974 0 53.47277 0.8861026
+53.5236435 0 53.5236435 0.113897331 0 53.5236435 0.8861027
+53.5745125 0 53.5745125 0.11389745 0 53.5745125 0.886102557
+53.6253853 0 53.6253853 0.113897383 0 53.6253853 0.8861026
+53.6762581 0 53.6762581 0.113897316 0 53.6762581 0.8861027
+53.7271271 0 53.7271271 0.113897435 0 53.7271271 0.886102557
+53.778 0 53.778 0.113897368 0 53.778 0.8861026
+53.8288727 0 53.8288727 0.1138973 0 53.8288727 0.8861027
+53.87974 0 53.87974 0.11389742 0 53.87974 0.886102557
+53.9306145 0 53.9306145 0.113897353 0 53.9306145 0.8861027
+53.9814873 0 53.9814873 0.113897286 0 53.9814873 0.886102736
+54.0323563 0 54.0323563 0.113897406 0 54.0323563 0.8861026
+54.08323 0 54.08323 0.113897339 0 54.08323 0.8861027
+54.1341 0 54.1341 0.113897271 0 54.1341 0.886102736
+54.18497 0 54.18497 0.113897391 0 54.18497 0.8861026
+54.2358437 0 54.2358437 0.113897324 0 54.2358437 0.8861027
+54.2867126 0 54.2867126 0.113897443 0 54.2867126 0.886102557
+54.3375854 0 54.3375854 0.113897376 0 54.3375854 0.8861026
+54.38846 0 54.38846 0.113897309 0 54.38846 0.8861027
+54.4393272 0 54.4393272 0.113897428 0 54.4393272 0.886102557
+54.4902 0 54.4902 0.113897361 0 54.4902 0.8861026
+54.5410728 0 54.5410728 0.113897294 0 54.5410728 0.8861027
+54.59194 0 54.59194 0.113897413 0 54.59194 0.886102557
+54.6428146 0 54.6428146 0.113897346 0 54.6428146 0.8861027
+54.6936874 0 54.6936874 0.113897279 0 54.6936874 0.886102736
+54.7445564 0 54.7445564 0.1138974 0 54.7445564 0.8861026
+54.79543 0 54.79543 0.113897331 0 54.79543 0.8861027
+54.8463 0 54.8463 0.11389745 0 54.8463 0.886102557
+54.89717 0 54.89717 0.113897383 0 54.89717 0.8861026
+54.9480438 0 54.9480438 0.113897316 0 54.9480438 0.8861027
+54.9989128 0 54.9989128 0.113897435 0 54.9989128 0.886102557
+55.0497856 0 55.0497856 0.113897368 0 55.0497856 0.8861026
+55.10066 0 55.10066 0.1138973 0 55.10066 0.8861027
+55.1515274 0 55.1515274 0.11389742 0 55.1515274 0.886102557
+55.2024 0 55.2024 0.113897353 0 55.2024 0.8861027
+55.253273 0 55.253273 0.113897286 0 55.253273 0.886102736
+55.304142 0 55.304142 0.113897406 0 55.304142 0.8861026
+55.3550148 0 55.3550148 0.113897339 0 55.3550148 0.8861027
+55.4058876 0 55.4058876 0.113897271 0 55.4058876 0.886102736
+55.4567566 0 55.4567566 0.1138974 0 55.4567566 0.8861026
+55.50763 0 55.50763 0.113897324 0 55.50763 0.8861027
+55.5585 0 55.5585 0.11389745 0 55.5585 0.886102557
+55.60937 0 55.60937 0.113897376 0 55.60937 0.8861026
+55.660244 0 55.660244 0.113897309 0 55.660244 0.8861027
+55.711113 0 55.711113 0.113897428 0 55.711113 0.886102557
+55.7619858 0 55.7619858 0.113897361 0 55.7619858 0.8861026
+55.81286 0 55.81286 0.113897294 0 55.81286 0.8861027
+55.8637276 0 55.8637276 0.113897413 0 55.8637276 0.886102557
+55.9146 0 55.9146 0.113897346 0 55.9146 0.8861027
+55.9654732 0 55.9654732 0.113897279 0 55.9654732 0.886102736
+56.0163422 0 56.0163422 0.1138974 0 56.0163422 0.8861026
+56.067215 0 56.067215 0.113897331 0 56.067215 0.8861027
+56.118084 0 56.118084 0.11389745 0 56.118084 0.886102557
+56.1689568 0 56.1689568 0.113897383 0 56.1689568 0.8861026
+56.21983 0 56.21983 0.113897316 0 56.21983 0.8861027
+56.2707 0 56.2707 0.113897435 0 56.2707 0.886102557
+56.32157 0 56.32157 0.113897368 0 56.32157 0.8861026
+56.3724442 0 56.3724442 0.1138973 0 56.3724442 0.8861027
+56.4233131 0 56.4233131 0.11389742 0 56.4233131 0.886102557
+56.4741859 0 56.4741859 0.113897353 0 56.4741859 0.8861026
+56.52506 0 56.52506 0.113897286 0 56.52506 0.886102736
+56.5759277 0 56.5759277 0.113897406 0 56.5759277 0.8861026
+56.6268 0 56.6268 0.113897339 0 56.6268 0.8861027
+56.67767 0 56.67767 0.113897458 0 56.67767 0.886102557
+56.7285423 0 56.7285423 0.113897391 0 56.7285423 0.8861026
+56.7794151 0 56.7794151 0.113897324 0 56.7794151 0.8861027
+56.8302841 0 56.8302841 0.113897443 0 56.8302841 0.886102557
+56.8811569 0 56.8811569 0.113897376 0 56.8811569 0.8861026
+56.93203 0 56.93203 0.113897309 0 56.93203 0.8861027
+56.9829 0 56.9829 0.113897428 0 56.9829 0.886102557
+57.03377 0 57.03377 0.113897361 0 57.03377 0.8861026
+57.0846443 0 57.0846443 0.113897294 0 57.0846443 0.886102736
+57.1355133 0 57.1355133 0.113897413 0 57.1355133 0.8861026
+57.1863861 0 57.1863861 0.113897346 0 57.1863861 0.8861027
+57.23726 0 57.23726 0.113897279 0 57.23726 0.886102736
+57.2881279 0 57.2881279 0.1138974 0 57.2881279 0.8861026
+57.339 0 57.339 0.113897331 0 57.339 0.8861027
+57.38987 0 57.38987 0.11389745 0 57.38987 0.886102557
+57.4407425 0 57.4407425 0.113897383 0 57.4407425 0.8861026
+57.4916153 0 57.4916153 0.113897316 0 57.4916153 0.8861027
+57.5424843 0 57.5424843 0.113897435 0 57.5424843 0.886102557
+57.5933571 0 57.5933571 0.113897368 0 57.5933571 0.8861026
+57.64423 0 57.64423 0.1138973 0 57.64423 0.8861027
+57.6951 0 57.6951 0.11389742 0 57.6951 0.886102557
+57.74597 0 57.74597 0.113897353 0 57.74597 0.8861027
+57.7968445 0 57.7968445 0.113897286 0 57.7968445 0.886102736
+57.8477135 0 57.8477135 0.113897406 0 57.8477135 0.8861026
+57.8985863 0 57.8985863 0.113897339 0 57.8985863 0.8861027
+57.9494553 0 57.9494553 0.113897458 0 57.9494553 0.886102557
+58.0003281 0 58.0003281 0.113897391 0 58.0003281 0.8861026
+58.0512 0 58.0512 0.113897324 0 58.0512 0.8861027
+58.10207 0 58.10207 0.113897443 0 58.10207 0.886102557
+58.1529427 0 58.1529427 0.113897376 0 58.1529427 0.8861026
+58.2038155 0 58.2038155 0.1138973 0 58.2038155 0.8861027
+58.2546844 0 58.2546844 0.113897428 0 58.2546844 0.886102557
+58.3055573 0 58.3055573 0.113897353 0 58.3055573 0.8861026
+58.35643 0 58.35643 0.113897286 0 58.35643 0.886102736
+58.4073 0 58.4073 0.113897413 0 58.4073 0.8861026
+58.45817 0 58.45817 0.113897346 0 58.45817 0.8861027
+58.5090446 0 58.5090446 0.113897279 0 58.5090446 0.886102736
+58.5599136 0 58.5599136 0.1138974 0 58.5599136 0.8861026
+58.6107864 0 58.6107864 0.113897331 0 58.6107864 0.8861027
+58.6616554 0 58.6616554 0.11389745 0 58.6616554 0.886102557
+58.71253 0 58.71253 0.113897383 0 58.71253 0.8861026
+58.7634 0 58.7634 0.113897309 0 58.7634 0.8861027
+58.81427 0 58.81427 0.113897435 0 58.81427 0.886102557
+58.8651428 0 58.8651428 0.113897361 0 58.8651428 0.8861026
+58.9160156 0 58.9160156 0.113897294 0 58.9160156 0.8861027
+58.9668846 0 58.9668846 0.11389742 0 58.9668846 0.886102557
+59.0177574 0 59.0177574 0.113897346 0 59.0177574 0.8861027
+59.06863 0 59.06863 0.113897279 0 59.06863 0.886102736
+59.1195 0 59.1195 0.113897406 0 59.1195 0.8861026
+59.170372 0 59.170372 0.113897339 0 59.170372 0.8861027
+59.22124 0 59.22124 0.113897458 0 59.22124 0.886102557
+59.2721138 0 59.2721138 0.113897383 0 59.2721138 0.8861026
+59.3229866 0 59.3229866 0.113897316 0 59.3229866 0.8861027
+59.3738556 0 59.3738556 0.113897443 0 59.3738556 0.886102557
+59.42473 0 59.42473 0.113897368 0 59.42473 0.8861026
+59.4756 0 59.4756 0.1138973 0 59.4756 0.8861027
+59.52647 0 59.52647 0.11389742 0 59.52647 0.886102557
+59.577343 0 59.577343 0.113897353 0 59.577343 0.8861026
+59.6282158 0 59.6282158 0.113897286 0 59.6282158 0.886102736
+59.6790848 0 59.6790848 0.113897406 0 59.6790848 0.8861026
+59.7299576 0 59.7299576 0.113897339 0 59.7299576 0.8861027
+59.78083 0 59.78083 0.113897271 0 59.78083 0.886102736
+59.8317 0 59.8317 0.1138974 0 59.8317 0.8861026
+59.8825722 0 59.8825722 0.113897331 0 59.8825722 0.8861027
+59.93344 0 59.93344 0.11389745 0 59.93344 0.886102557
+59.984314 0 59.984314 0.113897376 0 59.984314 0.8861026
+60.0351868 0 60.0351868 0.113897309 0 60.0351868 0.8861027
+60.0860558 0 60.0860558 0.113897428 0 60.0860558 0.886102557
+60.13693 0 60.13693 0.113897361 0 60.13693 0.8861026
+60.1878 0 60.1878 0.113897294 0 60.1878 0.8861027
+60.23867 0 60.23867 0.113897413 0 60.23867 0.886102557
+60.2895432 0 60.2895432 0.113897346 0 60.2895432 0.8861027
+60.340416 0 60.340416 0.113897279 0 60.340416 0.886102736
+60.3912849 0 60.3912849 0.113897406 0 60.3912849 0.8861026
+60.4421577 0 60.4421577 0.113897331 0 60.4421577 0.8861027
+60.4930267 0 60.4930267 0.11389745 0 60.4930267 0.886102557
+60.5439 0 60.5439 0.113897383 0 60.5439 0.8861026
+60.5947723 0 60.5947723 0.113897316 0 60.5947723 0.8861027
+60.64564 0 60.64564 0.113897435 0 60.64564 0.886102557
+60.6965141 0 60.6965141 0.113897368 0 60.6965141 0.8861026
+60.7473869 0 60.7473869 0.1138973 0 60.7473869 0.8861027
+60.7982559 0 60.7982559 0.11389742 0 60.7982559 0.886102557
+60.84913 0 60.84913 0.113897353 0 60.84913 0.8861027
+60.9 0 60.9 0.113897286 0 60.9 0.886102736
+60.95087 0 60.95087 0.113897406 0 60.95087 0.8861026
+61.0017433 0 61.0017433 0.113897339 0 61.0017433 0.8861027
+61.0526161 0 61.0526161 0.113897271 0 61.0526161 0.886102736
+61.1034851 0 61.1034851 0.113897391 0 61.1034851 0.8861026
+61.1543579 0 61.1543579 0.113897324 0 61.1543579 0.8861027
+61.2052269 0 61.2052269 0.113897443 0 61.2052269 0.886102557
+61.2561 0 61.2561 0.113897376 0 61.2561 0.8861026
+61.3069725 0 61.3069725 0.113897309 0 61.3069725 0.8861027
+61.35784 0 61.35784 0.113897428 0 61.35784 0.886102557
+61.4087143 0 61.4087143 0.113897361 0 61.4087143 0.8861026
+61.4595871 0 61.4595871 0.113897294 0 61.4595871 0.8861027
+61.5104561 0 61.5104561 0.113897413 0 61.5104561 0.886102557
+61.56133 0 61.56133 0.113897346 0 61.56133 0.8861027
+61.6122 0 61.6122 0.113897279 0 61.6122 0.886102736
+61.66307 0 61.66307 0.1138974 0 61.66307 0.8861026
+61.7139435 0 61.7139435 0.113897331 0 61.7139435 0.8861027
+61.7648125 0 61.7648125 0.11389745 0 61.7648125 0.886102557
+61.8156853 0 61.8156853 0.113897383 0 61.8156853 0.8861026
+61.8665581 0 61.8665581 0.113897316 0 61.8665581 0.8861027
+61.9174271 0 61.9174271 0.113897435 0 61.9174271 0.886102557
+61.9683 0 61.9683 0.113897368 0 61.9683 0.8861026
+62.0191727 0 62.0191727 0.1138973 0 62.0191727 0.8861027
+62.07004 0 62.07004 0.11389742 0 62.07004 0.886102557
+62.1209145 0 62.1209145 0.113897353 0 62.1209145 0.8861027
+62.1717873 0 62.1717873 0.113897286 0 62.1717873 0.886102736
+62.2226562 0 62.2226562 0.113897406 0 62.2226562 0.8861026
+62.27353 0 62.27353 0.113897339 0 62.27353 0.8861027
+62.3244 0 62.3244 0.113897271 0 62.3244 0.886102736
+62.37527 0 62.37527 0.113897391 0 62.37527 0.8861026
+62.4261436 0 62.4261436 0.113897324 0 62.4261436 0.8861027
+62.4770126 0 62.4770126 0.113897443 0 62.4770126 0.886102557
+62.5278854 0 62.5278854 0.113897376 0 62.5278854 0.8861026
+62.57876 0 62.57876 0.113897309 0 62.57876 0.8861027
+62.6296272 0 62.6296272 0.113897428 0 62.6296272 0.886102557
+62.6805 0 62.6805 0.113897361 0 62.6805 0.8861026
+62.7313728 0 62.7313728 0.113897294 0 62.7313728 0.8861027
+62.78224 0 62.78224 0.113897413 0 62.78224 0.886102557
+62.8331146 0 62.8331146 0.113897346 0 62.8331146 0.8861027
+62.8839874 0 62.8839874 0.113897279 0 62.8839874 0.886102736
+62.9348564 0 62.9348564 0.1138974 0 62.9348564 0.8861026
+62.98573 0 62.98573 0.113897331 0 62.98573 0.8861027
+63.0366 0 63.0366 0.11389745 0 63.0366 0.886102557
+63.08747 0 63.08747 0.113897383 0 63.08747 0.8861026
+63.1383438 0 63.1383438 0.113897316 0 63.1383438 0.8861027
+63.1892128 0 63.1892128 0.113897435 0 63.1892128 0.886102557
+63.2400856 0 63.2400856 0.113897368 0 63.2400856 0.8861026
+63.29096 0 63.29096 0.1138973 0 63.29096 0.8861027
+63.3418274 0 63.3418274 0.11389742 0 63.3418274 0.886102557
+63.3927 0 63.3927 0.113897353 0 63.3927 0.8861027
+63.443573 0 63.443573 0.113897286 0 63.443573 0.886102736
+63.494442 0 63.494442 0.113897406 0 63.494442 0.8861026
+63.5453148 0 63.5453148 0.113897339 0 63.5453148 0.8861027
+63.5961876 0 63.5961876 0.113897271 0 63.5961876 0.886102736
+63.6470566 0 63.6470566 0.1138974 0 63.6470566 0.8861026
+63.69793 0 63.69793 0.113897324 0 63.69793 0.8861027
+63.7488 0 63.7488 0.11389745 0 63.7488 0.886102557
+63.79967 0 63.79967 0.113897376 0 63.79967 0.8861026
+63.850544 0 63.850544 0.113897309 0 63.850544 0.8861027
+63.901413 0 63.901413 0.113897428 0 63.901413 0.886102557
+63.9522858 0 63.9522858 0.113897361 0 63.9522858 0.8861026
+64.00316 0 64.00316 0.113897294 0 64.00316 0.8861027
+64.05403 0 64.05403 0.113897227 0 64.05403 0.8861028
+64.1049 0 64.1049 0.11389754 0 64.1049 0.886102438
+64.15577 0 64.15577 0.113897465 0 64.15577 0.886102557
+64.20664 0 64.20664 0.1138974 0 64.20664 0.8861026
+64.257515 0 64.257515 0.113897331 0 64.257515 0.8861027
+64.30839 0 64.30839 0.113897264 0 64.30839 0.886102736
+64.35926 0 64.35926 0.1138972 0 64.35926 0.8861028
+64.4101257 0 64.4101257 0.11389751 0 64.4101257 0.8861025
+64.461 0 64.461 0.113897435 0 64.461 0.886102557
+64.51187 0 64.51187 0.113897368 0 64.51187 0.8861026
+64.5627441 0 64.5627441 0.1138973 0 64.5627441 0.8861027
+64.61362 0 64.61362 0.113897234 0 64.61362 0.886102736
+64.66448 0 64.66448 0.113897547 0 64.66448 0.886102438
+64.7153549 0 64.7153549 0.113897473 0 64.7153549 0.8861025
+64.76623 0 64.76623 0.113897406 0 64.76623 0.8861026
+64.8171 0 64.8171 0.113897339 0 64.8171 0.8861027
+64.86797 0 64.86797 0.113897271 0 64.86797 0.886102736
+64.9188461 0 64.9188461 0.113897204 0 64.9188461 0.8861028
+64.96971 0 64.96971 0.11389751 0 64.96971 0.8861025
+65.0205841 0 65.0205841 0.113897443 0 65.0205841 0.886102557
+65.07146 0 65.07146 0.113897376 0 65.07146 0.8861026
+65.12233 0 65.12233 0.113897309 0 65.12233 0.8861027
+65.1732 0 65.1732 0.113897242 0 65.1732 0.886102736
+65.22407 0 65.22407 0.113897547 0 65.22407 0.886102438
+65.27494 0 65.27494 0.11389748 0 65.27494 0.8861025
+65.32581 0 65.32581 0.113897406 0 65.32581 0.8861026
+65.3766861 0 65.3766861 0.113897339 0 65.3766861 0.8861027
+65.42756 0 65.42756 0.113897271 0 65.42756 0.886102736
+65.47843 0 65.47843 0.113897212 0 65.47843 0.8861028
+65.5293 0 65.5293 0.113897517 0 65.5293 0.8861025
+65.58017 0 65.58017 0.11389745 0 65.58017 0.886102557
+65.63104 0 65.63104 0.113897376 0 65.63104 0.8861026
+65.6819153 0 65.6819153 0.113897309 0 65.6819153 0.8861027
+65.73279 0 65.73279 0.113897249 0 65.73279 0.886102736
+65.78366 0 65.78366 0.113897182 0 65.78366 0.8861028
+65.8345261 0 65.8345261 0.113897488 0 65.8345261 0.8861025
+65.8854 0 65.8854 0.11389742 0 65.8854 0.886102557
+65.93627 0 65.93627 0.113897353 0 65.93627 0.8861027
+65.9871445 0 65.9871445 0.113897286 0 65.9871445 0.886102736
+66.03802 0 66.03802 0.113897219 0 66.03802 0.8861028
+66.08888 0 66.08888 0.113897525 0 66.08888 0.8861025
+66.1397552 0 66.1397552 0.113897458 0 66.1397552 0.886102557
+66.19063 0 66.19063 0.113897391 0 66.19063 0.8861026
+66.2415 0 66.2415 0.113897324 0 66.2415 0.8861027
+66.29237 0 66.29237 0.113897257 0 66.29237 0.886102736
+66.34325 0 66.34325 0.113897189 0 66.34325 0.8861028
+66.39411 0 66.39411 0.1138975 0 66.39411 0.8861025
+66.4449844 0 66.4449844 0.113897428 0 66.4449844 0.886102557
+66.49586 0 66.49586 0.113897361 0 66.49586 0.8861026
+66.54673 0 66.54673 0.113897294 0 66.54673 0.8861027
+66.5976 0 66.5976 0.113897227 0 66.5976 0.8861028
+66.64847 0 66.64847 0.11389754 0 66.64847 0.886102438
+66.69934 0 66.69934 0.113897465 0 66.69934 0.886102557
+66.75021 0 66.75021 0.1138974 0 66.75021 0.8861026
+66.80109 0 66.80109 0.113897331 0 66.80109 0.8861027
+66.85196 0 66.85196 0.113897264 0 66.85196 0.886102736
+66.90283 0 66.90283 0.1138972 0 66.90283 0.8861028
+66.9537 0 66.9537 0.11389751 0 66.9537 0.8861025
+67.00457 0 67.00457 0.113897435 0 67.00457 0.886102557
+67.05544 0 67.05544 0.113897368 0 67.05544 0.8861026
+67.1063156 0 67.1063156 0.1138973 0 67.1063156 0.8861027
+67.15719 0 67.15719 0.113897234 0 67.15719 0.886102736
+67.20805 0 67.20805 0.11389754 0 67.20805 0.886102438
+67.25893 0 67.25893 0.113897473 0 67.25893 0.886102557
+67.3098 0 67.3098 0.113897406 0 67.3098 0.8861026
+67.36067 0 67.36067 0.113897339 0 67.36067 0.8861027
+67.4115448 0 67.4115448 0.113897271 0 67.4115448 0.886102736
+67.46242 0 67.46242 0.113897204 0 67.46242 0.8861028
+67.51328 0 67.51328 0.11389751 0 67.51328 0.8861025
+67.5641556 0 67.5641556 0.113897443 0 67.5641556 0.886102557
+67.61503 0 67.61503 0.113897376 0 67.61503 0.8861026
+67.6659 0 67.6659 0.113897309 0 67.6659 0.8861027
+67.716774 0 67.716774 0.113897242 0 67.716774 0.886102736
+67.76764 0 67.76764 0.113897547 0 67.76764 0.886102438
+67.81851 0 67.81851 0.11389748 0 67.81851 0.8861025
+67.8693848 0 67.8693848 0.113897406 0 67.8693848 0.8861026
+67.92026 0 67.92026 0.113897339 0 67.92026 0.8861027
+67.97113 0 67.97113 0.113897271 0 67.97113 0.886102736
+68.022 0 68.022 0.113897212 0 68.022 0.8861028
+68.07287 0 68.07287 0.113897517 0 68.07287 0.8861025
+68.12374 0 68.12374 0.11389745 0 68.12374 0.886102557
+68.174614 0 68.174614 0.113897376 0 68.174614 0.8861026
+68.22549 0 68.22549 0.113897309 0 68.22549 0.8861027
+68.27636 0 68.27636 0.113897242 0 68.27636 0.886102736
+68.32723 0 68.32723 0.113897182 0 68.32723 0.8861028
+68.3781 0 68.3781 0.113897488 0 68.3781 0.8861025
+68.42897 0 68.42897 0.11389742 0 68.42897 0.886102557
+68.47984 0 68.47984 0.113897353 0 68.47984 0.8861027
+68.5307159 0 68.5307159 0.113897286 0 68.5307159 0.886102736
+68.58159 0 68.58159 0.113897219 0 68.58159 0.8861028
+68.6324539 0 68.6324539 0.113897525 0 68.6324539 0.8861025
+68.68333 0 68.68333 0.113897458 0 68.68333 0.886102557
+68.7342 0 68.7342 0.113897391 0 68.7342 0.8861026
+68.78507 0 68.78507 0.113897324 0 68.78507 0.8861027
+68.8359451 0 68.8359451 0.113897257 0 68.8359451 0.886102736
+68.88682 0 68.88682 0.113897189 0 68.88682 0.8861028
+68.93768 0 68.93768 0.113897495 0 68.93768 0.8861025
+68.9885559 0 68.9885559 0.113897428 0 68.9885559 0.886102557
+69.03943 0 69.03943 0.113897361 0 69.03943 0.8861026
+69.0903 0 69.0903 0.113897294 0 69.0903 0.886102736
+69.1411743 0 69.1411743 0.113897227 0 69.1411743 0.8861028
+69.19204 0 69.19204 0.113897532 0 69.19204 0.886102438
+69.24291 0 69.24291 0.113897465 0 69.24291 0.886102557
+69.2937851 0 69.2937851 0.1138974 0 69.2937851 0.8861026
+69.34466 0 69.34466 0.113897331 0 69.34466 0.8861027
+69.39553 0 69.39553 0.113897264 0 69.39553 0.886102736
+69.4464 0 69.4464 0.1138972 0 69.4464 0.8861028
+69.49727 0 69.49727 0.1138975 0 69.49727 0.8861025
+69.54814 0 69.54814 0.113897435 0 69.54814 0.886102557
+69.5990143 0 69.5990143 0.113897368 0 69.5990143 0.8861026
+69.64989 0 69.64989 0.1138973 0 69.64989 0.8861027
+69.70076 0 69.70076 0.113897234 0 69.70076 0.886102736
+69.7516251 0 69.7516251 0.11389754 0 69.7516251 0.886102438
+69.8025 0 69.8025 0.113897473 0 69.8025 0.886102557
+69.85337 0 69.85337 0.113897406 0 69.85337 0.8861026
+69.90424 0 69.90424 0.113897331 0 69.90424 0.8861027
+69.95512 0 69.95512 0.113897271 0 69.95512 0.886102736
+70.00599 0 70.00599 0.113897204 0 70.00599 0.8861028
+70.0568542 0 70.0568542 0.11389751 0 70.0568542 0.8861025
+70.10773 0 70.10773 0.113897443 0 70.10773 0.886102557
+70.1586 0 70.1586 0.113897376 0 70.1586 0.8861026
+70.20947 0 70.20947 0.113897309 0 70.20947 0.8861027
+70.2603455 0 70.2603455 0.113897242 0 70.2603455 0.886102736
+70.31121 0 70.31121 0.113897547 0 70.31121 0.886102438
+70.36208 0 70.36208 0.11389748 0 70.36208 0.8861025
+70.41296 0 70.41296 0.113897406 0 70.41296 0.8861026
+70.46383 0 70.46383 0.113897339 0 70.46383 0.8861027
+70.5147 0 70.5147 0.113897271 0 70.5147 0.886102736
+70.5655746 0 70.5655746 0.113897212 0 70.5655746 0.8861028
+70.61644 0 70.61644 0.113897517 0 70.61644 0.8861025
+70.66731 0 70.66731 0.11389745 0 70.66731 0.886102557
+70.7181854 0 70.7181854 0.113897376 0 70.7181854 0.8861026
+70.76906 0 70.76906 0.113897309 0 70.76906 0.8861027
+70.81993 0 70.81993 0.113897242 0 70.81993 0.886102736
+70.8708 0 70.8708 0.113897555 0 70.8708 0.886102438
+70.92167 0 70.92167 0.11389748 0 70.92167 0.8861025
+70.97254 0 70.97254 0.113897413 0 70.97254 0.8861026
+71.0234146 0 71.0234146 0.113897346 0 71.0234146 0.8861027
+71.07429 0 71.07429 0.113897279 0 71.07429 0.886102736
+71.12516 0 71.12516 0.113897212 0 71.12516 0.8861028
+71.1760254 0 71.1760254 0.113897517 0 71.1760254 0.8861025
+71.2269 0 71.2269 0.11389745 0 71.2269 0.886102557
+71.27777 0 71.27777 0.113897383 0 71.27777 0.8861026
+71.32864 0 71.32864 0.113897316 0 71.32864 0.8861027
+71.37952 0 71.37952 0.113897249 0 71.37952 0.886102736
+71.43039 0 71.43039 0.113897182 0 71.43039 0.8861028
+71.4812546 0 71.4812546 0.113897488 0 71.4812546 0.8861025
+71.53213 0 71.53213 0.11389742 0 71.53213 0.886102557
+71.583 0 71.583 0.113897353 0 71.583 0.8861027
+71.63387 0 71.63387 0.113897286 0 71.63387 0.886102736
+71.6847458 0 71.6847458 0.113897219 0 71.6847458 0.8861028
+71.73561 0 71.73561 0.113897532 0 71.73561 0.8861025
+71.78648 0 71.78648 0.113897458 0 71.78648 0.886102557
+71.83736 0 71.83736 0.113897391 0 71.83736 0.8861026
+71.88823 0 71.88823 0.113897324 0 71.88823 0.8861027
+71.9391 0 71.9391 0.113897257 0 71.9391 0.886102736
+71.989975 0 71.989975 0.113897189 0 71.989975 0.8861028
+72.04084 0 72.04084 0.1138975 0 72.04084 0.8861025
+72.09171 0 72.09171 0.113897428 0 72.09171 0.886102557
+72.1425858 0 72.1425858 0.113897361 0 72.1425858 0.8861026
+72.19346 0 72.19346 0.113897294 0 72.19346 0.8861027
+72.24433 0 72.24433 0.113897227 0 72.24433 0.8861028
diff --git a/test/BaselineOutput/SingleDebug/SavePipe/SavePipeIidSpike-Schema.txt b/test/BaselineOutput/SingleDebug/SavePipe/SavePipeIidSpike-Schema.txt
new file mode 100644
index 0000000000..8b7eac8b58
--- /dev/null
+++ b/test/BaselineOutput/SingleDebug/SavePipe/SavePipeIidSpike-Schema.txt
@@ -0,0 +1,54 @@
+---- BoundLoader ----
+1 columns:
+ Features: R4
+---- IidSpikeDetector ----
+2 columns:
+ Features: R4
+ Anomaly: Vec
+ Metadata 'SlotNames': Vec: Length=3, Count=3
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score'
+---- ConvertTransform ----
+3 columns:
+ Features: R4
+ Anomaly: Vec
+ Metadata 'SlotNames': Vec: Length=3, Count=3
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score'
+ fAnomaly: Vec
+ Metadata 'SlotNames': Vec: Length=3, Count=3
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score'
+---- IidSpikeDetector ----
+4 columns:
+ Features: R4
+ Anomaly: Vec
+ Metadata 'SlotNames': Vec: Length=3, Count=3
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score'
+ fAnomaly: Vec
+ Metadata 'SlotNames': Vec: Length=3, Count=3
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score'
+ Anomaly2: Vec
+ Metadata 'SlotNames': Vec: Length=3, Count=3
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score'
+---- ConvertTransform ----
+5 columns:
+ Features: R4
+ Anomaly: Vec
+ Metadata 'SlotNames': Vec: Length=3, Count=3
+ [0] 'Alert', [1] 'Raw Score', [2] 'P-Value Score'
+ fAnomaly: Vec