#region License Information /* HeuristicLab * Copyright (C) 2002-2019 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.Globalization; using System.Linq; using HeuristicLab.Analysis; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding; using HeuristicLab.Optimization; using HeuristicLab.Parameters; using HeuristicLab.Persistence.Default.CompositeSerializers.Storable; namespace HeuristicLab.Problems.DataAnalysis.Symbolic { /// /// Calculates the accumulated frequencies of variable-symbols over all trees in the population. /// [Item("SymbolicDataAnalysisVariableFrequencyAnalyzer", "Calculates the accumulated frequencies of variable-symbols over all trees in the population.")] [StorableClass] public sealed class SymbolicDataAnalysisVariableFrequencyAnalyzer : SymbolicDataAnalysisAnalyzer { private const string VariableFrequenciesParameterName = "VariableFrequencies"; private const string AggregateLaggedVariablesParameterName = "AggregateLaggedVariables"; private const string AggregateFactorVariablesParameterName = "AggregateFactorVariables"; private const string VariableImpactsParameterName = "VariableImpacts"; #region parameter properties public ILookupParameter VariableFrequenciesParameter { get { return (ILookupParameter)Parameters[VariableFrequenciesParameterName]; } } public ILookupParameter VariableImpactsParameter { get { return (ILookupParameter)Parameters[VariableImpactsParameterName]; } } public IValueLookupParameter AggregateLaggedVariablesParameter { get { return (IValueLookupParameter)Parameters[AggregateLaggedVariablesParameterName]; } } public IValueLookupParameter AggregateFactorVariablesParameter { get { return (IValueLookupParameter)Parameters[AggregateFactorVariablesParameterName]; } } #endregion #region properties public BoolValue AggregateLaggedVariables { get { return AggregateLaggedVariablesParameter.ActualValue; } set { AggregateLaggedVariablesParameter.Value = value; } } public BoolValue AggregateFactorVariables { get { return AggregateFactorVariablesParameter.ActualValue; } set { AggregateFactorVariablesParameter.Value = value; } } #endregion [StorableConstructor] private SymbolicDataAnalysisVariableFrequencyAnalyzer(bool deserializing) : base(deserializing) { } private SymbolicDataAnalysisVariableFrequencyAnalyzer(SymbolicDataAnalysisVariableFrequencyAnalyzer original, Cloner cloner) : base(original, cloner) { } public SymbolicDataAnalysisVariableFrequencyAnalyzer() : base() { Parameters.Add(new LookupParameter(VariableFrequenciesParameterName, "The relative variable reference frequencies aggregated over all trees in the population.")); Parameters.Add(new LookupParameter(VariableImpactsParameterName, "The relative variable relevance calculated as the average relative variable frequency over the whole run.")); Parameters.Add(new ValueLookupParameter(AggregateLaggedVariablesParameterName, "Switch that determines whether all references to a variable should be aggregated regardless of time-offsets. Turn off to analyze all variable references with different time offsets separately.", new BoolValue(true))); Parameters.Add(new ValueLookupParameter(AggregateFactorVariablesParameterName, "Switch that determines whether all references to factor variables should be aggregated regardless of the value. Turn off to analyze all factor variable references with different values separately.", new BoolValue(true))); } [StorableHook(HookType.AfterDeserialization)] private void AfterDeserialization() { // BackwardsCompatibility3.3 #region Backwards compatible code, remove with 3.4 if (!Parameters.ContainsKey(AggregateFactorVariablesParameterName)) { Parameters.Add(new ValueLookupParameter(AggregateFactorVariablesParameterName, "Switch that determines whether all references to factor variables should be aggregated regardless of the value. Turn off to analyze all factor variable references with different values separately.", new BoolValue(true))); } #endregion } public override IDeepCloneable Clone(Cloner cloner) { return new SymbolicDataAnalysisVariableFrequencyAnalyzer(this, cloner); } public override IOperation Apply() { ItemArray expressions = SymbolicExpressionTreeParameter.ActualValue; ResultCollection results = ResultCollection; DataTable datatable; if (VariableFrequenciesParameter.ActualValue == null) { datatable = new DataTable("Variable frequencies", "Relative frequency of variable references aggregated over the whole population."); datatable.VisualProperties.XAxisTitle = "Generation"; datatable.VisualProperties.YAxisTitle = "Relative Variable Frequency"; VariableFrequenciesParameter.ActualValue = datatable; results.Add(new Result("Variable frequencies", "Relative frequency of variable references aggregated over the whole population.", datatable)); results.Add(new Result("Variable impacts", "The relative variable relevance calculated as the average relative variable frequency over the whole run.", new DoubleMatrix())); } datatable = VariableFrequenciesParameter.ActualValue; // all rows must have the same number of values so we can just take the first int numberOfValues = datatable.Rows.Select(r => r.Values.Count).DefaultIfEmpty().First(); foreach (var pair in CalculateVariableFrequencies(expressions, AggregateLaggedVariables.Value, AggregateFactorVariables.Value)) { if (!datatable.Rows.ContainsKey(pair.Key)) { // initialize a new row for the variable and pad with zeros DataRow row = new DataRow(pair.Key, "", Enumerable.Repeat(0.0, numberOfValues)); row.VisualProperties.StartIndexZero = true; datatable.Rows.Add(row); } datatable.Rows[pair.Key].Values.Add(Math.Round(pair.Value, 3)); } // add a zero for each data row that was not modified in the previous loop foreach (var row in datatable.Rows.Where(r => r.Values.Count != numberOfValues + 1)) row.Values.Add(0.0); // update variable impacts matrix var orderedImpacts = (from row in datatable.Rows select new { Name = row.Name, Impact = Math.Round(datatable.Rows[row.Name].Values.Average(), 3) }) .OrderByDescending(p => p.Impact) .ToList(); var impacts = new DoubleMatrix(); var matrix = impacts as IStringConvertibleMatrix; matrix.Rows = orderedImpacts.Count; matrix.RowNames = orderedImpacts.Select(x => x.Name); matrix.Columns = 1; matrix.ColumnNames = new string[] { "Relative variable relevance" }; int i = 0; foreach (var p in orderedImpacts) { matrix.SetValue(p.Impact.ToString(), i++, 0); } VariableImpactsParameter.ActualValue = impacts; results["Variable impacts"].Value = impacts; return base.Apply(); } public static IEnumerable> CalculateVariableFrequencies(IEnumerable trees, bool aggregateLaggedVariables = true, bool aggregateFactorVariables = true) { var variableFrequencies = trees .SelectMany(t => GetVariableReferences(t, aggregateLaggedVariables, aggregateFactorVariables)) .GroupBy(pair => pair.Key, pair => pair.Value) .ToDictionary(g => g.Key, g => (double)g.Sum()); double totalNumberOfSymbols = variableFrequencies.Values.Sum(); foreach (var pair in variableFrequencies.OrderBy(p => p.Key, new NaturalStringComparer())) yield return new KeyValuePair(pair.Key, pair.Value / totalNumberOfSymbols); } private static IEnumerable> GetVariableReferences(ISymbolicExpressionTree tree, bool aggregateLaggedVariables = true, bool aggregateFactorVariables = true) { Dictionary references = new Dictionary(); if (aggregateLaggedVariables) { tree.Root.ForEachNodePrefix(node => { if (node is IVariableTreeNode) { var factorNode = node as BinaryFactorVariableTreeNode; if (factorNode != null && !aggregateFactorVariables) { IncReferenceCount(references, factorNode.VariableName + "=" + factorNode.VariableValue); } else { var varNode = node as IVariableTreeNode; IncReferenceCount(references, varNode.VariableName); } } }); } else { GetVariableReferences(references, tree.Root, 0, aggregateFactorVariables); } return references; } private static void GetVariableReferences(Dictionary references, ISymbolicExpressionTreeNode node, int currentLag, bool aggregateFactorVariables) { if (node is IVariableTreeNode) { var laggedVarTreeNode = node as LaggedVariableTreeNode; var binFactorVariableTreeNode = node as BinaryFactorVariableTreeNode; var varConditionTreeNode = node as VariableConditionTreeNode; if (laggedVarTreeNode != null) { IncReferenceCount(references, laggedVarTreeNode.VariableName, currentLag + laggedVarTreeNode.Lag); } else if (binFactorVariableTreeNode != null) { if (aggregateFactorVariables) { IncReferenceCount(references, binFactorVariableTreeNode.VariableName, currentLag); } else { IncReferenceCount(references, binFactorVariableTreeNode.VariableName + "=" + binFactorVariableTreeNode.VariableValue, currentLag); } } else if (varConditionTreeNode != null) { IncReferenceCount(references, varConditionTreeNode.VariableName, currentLag); GetVariableReferences(references, node.GetSubtree(0), currentLag, aggregateFactorVariables); GetVariableReferences(references, node.GetSubtree(1), currentLag, aggregateFactorVariables); } else { var varNode = node as IVariableTreeNode; IncReferenceCount(references, varNode.VariableName, currentLag); } } else if (node.Symbol is Integral) { var laggedNode = node as LaggedTreeNode; for (int l = laggedNode.Lag; l <= 0; l++) { GetVariableReferences(references, node.GetSubtree(0), currentLag + l, aggregateFactorVariables); } } else if (node.Symbol is Derivative) { for (int l = -4; l <= 0; l++) { GetVariableReferences(references, node.GetSubtree(0), currentLag + l, aggregateFactorVariables); } } else if (node.Symbol is TimeLag) { var laggedNode = node as LaggedTreeNode; GetVariableReferences(references, node.GetSubtree(0), currentLag + laggedNode.Lag, aggregateFactorVariables); } else { foreach (var subtree in node.Subtrees) { GetVariableReferences(references, subtree, currentLag, aggregateFactorVariables); } } } private static void IncReferenceCount(Dictionary references, string variableName, int timeLag = 0) { string referenceId = variableName + (timeLag == 0 ? "" : timeLag < 0 ? "(t" + timeLag + ")" : "(t+" + timeLag + ")"); if (references.ContainsKey(referenceId)) { references[referenceId]++; } else { references[referenceId] = 1; } } } }