Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Formatters/SymbolicDataAnalysisExpressionLatexFormatter.cs @ 14427

Last change on this file since 14427 was 14367, checked in by gkronber, 8 years ago

#2692: show constants = 1.0 directly in the formula. Also fixed a bug for division with only one argument.

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