#region License Information /* HeuristicLab * Copyright (C) 2002-2008 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.Text; using HeuristicLab.Core; using HeuristicLab.Constraints; using System.Diagnostics; using HeuristicLab.Data; using System.Linq; using HeuristicLab.Random; using HeuristicLab.Operators; using HeuristicLab.Selection; namespace HeuristicLab.StructureIdentification { internal class TreeGardener { private IRandom random; private IOperatorLibrary opLibrary; private List functions; private List terminals; internal IList Terminals { get { return terminals.AsReadOnly(); } } private List allOperators; internal IList AllOperators { get { return allOperators.AsReadOnly(); } } internal TreeGardener(IRandom random, IOperatorLibrary opLibrary) { this.random = random; this.opLibrary = opLibrary; this.allOperators = new List(); terminals = new List(); functions = new List(); // init functions and terminals based on constraints foreach (IOperator op in opLibrary.Group.Operators) { int maxA, minA; GetMinMaxArity(op, out minA, out maxA); if (maxA == 0) { terminals.Add(op); } else { functions.Add(op); } } allOperators.AddRange(functions); allOperators.AddRange(terminals); } #region random initialization internal IOperator CreateRandomTree(ICollection allowedOperators, int maxTreeSize, int maxTreeHeight, bool balanceTrees) { int minTreeHeight = allowedOperators.Select(op => ((IntData)op.GetVariable(GPOperatorLibrary.MIN_TREE_HEIGHT).Value).Data).Min(); if (minTreeHeight > maxTreeHeight) maxTreeHeight = minTreeHeight; int minTreeSize = allowedOperators.Select(op => ((IntData)op.GetVariable(GPOperatorLibrary.MIN_TREE_SIZE).Value).Data).Min(); if (minTreeSize > maxTreeSize) maxTreeSize = minTreeSize; int treeHeight = random.Next(minTreeHeight, maxTreeHeight + 1); int treeSize = random.Next(minTreeSize, maxTreeSize + 1); IOperator[] possibleOperators = allowedOperators.Where(op => ((IntData)op.GetVariable(GPOperatorLibrary.MIN_TREE_HEIGHT).Value).Data <= treeHeight && ((IntData)op.GetVariable(GPOperatorLibrary.MIN_TREE_SIZE).Value).Data <= treeSize).ToArray(); IOperator selectedOperator = (IOperator)possibleOperators[random.Next(possibleOperators.Length)].Clone(); IOperator rootOperator = CreateRandomTree(selectedOperator, treeSize, treeHeight, balanceTrees); return rootOperator; } internal IOperator CreateRandomTree(int maxTreeSize, int maxTreeHeight, bool balanceTrees) { if (balanceTrees) { if (maxTreeHeight == 1 || maxTreeSize==1) { IOperator selectedTerminal = (IOperator)terminals[random.Next(terminals.Count())].Clone(); return selectedTerminal; } else { IOperator[] possibleFunctions = functions.Where(f => GetMinimalTreeHeight(f) <= maxTreeHeight && GetMinimalTreeSize(f) <= maxTreeSize).ToArray(); IOperator selectedFunction = (IOperator)possibleFunctions[random.Next(possibleFunctions.Length)].Clone(); MakeBalancedTree(selectedFunction, maxTreeSize - 1, maxTreeHeight - 1); return selectedFunction; } } else { IOperator[] possibleOperators = allOperators.Where(op => GetMinimalTreeHeight(op) <= maxTreeHeight && GetMinimalTreeSize(op) <= maxTreeSize).ToArray(); IOperator selectedOperator = (IOperator)possibleOperators[random.Next(possibleOperators.Length)].Clone(); MakeUnbalancedTree(selectedOperator, maxTreeSize - 1, maxTreeHeight - 1); return selectedOperator; } } internal IOperator CreateRandomTree(IOperator root, int maxTreeSize, int maxTreeHeight, bool balanceTrees) { if (balanceTrees) { MakeBalancedTree(root, maxTreeSize - 1, maxTreeHeight - 1); } else { MakeUnbalancedTree(root, maxTreeSize - 1, maxTreeHeight - 1); } if (GetTreeSize(root) > maxTreeSize || GetTreeHeight(root) > maxTreeHeight) { throw new InvalidProgramException(); } return root; } private void MakeUnbalancedTree(IOperator parent, int maxTreeSize, int maxTreeHeight) { if (maxTreeHeight == 0 || maxTreeSize == 0) return; int minArity; int maxArity; GetMinMaxArity(parent, out minArity, out maxArity); if (maxArity >= maxTreeSize) { maxArity = maxTreeSize; } int actualArity = random.Next(minArity, maxArity + 1); if (actualArity > 0) { int maxSubTreeSize = maxTreeSize / actualArity; for (int i = 0; i < actualArity; i++) { IOperator[] possibleOperators = GetAllowedSubOperators(parent, i).Where(op => GetMinimalTreeHeight(op) <= maxTreeHeight && GetMinimalTreeSize(op) <= maxSubTreeSize).ToArray(); IOperator selectedOperator = (IOperator)possibleOperators[random.Next(possibleOperators.Length)].Clone(); parent.AddSubOperator(selectedOperator, i); MakeUnbalancedTree(selectedOperator, maxSubTreeSize - 1, maxTreeHeight - 1); } } } // NOTE: this method doesn't build fully balanced trees because we have constraints on the // types of possible suboperators which can indirectly impose a limit for the depth of a given suboperator private void MakeBalancedTree(IOperator parent, int maxTreeSize, int maxTreeHeight) { if (maxTreeHeight == 0 || maxTreeSize == 0) return; // should never happen anyway int minArity; int maxArity; GetMinMaxArity(parent, out minArity, out maxArity); if (maxArity >= maxTreeSize) { maxArity = maxTreeSize; } int actualArity = random.Next(minArity, maxArity + 1); if (actualArity > 0) { int maxSubTreeSize = maxTreeSize / actualArity; for (int i = 0; i < actualArity; i++) { if (maxTreeHeight == 1 || maxSubTreeSize == 1) { IOperator[] possibleTerminals = GetAllowedSubOperators(parent, i).Where( op => GetMinimalTreeHeight(op) <= maxTreeHeight && GetMinimalTreeSize(op) <= maxSubTreeSize && IsTerminal(op)).ToArray(); IOperator selectedTerminal = (IOperator)possibleTerminals[random.Next(possibleTerminals.Length)].Clone(); parent.AddSubOperator(selectedTerminal, i); } else { IOperator[] possibleFunctions = GetAllowedSubOperators(parent, i).Where( op => GetMinimalTreeHeight(op) <= maxTreeHeight && GetMinimalTreeSize(op) <= maxSubTreeSize && !IsTerminal(op)).ToArray(); IOperator selectedFunction = (IOperator)possibleFunctions[random.Next(possibleFunctions.Length)].Clone(); parent.AddSubOperator(selectedFunction, i); MakeBalancedTree(selectedFunction, maxSubTreeSize - 1, maxTreeHeight - 1); } } } } internal CompositeOperation CreateInitializationOperation(ICollection operators, IScope scope) { // needed for the parameter shaking operation CompositeOperation initializationOperation = new CompositeOperation(); Scope tempScope = new Scope("Temp. initialization scope"); var parametricOperators = operators.Where(o => o.GetVariable(GPOperatorLibrary.INITIALIZATION) != null); foreach (IOperator op in parametricOperators) { // enqueue an initialization operation for each operator with local variables IOperator initialization = (IOperator)op.GetVariable(GPOperatorLibrary.INITIALIZATION).Value; Scope initScope = new Scope(); // copy the local variables into a temporary scope used for initialization foreach (VariableInfo info in op.VariableInfos) { if (info.Local) { initScope.AddVariable(op.GetVariable(info.FormalName)); } } tempScope.AddSubScope(initScope); initializationOperation.AddOperation(new AtomicOperation(initialization, initScope)); } Scope backupScope = new Scope("backup"); foreach (Scope subScope in scope.SubScopes) { backupScope.AddSubScope(subScope); } scope.AddSubScope(tempScope); scope.AddSubScope(backupScope); // add an operation to remove the temporary scopes initializationOperation.AddOperation(new AtomicOperation(new RightReducer(), scope)); return initializationOperation; } #endregion #region tree information gathering internal int GetTreeSize(IOperator tree) { return 1 + tree.SubOperators.Sum(f => GetTreeSize(f)); } internal int GetTreeHeight(IOperator tree) { if (tree.SubOperators.Count == 0) return 1; return 1 + tree.SubOperators.Max(f => GetTreeHeight(f)); } internal IOperator GetRandomParentNode(IOperator tree) { List parentNodes = new List(); // add null for the parent of the root node parentNodes.Add(null); TreeForEach(tree, delegate(IOperator op) { if (op.SubOperators.Count > 0) { parentNodes.Add(op); } }); return parentNodes[random.Next(parentNodes.Count)]; } internal IList GetAllowedSubOperators(IOperator op, int index) { if (op == null) { return allOperators; } else { SubOperatorsConstraintAnalyser analyser = new SubOperatorsConstraintAnalyser(); analyser.AllPossibleOperators = allOperators; return analyser.GetAllowedOperators(op, index); } } internal void GetMinMaxArity(IOperator root, out int minArity, out int maxArity) { foreach (IConstraint constraint in root.Constraints) { NumberOfSubOperatorsConstraint theConstraint = constraint as NumberOfSubOperatorsConstraint; if (theConstraint != null) { minArity = theConstraint.MinOperators.Data; maxArity = theConstraint.MaxOperators.Data; return; } } // the default arity is 2 minArity = 2; maxArity = 2; } internal bool IsTerminal(IOperator f) { int minArity; int maxArity; GetMinMaxArity(f, out minArity, out maxArity); return minArity == 0 && maxArity == 0; } internal IList GetAllowedParents(IOperator child, int childIndex) { List parents = new List(); foreach (IOperator function in functions) { IList allowedSubOperators = GetAllowedSubOperators(function, childIndex); if (allowedSubOperators.Contains(child, new OperatorEqualityComparer())) { parents.Add(function); } } return parents; } internal ICollection GetAllOperators(IOperator root) { List allOps = new List(); TreeForEach(root, t => { allOps.Add(t); }); return allOps; } /// /// returns the height level of op in the tree /// if the op == tree => 1 /// if op is in the suboperators of tree => 2 /// ... /// if op is not found => -1 /// /// operator tree to process /// operater that is searched in the tree /// internal int GetNodeLevel(IOperator tree, IOperator op) { return GetNodeLevelHelper(tree, op, 1); } private int GetNodeLevelHelper(IOperator tree, IOperator op, int level) { if (op == tree) return level; foreach (IOperator subTree in tree.SubOperators) { int result = GetNodeLevelHelper(subTree, op, level + 1); if (result != -1) return result; } return -1; } internal bool IsValidTree(IOperator tree) { if (!tree.IsValid()) return false; foreach (IOperator subTree in tree.SubOperators) { if (!subTree.IsValid()) return false; } return true; } // returns a random node from the specified level in the tree internal IOperator GetRandomNode(IOperator tree, int level) { if (level == 0) return tree; List nodes = GetOperatorsAtLevel(tree, level); return nodes[random.Next(nodes.Count)]; } #endregion #region private utility methods private int GetMinimalTreeHeight(IOperator op) { return ((IntData)op.GetVariable(GPOperatorLibrary.MIN_TREE_HEIGHT).Value).Data; } private int GetMinimalTreeSize(IOperator op) { return ((IntData)op.GetVariable(GPOperatorLibrary.MIN_TREE_SIZE).Value).Data; } private void TreeForEach(IOperator tree, Action action) { action(tree); foreach (IOperator child in tree.SubOperators) { TreeForEach(child, action); } } private List GetOperatorsAtLevel(IOperator tree, int level) { if (level == 1) return new List(tree.SubOperators); List result = new List(); foreach (IOperator subOperator in tree.SubOperators) { result.AddRange(GetOperatorsAtLevel(subOperator, level - 1)); } return result; } #endregion internal class OperatorEqualityComparer : IEqualityComparer { #region IEqualityComparer Members public bool Equals(IOperator x, IOperator y) { return ((StringData)x.GetVariable(GPOperatorLibrary.TYPE_ID).Value).Data == ((StringData)y.GetVariable(GPOperatorLibrary.TYPE_ID).Value).Data; } public int GetHashCode(IOperator obj) { return ((StringData)obj.GetVariable(GPOperatorLibrary.TYPE_ID).Value).Data.GetHashCode(); } #endregion } internal ICollection GetPossibleParents(List list) { List result = new List(); foreach (IOperator op in functions) { if (IsPossibleParent(op, list)) { result.Add(op); } } return result; } private bool IsPossibleParent(IOperator op, List children) { int minArity; int maxArity; GetMinMaxArity(op, out minArity, out maxArity); // note: we can't assume that the operators in the children list have different types! // when the maxArity of this function is smaller than the list of operators that // should be included as sub-operators then it can't be a parent if (maxArity < children.Count()) { return false; } int nSlots = Math.Max(minArity, children.Count); SubOperatorsConstraintAnalyser analyzer = new SubOperatorsConstraintAnalyser(); analyzer.AllPossibleOperators = children; List> slotSets = new List>(); // we iterate through all slots for sub-operators and calculate the set of // allowed sub-operators for this slot. // we only count those slots that can hold at least one of the children that we should combine for (int slot = 0; slot < nSlots; slot++) { HashSet operatorSet = new HashSet(analyzer.GetAllowedOperators(op, slot)); if (operatorSet.Count() > 0) { slotSets.Add(operatorSet); } } // ok at the end of this operation we know how many slots of the parent can actually // hold one of our children. // if the number of slots is smaller than the number of children we can be sure that // we can never combine all children as sub-operators of the operator and thus the operator // can't be a parent. if (slotSets.Count() < children.Count()) { return false; } // finally we sort the sets by size and beginning from the first set select one // operator for the slot and thus remove it as possible sub-operator from the remaining sets. // when we can successfully assign all available children to a slot the operator is a valid parent // when only a subset of all children can be assigned to slots the operator is no valid parent slotSets.Sort((p, q) => p.Count() - q.Count()); int assignments = 0; for (int i = 0; i < slotSets.Count() - 1; i++) { if (slotSets[i].Count > 0) { IOperator selected = slotSets[i].ElementAt(0); assignments++; for (int j = i + 1; j < slotSets.Count(); j++) { slotSets[j].Remove(selected); } } } // sanity check if (assignments > children.Count) throw new InvalidProgramException(); return assignments == children.Count - 1; } } }