Changeset 18216 for branches/3136_Structural_GP/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Formatters
- Timestamp:
- 02/10/22 19:48:12 (2 years ago)
- Location:
- branches/3136_Structural_GP
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/3136_Structural_GP
- Property svn:mergeinfo changed
/trunk merged: 18203,18208-18211
- Property svn:mergeinfo changed
-
branches/3136_Structural_GP/HeuristicLab.Problems.DataAnalysis.Symbolic
- Property svn:mergeinfo changed
/trunk/HeuristicLab.Problems.DataAnalysis.Symbolic merged: 18203,18210-18211
- Property svn:mergeinfo changed
-
branches/3136_Structural_GP/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Formatters/InfixExpressionFormatter.cs
r18176 r18216 32 32 namespace HeuristicLab.Problems.DataAnalysis.Symbolic { 33 33 public static class BaseInfixExpressionFormatter { 34 /// <summary> 35 /// Performs some basic re-writing steps to simplify the code for formatting. Tree is changed. 36 /// Removes single-argument +, * which have no effect 37 /// Removes SubFunctions (no effect) 38 /// Replaces variables with coefficients by an explicitly multiplication 39 /// Replaces single-argument / with 1 / (..) 40 /// Replaces multi-argument +, *, /, - with nested binary operations 41 /// Rotates operations to remove necessity to group sub-expressions 42 /// </summary> 43 /// <param name="tree">The tree is changed</param> 44 public static void ConvertToBinaryLeftAssoc(ISymbolicExpressionTree tree) { 45 ConvertToBinaryLeftAssocRec(tree.Root.GetSubtree(0), tree.Root.GetSubtree(0).GetSubtree(0)); 46 } 47 48 private static void ConvertToBinaryLeftAssocRec(ISymbolicExpressionTreeNode parent, ISymbolicExpressionTreeNode n) { 49 // recurse post-order 50 foreach (var subtree in n.Subtrees.ToArray()) ConvertToBinaryLeftAssocRec(n, subtree); // ToArray required as n.Subtrees is changed in method 51 52 if (n is VariableTreeNode varTreeNode && varTreeNode.Weight != 1.0) { 53 var mul = new Multiplication().CreateTreeNode(); 54 var num = (NumberTreeNode)new Number().CreateTreeNode(); 55 num.Value = varTreeNode.Weight; 56 varTreeNode.Weight = 1.0; 57 parent.ReplaceSubtree(n, mul); 58 mul.AddSubtree(num); 59 mul.AddSubtree(varTreeNode); 60 } else if (n.Symbol is SubFunctionSymbol) { 61 parent.ReplaceSubtree(n, n.GetSubtree(0)); 62 } else if (n.SubtreeCount == 1 && (n.Symbol is Addition || n.Symbol is Multiplication || n.Symbol is And || n.Symbol is Or || n.Symbol is Xor)) { 63 // single-argument addition or multiplication has no effect -> remove 64 parent.ReplaceSubtree(n, n.GetSubtree(0)); 65 } else if (n.SubtreeCount == 1 && (n.Symbol is Division)) { 66 // single-argument division is 1/f(x) 67 n.InsertSubtree(0, new Constant() { Value = 1.0 }.CreateTreeNode()); 68 } else if (n.SubtreeCount > 2 && IsLeftAssocOp(n.Symbol)) { 69 // multi-argument +, -, *, / are the same as multiple binary operations (left-associative) 70 var sy = n.Symbol; 71 72 var additionalTrees = n.Subtrees.Skip(2).ToArray(); 73 while (n.SubtreeCount > 2) n.RemoveSubtree(2); // keep only the first two arguments 74 75 var childIdx = parent.IndexOfSubtree(n); 76 parent.RemoveSubtree(childIdx); 77 var newChild = n; 78 // build a tree bottom to top, each time adding a subtree on the right 79 for (int i = 0; i < additionalTrees.Length; i++) { 80 var newOp = sy.CreateTreeNode(); 81 newOp.AddSubtree(newChild); 82 newOp.AddSubtree(additionalTrees[i]); 83 newChild = newOp; 84 } 85 parent.InsertSubtree(childIdx, newChild); 86 } else if (n.SubtreeCount == 2 && n.GetSubtree(1).SubtreeCount == 2 && 87 IsAssocOp(n.Symbol) && IsOperator(n.GetSubtree(1).Symbol) && 88 Priority(n.Symbol) == Priority(n.GetSubtree(1).Symbol)) { 89 // f(x) <op> (g(x) <op> h(x))) is the same as (f(x) <op> g(x)) <op> h(x) for associative <op> 90 // which is the same as f(x) <op> g(x) <op> h(x) for left-associative <op> 91 // The latter version is preferred because we do not need to write the parentheses. 92 // rotation: 93 // (op1) (op2) 94 // / \ / \ 95 // a (op2) (op1) c 96 // / \ / \ 97 // b c a b 98 var op1 = n; 99 var op2 = n.GetSubtree(1); 100 var b = op2.GetSubtree(0); op2.RemoveSubtree(0); 101 op1.ReplaceSubtree(op2, b); 102 parent.ReplaceSubtree(op1, op2); 103 op2.InsertSubtree(0, op1); 104 } 105 } 34 106 public static void FormatRecursively(ISymbolicExpressionTreeNode node, StringBuilder strBuilder, 35 107 NumberFormatInfo numberFormat, string formatString, List<KeyValuePair<string, double>> parameters = null) { 36 if (node.SubtreeCount > 1) { 37 var token = GetToken(node.Symbol); 38 // operators 39 if (token == "+" || token == "-" || token == "OR" || token == "XOR") { 40 var parenthesisRequired = false; 41 if (node.Parent != null && node.Parent.SubtreeCount > 1) { 42 var parentOp = GetToken(node.Parent.Symbol); 43 if (parentOp != "+" && parentOp != "-" && parentOp != "OR" && parentOp != "XOR") 44 parenthesisRequired = true; 45 } 108 // This method assumes that the tree has been converted to binary and left-assoc form (see ConvertToBinaryLeftAssocRec). 109 if (node.SubtreeCount == 0) { 110 // no subtrees 111 if (node.Symbol is Variable) { 112 var varNode = node as VariableTreeNode; 113 if (varNode.Weight != 1.0) { throw new NotSupportedException("Infix formatter does not support variables with coefficients."); } 114 AppendVariableName(strBuilder, varNode.VariableName); 115 } else if (node is INumericTreeNode numNode) { 116 var parenthesisRequired = RequiresParenthesis(node.Parent, node); 46 117 if (parenthesisRequired) strBuilder.Append("("); 47 FormatRecursively(node.Subtrees.First(), strBuilder, numberFormat, formatString, parameters); 48 49 foreach (var subtree in node.Subtrees.Skip(1)) { 50 strBuilder.Append(" ").Append(token).Append(" "); 51 FormatRecursively(subtree, strBuilder, numberFormat, formatString, parameters); 52 } 53 118 119 AppendNumber(strBuilder, parameters, numNode.Value, formatString, numberFormat); 54 120 if (parenthesisRequired) strBuilder.Append(")"); 55 } else if (token == "*" || token == "/" || token == "AND") { 56 FormatRecursively(node.Subtrees.First(), strBuilder, numberFormat, formatString, parameters); 57 58 foreach (var subtree in node.Subtrees.Skip(1)) { 59 strBuilder.Append(" ").Append(token).Append(" "); 60 // a / b * c => a / (b * C) 61 if (subtree.SubtreeCount > 1 && token == "/" && GetToken(subtree.Symbol) == "*") { 62 strBuilder.Append("("); 63 FormatRecursively(subtree, strBuilder, numberFormat, formatString, parameters); 64 strBuilder.Append(")"); 65 } else { 66 FormatRecursively(subtree, strBuilder, numberFormat, formatString, parameters); 67 } 68 } 69 } else if (token == "^") { 70 // handle integer powers directly 71 FormatRecursively(node.Subtrees.First(), strBuilder, numberFormat, formatString, parameters); 72 73 var power = node.GetSubtree(1); 74 if (power is INumericTreeNode numNode && Math.Truncate(numNode.Value) == numNode.Value) { 75 strBuilder.Append(" ").Append(token).Append(" ").Append(numNode.Value.ToString(formatString, numberFormat)); 76 } else { 77 strBuilder.Append(" ").Append(token).Append(" "); 78 FormatRecursively(power, strBuilder, numberFormat, formatString, parameters); 79 } 80 } else { 81 // function with multiple arguments 82 strBuilder.Append(token).Append("("); 83 FormatRecursively(node.Subtrees.First(), strBuilder, numberFormat, formatString, parameters); 84 foreach (var subtree in node.Subtrees.Skip(1)) { 85 strBuilder.Append(", "); 86 FormatRecursively(subtree, strBuilder, numberFormat, formatString, parameters); 87 } 88 89 strBuilder.Append(")"); 90 } 91 } else if (node.Symbol is SubFunctionSymbol) { 92 FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, parameters); 93 } else if (node.SubtreeCount == 1) { 94 var token = GetToken(node.Symbol); 95 if (token == "-" || token == "NOT") { 96 strBuilder.Append(token); 97 FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, parameters); 98 } else if (token == "/") { 99 strBuilder.Append("1/"); 100 FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, parameters); 101 } else if (token == "+" || token == "*") { 102 FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, parameters); 103 } else { 104 // function with only one argument 105 strBuilder.Append(token).Append("("); 106 FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, parameters); 107 strBuilder.Append(")"); 108 } 109 } else { 110 // no subtrees 111 if (node.Symbol is LaggedVariable) { 121 } else if (node.Symbol is LaggedVariable) { 112 122 var varNode = node as LaggedVariableTreeNode; 113 123 if (!varNode.Weight.IsAlmost(1.0)) { … … 121 131 .AppendFormat(numberFormat, "{0}", varNode.Lag) 122 132 .Append(")"); 123 } else if (node.Symbol is Variable) {124 var varNode = node as VariableTreeNode;125 if (!varNode.Weight.IsAlmost(1.0)) {126 AppendNumber(strBuilder, parameters, varNode.Weight, formatString, numberFormat);127 strBuilder.Append("*");128 }129 AppendVariableName(strBuilder, varNode.VariableName);130 133 } else if (node.Symbol is FactorVariable) { 131 134 var factorNode = node as FactorVariableTreeNode; … … 147 150 strBuilder.Append(" = "); 148 151 AppendVariableName(strBuilder, factorNode.VariableValue); 149 } else if (node is INumericTreeNode numNode) {150 if (parameters == null && numNode.Value < 0) {151 // negative value152 strBuilder.Append(numNode.Value.ToString(formatString, numberFormat));153 } else {154 AppendNumber(strBuilder, parameters, numNode.Value, formatString, numberFormat);155 }156 152 } 157 } 153 } else if (node.SubtreeCount == 1) { 154 // only functions and single-argument subtraction (=negation) or NOT are possible here. 155 var token = GetToken(node.Symbol); 156 // the only operators that are allowed with a single argument 157 if (node.Symbol is Subtraction || node.Symbol is Not) { 158 if (RequiresParenthesis(node.Parent, node)) { 159 strBuilder.Append("("); 160 } 161 strBuilder.Append(token); 162 FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, parameters); 163 if (RequiresParenthesis(node.Parent, node)) { 164 strBuilder.Append(")"); 165 } 166 } else if (IsOperator(node.Symbol)) { 167 throw new FormatException("Single-argument version of " + node.Symbol.Name + " is not supported."); 168 } else { 169 // function with only one argument 170 strBuilder.Append(token); 171 strBuilder.Append("("); 172 FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, parameters); 173 strBuilder.Append(")"); 174 } 175 } else if (node.SubtreeCount > 1) { 176 var token = GetToken(node.Symbol); 177 // operators 178 if (IsOperator(node.Symbol)) { 179 var parenthesisRequired = RequiresParenthesis(node.Parent, node); 180 if (parenthesisRequired) strBuilder.Append("("); 181 FormatRecursively(node.Subtrees.First(), strBuilder, numberFormat, formatString, parameters); 182 183 foreach (var subtree in node.Subtrees.Skip(1)) { 184 strBuilder.Append(" ").Append(token).Append(" "); 185 FormatRecursively(subtree, strBuilder, numberFormat, formatString, parameters); 186 } 187 188 if (parenthesisRequired) strBuilder.Append(")"); 189 } else { 190 // function with multiple arguments (AQ) 191 192 strBuilder.Append(token); 193 strBuilder.Append("("); 194 195 FormatRecursively(node.Subtrees.First(), strBuilder, numberFormat, formatString, parameters); 196 foreach (var subtree in node.Subtrees.Skip(1)) { 197 strBuilder.Append(", "); 198 FormatRecursively(subtree, strBuilder, numberFormat, formatString, parameters); 199 } 200 strBuilder.Append(")"); 201 } 202 } 203 } 204 205 private static int Priority(ISymbol symbol) { 206 if (symbol is Addition || symbol is Subtraction || symbol is Or || symbol is Xor) return 1; 207 if (symbol is Division || symbol is Multiplication || symbol is And) return 2; 208 if (symbol is Power || symbol is Not) return 3; 209 throw new NotSupportedException(); 210 } 211 212 private static bool RequiresParenthesis(ISymbolicExpressionTreeNode parent, ISymbolicExpressionTreeNode child) { 213 if (child.SubtreeCount > 2 && IsOperator(child.Symbol)) throw new NotSupportedException("Infix formatter does not support operators with more than two children."); 214 215 // Basically: We need a parenthesis for child if the parent symbol binds stronger than child symbol. 216 if (parent.Symbol == null || parent.Symbol is ProgramRootSymbol || parent.Symbol is StartSymbol) return false; 217 if (IsFunction(parent.Symbol)) return false; 218 if (parent.SubtreeCount == 1 && (parent.Symbol is Subtraction)) return true; 219 if (child.SubtreeCount == 0) return false; 220 var parentPrio = Priority(parent.Symbol); 221 var childPrio = Priority(child.Symbol); 222 if (parentPrio > childPrio) return true; 223 else if (parentPrio == childPrio) { 224 if (IsLeftAssocOp(child.Symbol)) return parent.GetSubtree(0) != child; // (..) required only for right child for left-assoc op 225 if (IsRightAssocOp(child.Symbol)) return parent.GetSubtree(1) != child; 226 } 227 return false; 228 } 229 230 private static bool IsFunction(ISymbol symbol) { 231 // functions are formatted in prefix form e.g. sin(...) 232 return !IsOperator(symbol) && !IsLeaf(symbol); 233 } 234 235 private static bool IsLeaf(ISymbol symbol) { 236 return symbol.MaximumArity == 0; 237 } 238 239 private static bool IsOperator(ISymbol symbol) { 240 return IsLeftAssocOp(symbol) || IsRightAssocOp(symbol); 241 } 242 243 private static bool IsAssocOp(ISymbol symbol) { 244 // (a <op> b) <op> c = a <op> (b <op> c) 245 return symbol is Addition || 246 symbol is Multiplication || 247 symbol is And || 248 symbol is Or || 249 symbol is Xor; 250 } 251 252 private static bool IsLeftAssocOp(ISymbol symbol) { 253 // a <op> b <op> c = (a <op> b) <op> c 254 return symbol is Addition || 255 symbol is Subtraction || 256 symbol is Multiplication || 257 symbol is Division || 258 symbol is And || 259 symbol is Or || 260 symbol is Xor; 261 } 262 263 private static bool IsRightAssocOp(ISymbol symbol) { 264 // a <op> b <op> c = a <op> (b <op> c) 265 // Negation (single-argument subtraction) is also right-assoc, but we do not have a separate symbol for negation. 266 return symbol is Not || 267 symbol is Power; // x^y^z = x^(y^z) (as in Fortran or Mathematica) 158 268 } 159 269 … … 216 326 // skip root and start symbols 217 327 StringBuilder strBuilder = new StringBuilder(); 218 BaseInfixExpressionFormatter.FormatRecursively(symbolicExpressionTree.Root.GetSubtree(0).GetSubtree(0), 328 var cleanTree = (ISymbolicExpressionTree)symbolicExpressionTree.Clone(); 329 BaseInfixExpressionFormatter.ConvertToBinaryLeftAssoc(cleanTree); 330 BaseInfixExpressionFormatter.FormatRecursively(cleanTree.Root.GetSubtree(0).GetSubtree(0), 219 331 strBuilder, numberFormat, formatString); 220 332 return strBuilder.ToString(); … … 227 339 228 340 [StorableType("54D917E8-134E-4066-9A60-2737C12D81DC")] 229 [Item("Infix String Format er", "Formatter for symbolic expressions, which produces an infix expression " +341 [Item("Infix String Formatter", "Formatter for symbolic expressions, which produces an infix expression " + 230 342 "as well as a list of all coefficient values")] 231 343 public sealed class InfixExpressionStringFormatter : NamedItem, ISymbolicExpressionTreeStringFormatter { … … 247 359 StringBuilder strBuilder = new StringBuilder(); 248 360 var parameters = new List<KeyValuePair<string, double>>(); 249 BaseInfixExpressionFormatter.FormatRecursively(symbolicExpressionTree.Root.GetSubtree(0).GetSubtree(0), 361 var cleanTree = (ISymbolicExpressionTree)symbolicExpressionTree.Clone(); 362 BaseInfixExpressionFormatter.ConvertToBinaryLeftAssoc(cleanTree); 363 BaseInfixExpressionFormatter.FormatRecursively(cleanTree.Root.GetSubtree(0).GetSubtree(0), 250 364 strBuilder, NumberFormatInfo.InvariantInfo, "G", parameters); 251 365 strBuilder.Append($"{Environment.NewLine}{Environment.NewLine}");
Note: See TracChangeset
for help on using the changeset viewer.