#region License Information /* HeuristicLab * Copyright (C) Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.Linq; using HEAL.Attic; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding; using HeuristicLab.Parameters; namespace HeuristicLab.Problems.DataAnalysis.Symbolic { [StorableType("C8539434-6FB0-47D0-9F5A-2CAE5D8B8B4F")] [Item("Interval Arithmetic Bounds Estimator", "Interpreter for calculation of intervals of symbolic models.")] public sealed class IntervalArithBoundsEstimator : ParameterizedNamedItem, IBoundsEstimator { #region Parameters private const string EvaluatedSolutionsParameterName = "EvaluatedSolutions"; public IFixedValueParameter EvaluatedSolutionsParameter => (IFixedValueParameter)Parameters[EvaluatedSolutionsParameterName]; public int EvaluatedSolutions { get => EvaluatedSolutionsParameter.Value.Value; set => EvaluatedSolutionsParameter.Value.Value = value; } #endregion #region Constructors [StorableConstructor] private IntervalArithBoundsEstimator(StorableConstructorFlag _) : base(_) { } protected IntervalArithBoundsEstimator(IntervalArithBoundsEstimator original, Cloner cloner) : base(original, cloner) { } public IntervalArithBoundsEstimator() : base("Interval Arithmetic Bounds Estimator", "Estimates the bounds of the model with interval arithmetic") { Parameters.Add(new FixedValueParameter(EvaluatedSolutionsParameterName, "A counter for the total number of solutions the estimator has evaluated.", new IntValue(0))); } public override IDeepCloneable Clone(Cloner cloner) { return new IntervalArithBoundsEstimator(this, cloner); } #endregion #region IStatefulItem Members private readonly object syncRoot = new object(); public void InitializeState() { EvaluatedSolutions = 0; } public void ClearState() { } #endregion #region Evaluation private static Instruction[] PrepareInterpreterState( ISymbolicExpressionTree tree, IDictionary variableRanges) { if (variableRanges == null) throw new ArgumentNullException("No variable ranges are present!", nameof(variableRanges)); // Check if all variables used in the tree are present in the dataset foreach (var variable in tree.IterateNodesPrefix().OfType().Select(n => n.VariableName) .Distinct()) if (!variableRanges.ContainsKey(variable)) throw new InvalidOperationException($"No ranges for variable {variable} is present"); var code = SymbolicExpressionTreeCompiler.Compile(tree, OpCodes.MapSymbolToOpCode); foreach (var instr in code.Where(i => i.opCode == OpCodes.Variable)) { var variableTreeNode = (VariableTreeNode)instr.dynamicNode; instr.data = variableRanges[variableTreeNode.VariableName]; } return code; } // Use ref parameter, because the tree will be iterated through recursively from the left-side branch to the right side // Update instructionCounter, whenever Evaluate is called public static Interval Evaluate( Instruction[] instructions, ref int instructionCounter, IDictionary nodeIntervals = null, IDictionary variableIntervals = null) { var currentInstr = instructions[instructionCounter]; instructionCounter++; Interval result; switch (currentInstr.opCode) { case OpCodes.Variable: { var variableTreeNode = (VariableTreeNode)currentInstr.dynamicNode; var weightInterval = new Interval(variableTreeNode.Weight, variableTreeNode.Weight); Interval variableInterval; if (variableIntervals != null && variableIntervals.ContainsKey(variableTreeNode.VariableName)) variableInterval = variableIntervals[variableTreeNode.VariableName]; else variableInterval = (Interval)currentInstr.data; result = Interval.Multiply(variableInterval, weightInterval); break; } case OpCodes.Constant: { var constTreeNode = (ConstantTreeNode)currentInstr.dynamicNode; result = new Interval(constTreeNode.Value, constTreeNode.Value); break; } case OpCodes.Add: { result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); for (var i = 1; i < currentInstr.nArguments; i++) { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Add(result, argumentInterval); } break; } case OpCodes.Sub: { result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); if (currentInstr.nArguments == 1) result = Interval.Multiply(new Interval(-1, -1), result); for (var i = 1; i < currentInstr.nArguments; i++) { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Subtract(result, argumentInterval); } break; } case OpCodes.Mul: { result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); for (var i = 1; i < currentInstr.nArguments; i++) { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Multiply(result, argumentInterval); } break; } case OpCodes.Div: { result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); if (currentInstr.nArguments == 1) result = Interval.Divide(new Interval(1, 1), result); for (var i = 1; i < currentInstr.nArguments; i++) { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Divide(result, argumentInterval); } break; } case OpCodes.Sin: { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Sine(argumentInterval); break; } case OpCodes.Cos: { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Cosine(argumentInterval); break; } case OpCodes.Tan: { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Tangens(argumentInterval); break; } case OpCodes.Tanh: { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.HyperbolicTangent(argumentInterval); break; } case OpCodes.Log: { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Logarithm(argumentInterval); break; } case OpCodes.Exp: { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Exponential(argumentInterval); break; } case OpCodes.Square: { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Square(argumentInterval); break; } case OpCodes.SquareRoot: { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.SquareRoot(argumentInterval); break; } case OpCodes.Cube: { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Cube(argumentInterval); break; } case OpCodes.CubeRoot: { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.CubicRoot(argumentInterval); break; } case OpCodes.Power: { var a = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); var b = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); // support only integer powers if (b.LowerBound == b.UpperBound && Math.Truncate(b.LowerBound) == b.LowerBound) { result = Interval.Power(a, (int)b.LowerBound); } else { throw new NotSupportedException("Interval is only supported for integer powers"); } break; } case OpCodes.Absolute: { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.Absolute(argumentInterval); break; } case OpCodes.AnalyticQuotient: { result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); for (var i = 1; i < currentInstr.nArguments; i++) { var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals); result = Interval.AnalyticQuotient(result, argumentInterval); } break; } default: throw new NotSupportedException( $"The tree contains the unknown symbol {currentInstr.dynamicNode.Symbol}"); } if (!(nodeIntervals == null || nodeIntervals.ContainsKey(currentInstr.dynamicNode))) nodeIntervals.Add(currentInstr.dynamicNode, result); return result; } #endregion #region Helpers private static IDictionary GetOccurringVariableRanges( ISymbolicExpressionTree tree, IntervalCollection variableRanges) { var variables = tree.IterateNodesPrefix().OfType().Select(v => v.VariableName).Distinct() .ToList(); return variables.ToDictionary(x => x, x => variableRanges.GetReadonlyDictionary()[x]); } #endregion public Interval GetModelBound(ISymbolicExpressionTree tree, IntervalCollection variableRanges) { lock (syncRoot) { EvaluatedSolutions++; } var occuringVariableRanges = GetOccurringVariableRanges(tree, variableRanges); var instructions = PrepareInterpreterState(tree, occuringVariableRanges); Interval resultInterval; var instructionCounter = 0; resultInterval = Evaluate(instructions, ref instructionCounter, variableIntervals: occuringVariableRanges); // because of numerical errors the bounds might be incorrect if (resultInterval.IsInfiniteOrUndefined || resultInterval.LowerBound <= resultInterval.UpperBound) return resultInterval; return new Interval(resultInterval.UpperBound, resultInterval.LowerBound); } public IDictionary GetModelNodeBounds( ISymbolicExpressionTree tree, IntervalCollection variableRanges) { throw new NotImplementedException(); } public double GetConstraintViolation( ISymbolicExpressionTree tree, IntervalCollection variableRanges, ShapeConstraint constraint) { var occuringVariableRanges = GetOccurringVariableRanges(tree, variableRanges); var instructions = PrepareInterpreterState(tree, occuringVariableRanges); var instructionCounter = 0; var modelBound = Evaluate(instructions, ref instructionCounter, variableIntervals: occuringVariableRanges); if (constraint.Interval.Contains(modelBound)) return 0.0; var error = 0.0; if (!constraint.Interval.Contains(modelBound.LowerBound)) { error += Math.Abs(modelBound.LowerBound - constraint.Interval.LowerBound); } if (!constraint.Interval.Contains(modelBound.UpperBound)) { error += Math.Abs(modelBound.UpperBound - constraint.Interval.UpperBound); } return error; } public bool IsCompatible(ISymbolicExpressionTree tree) { var containsUnknownSymbols = ( from n in tree.Root.GetSubtree(0).IterateNodesPrefix() where !(n.Symbol is Variable) && !(n.Symbol is Constant) && !(n.Symbol is StartSymbol) && !(n.Symbol is Addition) && !(n.Symbol is Subtraction) && !(n.Symbol is Multiplication) && !(n.Symbol is Division) && !(n.Symbol is Sine) && !(n.Symbol is Cosine) && !(n.Symbol is Tangent) && !(n.Symbol is HyperbolicTangent) && !(n.Symbol is Logarithm) && !(n.Symbol is Exponential) && !(n.Symbol is Square) && !(n.Symbol is SquareRoot) && !(n.Symbol is Cube) && !(n.Symbol is CubeRoot) && !(n.Symbol is Power) && !(n.Symbol is Absolute) && !(n.Symbol is AnalyticQuotient) select n).Any(); return !containsUnknownSymbols; } } }