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