Free cookie consent management tool by TermsFeed Policy Generator

Ignore:
Timestamp:
02/10/22 19:48:12 (2 years ago)
Author:
gkronber
Message:

#3136: merged r18203:18211 from trunk to branch. Merged changes, fixed compile problem ('is not')

Location:
branches/3136_Structural_GP
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • branches/3136_Structural_GP

  • branches/3136_Structural_GP/HeuristicLab.Problems.DataAnalysis.Symbolic

  • branches/3136_Structural_GP/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Formatters/InfixExpressionFormatter.cs

    r18176 r18216  
    3232namespace HeuristicLab.Problems.DataAnalysis.Symbolic {
    3333  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    }
    34106    public static void FormatRecursively(ISymbolicExpressionTreeNode node, StringBuilder strBuilder,
    35107                                          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);
    46117          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);
    54120          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) {
    112122          var varNode = node as LaggedVariableTreeNode;
    113123          if (!varNode.Weight.IsAlmost(1.0)) {
     
    121131                    .AppendFormat(numberFormat, "{0}", varNode.Lag)
    122132                    .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);
    130133        } else if (node.Symbol is FactorVariable) {
    131134          var factorNode = node as FactorVariableTreeNode;
     
    147150          strBuilder.Append(" = ");
    148151          AppendVariableName(strBuilder, factorNode.VariableValue);
    149         } else if (node is INumericTreeNode numNode) {
    150           if (parameters == null && numNode.Value < 0) {
    151             // negative value
    152             strBuilder.Append(numNode.Value.ToString(formatString, numberFormat));
    153           } else {
    154             AppendNumber(strBuilder, parameters, numNode.Value, formatString, numberFormat);
    155           }
    156152        }
    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)
    158268    }
    159269
     
    216326      // skip root and start symbols
    217327      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),
    219331        strBuilder, numberFormat, formatString);
    220332      return strBuilder.ToString();
     
    227339
    228340  [StorableType("54D917E8-134E-4066-9A60-2737C12D81DC")]
    229   [Item("Infix String Formater", "Formatter for symbolic expressions, which produces an infix expression " +
     341  [Item("Infix String Formatter", "Formatter for symbolic expressions, which produces an infix expression " +
    230342                                 "as well as a list of all coefficient values")]
    231343  public sealed class InfixExpressionStringFormatter : NamedItem, ISymbolicExpressionTreeStringFormatter {
     
    247359      StringBuilder strBuilder = new StringBuilder();
    248360      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),
    250364        strBuilder, NumberFormatInfo.InvariantInfo, "G", parameters);
    251365      strBuilder.Append($"{Environment.NewLine}{Environment.NewLine}");
Note: See TracChangeset for help on using the changeset viewer.