#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.Collections.ObjectModel;
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("DE6C1E1E-D7C1-4070-847E-63B68562B10C")]
[Item("IntervalInterpreter", "Interpreter for calculation of intervals of symbolic models.")]
public sealed class IntervalInterpreter : ParameterizedNamedItem, IStatefulItem {
private const string EvaluatedSolutionsParameterName = "EvaluatedSolutions";
private const string MinSplittingWidthParameterName = "MinSplittingWidth";
private const string MaxSplittingDepthParameterName = "MaxSplittingDepth";
private const string UseIntervalSplittingParameterName = "UseIntervalSplitting";
public IFixedValueParameter EvaluatedSolutionsParameter =>
(IFixedValueParameter)Parameters[EvaluatedSolutionsParameterName];
public IFixedValueParameter MinSplittingWithParameter =>
(IFixedValueParameter)Parameters[MinSplittingWidthParameterName];
public IFixedValueParameter MaxSplittingDepthParameter =>
(IFixedValueParameter)Parameters[MaxSplittingDepthParameterName];
public IFixedValueParameter UseIntervalSplittingParameter =>
(IFixedValueParameter)Parameters[UseIntervalSplittingParameterName];
public int MinSplittingWidth {
get => MinSplittingWithParameter.Value.Value;
set => MinSplittingWithParameter.Value.Value = value;
}
public int MaxSplittingDepth {
get => MaxSplittingDepthParameter.Value.Value;
set => MaxSplittingDepthParameter.Value.Value = value;
}
public bool UseIntervalSplitting {
get => UseIntervalSplittingParameter.Value.Value;
set => UseIntervalSplittingParameter.Value.Value = value;
}
public int EvaluatedSolutions {
get => EvaluatedSolutionsParameter.Value.Value;
set => EvaluatedSolutionsParameter.Value.Value = value;
}
[StorableConstructor]
private IntervalInterpreter(StorableConstructorFlag _) : base(_) { }
private IntervalInterpreter(IntervalInterpreter original, Cloner cloner)
: base(original, cloner) { }
public IntervalInterpreter()
: base("IntervalInterpreter", "Interpreter for calculation of intervals of symbolic models.") {
Parameters.Add(new FixedValueParameter(EvaluatedSolutionsParameterName,
"A counter for the total number of solutions the interpreter has evaluated", new IntValue(0)));
Parameters.Add(new FixedValueParameter(MinSplittingWidthParameterName,
"Minimum interval width until splitting is stopped", new IntValue(0)));
Parameters.Add(new FixedValueParameter(MaxSplittingDepthParameterName,
"Maximum recursion depth of the splitting", new IntValue(5)));
Parameters.Add(new FixedValueParameter(UseIntervalSplittingParameterName, "", new BoolValue(false)));
}
public override IDeepCloneable Clone(Cloner cloner) {
return new IntervalInterpreter(this, cloner);
}
private readonly object syncRoot = new object();
#region IStatefulItem Members
public void InitializeState() {
EvaluatedSolutions = 0;
}
public void ClearState() { }
#endregion
public Interval GetSymbolicExpressionTreeInterval(
ISymbolicExpressionTree tree, IDataset dataset,
IEnumerable rows = null) {
var variableRanges = DatasetUtil.GetVariableRanges(dataset, rows);
return GetSymbolicExpressionTreeInterval(tree, variableRanges);
}
public Interval GetSymbolicExpressionTreeIntervals(
ISymbolicExpressionTree tree, IDataset dataset,
out IDictionary
nodeIntervals, IEnumerable rows = null) {
var variableRanges = DatasetUtil.GetVariableRanges(dataset, rows);
return GetSymbolicExpressionTreeIntervals(tree, variableRanges, out nodeIntervals);
}
public Interval GetSymbolicExpressionTreeInterval(
ISymbolicExpressionTree tree,
IReadOnlyDictionary variableRanges) {
lock (syncRoot) {
EvaluatedSolutions++;
}
Interval outputInterval;
if (UseIntervalSplitting) {
outputInterval = GetSymbolicExpressionTreeIntervals(tree, variableRanges,
out var _);
} else {
var instructionCount = 0;
var instructions = PrepareInterpreterState(tree, variableRanges);
outputInterval = Evaluate(instructions, ref instructionCount);
}
return outputInterval.LowerBound <= outputInterval.UpperBound
? outputInterval
: new Interval(outputInterval.UpperBound, outputInterval.LowerBound);
}
public Interval GetSymbolicExpressionTreeIntervals(
ISymbolicExpressionTree tree,
IReadOnlyDictionary variableRanges,
out IDictionary
nodeIntervals) {
lock (syncRoot) {
EvaluatedSolutions++;
}
var intervals = new Dictionary();
var instructions = PrepareInterpreterState(tree, variableRanges);
Interval outputInterval;
if (UseIntervalSplitting) {
//var variables = tree.IterateNodesPrefix().OfType().Select(x => x.VariableName).Distinct()
// .ToList();
var containsDependencyProblem = ContainsVariableMultipleTimes(tree, out var variables);
if (containsDependencyProblem) {
var currIndex = 0;
var currDepth = 0;
IDictionary writeableVariableRanges =
variableRanges.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
//outputInterval = EvaluateRecursive(instructions, intervals, writeableVariableRanges, variables, MinSplittingWidth, MaxSplittingDepth,
// ref currIndex, ref currDepth, tree);
outputInterval = EvaluateWithSplitting(instructions, intervals, writeableVariableRanges, variables);
} else {
var instructionCount = 0;
outputInterval = Evaluate(instructions, ref instructionCount, intervals);
}
} else {
var instructionCount = 0;
outputInterval = Evaluate(instructions, ref instructionCount, intervals);
}
nodeIntervals = new Dictionary();
foreach (var kvp in intervals) {
var interval = kvp.Value;
if (interval.IsInfiniteOrUndefined || interval.LowerBound <= interval.UpperBound)
nodeIntervals.Add(kvp.Key, interval);
else
nodeIntervals.Add(kvp.Key, new Interval(interval.UpperBound, interval.LowerBound));
}
// because of numerical errors the bounds might be incorrect
if (outputInterval.IsInfiniteOrUndefined || outputInterval.LowerBound <= outputInterval.UpperBound)
return outputInterval;
return new Interval(outputInterval.UpperBound, outputInterval.LowerBound);
}
private static Instruction[] PrepareInterpreterState(
ISymbolicExpressionTree tree,
IReadOnlyDictionary variableRanges) {
if (variableRanges == null)
throw new ArgumentNullException("No variablew 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;
}
public static Interval EvaluateWithSplitting(Instruction[] instructions,
IDictionary nodeIntervals,
IDictionary variableIntervals, List multipleOccurenceVariables) {
var savedIntervals = variableIntervals.ToDictionary(entry => entry.Key, entry => entry.Value);
var min = FindBound(instructions, nodeIntervals, variableIntervals, multipleOccurenceVariables, minimization: true);
var max = FindBound(instructions, nodeIntervals, savedIntervals, multipleOccurenceVariables, minimization: false);
return new Interval(min, max);
}
private static double FindBound(Instruction[] instructions,
IDictionary nodeIntervals,
IDictionary variableIntervals, List multipleOccurenceVariables, bool minimization = true) {
SortedSet prioQ = new SortedSet();
var ic = 0;
//Calculate full box
IReadOnlyDictionary readonlyRanges = variableIntervals.ToDictionary(k => k.Key, k => k.Value);
var interval = Evaluate(instructions, ref ic, nodeIntervals, readonlyRanges);
// the order of keys in a dictionary is guaranteed to be the same order as values in a dictionary
// https://docs.microsoft.com/en-us/dotnet/api/system.collections.idictionary.keys?view=netcore-3.1#remarks
//var box = variableIntervals.Values;
//Box only contains intervals from multiple occurence variables
var box = multipleOccurenceVariables.Select(k => variableIntervals[k]);
if (minimization) {
prioQ.Add(new BoxBound(box, interval.LowerBound));
} else {
prioQ.Add(new BoxBound(box, -interval.UpperBound));
}
// TODO a fixed limit for depth?!
for (var depth = 0; depth < 200; ++depth) {
var currentBound = prioQ.Min;
prioQ.Remove(currentBound);
var newBoxes = Split(currentBound.box);
foreach (var newBox in newBoxes) {
//var intervalEnum = newBox.GetEnumerator();
//var keyEnum = readonlyRanges.Keys.GetEnumerator();
//while (intervalEnum.MoveNext() & keyEnum.MoveNext()) {
// variableIntervals[keyEnum.Current] = intervalEnum.Current;
//}
//Set the splitted variables
var intervalEnum = newBox.GetEnumerator();
foreach (var key in multipleOccurenceVariables) {
intervalEnum.MoveNext();
variableIntervals[key] = intervalEnum.Current;
}
ic = 0;
var res = Evaluate(instructions, ref ic, nodeIntervals,
new ReadOnlyDictionary(variableIntervals));
if (minimization) {
prioQ.Add(new BoxBound(newBox, res.LowerBound));
} else {
prioQ.Add(new BoxBound(newBox, -res.UpperBound));
}
}
}
return minimization ?
prioQ.First().bound :
-prioQ.First().bound;
}
private static IEnumerable> Split(List box) {
var boxes = box.Select(region => region.Split()).Select(split => new List { split.Item1, split.Item2 })
.ToList();
return boxes.CartesianProduct();
}
// a multi-dimensional box with an associated bound
// boxbounds are ordered first by bound (smaller first), then by size of box (larger first) then by distance of bottom left corner to origin
private class BoxBound : IComparable {
public List box;
public double bound;
public BoxBound(IEnumerable box, double bound) {
this.box = new List(box);
this.bound = bound;
}
public int CompareTo(BoxBound other) {
if (bound != other.bound) return bound.CompareTo(other.bound);
var thisSize = box.Aggregate(1.0, (current, dimExtent) => current * dimExtent.Width);
var otherSize = other.box.Aggregate(1.0, (current, dimExtent) => current * dimExtent.Width);
if (thisSize != otherSize) return -thisSize.CompareTo(otherSize);
var thisDist = box.Sum(dimExtent => dimExtent.LowerBound * dimExtent.LowerBound);
var otherDist = other.box.Sum(dimExtent => dimExtent.LowerBound * dimExtent.LowerBound);
if (thisDist != otherDist) return thisDist.CompareTo(otherDist);
// which is smaller first along the dimensions?
for (int i = 0; i < box.Count; i++) {
if (box[i].LowerBound != other.box[i].LowerBound) return box[i].LowerBound.CompareTo(other.box[i].LowerBound);
}
return 0;
}
}
public static Interval EvaluateRecursive(
Instruction[] instructions,
IDictionary nodeIntervals,
IDictionary variableIntervals, IList variables,
double minWidth, int maxDepth, ref int currIndex, ref int currDepth,
ISymbolicExpressionTree tree) {
Interval evaluate() {
var ic = 0;
IReadOnlyDictionary readonlyRanges =
new ReadOnlyDictionary(variableIntervals);
return Evaluate(instructions, ref ic, nodeIntervals, readonlyRanges);
}
Interval recurse(ref int idx, ref int depth) {
return EvaluateRecursive(instructions, nodeIntervals, variableIntervals, variables, minWidth, maxDepth, ref idx,
ref depth, tree);
}
var v = variables[currIndex];
var x = variableIntervals[v];
if (x.Width < minWidth || currDepth == maxDepth || !MultipleTimes(tree, v)) {
if (currIndex + 1 < variables.Count) {
currDepth = 0;
currIndex++;
var z = recurse(ref currIndex, ref currDepth);
currIndex--;
return z;
}
return evaluate();
}
var t = x.Split();
var xa = t.Item1;
var xb = t.Item2;
var d = currDepth;
currDepth = d + 1;
variableIntervals[v] = xa;
var ya = recurse(ref currIndex, ref currDepth);
currDepth = d + 1;
variableIntervals[v] = xb;
var yb = recurse(ref currIndex, ref currDepth);
variableIntervals[v] = x; // restore interval
return ya | yb;
}
public static Interval Evaluate(
Instruction[] instructions, ref int instructionCounter,
IDictionary nodeIntervals = null,
IReadOnlyDictionary variableIntervals = null) {
var currentInstr = instructions[instructionCounter];
//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
instructionCounter++;
Interval result = null;
switch (currentInstr.opCode) {
//Variables, Constants, ...
case OpCodes.Variable: {
var variableTreeNode = (VariableTreeNode)currentInstr.dynamicNode;
var weightInterval = new Interval(variableTreeNode.Weight, variableTreeNode.Weight);
//var variableInterval = (Interval)currentInstr.data;
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;
}
//Elementary arithmetic rules
case OpCodes.Add: {
//result = Evaluate(instructions, ref instructionCounter, nodeIntervals);
result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
for (var i = 1; i < currentInstr.nArguments; i++) {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.Add(result, argumentInterval);
}
break;
}
case OpCodes.Sub: {
//result = Evaluate(instructions, ref instructionCounter, nodeIntervals);
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);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.Subtract(result, argumentInterval);
}
break;
}
case OpCodes.Mul: {
//result = Evaluate(instructions, ref instructionCounter, nodeIntervals);
result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
for (var i = 1; i < currentInstr.nArguments; i++) {
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
result = Interval.Multiply(result, argumentInterval);
}
break;
}
case OpCodes.Div: {
//result = Evaluate(instructions, ref instructionCounter, nodeIntervals);
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);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.Divide(result, argumentInterval);
}
break;
}
//Trigonometric functions
case OpCodes.Sin: {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.Sine(argumentInterval);
break;
}
case OpCodes.Cos: {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.Cosine(argumentInterval);
break;
}
case OpCodes.Tan: {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.Tangens(argumentInterval);
break;
}
case OpCodes.Tanh: {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.HyperbolicTangent(argumentInterval);
break;
}
//Exponential functions
case OpCodes.Log: {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.Logarithm(argumentInterval);
break;
}
case OpCodes.Exp: {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.Exponential(argumentInterval);
break;
}
case OpCodes.Square: {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.Square(argumentInterval);
break;
}
case OpCodes.SquareRoot: {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.SquareRoot(argumentInterval);
break;
}
case OpCodes.Cube: {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.Cube(argumentInterval);
break;
}
case OpCodes.CubeRoot: {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.CubicRoot(argumentInterval);
break;
}
case OpCodes.Absolute: {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.Absolute(argumentInterval);
break;
}
case OpCodes.AnalyticQuotient: {
//result = Evaluate(instructions, ref instructionCounter, nodeIntervals);
result = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
for (var i = 1; i < currentInstr.nArguments; i++) {
//var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals);
var argumentInterval = Evaluate(instructions, ref instructionCounter, nodeIntervals, variableIntervals);
result = Interval.AnalyticalQuotient(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;
}
private static bool MultipleTimes(ISymbolicExpressionTree tree, string variable) {
var varlist = tree.IterateNodesPrefix().OfType().GroupBy(x => x.VariableName);
var group = varlist.Select(x => x.Key == variable).Count();
return group > 1;
}
private static bool ContainsVariableMultipleTimes(ISymbolicExpressionTree tree, out List variables) {
variables = new List();
var varlist = tree.IterateNodesPrefix().OfType().GroupBy(x => x.VariableName);
foreach (var group in varlist) {
if (group.Count() > 1) {
variables.Add(group.Key);
}
}
return varlist.Any(group => group.Count() > 1);
}
public static 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 Absolute) &&
!(n.Symbol is AnalyticQuotient)
select n).Any();
return !containsUnknownSymbols;
}
}
}