Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Formatters/InfixExpressionFormatter.cs @ 17912

Last change on this file since 17912 was 17811, checked in by gkronber, 4 years ago

#2968: integer powers are included directly in the expression instead of the list of coefficients.

File size: 11.8 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Globalization;
25using System.Linq;
26using System.Text;
27using HEAL.Attic;
28using HeuristicLab.Common;
29using HeuristicLab.Core;
30using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
31
32namespace HeuristicLab.Problems.DataAnalysis.Symbolic {
33  public static class BaseInfixExpressionFormatter {
34    public static void FormatRecursively(ISymbolicExpressionTreeNode node, StringBuilder strBuilder,
35                                          NumberFormatInfo numberFormat, string formatString, List<KeyValuePair<string, double>> constants = null) {
36      if (node.SubtreeCount > 1) {
37        var token = GetToken(node.Symbol);
38        // operators
39        if (token == "+" || token == "-" || token == "OR" || token == "XOR" ||
40            token == "*" || token == "/" || token == "AND") {
41          strBuilder.Append("(");
42          FormatRecursively(node.Subtrees.First(), strBuilder, numberFormat, formatString, constants);
43
44          foreach (var subtree in node.Subtrees.Skip(1)) {
45            strBuilder.Append(" ").Append(token).Append(" ");
46            FormatRecursively(subtree, strBuilder, numberFormat, formatString, constants);
47          }
48
49          strBuilder.Append(")");
50        } else if (token == "^") {
51          // handle integer powers directly
52          strBuilder.Append("(");
53          FormatRecursively(node.Subtrees.First(), strBuilder, numberFormat, formatString, constants);
54
55          var power = node.GetSubtree(1);
56          if(power is ConstantTreeNode constNode && Math.Truncate(constNode.Value) == constNode.Value) {
57            strBuilder.Append(" ").Append(token).Append(" ").Append(constNode.Value.ToString(formatString, numberFormat));
58          } else {
59            strBuilder.Append(" ").Append(token).Append(" ");
60            FormatRecursively(power, strBuilder, numberFormat, formatString, constants);
61          }
62
63          strBuilder.Append(")");
64        } else {
65          // function with multiple arguments
66          strBuilder.Append(token).Append("(");
67          FormatRecursively(node.Subtrees.First(), strBuilder, numberFormat, formatString, constants);
68          foreach (var subtree in node.Subtrees.Skip(1)) {
69            strBuilder.Append(", ");
70            FormatRecursively(subtree, strBuilder, numberFormat, formatString, constants);
71          }
72
73          strBuilder.Append(")");
74        }
75      } else if (node.SubtreeCount == 1) {
76        var token = GetToken(node.Symbol);
77        if (token == "-" || token == "NOT") {
78          strBuilder.Append("(").Append(token).Append("(");
79          FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, constants);
80          strBuilder.Append("))");
81        } else if (token == "/") {
82          strBuilder.Append("1/");
83          FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, constants);
84        } else if (token == "+" || token == "*") {
85          FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, constants);
86        } else {
87          // function with only one argument
88          strBuilder.Append(token).Append("(");
89          FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, constants);
90          strBuilder.Append(")");
91        }
92      } else {
93        // no subtrees
94        if (node.Symbol is LaggedVariable) {
95          var varNode = node as LaggedVariableTreeNode;
96          if (!varNode.Weight.IsAlmost(1.0)) {
97            strBuilder.Append("(");
98            AppendConstant(strBuilder, constants, varNode.Weight, formatString, numberFormat);
99            strBuilder.Append("*");
100          }
101
102          strBuilder.Append("LAG(");
103          AppendVariableName(strBuilder, varNode.VariableName);
104          strBuilder.Append(", ")
105                    .AppendFormat(numberFormat, "{0}", varNode.Lag)
106                    .Append(")");
107          if (!varNode.Weight.IsAlmost(1.0)) strBuilder.Append(")");
108        } else if (node.Symbol is Variable) {
109          var varNode = node as VariableTreeNode;
110          if (!varNode.Weight.IsAlmost(1.0)) {
111            strBuilder.Append("(");
112            AppendConstant(strBuilder, constants, varNode.Weight, formatString, numberFormat);
113            strBuilder.Append("*");
114          }
115
116          AppendVariableName(strBuilder, varNode.VariableName);
117
118          if (!varNode.Weight.IsAlmost(1.0)) strBuilder.Append(")");
119        } else if (node.Symbol is FactorVariable) {
120          var factorNode = node as FactorVariableTreeNode;
121          AppendVariableName(strBuilder, factorNode.VariableName);
122
123          strBuilder.Append("[");
124          for (int i = 0; i < factorNode.Weights.Length; i++) {
125            if (i > 0) strBuilder.Append(", ");
126            AppendConstant(strBuilder, constants, factorNode.Weights[i], formatString, numberFormat);
127          }
128          strBuilder.Append("]");
129        } else if (node.Symbol is BinaryFactorVariable) {
130          var factorNode = node as BinaryFactorVariableTreeNode;
131          if (!factorNode.Weight.IsAlmost(1.0)) {
132            strBuilder.Append("(");
133            AppendConstant(strBuilder, constants, factorNode.Weight, formatString, numberFormat);
134
135            strBuilder.Append("*");
136          }
137
138          AppendVariableName(strBuilder, factorNode.VariableName);
139          strBuilder.Append(" = ");
140          AppendVariableName(strBuilder, factorNode.VariableValue);
141
142          if (!factorNode.Weight.IsAlmost(1.0)) strBuilder.Append(")");
143        } else if (node.Symbol is Constant) {
144          var constNode = node as ConstantTreeNode;
145          if (constants == null && constNode.Value < 0) {
146            strBuilder.Append("(").Append(constNode.Value.ToString(formatString, numberFormat))
147                      .Append(")"); // (-1
148          } else {
149            AppendConstant(strBuilder, constants, constNode.Value, formatString, numberFormat);
150          }
151        }
152      }
153    }
154
155    private static void AppendConstant(StringBuilder strBuilder, List<KeyValuePair<string, double>> constants, double value, string formatString, NumberFormatInfo numberFormat) {
156      if (constants != null) {
157        string constantKey = $"c_{constants.Count}";
158        strBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0}", constantKey);
159        constants.Add(new KeyValuePair<string, double>(constantKey, value));
160      } else {
161        strBuilder.Append(value.ToString(formatString, numberFormat));
162      }
163    }
164
165    private static void AppendVariableName(StringBuilder strBuilder, string name) {
166      if (name.Contains("'"))
167        strBuilder.AppendFormat("\"{0}\"", name);
168      else
169        strBuilder.AppendFormat("'{0}'", name);
170    }
171
172    private static string GetToken(ISymbol symbol) {
173      var tok = InfixExpressionParser.knownSymbols.GetBySecond(symbol).FirstOrDefault();
174      if (tok == null)
175        throw new ArgumentException(string.Format("Unknown symbol {0} found.", symbol.Name));
176      return tok;
177    }
178  }
179
180  /// <summary>
181  /// Formats mathematical expressions in infix form. E.g. x1 * (3.0 * x2 + x3)
182  /// </summary>
183  [StorableType("6FE2C83D-A594-4ABF-B101-5AEAEA6D3E3D")]
184  [Item("Infix Symbolic Expression Tree Formatter",
185    "A string formatter that converts symbolic expression trees to infix expressions.")]
186  public sealed class InfixExpressionFormatter : NamedItem, ISymbolicExpressionTreeStringFormatter {
187    [StorableConstructor]
188    private InfixExpressionFormatter(StorableConstructorFlag _) : base(_) { }
189
190    private InfixExpressionFormatter(InfixExpressionFormatter original, Cloner cloner) : base(original, cloner) { }
191
192    public InfixExpressionFormatter()
193      : base() {
194      Name = ItemName;
195      Description = ItemDescription;
196    }
197
198    public override IDeepCloneable Clone(Cloner cloner) {
199      return new InfixExpressionFormatter(this, cloner);
200    }
201
202    /// <summary>
203    /// Produces an infix expression for a given expression tree.
204    /// </summary>
205    /// <param name="symbolicExpressionTree">The tree representation of the expression.</param>
206    /// <param name="numberFormat">Number format that should be used for numeric parameters (e.g. NumberFormatInfo.InvariantInfo (default)).</param>
207    /// <param name="formatString">The format string for numeric parameters (e.g. \"G4\" to limit to 4 digits, default is \"G\")</param>
208    /// <returns>Infix expression</returns>
209    public string Format(ISymbolicExpressionTree symbolicExpressionTree, NumberFormatInfo numberFormat,
210                         string formatString = "G") {
211      // skip root and start symbols
212      StringBuilder strBuilder = new StringBuilder();
213      BaseInfixExpressionFormatter.FormatRecursively(symbolicExpressionTree.Root.GetSubtree(0).GetSubtree(0),
214        strBuilder, numberFormat, formatString);
215      return strBuilder.ToString();
216    }
217
218    public string Format(ISymbolicExpressionTree symbolicExpressionTree) {
219      return Format(symbolicExpressionTree, NumberFormatInfo.InvariantInfo);
220    }
221  }
222
223  [StorableType("54D917E8-134E-4066-9A60-2737C12D81DC")]
224  [Item("Infix String Formater", "Formatter for symbolic expressions, which produces an infix expression " +
225                                 "as well as a list of all coefficient values")]
226  public sealed class InfixExpressionStringFormatter : NamedItem, ISymbolicExpressionTreeStringFormatter {
227    [StorableConstructor]
228    private InfixExpressionStringFormatter(StorableConstructorFlag _) : base(_) { }
229
230    private InfixExpressionStringFormatter(InfixExpressionStringFormatter original, Cloner cloner) : base(original, cloner) { }
231
232    public InfixExpressionStringFormatter() : base() {
233      Name = ItemName;
234      Description = ItemDescription;
235    }
236
237    public override IDeepCloneable Clone(Cloner cloner) {
238      return new InfixExpressionStringFormatter(this, cloner);
239    }
240
241    public string Format(ISymbolicExpressionTree symbolicExpressionTree) {
242      StringBuilder strBuilder = new StringBuilder();
243      var constants = new List<KeyValuePair<string, double>>();
244      BaseInfixExpressionFormatter.FormatRecursively(symbolicExpressionTree.Root.GetSubtree(0).GetSubtree(0),
245        strBuilder, NumberFormatInfo.InvariantInfo, "G", constants);
246      strBuilder.Append($"{Environment.NewLine}{Environment.NewLine}");
247
248      int maxDigits = GetDigits(constants.Count);
249      int padding = constants.Max(x => x.Value.ToString("F12", CultureInfo.InvariantCulture).Length);
250      foreach (var constant in constants) {
251        int digits = GetDigits(Int32.Parse(constant.Key.Substring(2)));
252        strBuilder.Append($"{constant.Key}{new String(' ', maxDigits - digits)} = " +
253                          string.Format($"{{0,{padding}:F12}}", constant.Value, CultureInfo.InvariantCulture) +
254                          Environment.NewLine);
255      }
256
257      return strBuilder.ToString();
258    }
259
260    private int GetDigits(int x) {
261      if (x == 0) return 1;
262      return (int)Math.Floor(Math.Log10(x) + 1);
263    }
264  }
265}
Note: See TracBrowser for help on using the repository browser.