Free cookie consent management tool by TermsFeed Policy Generator

source: branches/3140_NumberSymbol/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Formatters/InfixExpressionFormatter.cs @ 18115

Last change on this file since 18115 was 18115, checked in by gkronber, 2 years ago

#3140: made several more changes for the constant -> number branch

File size: 11.7 KB
RevLine 
[14024]1#region License Information
2/* HeuristicLab
[17180]3 * Copyright (C) Heuristic and Evolutionary Algorithms Laboratory (HEAL)
[14024]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;
[17581]23using System.Collections.Generic;
[14024]24using System.Globalization;
25using System.Linq;
26using System.Text;
[17581]27using HEAL.Attic;
[14024]28using HeuristicLab.Common;
29using HeuristicLab.Core;
30using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
31
32namespace HeuristicLab.Problems.DataAnalysis.Symbolic {
[17581]33  public static class BaseInfixExpressionFormatter {
34    public static void FormatRecursively(ISymbolicExpressionTreeNode node, StringBuilder strBuilder,
[18114]35                                          NumberFormatInfo numberFormat, string formatString, List<KeyValuePair<string, double>> parameters = null) {
[14024]36      if (node.SubtreeCount > 1) {
37        var token = GetToken(node.Symbol);
[16359]38        // operators
39        if (token == "+" || token == "-" || token == "OR" || token == "XOR" ||
[17811]40            token == "*" || token == "/" || token == "AND") {
[14024]41          strBuilder.Append("(");
[18114]42          FormatRecursively(node.Subtrees.First(), strBuilder, numberFormat, formatString, parameters);
[14024]43
44          foreach (var subtree in node.Subtrees.Skip(1)) {
45            strBuilder.Append(" ").Append(token).Append(" ");
[18114]46            FormatRecursively(subtree, strBuilder, numberFormat, formatString, parameters);
[14024]47          }
[17581]48
[14024]49          strBuilder.Append(")");
[17811]50        } else if (token == "^") {
51          // handle integer powers directly
52          strBuilder.Append("(");
[18114]53          FormatRecursively(node.Subtrees.First(), strBuilder, numberFormat, formatString, parameters);
[17811]54
55          var power = node.GetSubtree(1);
[18115]56          if(power is INumericTreeNode numNode && Math.Truncate(numNode.Value) == numNode.Value) {
57            strBuilder.Append(" ").Append(token).Append(" ").Append(numNode.Value.ToString(formatString, numberFormat));
[17811]58          } else {
59            strBuilder.Append(" ").Append(token).Append(" ");
[18114]60            FormatRecursively(power, strBuilder, numberFormat, formatString, parameters);
[17811]61          }
62
63          strBuilder.Append(")");
[14347]64        } else {
65          // function with multiple arguments
66          strBuilder.Append(token).Append("(");
[18114]67          FormatRecursively(node.Subtrees.First(), strBuilder, numberFormat, formatString, parameters);
[14347]68          foreach (var subtree in node.Subtrees.Skip(1)) {
69            strBuilder.Append(", ");
[18114]70            FormatRecursively(subtree, strBuilder, numberFormat, formatString, parameters);
[14347]71          }
[17581]72
[14347]73          strBuilder.Append(")");
[14024]74        }
75      } else if (node.SubtreeCount == 1) {
76        var token = GetToken(node.Symbol);
77        if (token == "-" || token == "NOT") {
78          strBuilder.Append("(").Append(token).Append("(");
[18114]79          FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, parameters);
[14024]80          strBuilder.Append("))");
81        } else if (token == "/") {
82          strBuilder.Append("1/");
[18114]83          FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, parameters);
[14024]84        } else if (token == "+" || token == "*") {
[18114]85          FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, parameters);
[14024]86        } else {
[14347]87          // function with only one argument
[14024]88          strBuilder.Append(token).Append("(");
[18114]89          FormatRecursively(node.GetSubtree(0), strBuilder, numberFormat, formatString, parameters);
[14024]90          strBuilder.Append(")");
91        }
92      } else {
93        // no subtrees
[14350]94        if (node.Symbol is LaggedVariable) {
95          var varNode = node as LaggedVariableTreeNode;
96          if (!varNode.Weight.IsAlmost(1.0)) {
97            strBuilder.Append("(");
[18114]98            AppendNumber(strBuilder, parameters, varNode.Weight, formatString, numberFormat);
[14350]99            strBuilder.Append("*");
100          }
[17581]101
[14350]102          strBuilder.Append("LAG(");
[17685]103          AppendVariableName(strBuilder, varNode.VariableName);
[14350]104          strBuilder.Append(", ")
[17581]105                    .AppendFormat(numberFormat, "{0}", varNode.Lag)
106                    .Append(")");
[17685]107          if (!varNode.Weight.IsAlmost(1.0)) strBuilder.Append(")");
[14350]108        } else if (node.Symbol is Variable) {
[14024]109          var varNode = node as VariableTreeNode;
110          if (!varNode.Weight.IsAlmost(1.0)) {
111            strBuilder.Append("(");
[18114]112            AppendNumber(strBuilder, parameters, varNode.Weight, formatString, numberFormat);
[14024]113            strBuilder.Append("*");
114          }
[17581]115
[17685]116          AppendVariableName(strBuilder, varNode.VariableName);
[17581]117
118          if (!varNode.Weight.IsAlmost(1.0)) strBuilder.Append(")");
[14826]119        } else if (node.Symbol is FactorVariable) {
120          var factorNode = node as FactorVariableTreeNode;
[17685]121          AppendVariableName(strBuilder, factorNode.VariableName);
[17581]122
[17685]123          strBuilder.Append("[");
124          for (int i = 0; i < factorNode.Weights.Length; i++) {
125            if (i > 0) strBuilder.Append(", ");
[18114]126            AppendNumber(strBuilder, parameters, factorNode.Weights[i], formatString, numberFormat);
[17685]127          }
128          strBuilder.Append("]");
[14826]129        } else if (node.Symbol is BinaryFactorVariable) {
130          var factorNode = node as BinaryFactorVariableTreeNode;
131          if (!factorNode.Weight.IsAlmost(1.0)) {
132            strBuilder.Append("(");
[18114]133            AppendNumber(strBuilder, parameters, factorNode.Weight, formatString, numberFormat);
[17581]134
[14826]135            strBuilder.Append("*");
136          }
[17581]137
[17685]138          AppendVariableName(strBuilder, factorNode.VariableName);
[14826]139          strBuilder.Append(" = ");
[17685]140          AppendVariableName(strBuilder, factorNode.VariableValue);
[14826]141
[17581]142          if (!factorNode.Weight.IsAlmost(1.0)) strBuilder.Append(")");
[18115]143        } else if (node is INumericTreeNode numNode) {
[18114]144          if (parameters == null && numNode.Value < 0) {
[18115]145            // negative value
[18114]146            strBuilder.Append("(").Append(numNode.Value.ToString(formatString, numberFormat))
[18115]147                      .Append(")");
[17581]148          } else {
[18114]149            AppendNumber(strBuilder, parameters, numNode.Value, formatString, numberFormat);
[17581]150          }
[14024]151        }
152      }
153    }
154
[18114]155    private static void AppendNumber(StringBuilder strBuilder, List<KeyValuePair<string, double>> parameters, double value, string formatString, NumberFormatInfo numberFormat) {
156      if (parameters != null) {
157        string paramKey = $"c_{parameters.Count}";
158        strBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0}", paramKey);
159        parameters.Add(new KeyValuePair<string, double>(paramKey, value));
[17685]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
[16700]172    private static string GetToken(ISymbol symbol) {
[16359]173      var tok = InfixExpressionParser.knownSymbols.GetBySecond(symbol).FirstOrDefault();
[14024]174      if (tok == null)
175        throw new ArgumentException(string.Format("Unknown symbol {0} found.", symbol.Name));
176      return tok;
177    }
178  }
[17581]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>
[18114]206    /// <param name="numberFormat">Number format that should be used for parameters (e.g. NumberFormatInfo.InvariantInfo (default)).</param>
207    /// <param name="formatString">The format string for parameters (e.g. \"G4\" to limit to 4 digits, default is \"G\")</param>
[17581]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();
[18114]243      var parameters = new List<KeyValuePair<string, double>>();
[17581]244      BaseInfixExpressionFormatter.FormatRecursively(symbolicExpressionTree.Root.GetSubtree(0).GetSubtree(0),
[18114]245        strBuilder, NumberFormatInfo.InvariantInfo, "G", parameters);
[17581]246      strBuilder.Append($"{Environment.NewLine}{Environment.NewLine}");
247
[18114]248      int maxDigits = GetDigits(parameters.Count);
249      int padding = parameters.Max(x => x.Value.ToString("F12", CultureInfo.InvariantCulture).Length);
250      foreach (var param in parameters) {
251        int digits = GetDigits(int.Parse(param.Key.Substring(2)));
252        strBuilder.Append($"{param.Key}{new string(' ', maxDigits - digits)} = " +
253                          string.Format($"{{0,{padding}:F12}}", param.Value, CultureInfo.InvariantCulture) +
[17581]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  }
[14024]265}
Note: See TracBrowser for help on using the repository browser.