source: branches/symbreg-factors-2650/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Formatters/SymbolicDataAnalysisExpressionLatexFormatter.cs @ 14259

Last change on this file since 14259 was 14259, checked in by gkronber, 5 years ago

#2650: added support for factor variables to Excel formatter and Excel exporter as well as to the Latex formatter and consequently the mathematical representation view.

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