public void CouldUseWindowCursor() { SortedMap <int, double> sm = new SortedMap <int, double>(); var count = 100; for (int i = 0; i < count; i++) { sm.Add(i, i); } var window = sm.Window(10); // NB no type annotations needed to get the same result // Even though `.Source` saves a great deal of typing, manual construction is still a dealbreaker // Extension methods are smart and keep all types info without requiring any type annotations var windowLong = new Window <int, double, SortedMapCursor <int, double> >(sm.GetEnumerator(), 10).Source; foreach (var pair in window) { var expected = ((2 * pair.Key - 10 + 1) / 2.0) * 10; Assert.AreEqual(expected, pair.Value.Values.Sum()); Console.WriteLine(pair.Value.Values.Sum()); //Console.WriteLine(keyValuePair.Value.Aggregate("", (st,kvp) => st + "," + kvp.Value)); } }
public void CouldCalculateAverageOnMovingWindowWithStep() { var sm = new SortedMap <DateTime, double>(); var count = 100000; for (int i = 0; i < count; i++) { sm.Add(DateTime.UtcNow.Date.AddSeconds(i), i); } // slow implementation var sw = new Stopwatch(); sw.Start(); var ma = sm.Window(20, 2);//.ToSortedMap(); var c = 1; foreach (var m in ma) { var innersm = m.Value;//.ToSortedMap(); if (innersm.Values.Average() != c + 8.5) { Console.WriteLine(m.Value.Values.Average()); throw new ApplicationException("Invalid value"); } c++; c++; } sw.Stop(); Console.WriteLine($"Final c: {c}"); Console.WriteLine("Window MA, elapsed: {0}, ops: {1}", sw.ElapsedMilliseconds, (int)((double)count / (sw.ElapsedMilliseconds / 1000.0))); Console.WriteLine("Calculation ops: {0}", (int)((double)count * 20.0 / (sw.ElapsedMilliseconds / 1000.0))); }
public void CouldUseWindowCursorWithIncomplete() { SortedMap <int, double> sm = new SortedMap <int, double>(); var count = 20; for (int i = 0; i < count; i++) { sm.Add(i, i); } var window = sm.Window(10, true); foreach (var pair in window) { Assert.AreEqual(Math.Min(pair.Key + 1, 10), pair.Value.Count()); } var window2 = sm.Window(count * 2, true); foreach (var pair in window2) { Assert.AreEqual(sm.Values.Take(pair.Key + 1).Sum(), pair.Value.Values.Sum()); Assert.AreEqual(pair.Key + 1, pair.Value.Count()); } var window3 = sm.Window(3, Lookup.EQ); foreach (var pair in window3) { Console.WriteLine($"{pair.Key}:"); foreach (var keyValuePair in pair.Value) { Console.WriteLine($"{keyValuePair.Key} - {keyValuePair.Value}"); } Console.WriteLine("--------------"); } }
public void CouldCalculateAverageOnMovingWindowWithStep() { var sm = new SortedMap<DateTime, double>(); var count = 100000; for (int i = 0; i < count; i++) { sm.Add(DateTime.UtcNow.Date.AddSeconds(i), i); } // slow implementation var sw = new Stopwatch(); sw.Start(); var ma = sm.Window(20, 2);//.ToSortedMap(); var c = 1; foreach (var m in ma) { var innersm = m.Value;//.ToSortedMap(); if (innersm.Values.Average() != c + 8.5) { Console.WriteLine(m.Value.Values.Average()); throw new ApplicationException("Invalid value"); } c++; c++; } sw.Stop(); Console.WriteLine($"Final c: {c}"); Console.WriteLine("Window MA, elapsed: {0}, ops: {1}", sw.ElapsedMilliseconds, (int)((double)count / (sw.ElapsedMilliseconds / 1000.0))); Console.WriteLine("Calculation ops: {0}", (int)((double)count * 20.0 / (sw.ElapsedMilliseconds / 1000.0))); }
public void TypeSystemSurvivesViolentAbuse() { var count = 1000; #region Create data var sm = new SortedMap <int, double>(); sm.Add(0, 0); // make irregular, it's faster but more memory for (int i = 2; i <= count; i++) { sm.Add(i, i); } #endregion Create data for (int round = 0; round < 20; round++) { var window = sm.Window(20).Window(20).Window(20).Window(20).Window(20); // this becomes too much: .Window(20).Window(20).Window(20); var c = 0; using (Benchmark.Run("Struct", count * 1000_000)) { foreach (var keyValuePair in window) { if (keyValuePair.Key >= 0) { c++; } } } using (Benchmark.Run("Struct Lazy", count * 1000_000)) { var cursor = window.GetEnumerator(); while (cursor.MoveNext()) { if (cursor.CurrentKey >= 0) { c++; } } } if (c < 0) { Console.WriteLine($"Count: {c}"); } var window2 = sm.Window_(20).Window_(20).Window_(20).Window_(20).Window_(20); var c2 = 0; using (Benchmark.Run("Interface", count * 1000_000)) { foreach (var keyValuePair in window2) { if (keyValuePair.Key >= 0) { c2++; } } } if (c2 < 0) { Console.WriteLine($"Count2: {c2}"); } } Benchmark.Dump("MOPS is scaled by x1M, so it is OPS"); }
public void WindowDirectAndIndirectSpanOpBenchmark() { Settings.DoAdditionalCorrectnessChecks = false; var count = 100000; var width = 20; var sm = new SortedMap <int, double>(); sm.Add(0, 0); // make irregular, it's faster but more memory for (int i = 2; i <= count; i++) { sm.Add(i, i); } var op = new WindowOnlineOp <int, double, SortedMapCursor <int, double> >(); var spanOp = new SpanOpCount <int, double, Range <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double>, WindowOnlineOp <int, double, SortedMapCursor <int, double> > >(20, false, op); var window = new SpanOpImpl <int, double, Range <int, double, SortedMapCursor <int, double> >, SpanOpCount <int, double, Range <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double>, WindowOnlineOp <int, double, SortedMapCursor <int, double> > >, SortedMapCursor <int, double> >(sm.GetEnumerator(), spanOp).Source .Map(x => { var sum = 0.0; var c = 0; foreach (var keyValuePair in x.Source) { sum += keyValuePair.Value; c++; } return(sum / c); // x.CursorDefinition.Count TODO }); var spanOpCombined = new SpanOp <int, double, Range <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double>, WindowOnlineOp <int, double, SortedMapCursor <int, double> > >(20, false, op, sm.comparer); var windowCombined = new SpanOpImpl <int, double, Range <int, double, SortedMapCursor <int, double> >, SpanOp <int, double, Range <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double>, WindowOnlineOp <int, double, SortedMapCursor <int, double> > >, SortedMapCursor <int, double> >(sm.GetEnumerator(), spanOpCombined).Source .Map(x => { var sum = 0.0; var c = 0; foreach (var keyValuePair in x.Source) { sum += keyValuePair.Value; c++; } return(sum / c); // x.CursorDefinition.Count TODO }); var windowExtension = sm.Window(width).Map(x => { var sum = 0.0; var c = 0; foreach (var keyValuePair in x) { sum += keyValuePair.Value; c++; } return(sum / c); // x.CursorDefinition.Count TODO }); for (int round = 0; round < 20; round++) { double sum1 = 0.0; using (Benchmark.Run("Window SpanOpCount", count * width)) { foreach (var keyValuePair in window) { sum1 += keyValuePair.Value; } } double sum2 = 0.0; using (Benchmark.Run("Window SpanOp", count * width)) { foreach (var keyValuePair in windowCombined) { sum2 += keyValuePair.Value; } } double sum3 = 0.0; using (Benchmark.Run("Window Extension", count * width)) { foreach (var keyValuePair in windowExtension) { sum3 += keyValuePair.Value; } } Assert.AreEqual(sum1, sum2); Assert.AreEqual(sum1, sum3); } Benchmark.Dump($"The window width is {width}."); }
public void SMADirectAndIndirectSpanOpBenchmark() { Settings.DoAdditionalCorrectnessChecks = false; var count = 1000000; var width = 20; var sm = new SortedMap <int, double>(); sm.Add(0, 0); // make irregular, it's faster but more memory for (int i = 2; i <= count; i++) { sm.Add(i, i); } var directSMA = new SpanOpImpl <int, double, double, MAvgCount <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double> >(sm.GetEnumerator(), new MAvgCount <int, double, SortedMapCursor <int, double> >(width, false)).Source; var indirectSma = new SpanOpImpl <int, double, double, SpanOpCount <int, double, double, SortedMapCursor <int, double>, SumAvgOnlineOp <int, double, SortedMapCursor <int, double> > >, SortedMapCursor <int, double> >(sm.GetEnumerator(), new SpanOpCount <int, double, double, SortedMapCursor <int, double>, SumAvgOnlineOp <int, double, SortedMapCursor <int, double> > >(width, false, new SumAvgOnlineOp <int, double, SortedMapCursor <int, double> >())).Source; var indirectSmaCombined = new SpanOpImpl <int, double, double, SpanOp <int, double, double, SortedMapCursor <int, double>, SumAvgOnlineOp <int, double, SortedMapCursor <int, double> > >, SortedMapCursor <int, double> >(sm.GetEnumerator(), new SpanOp <int, double, double, SortedMapCursor <int, double>, SumAvgOnlineOp <int, double, SortedMapCursor <int, double> > >(width, false, new SumAvgOnlineOp <int, double, SortedMapCursor <int, double> >(), sm.comparer)).Source; var extensionSma = sm.SMA(width); var windowSma = sm.Window(width).Map(x => { var sum = 0.0; var c = 0; foreach (var keyValuePair in x) { sum += keyValuePair.Value; c++; } return(sum / c); // x.CursorDefinition.Count TODO }); for (int round = 0; round < 20; round++) { double sum1 = 0.0; using (Benchmark.Run("SMA Direct", count * width)) { foreach (var keyValuePair in directSMA) { sum1 += keyValuePair.Value; } } double sum2 = 0.0; using (Benchmark.Run("SMA Indirect", count * width)) { foreach (var keyValuePair in indirectSma) { sum2 += keyValuePair.Value; } } double sum2_2 = 0.0; using (Benchmark.Run("SMA Indirect Combined", count * width)) { foreach (var keyValuePair in indirectSmaCombined) { sum2_2 += keyValuePair.Value; } } double sum2_3 = 0.0; using (Benchmark.Run("SMA Extension", count * width)) { foreach (var keyValuePair in extensionSma) { sum2_3 += keyValuePair.Value; } } double sum3 = 0.0; using (Benchmark.Run("SMA Window", count * width)) { foreach (var keyValuePair in windowSma) { sum3 += keyValuePair.Value; } } var sumxx = 0.0; using (Benchmark.Run("SortedMap", count)) { foreach (var keyValuePair in sm) { sumxx += keyValuePair.Value; } } Assert.AreEqual(sum1, sum2); Assert.AreEqual(sum1, sum2_2); Assert.AreEqual(sum1, sum2_3); Assert.AreEqual(sum1, sum3); } Benchmark.Dump($"The window width is {width}. SMA MOPS are calculated as a number of calculated values multiplied by width, " + $"which is equivalent to the total number of cursor moves for Window case. SortedMap line is for reference - it is the " + $"speed of raw iteration over SM without Windows overheads."); }
public void CouldCalculateSMAWithWidth() { var count = 20; var sm = new SortedMap <int, double>(); for (int i = 1; i <= count; i++) { sm.Add(i, i); } DoTest(Lookup.EQ); DoTest(Lookup.GE); DoTest(Lookup.GT); DoTest(Lookup.LE); DoTest(Lookup.LT); DoTestViaSpanOpWidth(Lookup.EQ); DoTestViaSpanOpWidth(Lookup.GE); DoTestViaSpanOpWidth(Lookup.GT); DoTestViaSpanOpWidth(Lookup.LE); DoTestViaSpanOpWidth(Lookup.LT); void DoTest(Lookup lookup) { // width 9 is the same as count = 10 for the regular int series var smaOp = new MAvgWidth <int, double, SortedMapCursor <int, double> >(9, lookup); var smaCursor = new SpanOpImpl <int, double, double, MAvgWidth <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double> >(sm.GetEnumerator(), smaOp); // this monster type must be hidden in the same way Lag hides its implementation Series <int, double, SpanOpImpl <int, double, double, MAvgWidth <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double> > > smaSeries; smaSeries = smaCursor.Source; var sm2 = smaSeries.ToSortedMap(); Assert.AreEqual(sm2.First, smaSeries.First); Assert.AreEqual(sm2.Last, smaSeries.Last); Assert.True(SeriesContract.MoveAtShouldWorkOnLazySeries(smaSeries)); Assert.True(SeriesContract.ClonedCursorsAreIndependent(smaSeries)); if (lookup == Lookup.EQ || lookup == Lookup.GE) { var trueSma = sm.Window(10).Map(x => x.Values.Average()); Assert.True(trueSma.Keys.SequenceEqual(smaSeries.Keys)); Assert.True(trueSma.Values.SequenceEqual(smaSeries.Values)); } if (lookup == Lookup.GT) { var trueSma = sm.Window(11).Map(x => x.Values.Average()); Assert.True(trueSma.Keys.SequenceEqual(smaSeries.Keys)); Assert.True(trueSma.Values.SequenceEqual(smaSeries.Values)); } if (lookup == Lookup.LE) { var smaOp1 = new MAvgCount <int, double, SortedMapCursor <int, double> >(10, true); var trueSma = new SpanOpImpl <int, double, double, MAvgCount <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double> >(sm.GetEnumerator(), smaOp1).Source; Assert.True(trueSma.Keys.SequenceEqual(smaSeries.Keys)); Assert.True(trueSma.Values.SequenceEqual(smaSeries.Values)); } if (lookup == Lookup.LT) { var smaOp1 = new MAvgCount <int, double, SortedMapCursor <int, double> >(9, true); var trueSma = new SpanOpImpl <int, double, double, MAvgCount <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double> >(sm.GetEnumerator(), smaOp1).Source; Assert.True(trueSma.Keys.SequenceEqual(smaSeries.Keys)); Assert.True(trueSma.Values.SequenceEqual(smaSeries.Values)); } Debug.WriteLine("SMA"); foreach (var keyValuePair in smaSeries) { Debug.WriteLine($"{keyValuePair.Key} - {keyValuePair.Value}"); } } void DoTestViaSpanOpWidth(Lookup lookup) { // width 9 is the same as count = 10 for the regular int series var onlineOp = new SumAvgOnlineOp <int, double, SortedMapCursor <int, double> >(); var smaOp = new SpanOpWidth <int, double, double, SortedMapCursor <int, double>, SumAvgOnlineOp <int, double, SortedMapCursor <int, double> > >(9, lookup, onlineOp); var smaCursor = new SpanOpImpl <int, double, double, SpanOpWidth <int, double, double, SortedMapCursor <int, double>, SumAvgOnlineOp <int, double, SortedMapCursor <int, double> > >, SortedMapCursor <int, double> >(sm.GetEnumerator(), smaOp); // this monster type must be hidden in the same way Lag hides its implementation Series <int, double, SpanOpImpl <int, double, double, SpanOpWidth <int, double, double, SortedMapCursor <int, double>, SumAvgOnlineOp <int, double, SortedMapCursor <int, double> > >, SortedMapCursor <int, double> > > smaSeries; smaSeries = smaCursor.Source; var sm2 = smaSeries.ToSortedMap(); Assert.AreEqual(sm2.First, smaSeries.First); Assert.AreEqual(sm2.Last, smaSeries.Last); Assert.True(SeriesContract.MoveAtShouldWorkOnLazySeries(smaSeries)); Assert.True(SeriesContract.ClonedCursorsAreIndependent(smaSeries)); if (lookup == Lookup.EQ || lookup == Lookup.GE) { var trueSma = sm.Window(10).Map(x => x.Values.Average()); Assert.True(trueSma.Keys.SequenceEqual(smaSeries.Keys)); Assert.True(trueSma.Values.SequenceEqual(smaSeries.Values)); } if (lookup == Lookup.GT) { var trueSma = sm.Window(11).Map(x => x.Values.Average()); Assert.True(trueSma.Keys.SequenceEqual(smaSeries.Keys)); Assert.True(trueSma.Values.SequenceEqual(smaSeries.Values)); } if (lookup == Lookup.LE) { var smaOp1 = new MAvgCount <int, double, SortedMapCursor <int, double> >(10, true); var trueSma = new SpanOpImpl <int, double, double, MAvgCount <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double> >(sm.GetEnumerator(), smaOp1).Source; Assert.True(trueSma.Keys.SequenceEqual(smaSeries.Keys)); Assert.True(trueSma.Values.SequenceEqual(smaSeries.Values)); } if (lookup == Lookup.LT) { var smaOp1 = new MAvgCount <int, double, SortedMapCursor <int, double> >(9, true); var trueSma = new SpanOpImpl <int, double, double, MAvgCount <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double> >(sm.GetEnumerator(), smaOp1).Source; Assert.True(trueSma.Keys.SequenceEqual(smaSeries.Keys)); Assert.True(trueSma.Values.SequenceEqual(smaSeries.Values)); } Debug.WriteLine("SMA"); foreach (var keyValuePair in smaSeries) { Debug.WriteLine($"{keyValuePair.Key} - {keyValuePair.Value}"); } } }
public void CouldCalculateSMAWithCount() { var count = 20; var sm = new SortedMap <int, double>(); for (int i = 1; i <= count; i++) { sm.Add(i, i); } DoTest(true); DoTest(false); DoTestViaSpanOpCount(true); DoTestViaSpanOpCount(false); void DoTest(bool allowIncomplete) { // TODO separate tests/cases for true/false var smaOp = new MAvgCount <int, double, SortedMapCursor <int, double> >(10, allowIncomplete); var smaCursor = new SpanOpImpl <int, double, double, MAvgCount <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double> >(sm.GetEnumerator(), smaOp); // this monster type must be hidden in the same way Lag hides its implementation Series <int, double, SpanOpImpl <int, double, double, MAvgCount <int, double, SortedMapCursor <int, double> >, SortedMapCursor <int, double> > > smaSeries; smaSeries = smaCursor.Source; var sm2 = smaSeries.ToSortedMap(); Assert.AreEqual(sm2.First, smaSeries.First); Assert.AreEqual(sm2.Last, smaSeries.Last); Assert.True(SeriesContract.MoveAtShouldWorkOnLazySeries(smaSeries)); Assert.True(SeriesContract.ClonedCursorsAreIndependent(smaSeries)); if (!allowIncomplete) { var trueSma = sm.Window(10).Map(x => x.Values.Average()); Assert.True(trueSma.Keys.SequenceEqual(smaSeries.Keys)); Assert.True(trueSma.Values.SequenceEqual(smaSeries.Values)); } Debug.WriteLine("SMA"); foreach (var keyValuePair in smaSeries) { Debug.WriteLine($"{keyValuePair.Key} - {keyValuePair.Value}"); } } void DoTestViaSpanOpCount(bool allowIncomplete) { // TODO separate tests/cases for true/false var onlineOp = new SumAvgOnlineOp <int, double, SortedMapCursor <int, double> >(); var smaOp = new SpanOpCount <int, double, double, SortedMapCursor <int, double>, SumAvgOnlineOp <int, double, SortedMapCursor <int, double> > >(10, allowIncomplete, onlineOp); var smaCursor = new SpanOpImpl <int, double, double, SpanOpCount <int, double, double, SortedMapCursor <int, double>, SumAvgOnlineOp <int, double, SortedMapCursor <int, double> > >, SortedMapCursor <int, double> >(sm.GetEnumerator(), smaOp); // this monster type must be hidden in the same way Lag hides its implementation Series <int, double, SpanOpImpl <int, double, double, SpanOpCount <int, double, double, SortedMapCursor <int, double>, SumAvgOnlineOp <int, double, SortedMapCursor <int, double> > >, SortedMapCursor <int, double> > > smaSeries; smaSeries = smaCursor.Source; var sm2 = smaSeries.ToSortedMap(); Assert.AreEqual(sm2.First, smaSeries.First); Assert.AreEqual(sm2.Last, smaSeries.Last); Assert.True(SeriesContract.MoveAtShouldWorkOnLazySeries(smaSeries)); Assert.True(SeriesContract.ClonedCursorsAreIndependent(smaSeries)); if (!allowIncomplete) { var trueSma = sm.Window(10).Map(x => x.Values.Average()); Assert.True(trueSma.Keys.SequenceEqual(smaSeries.Keys)); Assert.True(trueSma.Values.SequenceEqual(smaSeries.Values)); } Debug.WriteLine("SMA"); foreach (var keyValuePair in smaSeries) { Debug.WriteLine($"{keyValuePair.Key} - {keyValuePair.Value}"); } } }
public void Stat2StDevBenchmark() { var comparer = KeyComparer <int> .Default; var count = 1_000_000; var width = 20; var sm = new SortedMap <int, double>(count, comparer); sm.Add(0, 0); for (int i = 2; i <= count; i++) { sm.Add(i, i); } // var ds1 = new Deedle.Series<int, double>(sm.Keys.ToArray(), sm.Values.ToArray()); var sum = 0.0; for (int r = 0; r < 10; r++) { var sum1 = 0.0; using (Benchmark.Run("Stat2 Online N", count * width)) { foreach (var stat2 in sm.Stat2(width).Values) { sum1 += stat2.StDev; } } Assert.True(sum1 != 0); var sum2 = 0.0; using (Benchmark.Run("Stat2 Online Width GE", count * width)) { foreach (var stat2 in sm.Stat2(width - 1, Lookup.GE).Values) { sum2 += stat2.StDev; } } Assert.True(sum2 != 0); var sum3 = 0.0; using (Benchmark.Run("Stat2 Window N", count * width)) { foreach (var stat2 in sm.Window(width).Map(x => x.Stat2()).Values) { sum3 += stat2.StDev; } } Assert.True(sum3 != 0); var sum4 = 0.0; using (Benchmark.Run("Stat2 Window Width GE", count * width)) { foreach (var stat2 in sm.Window(width - 1, Lookup.GE).Map(x => x.Stat2()).Values) { sum4 += stat2.StDev; } } Assert.True(sum4 != 0); //var sum5 = 0.0; //using (Benchmark.Run("Deedle (online)", count * width)) //{ // var deedleStDev = Deedle.Stats.movingStdDev(width, ds1); // foreach (var keyValuePair in deedleStDev.Values) // { // sum5 += keyValuePair; // } //} //Assert.True(sum5 != 0); Assert.True(Math.Abs(sum1 / sum2 - 1) < 0.000001); Assert.True(Math.Abs(sum1 / sum3 - 1) < 0.000001); Assert.True(Math.Abs(sum1 / sum4 - 1) < 0.000001); // Assert.True(Math.Abs(sum1 / sum5 - 1) < 0.000001); sum = 0.0; using (Benchmark.Run("SortedMap enumeration", count)) { var cursor = sm.GetEnumerator(); while (cursor.MoveNext()) { sum += cursor.CurrentValue; } } Assert.True(sum != 0); } Benchmark.Dump($"The window width is {width}. Stat2 MOPS are calculated as a number of calculated values multiplied by width, " + $"which is equivalent to the total number of cursor moves for Window case. SortedMap line is for reference - it is the " + $"speed of raw iteration over SM without Stat2/Windows overheads (and not multiplied by width)."); }