using System; using System.Collections.Generic; using System.Linq; using System.Text; using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding; using HeuristicLab.Problems.DataAnalysis; using HeuristicLab.Problems.DataAnalysis.Symbolic; using HeuristicLab.Random; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace HeuristicLab.Tests { [TestClass] public class IntervalCalculationComparison { [TestMethod] [TestCategory("Problems.DataAnalysis")] [TestProperty("Time", "long")] public void TestIntervalCalculationForRandomExpressions() { var grammar = new TypeCoherentExpressionGrammar(); grammar.ConfigureAsDefaultRegressionGrammar(); // activate supported symbols grammar.Symbols.First(s => s is Square).Enabled = true; grammar.Symbols.First(s => s is SquareRoot).Enabled = true; grammar.Symbols.First(s => s is Cube).Enabled = true; grammar.Symbols.First(s => s is CubeRoot).Enabled = true; grammar.Symbols.First(s => s is Sine).Enabled = true; grammar.Symbols.First(s => s is Cosine).Enabled = true; grammar.Symbols.First(s => s is Exponential).Enabled = true; grammar.Symbols.First(s => s is Logarithm).Enabled = true; grammar.Symbols.First(s => s is Absolute).Enabled = false; // XXX not yet supported by old interval calculator grammar.Symbols.First(s => s is AnalyticQuotient).Enabled = false; // not yet supported by old interval calculator var varSy = (Variable)grammar.Symbols.First(s => s is Variable); varSy.AllVariableNames = new string[] { "x", "y" }; varSy.VariableNames = varSy.AllVariableNames; varSy.WeightMu = 1.0; varSy.WeightSigma = 1.0; var rand = new FastRandom(1234); var eval1 = new IntervalEvaluator(); var eval2 = new IntervalInterpreter(); IDictionary posIntervals = new Dictionary() { { "x", new Interval(1, 2) }, { "y", new Interval(0, 1) } }; IDictionary negIntervals = new Dictionary() { { "x", new Interval(-2, -1) }, { "y", new Interval(-1, 0) } }; IDictionary fullIntervals = new Dictionary() { { "x", new Interval(-2, 2) }, { "y", new Interval(-1, 1) } }; IDictionary specialIntervals = new Dictionary() { { "x", new Interval(1, double.PositiveInfinity) }, { "y", new Interval(double.NegativeInfinity, double.PositiveInfinity) } }; var formatter = new InfixExpressionFormatter(); var sb = new StringBuilder(); foreach (var interval in new[] { posIntervals, negIntervals, fullIntervals, specialIntervals }) { int N = 10000; int i = 0; while (i < N) { var t = ProbabilisticTreeCreator.Create(rand, grammar, maxTreeLength: 5, maxTreeDepth: 5); var r1 = eval1.Evaluate(t, interval); var r2 = eval2.GetSymbolicExpressionTreeInterval(t, interval); // Console.WriteLine(formatter.Format(t)); // all NaN is ok (but don't count NaN expressions) if (double.IsNaN(r1.LowerBound) && double.IsNaN(r2.LowerBound) && double.IsNaN(r1.UpperBound) && double.IsNaN(r2.UpperBound)) continue; if (r1.LowerBound == r2.LowerBound && r1.UpperBound == r2.UpperBound) { /* exactly the same value (incl. Inf / -Inf) => ok */ } else if ((Math.Abs(r1.LowerBound - r2.LowerBound) < Math.Max(1e-10, Math.Abs(r1.LowerBound * 1e-4))) && (Math.Abs(r1.UpperBound - r2.UpperBound) < Math.Max(1e-10, Math.Abs(r1.UpperBound * 1e-4)))) { /* approximately the same value => OK */ } else { sb.AppendLine($"{r1} <> {r2} for {formatter.Format(t)} x={interval["x"]} y={interval["y"]}"); } i++; } } if (sb.Length > 0) { Console.WriteLine(sb.ToString()); Assert.Fail("There were different interval calculation results"); } } [TestMethod] [TestCategory("Problems.DataAnalysis")] [TestProperty("Time", "short")] public void TestExampleIntervalExpressions() { var parser = new InfixExpressionParser(); var eval1 = new IntervalEvaluator(); var eval2 = new IntervalInterpreter(); IDictionary interval = new Dictionary() { { "x", new Interval(1, 2) }, { "y", new Interval(0, 1) }, { "z", new Interval(double.NegativeInfinity, double.PositiveInfinity) }, }; var exprs = new string[] { "CUBE((0.642971622547268*'x')) * (-16.5400720573962)", "sqr(y / y)", // one interpreter produces [NaN, inf], the other [NaN, 0] "cuberoot(-x)", // the old interpreter calculates cuberoot incorrectly "sqr(log(-x))", // Interval: [NaN, NaN] <> Interval (old): [NaN, 0] "log(1.8*'y' - 1.4*'y')", // Interval: [NaN, 0,587786664902119] <> Interval (old): [0,587786664902119, NaN] "log(z)", // Interval: [NaN, ∞] <> Interval (old): [∞, NaN] "sqr(sqrt(-1))" // Interval: [NaN, NaN] <> Interval (old): [NaN, 0] }; var formatter = new InfixExpressionFormatter(); var sb = new StringBuilder(); foreach (var expr in exprs) { var t = parser.Parse(expr); var r1 = eval1.Evaluate(t, interval); var r2 = eval2.GetSymbolicExpressionTreeInterval(t, interval); // Console.WriteLine(formatter.Format(t)); // all NaN is ok if (double.IsNaN(r1.LowerBound) && double.IsNaN(r2.LowerBound) && double.IsNaN(r1.UpperBound) && double.IsNaN(r2.UpperBound)) continue; if (r1.LowerBound == r2.LowerBound && r1.UpperBound == r2.UpperBound) continue; // Inf, -Inf and exactly the same value are ok if ((Math.Abs(r1.LowerBound - r2.LowerBound) < Math.Abs(r1.LowerBound * 1e-4)) && (Math.Abs(r1.UpperBound - r2.UpperBound) < Math.Abs(r1.UpperBound * 1e-4))) { /* OK */ } else { sb.AppendLine($"{r1} <> {r2} for {formatter.Format(t)} x={interval["x"]} y={interval["y"]}"); } } if (sb.Length > 0) { Console.WriteLine(sb.ToString()); Assert.Fail("There were different interval calculation results"); } } } }