source: branches/HeuristicLab.DataAnalysis.Symbolic.LinearInterpreter/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Formatters/SymbolicDataAnalysisExpressionLatexFormatter.cs @ 9732

Last change on this file since 9732 was 9732, checked in by bburlacu, 6 years ago

#2021: Merged trunk changes for HeuristicLab.Encodings.SymbolicExpressionTreeEncoding and HeuristicLab.Problems.DataAnalysis.Symbolic. Replaced prefix iteration of nodes in the linear interpretation with breadth iteration for simplified logic and extra performance. Reversed unnecessary changes to other projects.

File size: 19.7 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2013 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.Linq;
25using System.Text;
26using HeuristicLab.Common;
27using HeuristicLab.Core;
28using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
29using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
30
31namespace HeuristicLab.Problems.DataAnalysis.Symbolic {
32  [Item("LaTeX String Formatter", "Formatter for symbolic expression trees for import into LaTeX documents.")]
33  [StorableClass]
34  public sealed class SymbolicDataAnalysisExpressionLatexFormatter : NamedItem, ISymbolicExpressionTreeStringFormatter {
35    private readonly List<double> constants;
36    private int targetCount;
37    private int currentLag;
38
39    [StorableConstructor]
40    private SymbolicDataAnalysisExpressionLatexFormatter(bool deserializing) : base(deserializing) { }
41    private SymbolicDataAnalysisExpressionLatexFormatter(SymbolicDataAnalysisExpressionLatexFormatter original, Cloner cloner)
42      : base(original, cloner) {
43      constants = new List<double>(original.constants);
44    }
45    public SymbolicDataAnalysisExpressionLatexFormatter()
46      : base() {
47      Name = ItemName;
48      Description = ItemDescription;
49      constants = new List<double>();
50    }
51
52    public override IDeepCloneable Clone(Cloner cloner) {
53      return new SymbolicDataAnalysisExpressionLatexFormatter(this, cloner);
54    }
55
56    public string Format(ISymbolicExpressionTree symbolicExpressionTree) {
57      try {
58        StringBuilder strBuilder = new StringBuilder();
59        constants.Clear();
60        strBuilder.AppendLine(FormatRecursively(symbolicExpressionTree.Root));
61        return strBuilder.ToString();
62      }
63      catch (NotImplementedException ex) {
64        return ex.Message + Environment.NewLine + ex.StackTrace;
65      }
66    }
67
68    private string FormatRecursively(ISymbolicExpressionTreeNode node) {
69      StringBuilder strBuilder = new StringBuilder();
70      currentLag = 0;
71      FormatBegin(node, strBuilder);
72
73      if (node.SubtreeCount > 0) {
74        strBuilder.Append(FormatRecursively(node.GetSubtree(0)));
75      }
76      int i = 1;
77      foreach (SymbolicExpressionTreeNode subTree in node.Subtrees.Skip(1)) {
78        FormatSep(node, strBuilder, i);
79        // format the whole subtree
80        strBuilder.Append(FormatRecursively(subTree));
81        i++;
82      }
83
84      FormatEnd(node, strBuilder);
85
86      return strBuilder.ToString();
87    }
88
89    private void FormatBegin(ISymbolicExpressionTreeNode node, StringBuilder strBuilder) {
90      if (node.Symbol is Addition) {
91        strBuilder.Append(@" \left( ");
92      } else if (node.Symbol is Subtraction) {
93        if (node.SubtreeCount == 1) {
94          strBuilder.Append(@"- \left( ");
95        } else {
96          strBuilder.Append(@" \left( ");
97        }
98      } else if (node.Symbol is Multiplication) {
99      } else if (node.Symbol is Division) {
100        if (node.SubtreeCount == 1) {
101          strBuilder.Append(@" \cfrac{1");
102        } else {
103          strBuilder.Append(@" \cfrac{ ");
104        }
105      } else if (node.Symbol is Average) {
106        // skip output of (1/1) if only one subtree
107        if (node.SubtreeCount > 1) {
108          strBuilder.Append(@" \cfrac{1}{" + node.SubtreeCount + @"}");
109        }
110        strBuilder.Append(@" \left( ");
111      } else if (node.Symbol is Logarithm) {
112        strBuilder.Append(@"\log \left( ");
113      } else if (node.Symbol is Exponential) {
114        strBuilder.Append(@"\exp \left( ");
115      } else if (node.Symbol is Square) {
116        strBuilder.Append(@"\left(");
117      } else if (node.Symbol is SquareRoot) {
118        strBuilder.Append(@"\sqrt{");
119      } else if (node.Symbol is Sine) {
120        strBuilder.Append(@"\sin \left( ");
121      } else if (node.Symbol is Cosine) {
122        strBuilder.Append(@"\cos \left( ");
123      } else if (node.Symbol is Tangent) {
124        strBuilder.Append(@"\tan \left( ");
125      } else if (node.Symbol is AiryA) {
126        strBuilder.Append(@"\operatorname{airy}_a \left( ");
127      } else if (node.Symbol is AiryB) {
128        strBuilder.Append(@"\operatorname{airy}_b \left( ");
129      } else if (node.Symbol is Bessel) {
130        strBuilder.Append(@"\operatorname{bessel}_1 \left( ");
131      } else if (node.Symbol is CosineIntegral) {
132        strBuilder.Append(@"\operatorname{cosInt} \left( ");
133      } else if (node.Symbol is Dawson) {
134        strBuilder.Append(@"\operatorname{dawson} \left( ");
135      } else if (node.Symbol is Erf) {
136        strBuilder.Append(@"\operatorname{erf} \left( ");
137      } else if (node.Symbol is ExponentialIntegralEi) {
138        strBuilder.Append(@"\operatorname{expInt}_i \left( ");
139      } else if (node.Symbol is FresnelCosineIntegral) {
140        strBuilder.Append(@"\operatorname{fresnel}_\operatorname{cosInt} \left( ");
141      } else if (node.Symbol is FresnelSineIntegral) {
142        strBuilder.Append(@"\operatorname{fresnel}_\operatorname{sinInt} \left( ");
143      } else if (node.Symbol is Gamma) {
144        strBuilder.Append(@"\Gamma \left( ");
145      } else if (node.Symbol is HyperbolicCosineIntegral) {
146        strBuilder.Append(@"\operatorname{hypCosInt} \left( ");
147      } else if (node.Symbol is HyperbolicSineIntegral) {
148        strBuilder.Append(@"\operatorname{hypSinInt} \left( ");
149      } else if (node.Symbol is Norm) {
150        strBuilder.Append(@"\operatorname{norm} \left( ");
151      } else if (node.Symbol is Psi) {
152        strBuilder.Append(@"\operatorname{digamma} \left( ");
153      } else if (node.Symbol is SineIntegral) {
154        strBuilder.Append(@"\operatorname{sinInt} \left( ");
155      } else if (node.Symbol is GreaterThan) {
156        strBuilder.Append(@"  \left( ");
157      } else if (node.Symbol is LessThan) {
158        strBuilder.Append(@"  \left( ");
159      } else if (node.Symbol is And) {
160        strBuilder.Append(@"  \left( \left( ");
161      } else if (node.Symbol is Or) {
162        strBuilder.Append(@"   \left( \left( ");
163      } else if (node.Symbol is Not) {
164        strBuilder.Append(@" \neg \left( ");
165      } else if (node.Symbol is IfThenElse) {
166        strBuilder.Append(@" \operatorname{if}  \left( ");
167      } else if (node.Symbol is Constant) {
168        strBuilder.Append("c_{" + constants.Count + "} ");
169        var constNode = node as ConstantTreeNode;
170        constants.Add(constNode.Value);
171      } else if (node.Symbol is LaggedVariable) {
172        var laggedVarNode = node as LaggedVariableTreeNode;
173        if (!laggedVarNode.Weight.IsAlmost(1.0)) {
174          strBuilder.Append("c_{" + constants.Count + "} \\cdot ");
175          constants.Add(laggedVarNode.Weight);
176        }
177        strBuilder.Append(EscapeLatexString(laggedVarNode.VariableName));
178        strBuilder.Append(LagToString(currentLag + laggedVarNode.Lag));
179
180      } else if (node.Symbol is Variable) {
181        var varNode = node as VariableTreeNode;
182        if (!varNode.Weight.IsAlmost((1.0))) {
183          strBuilder.Append("c_{" + constants.Count + "} \\cdot ");
184          constants.Add(varNode.Weight);
185        }
186        strBuilder.Append(EscapeLatexString(varNode.VariableName));
187        strBuilder.Append(LagToString(currentLag));
188      } else if (node.Symbol is ProgramRootSymbol) {
189        strBuilder
190          .AppendLine("\\begin{align*}")
191          .AppendLine("\\nonumber");
192      } else if (node.Symbol is Defun) {
193        var defunNode = node as DefunTreeNode;
194        strBuilder.Append(defunNode.FunctionName + " & = ");
195      } else if (node.Symbol is InvokeFunction) {
196        var invokeNode = node as InvokeFunctionTreeNode;
197        strBuilder.Append(invokeNode.Symbol.FunctionName + @" \left( ");
198      } else if (node.Symbol is StartSymbol) {
199        strBuilder.Append("target_" + (targetCount++) + "(t) & = ");
200      } else if (node.Symbol is Argument) {
201        var argSym = node.Symbol as Argument;
202        strBuilder.Append(" ARG+" + argSym.ArgumentIndex + " ");
203      } else if (node.Symbol is Derivative) {
204        strBuilder.Append(@" \cfrac{d \left( ");
205      } else if (node.Symbol is TimeLag) {
206        var laggedNode = node as ILaggedTreeNode;
207        currentLag += laggedNode.Lag;
208      } else if (node.Symbol is Power) {
209        strBuilder.Append(@" \left( ");
210      } else if (node.Symbol is Root) {
211        strBuilder.Append(@" \left( ");
212      } else if (node.Symbol is Integral) {
213        // actually a new variable for t is needed in all subtrees (TODO)
214        var laggedTreeNode = node as ILaggedTreeNode;
215        strBuilder.Append(@"\sum_{t=" + (laggedTreeNode.Lag + currentLag) + @"}^0 \left( ");
216      } else if (node.Symbol is VariableCondition) {
217        var conditionTreeNode = node as VariableConditionTreeNode;
218        string p = @"1 /  1 + \exp  - c_{" + constants.Count + "} ";
219        constants.Add(conditionTreeNode.Slope);
220        p += @" \cdot " + EscapeLatexString(conditionTreeNode.VariableName) + LagToString(currentLag) + " - c_{" + constants.Count + @"}   ";
221        constants.Add(conditionTreeNode.Threshold);
222        strBuilder.Append(@" \left( " + p + @"\cdot ");
223      } else {
224        throw new NotImplementedException("Export of " + node.Symbol + " is not implemented.");
225      }
226    }
227
228    private void FormatSep(ISymbolicExpressionTreeNode node, StringBuilder strBuilder, int step) {
229      if (node.Symbol is Addition) {
230        strBuilder.Append(" + ");
231      } else if (node.Symbol is Subtraction) {
232        strBuilder.Append(" - ");
233      } else if (node.Symbol is Multiplication) {
234        strBuilder.Append(@" \cdot ");
235      } else if (node.Symbol is Division) {
236        if (step + 1 == node.SubtreeCount)
237          strBuilder.Append(@"}{");
238        else
239          strBuilder.Append(@" }{ \cfrac{ ");
240      } else if (node.Symbol is Average) {
241        strBuilder.Append(@" + ");
242      } else if (node.Symbol is Logarithm) {
243        throw new InvalidOperationException();
244      } else if (node.Symbol is Exponential) {
245        throw new InvalidOperationException();
246      } else if (node.Symbol is Square) {
247        throw new InvalidOperationException();
248      } else if (node.Symbol is SquareRoot) {
249        throw new InvalidOperationException();
250      } else if (node.Symbol is Sine) {
251        throw new InvalidOperationException();
252      } else if (node.Symbol is Cosine) {
253        throw new InvalidOperationException();
254      } else if (node.Symbol is Tangent) {
255        throw new InvalidOperationException();
256      } else if (node.Symbol is AiryA) {
257        throw new InvalidOperationException();
258      } else if (node.Symbol is AiryB) {
259        throw new InvalidOperationException();
260      } else if (node.Symbol is Bessel) {
261        throw new InvalidOperationException();
262      } else if (node.Symbol is CosineIntegral) {
263        throw new InvalidOperationException();
264      } else if (node.Symbol is Dawson) {
265        throw new InvalidOperationException();
266      } else if (node.Symbol is Erf) {
267        throw new InvalidOperationException();
268      } else if (node.Symbol is ExponentialIntegralEi) {
269        throw new InvalidOperationException();
270      } else if (node.Symbol is FresnelCosineIntegral) {
271        throw new InvalidOperationException();
272      } else if (node.Symbol is FresnelSineIntegral) {
273        throw new InvalidOperationException();
274      } else if (node.Symbol is Gamma) {
275        throw new InvalidOperationException();
276      } else if (node.Symbol is HyperbolicCosineIntegral) {
277        throw new InvalidOperationException();
278      } else if (node.Symbol is HyperbolicSineIntegral) {
279        throw new InvalidOperationException();
280      } else if (node.Symbol is Norm) {
281        throw new InvalidOperationException();
282      } else if (node.Symbol is Psi) {
283        throw new InvalidOperationException();
284      } else if (node.Symbol is SineIntegral) {
285        throw new InvalidOperationException();
286      } else if (node.Symbol is GreaterThan) {
287        strBuilder.Append(@" > ");
288      } else if (node.Symbol is LessThan) {
289        strBuilder.Append(@" < ");
290      } else if (node.Symbol is And) {
291        strBuilder.Append(@" > 0  \right) \land \left(");
292      } else if (node.Symbol is Or) {
293        strBuilder.Append(@" > 0  \right) \lor \left(");
294      } else if (node.Symbol is Not) {
295        throw new InvalidOperationException();
296      } else if (node.Symbol is IfThenElse) {
297        strBuilder.Append(@" , ");
298      } else if (node.Symbol is ProgramRootSymbol) {
299        strBuilder.Append(@"\\" + Environment.NewLine);
300      } else if (node.Symbol is Defun) {
301      } else if (node.Symbol is InvokeFunction) {
302        strBuilder.Append(" , ");
303      } else if (node.Symbol is StartSymbol) {
304        strBuilder.Append(@"\\" + Environment.NewLine);
305        strBuilder.Append("target_" + (targetCount++) + "(t) & = ");
306      } else if (node.Symbol is Power) {
307        strBuilder.Append(@"\right) ^ { \operatorname{round} \left(");
308      } else if (node.Symbol is Root) {
309        strBuilder.Append(@"\right) ^ {  \cfrac{1}{ \operatorname{round} \left(");
310      } else if (node.Symbol is VariableCondition) {
311        var conditionTreeNode = node as VariableConditionTreeNode;
312        string p = @"1 / \left( 1 + \exp \left( - c_{" + constants.Count + "} ";
313        constants.Add(conditionTreeNode.Slope);
314        p += @" \cdot " + EscapeLatexString(conditionTreeNode.VariableName) + LagToString(currentLag) + " - c_{" + constants.Count + @"} \right) \right) \right)   ";
315        constants.Add(conditionTreeNode.Threshold);
316        strBuilder.Append(@" +  \left( 1 - " + p + @" \right) \cdot ");
317      } else {
318        throw new NotImplementedException("Export of " + node.Symbol + " is not implemented.");
319      }
320    }
321
322    private void FormatEnd(ISymbolicExpressionTreeNode node, StringBuilder strBuilder) {
323      if (node.Symbol is Addition) {
324        strBuilder.Append(@" \right) ");
325      } else if (node.Symbol is Subtraction) {
326        strBuilder.Append(@" \right) ");
327      } else if (node.Symbol is Multiplication) {
328      } else if (node.Symbol is Division) {
329        strBuilder.Append(" } ");
330        for (int i = 2; i < node.SubtreeCount; i++)
331          strBuilder.Append(" } ");
332      } else if (node.Symbol is Average) {
333        strBuilder.Append(@" \right) ");
334      } else if (node.Symbol is Logarithm) {
335        strBuilder.Append(@" \right) ");
336      } else if (node.Symbol is Exponential) {
337        strBuilder.Append(@" \right) ");
338      } else if (node.Symbol is Square) {
339        strBuilder.Append(@"\right)^2");
340      } else if (node.Symbol is SquareRoot) {
341        strBuilder.Append(@"}");
342      } else if (node.Symbol is Sine) {
343        strBuilder.Append(@" \right) ");
344      } else if (node.Symbol is Cosine) {
345        strBuilder.Append(@" \right) ");
346      } else if (node.Symbol is Tangent) {
347        strBuilder.Append(@" \right) ");
348      } else if (node.Symbol is AiryA) {
349        strBuilder.Append(@" \right) ");
350      } else if (node.Symbol is AiryB) {
351        strBuilder.Append(@" \right) ");
352      } else if (node.Symbol is Bessel) {
353        strBuilder.Append(@" \right) ");
354      } else if (node.Symbol is CosineIntegral) {
355        strBuilder.Append(@" \right) ");
356      } else if (node.Symbol is Dawson) {
357        strBuilder.Append(@" \right) ");
358      } else if (node.Symbol is Erf) {
359        strBuilder.Append(@" \right) ");
360      } else if (node.Symbol is ExponentialIntegralEi) {
361        strBuilder.Append(@" \right) ");
362      } else if (node.Symbol is FresnelCosineIntegral) {
363        strBuilder.Append(@" \right) ");
364      } else if (node.Symbol is FresnelSineIntegral) {
365        strBuilder.Append(@" \right) ");
366      } else if (node.Symbol is Gamma) {
367        strBuilder.Append(@" \right) ");
368      } else if (node.Symbol is HyperbolicCosineIntegral) {
369        strBuilder.Append(@" \right) ");
370      } else if (node.Symbol is HyperbolicSineIntegral) {
371        strBuilder.Append(@" \right) ");
372      } else if (node.Symbol is Norm) {
373        strBuilder.Append(@" \right) ");
374      } else if (node.Symbol is Psi) {
375        strBuilder.Append(@" \right) ");
376      } else if (node.Symbol is SineIntegral) {
377        strBuilder.Append(@" \right) ");
378      } else if (node.Symbol is GreaterThan) {
379        strBuilder.Append(@" \right) ");
380      } else if (node.Symbol is LessThan) {
381        strBuilder.Append(@" \right) ");
382      } else if (node.Symbol is And) {
383        strBuilder.Append(@" > 0 \right) \right) ");
384      } else if (node.Symbol is Or) {
385        strBuilder.Append(@" > 0 \right) \right) ");
386      } else if (node.Symbol is Not) {
387        strBuilder.Append(@" \right) ");
388      } else if (node.Symbol is IfThenElse) {
389        strBuilder.Append(@" \right) ");
390      } else if (node.Symbol is Constant) {
391      } else if (node.Symbol is LaggedVariable) {
392      } else if (node.Symbol is Variable) {
393      } else if (node.Symbol is ProgramRootSymbol) {
394        strBuilder
395          .AppendLine("\\end{align*}")
396          .AppendLine("\\begin{align*}")
397          .AppendLine("\\nonumber");
398        // output all constant values
399        if (constants.Count > 0) {
400          int i = 0;
401          foreach (var constant in constants) {
402            // replace "." with ".&" to align decimal points
403            var constStr = string.Format(System.Globalization.NumberFormatInfo.InvariantInfo, "{0:G5}", constant);
404            if (!constStr.Contains(".")) constStr = constStr + ".0";
405            constStr = constStr.Replace(".", "\\negthickspace&.");  // fix problem in rendering of aligned expressions
406            strBuilder.Append("c_{" + i + "}& = & " + constStr);
407            strBuilder.Append(@"\\");
408            i++;
409          }
410        }
411        strBuilder.AppendLine("\\end{align*}");
412      } else if (node.Symbol is Defun) {
413      } else if (node.Symbol is InvokeFunction) {
414        strBuilder.Append(@" \right) ");
415      } else if (node.Symbol is StartSymbol) {
416      } else if (node.Symbol is Argument) {
417      } else if (node.Symbol is Derivative) {
418        strBuilder.Append(@" \right) }{dt} ");
419      } else if (node.Symbol is TimeLag) {
420        var laggedNode = node as ILaggedTreeNode;
421        currentLag -= laggedNode.Lag;
422      } else if (node.Symbol is Power) {
423        strBuilder.Append(@" \right) } ");
424      } else if (node.Symbol is Root) {
425        strBuilder.Append(@" \right) } } ");
426      } else if (node.Symbol is Integral) {
427        strBuilder.Append(@" \right) ");
428      } else if (node.Symbol is VariableCondition) {
429        strBuilder.Append(@"\right) ");
430      } else {
431        throw new NotImplementedException("Export of " + node.Symbol + " is not implemented.");
432      }
433    }
434
435    private string LagToString(int lag) {
436      if (lag < 0) {
437        return "(t" + lag + ")";
438      } else if (lag > 0) {
439        return "(t+" + lag + ")";
440      } else return "";
441    }
442
443    private string EscapeLatexString(string s) {
444      return "\\text{" +
445        s
446         .Replace("\\", "\\\\")
447         .Replace("{", "\\{")
448         .Replace("}", "\\}")
449        + "}";
450    }
451  }
452}
Note: See TracBrowser for help on using the repository browser.