-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSymboMath.tex
1286 lines (931 loc) · 64.8 KB
/
SymboMath.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\documentclass[12pt]{article}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{graphicx}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{xparse}
\usepackage{comment}
\usepackage{dirtree}
\usepackage{csquotes}
\usepackage{subfig}
\usepackage{physics}
\usepackage{algorithm}
\usepackage{amsfonts}
\usepackage{algorithmicx}
\usepackage{algpseudocode}
\usepackage{siunitx}
\usepackage{upquote}
\usepackage{tikz}
\usetikzlibrary{positioning}
\usetikzlibrary{intersections}
\usetikzlibrary{backgrounds}
\usetikzlibrary{mindmap}
\usetikzlibrary{shadows}
\usetikzlibrary{trees}
\usetikzlibrary{shapes.misc}
\usetikzlibrary{arrows.meta}
\usetikzlibrary{decorations.pathmorphing}
\usepackage{hyperref}
% \usepackage[margin=0.25in]{geometry}
\usepackage{pgfplots}
\pgfplotsset{width=10cm,compat=1.9}
% We will externalize the figures
% \usepgfplotslibrary{external}
% \tikzexternalize
\hypersetup{
colorlinks,
linkcolor={red!50!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
% Rename "Contents" to "Summary"
\renewcommand*\contentsname{Summary}
% Register codeword as a command
\NewDocumentCommand{\codeword}{v}{%
\texttt{\textcolor{darkgray}{#1}}%
}
% C++
\def\CC{{C\nolinebreak[4]\hspace{-.05em}\raisebox{.4ex}{\tiny\bf ++ }}}
% SymboMath
\def\Symbo{{$\texttt{SymboMath}$}}
% WolframAlpha
\def\Wolf{{{\color{red}Wolfram\color{orange}Alpha}}}
\title{
\textsc{Implementing Symbolic Mathematics in a Statically-Typed Language}\\[2.2cm]
% Title and Subtitle
{\LARGE \bfseries
SymboMath}\\
{\Large\bfseries
Symbolic Mathematics in \CC}
}
% Author
\author{Toby Davis\\[0.2cm]Hampton School}
% Code style
\definecolor{codegreen}{rgb}{0,0.6,0}
\definecolor{codegray}{rgb}{0.5,0.5,0.5}
\definecolor{codepurple}{rgb}{0.58,0,0.82}
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}
\lstdefinestyle{mystyle}{
backgroundcolor=\color{backcolour},
commentstyle=\color{codegreen},
keywordstyle=\color{magenta},
numberstyle=\tiny\color{codegray},
stringstyle=\color{codepurple},
basicstyle=\ttfamily\footnotesize,
breakatwhitespace=false,
breaklines=true,
captionpos=b,
keepspaces=true,
numbers=left,
numbersep=5pt,
showspaces=false,
showstringspaces=false,
showtabs=false,
tabsize=2
}
\lstset{style=mystyle}
\begin{document}
\maketitle
\thispagestyle{empty}
\vspace{0.5cm}
\hrule
\vspace{1cm}
\begin{abstract} % 150-250 words
While computers excel at solving numerical calculations, it is incredibly difficult to efficiently compute symbolic equations -- i.e., equations including variables, derivatives and integrals. For numerical calculations and the vast majority of software, symbolic mathematics provides very limited benefits. However, in certain applications, such as visual calculators and scientific programs, the ability to manipulate equations algebraically can be incredibly useful.
This paper investigates how symbolic mathematics can be implemented in \CC, an object-oriented, statically-typed language which operates closer to the hardware than languages like Python and Java.
\end{abstract}
\vfill
\newpage
{
\hypersetup{linkcolor=black}
\tableofcontents
}
\pagebreak
\section{What is Symbolic Mathematics?} \label{whatisit}
Symbolic mathematics is ``the use of computers to manipulate mathematical equations and expressions in symbolic form, as opposed to manipulating the numerical quantities represented by those symbols''\cite{symbolicdefinition}. In practice, this means the computer can deal with an expression like $5x+7$, instead of, for example, $5 \cdot 2 + 7$. It is also possible to compute derivatives and, in some cases, integrals, using a symbolic mathematics engine.
\subsection{Why is Symbolic Mathematics Useful?} \label{whyuseful}
The most common use-case for symbolic mathematics is for solving incredibly large, tedious problems, which would be prone to trivial errors if solved by a human \cite{computeraidformathematics}. By providing a computer with suitable instructions and rules, it is possible to answer questions that had been ignored for millennia due to their scale and complexity. Furthermore, the program can evaluate, manipulate and solve equations step-by-step, helping students, researchers and hobbyists understand their problem better.
For example, a huge number of research projects make use of Wolfram Mathematica\cite{mathematica}
Another common use for a symbolic maths engine is to present data in a more human-readable form. For example, \Wolf{} is capable of taking complex, multi-variable equations as inputs and manipulating them to solve equations, plot interactive graphs and provide general solutions to queries.
\subsection{Comparison to Naive Methods}
As mentioned in \ref{whyuseful}, symbolic mathematics engines can directly differentiate or integrate an expression, providing exact answers as opposed to approximations.
\subsubsection{Derivatives}
The derivative of a function is defined with the following expression:
\begin{equation}
\dv{x} f(x) = \lim_{h \rightarrow 0}{\frac{f(x+h)-f(x)}{h}}
\end{equation}
However, this is very difficult for a computer to deal with, due to the limit.
To approximate the derivative of a function, it is possible to use the definition of the derivative without the limit, replacing $h$ with $\epsilon$, where $\epsilon$ is a very small number.
This produces the following expression, which can, in some cases, be suitable for derivative calculations in simple programs.
\begin{equation}
\dv{x} f(x) = \frac{f(x + \epsilon) - f(x)}{\epsilon}
\end{equation}
However, there are many issues with this approach, though the most significant is the lack of precision. Using a relatively large\footnote{The size of $epsilon$ required for a given precision will depend heavily on the function being differentiated.} value of $\epsilon$ will result in a more precise \textit{calculation}, but a less precise \textit{result}. This discrepancy is due to the large difference in the values substituted into $f(x)$.
\begin{tikzpicture}
\begin{axis}[
axis lines = left,
xlabel = \(x\),
ylabel = {\(f(x)\)},
]
% Plot the curve
\addplot [
domain=-2.5:6.5,
samples=100,
color=black,
]
{x^3-6*x^2+7*x+10};
\addlegendentry{\(\text{Function}\)}
% Plot true gradient
\addplot [
domain=-2.5:6.5,
samples=100,
color=green,
]
{7*(x-4)+6};
\addlegendentry{\(\text{True Derivative}\)}
% Plot bad gradient
\addplot[
color=blue,
mark=square,
]
coordinates {
(4, 6)(4.5, 11.125)
};
\addplot [
domain=-2.5:6.5,
samples=100,
color=blue,
]
{(41/4)*(x-4)+6};
\addlegendentry{\(\text{Estimated Gradient Line}\)}
\end{axis}
\end{tikzpicture}
The plot above shows $y=x^3-6x^2+7x+10$, along with the true derivative and the estimated derivative, with $\epsilon=0.5$.
The equations below show how the true derivative differs from the approximated one, using two different values of $\epsilon$ to show how greater precision can be achieved.
\begin{equation}
\begin{split}
&\text{Symbolic Derivative} \\
f(x)&=x^3-6x^2+7x+10 \\
g(x)&=\dv{f}{x}=6x^2-12x+7 \\
g(4)&=7
\end{split}
\end{equation}
\begin{equation}
\begin{split}
&\text{Approximated Derivative} \\
f(x)&=x^3-6x^2+7x+10 \\
h(x, \epsilon)&=\dv{f}{x}=\frac{f(x+\epsilon)-f(x)}{\epsilon} \\
h(4, 0.5)&=\frac{41}{4}=10.25 \\
h(4, 1 \cdot 10^{-5})& \approx 7.00005999832
\end{split}
\end{equation}
From the above results, it appears that continually reducing the value of $\epsilon$ will result in more precise results. While this is true numerically, it is not the case for floating-point arithmetic.
As $\epsilon$ becomes increasingly small, floating point rounding errors will cause the final result to become \textit{less} accurate. It is likely that the resulting gradient will become closer to zero, as, in most cases, the error occurs in the evaluation of $f(x+\epsilon)$:
$$
x+\epsilon \approx x
$$
$$
\therefore f(x+\epsilon) \approx f(x)
$$
$$
\therefore f(x+\epsilon) - f(x) \approx 0
$$
$$
\therefore \frac{\Delta f}{\epsilon} \approx \frac{0}{\epsilon} = 0
$$
For a coarse approximation of a derivative, this method works very well and can be quite efficient\footnote{The efficiency of this will depend on the function itself.}. However, this approach will not allow you to express the exact derivative of a function. For example, you would not be able to compute $\dv{x}x^2=2x$ with a purely numeric algorithm.
\subsubsection{Integration}
Integration is often much more difficult than differentiation, and requires a wide range of different techniques to compute efficiently.
One common method of integrating numerically is with a Riemann sum\cite{riemannsum}. Riemann sums approximate an integral by summing $N$ rectangles with width $w$ and height $f(x)$. In practice, this takes the following form:
$$
\int_{a}^{b}{f(x) \ \text{dx}} = \lim_{N \rightarrow \infty}\left(\sum_{n=0}^{N}{r_1+r_2+r_3+...+r_{n-1}+r_{n}} \right)
$$
\noindent
where $r_m$ is the area of rectangle $m$, and $w=(b-a)N^{-1}$. Substituting these values into the above sum produces the following expression.
$$
\int_{a}^{b}{f(x) \ \text{dx}} = \lim_{N \rightarrow \infty} \left( \sum_{n=0}^{N}{f \left( a+nw \right) w } \right)
$$
The above expression can be written in Python with something as simple as the following function:
\begin{lstlisting}[language=python]
def integrate(f, a, b, N=10):
tot = 0
w = (b - a) / N
for i in range(N):
tot += f(a + w * i) * w
return tot
# f(x)=x a b N result
integrate(lambda x: x, 0, 10, 5 ) # 40.0
integrate(lambda x: x, 0, 10, 10 ) # 45.0
integrate(lambda x: x, 0, 10, 100 ) # 49.5
integrate(lambda x: x, 0, 10, 1000 ) # 49.949999999999996
integrate(lambda x: x, 0, 10, 10000) # 49.99500000000002
\end{lstlisting}
While this implementation works in theory, it is incredibly inefficient and impractical to use for precise calculations. This is because $N$ must be very large, and hence many iterations must be run.
To solve this problem, one could integrate symbolically. This would produce a new, explicit function, into which a value of $x$ can be substituted directly, giving an exact value for the integral.
Unfortunately, symbolically integrating equations is incredibly difficult, especially with a program. However, it is possible, and some methods for achieving it are explained later.
\subsection{Why \CC?}
\CC is a statically-typed language based on C, providing classes, structs, template-meta-programming and more. It is also possible to write highly efficient code in \CC, making it the programming language of choice for many high-performance applications. However, since \CC is statically typed, it adds another layer of complexity to implementing a symbolic mathematics library, as it makes it vastly more complicated to deal with different types for numbers, variables, functions, operations, etc.
If this project were to be implemented in Python, which is dynamically typed, it would be much easier to design a data-structure which could store a symbolic equation and operate on it accordingly because the language does not require the type of a variable to be specified.
\CC also provides a much greater opportunity for code optimisation and more interesting implementations, as well as having far fewer symbolic mathematics libraries available for it \cite{githubmathlibs}. Hence, so a novel implementation could help a wider range of projects. Finally, \CC is easy to integrate into other programming languages, as they are often written in \CC themselves, meaning a \CC library could easily be used in Python, JavaScript, Rust, Julia, etc.
\subsection{What About Automatic Differentiation?}
When researching symbolic mathematics, particularly symbolic \textit{differentiation}, one is bound to come across the term ``automatic differentiation''. This varies from symbolic mathematics in a few significant ways.
Firstly, automatic differentiation does not operate on symbolic equations. Instead, it operates entirely numerically, though may operate on vectors, matrices or arrays -- not just scalar values.
Secondly, automatic differentiation aims to provide a means of \textit{evaluating} the derivative of a function at a given point, not \textit{calculating} it symbolically. This may seem exactly like the approximation methods spoken about earlier, but it does still provide a function for the derivative.
Automatic differentiation is most commonly used in machine-learning applications, where a complex activation function needs to be differentiated in order to make use of the gradient descent algorithm. The reason it's used in such cases is because, given a static, compile-time definition for a function, it is possible to generate another function, also at compile-time, which represents the derivative of the initial function. This means no time needs to be spent at runtime parsing the input and differentiating it before evaluating the result.
\pagebreak
\section{Existing Symbolic Mathematics Libraries}
Pre-existing symbolic mathematics libraries are a good place to start to identify implementation techniques and overall design architectures, as they are shown to work effectively.
$\texttt{mathiu.cpp}$\cite{mathiucpp} is a \CC symbolic mathematics library based heavily on pattern matching. By making use of the $\texttt{match(it)}$ library, $\texttt{mathiu.cpp}$ can combine and compare operations and expressions to form an equation. Differentiation is performed recursively, applying a small set of rules to individual functions and combining them to produce the final result.
Wolfram Mathematica is another program capable of symbolic manipulation of equations. Although Mathematica's code is not available online, there are detailed explanations on the Wolfram Reference website \cite{mathematicainternals} of how it's internal systems handle the data. According to their explanations, Wolfram Mathematica has four fundamental data types:
\begin{align*}
&\texttt{Numbers}
\\
&\texttt{Strings}
\\
&\texttt{Symbols}
\\
&\texttt{General Expressions}
\end{align*}
Everything in the Wolfram Language keeps a reference count, tracking how many other Wolfram Language objects are pointing to it. This allows the language to free anything immediately after the last thing pointing to it is destroyed, saving memory and improving efficiency.
To process the inputs provided to the language, the Wolfram Language first creates an expression representing the input. It then applies all known rules for the contained objects before outputting the result. The important thing to note here is that \textit{all} known rules are applied. This implies that no single rule can achieve the desired result, and that intelligently analysing the expression to identify the most effective rule is not always possible.
Another incredibly powerful symbolic mathematics engine is $\texttt{Symengine}$\cite{symengine}, which implements an incredibly wide range of functions. Analysing the full workings of this library is far beyond the scope of this project. However, at the most basic level, it uses the same reference-counted pointer system used by $\texttt{mathiu.cpp}$, suggesting that this is the optimal design choice for such a program.
\subsection{Issues with the Existing Libraries}
While the existing libraries and frameworks such as Wolfram Mathematics, $\texttt{mathiu.cpp}$ and $\texttt{Symengine}$ are incredibly powerful, they often have one of two major shortcomings -- a lack of features or excessive code size.
Many symbolic mathematics engines implement only a very limited subset of features, and hence are not applicable to large-scale projects. Only a select few projects implement the necessary functions and routines to be used in critical software.
On the other hand, the engines which are capable of handling an extremely wide range of mathematical expressions often contain upwards of 100,000 lines of code, making them difficult and slow to compile, as well as unsuitable for smaller projects.
\pagebreak
\section{\Symbo's Goals} \label{symbogoals}
\Symbo{}, implemented following this paper, is not a fully featured library and does not implement all the possible functions, derivatives, simplification rules, etc. required for an advanced program. It does, however, lay out the groundwork for further implementations to be built on top of.
\Symbo \footnote{\Symbo{} GitHub repository: \href{https://github.com/Pencilcaseman/SymboMathPaper}{https://github.com/Pencilcaseman/SymboMathPaper}.} should be a library usable in any \codeword{CMake} project, and should be wrapped in an easy-to-use, \CC-styled class for easy reuse of variables, equations and functions.
\Symbo{} \textit{could} also support some form of basic equation solving, though this might be outside the scope of simple algebraic manipulation of equations.
\subsection{Numeric Evaluation}
The program should be able to evaluate any numeric expression involving the four basic arithmetic operations, as well as exponentiation, trigonometry and nth-roots.
\subsection{Functions}
It should be easy to register custom functions with procedures to evaluate, differentiate and potentially integrate them. There should also be a wide set of common functions implemented by default, including trigonometry, logarithms and exponents, square roots, etc.
\subsection{Variables}
\Symbo{} must be able to support user-defined variables, including value substitution, manipulation of equations including variables and potentially simplification of expressions containing variables (see \ref{simplificationgoal}). Variables should act like any other object within the data structure of an equation, and operations applied to them should perform as expected, with no unwanted side-effects.
\subsection{Calculus}
The primary data structure used in \Symbo{} should be easily differentiated given that every function being used has a well-defined derivative. Integration of functions is far more complex, and is beyond the scope of this project. However, it should be possible with intelligent substitutions and better simplification rules. Calculus should also be performed with respect to a specified variable, meaning multi-variable equations can still be operated on correctly. In such cases, all other variables will be treated as constants.
\subsection{Simplification} \label{simplificationgoal}
Ideally, \Symbo{} should be able to simplify a given expression and reduce it to it is most fundamental form. This means $2 \cdot 5x$ will automatically be transformed into $10x$, and $\frac{10}{2}x$ will be transformed into $5x$. This will be most important for the result of differentiation/integration, as, depending on how these are implemented, the resulting expressions may include many redundant operations and values.
Simplifications will be defined with certain rules, which can be applied to constructs matching a certain pattern. In the case where none of the specified rules can be applied (irrespective of whether the expression is fully simplified), the equation should always remain valid, and hence could be simplified by hand if necessary.
\pagebreak
\section{Overall Program Design}
\subsection{Data Types} \label{datatypes}
Based on previous research and extensive testing, a polymorphic approach using \CC pointers and virtual functions appears to be the most efficient way of implementing the required features outlined in \ref{symbogoals}. Additionally, instead of using raw pointers, the program will make use of the \CC standard library's \codeword{std::shared_ptr<T>} type, as it can automatically handle reference counting and memory allocation/deallocation, reducing the scope for memory leaks and bugs in the final program.
Another benefit of using \codeword{std::shared_ptr<T>} is that, if all classes inherit from a single base type, it is possible to cast between the base type and the inherited types at runtime. This will allow for the creation of trees which can store multiple datatypes. This concept will be addressed later.
\vspace{1cm}
\Symbo{} will make use of the following data types:
\begin{align*}
&\texttt{Component: The most fundamental type in the system}
\\
&\texttt{Number: Stores a single numeric value}
\\
&\texttt{Variable: Stores a string value representing a variable name}
\\
&\texttt{Function: An operation taking $n$ arguments, returning a scalar}
\\
&\texttt{Tree: Stores an equation and provides some utility functions}
\end{align*}
\vspace{1cm}
All objects will store only the data and instructions necessary for their operation, and will not access global variables unless it is necessary (searching for a function, for example).
An equation will be a \codeword{Tree} object storing a single \codeword{Function} object, which will be the highest-level function/operation in the equation. The function objects will have $n$ branches, where $n$ represents the number of arguments the function takes. For an arithmetic operation such as addition or subtraction, $n=2$. For a single-argument function like $\sin(x)$, $n=1$.
Each branch of the function will contain another object -- this may be a \codeword{Number}, \codeword{Variable} or a \codeword{Function}. Thankfully, the data structure identified earlier allows for any of these types to be stored, effectively circumventing the \CC static-typing system. Getting around this issue is the most important step in implementing a symbolic mathematics library\footnote{A symbolic mathematics library that operates at runtime. It is possible to do everything described with compile-time operations, which will be much more efficient but also has different limitations}.
\subsubsection{Examples} \label{treeexample}
\begin{minipage}{.4\textwidth}
\begin{tikzpicture}[
equationnode/.style={rectangle, draw=purple!60, fill=purple!5, very thick, minimum size=7mm, align=center},
objectnode/.style={circle, draw=green!60, fill=green!5, very thick, minimum size=7mm, align=center},
variablenode/.style={rectangle, draw=red!60, fill=red!5, very thick, minimum size=5mm, align=center},
]
%Nodes
\node[equationnode] (eqn) {$5 + 8$};
\node[objectnode] (treeobj) [below=of eqn] {\codeword{Tree}};
\node[objectnode] (addobj) [below=of treeobj] {\codeword{Add}};
\node[variablenode] (num1) [below=of addobj] {$5$};
\node[variablenode] (num2) [right=of num1] {$8$};
%Lines
\draw[->] (eqn.south) -- (treeobj.north);
\draw[->] (treeobj.south) -- (addobj.north);
\draw[->] (treeobj.south) -- (addobj.north);
\draw[->] (addobj.south) -- (num1.north);
\draw[->] (addobj.south) -- (num2.north);
\end{tikzpicture}
\end{minipage}
\begin{minipage}{.4\textwidth}
\begin{tikzpicture}[
equationnode/.style={rectangle, draw=purple!60, fill=purple!5, very thick, minimum size=7mm, align=center},
objectnode/.style={circle, draw=green!60, fill=green!5, very thick, minimum size=7mm, align=center},
variablenode/.style={rectangle, draw=red!60, fill=red!5, very thick, minimum size=5mm, align=center},
]
%Nodes
\node[equationnode] (eqn) {$2 + 3 \cdot 4$};
\node[objectnode] (treeobj) [below=of eqn] {\codeword{Tree}};
\node[objectnode] (addobj) [below=of treeobj] {\codeword{Add}};
\node[variablenode] (num1) [below=of addobj] {$2$};
\node[objectnode] (mulobj) [right=of num1] {\codeword{Mul}};
\node[variablenode] (num2) [below=of num1] {$3$};
\node[variablenode] (num3) [below=of mulobj] {$4$};
%Lines
\draw[->] (eqn.south) -- (treeobj.north);
\draw[->] (treeobj.south) -- (addobj.north);
\draw[->] (treeobj.south) -- (addobj.north);
\draw[->] (addobj.south) -- (num1.north);
\draw[->] (addobj.south) -- (mulobj.north);
\draw[->] (mulobj.south) -- (num2.north);
\draw[->] (mulobj.south) -- (num3.north);
\end{tikzpicture}
\end{minipage}
\begin{minipage}{.3\textwidth}
\begin{tikzpicture}[
equationnode/.style={rectangle, draw=purple!60, fill=purple!5, very thick, minimum size=7mm, align=center},
objectnode/.style={circle, draw=green!60, fill=green!5, very thick, minimum size=7mm, align=center},
variablenode/.style={rectangle, draw=red!60, fill=red!5, very thick, minimum size=5mm, align=center},
]
%Nodes
\node[equationnode] (eqn) {$\sin(\frac{22}{7})$};
\node[objectnode] (treeobj) [below=of eqn] {\codeword{Tree}};
\node[objectnode] (sinobj) [below=of treeobj] {\codeword{Sin}};
\node[objectnode] (divobj) [below=of sinobj] {\codeword{Div}};
\node[variablenode] (num1) [below=of divobj] {$22$};
\node[variablenode] (num2) [right=of num1] {$7$};
%Lines
\draw[->] (eqn.south) -- (treeobj.north);
\draw[->] (treeobj.south) -- (sinobj.north);
\draw[->] (sinobj.south) -- (divobj.north);
\draw[->] (divobj.south) -- (num1.north);
\draw[->] (divobj.south) -- (num2.north);
\end{tikzpicture}
\end{minipage}
\subsection{Algorithms}
All high-level algorithms in \Symbo{} will operate recursively on the data they contain, and should not require external information to operate correctly.
For example, to evaluate the numeric result of an expression, the \codeword{eval} function will be called on the \codeword{Tree} object containing the equation. The \codeword{Tree} will then recursively call \codeword{eval} on the primary function/operation, which, in turn, calls \codeword{eval} on each of its branches. The numeric result of these function calls can then be propagated back up the tree, applying the relevant operations where necessary. This results in a final numeric result being returned from the \codeword{Tree} object.
\subsubsection{Example}
For example, if we evaluate the middle example from \ref{treeexample}:
\vspace{1cm}
\begin{minipage}{.33\textwidth}
\begin{tikzpicture}[
equationnode/.style={rectangle, draw=purple!60, fill=purple!5, very thick, minimum size=7mm, align=center},
objectnode/.style={circle, draw=green!60, fill=green!5, very thick, minimum size=7mm, align=center},
variablenode/.style={rectangle, draw=red!60, fill=red!5, very thick, minimum size=5mm, align=center},
]
%Nodes
\node[equationnode] (eqn) {$2 + 3 \cdot 4$};
\node[objectnode] (treeobj) [below=of eqn] {\codeword{Tree}};
\node[objectnode] (addobj) [below=of treeobj] {\codeword{Add}};
\node[variablenode] (num1) [below=of addobj] {$2$};
\node[objectnode] (mulobj) [right=of num1] {\codeword{Mul}};
\node[variablenode] (num2) [below=of num1] {$3$};
\node[variablenode] (num3) [below=of mulobj] {$4$};
%Lines
\draw[->] (eqn.south) -- (treeobj.north);
\draw[->] (treeobj.south) -- (addobj.north);
\draw[->] (treeobj.south) -- (addobj.north);
\draw[->] (addobj.south) -- (num1.north);
\draw[->] (addobj.south) -- (mulobj.north);
\draw[->] (mulobj.south) -- (num2.north);
\draw[->] (mulobj.south) -- (num3.north);
\end{tikzpicture}
\end{minipage}
\begin{minipage}{.33\textwidth}
\begin{tikzpicture}[
equationnode/.style={rectangle, draw=purple!60, fill=purple!5, very thick, minimum size=7mm, align=center},
objectnode/.style={circle, draw=green!60, fill=green!5, very thick, minimum size=7mm, align=center},
variablenode/.style={rectangle, draw=red!60, fill=red!5, very thick, minimum size=5mm, align=center},
evalnode/.style={rectangle, draw=blue!60, fill=blue!5, very thick, minimum size=5mm, align=center},
]
%Nodes
\node[equationnode] (eqn) {$2 + 3 \cdot 4$};
\node[objectnode] (treeobj) [below=of eqn] {\codeword{Tree}};
\node[objectnode] (addobj) [below=of treeobj] {\codeword{Add}};
\node[variablenode] (num1) [below=of addobj] {$2$};
\node[evalnode] (mulobj) [right=of num1] {$3 \cdot 4 = 12$};
\node[variablenode] (num2) [below=of num1] {$3$};
\node[variablenode] (num3) [below=of mulobj] {$4$};
%Lines
\draw[->] (eqn.south) -- (treeobj.north);
\draw[->] (treeobj.south) -- (addobj.north);
\draw[->] (treeobj.south) -- (addobj.north);
\draw[->] (addobj.south) -- (num1.north);
\draw[->] (addobj.south) -- (mulobj.north);
\draw[purple, very thick, ->] (num2.north) -- (mulobj.west);
\draw[purple, very thick, ->] (num3.north) -- (mulobj.south);
\end{tikzpicture}
\end{minipage}
\begin{minipage}{.33\textwidth}
\begin{tikzpicture}[
equationnode/.style={rectangle, draw=purple!60, fill=purple!5, very thick, minimum size=7mm, align=center},
objectnode/.style={circle, draw=green!60, fill=green!5, very thick, minimum size=7mm, align=center},
variablenode/.style={rectangle, draw=red!60, fill=red!5, very thick, minimum size=5mm, align=center},
evalnode/.style={rectangle, draw=blue!60, fill=blue!5, very thick, minimum size=5mm, align=center},
]
%Nodes
\node[equationnode] (eqn) {$2 + 3 \cdot 4 = 14$};
\node[objectnode] (treeobj) [below=of eqn] {\codeword{Tree}};
\node[evalnode] (addobj) [below=of treeobj] {$2 + 12 = 14$};
\node[variablenode] (num1) [below=of addobj] {$2$};
\node[evalnode] (mulobj) [right=of num1] {$12$};
\node[variablenode] (num2) [below=of num1] {$3$};
\node[variablenode] (num3) [below=of mulobj] {$4$};
%Lines
\draw[->] (eqn.south) -- (treeobj.north);
\draw[->] (treeobj.south) -- (addobj.north);
\draw[->] (treeobj.south) -- (addobj.north);
\draw[purple, very thick, ->] (num1.north) -- (addobj.south);
\draw[purple, very thick, ->] (mulobj.north) -- (addobj.east);
\draw[purple, very thick, ->] (num2.north) -- (mulobj.west);
\draw[purple, very thick, ->] (num3.north) -- (mulobj.south);
%Curvy line :)
\draw[purple, very thick, ->] (addobj.east) .. controls(2, -3) and (3, -1) .. (eqn.east);
\end{tikzpicture}
\end{minipage}
\vspace{1cm}
Hopefully, the example above shows that it is possible to evaluate a tree recursively to calculate a numeric result. These steps can be extended to differentiation, simplification and potentially integration.
\pagebreak
\subsection{External Libraries}
In order to reduce the chance of bugs appearing and to improve functionality, \Symbo{} makes use of a few external libraries.
\vspace{0.5cm}
\dirtree{%
.1 \Symbo.
.2 {LibRapid \cite{librapid} -- Provides advanced maths functionality}.
.3 {MPFR \cite{MPFR} -- Support for multi-precision floating point types}.
.3 {MPIR \cite{MPIR} -- Required by MPFR}.
.2 {\{fmt\} \cite{fmt} -- For faster, easier string manipulation and printing}.
.2 {scnlib \cite{scnlib} -- Provides fast string scanning and casting routines}.
}
\pagebreak
\section{Implementing the Data Types}
This section defines the abstract class interface for the different data types described in \ref{datatypes}. As mentioned there, each data type should hold only the data necessary for its operation, and efforts should be made to avoid global data accesses.
\subsection{The Component Type}
For the data structure identified in \ref{datatypes}, it is necessary to have a single base object which all other types will inherit from. This data type should have very minimal functionality, and all the functions it provides should ultimately be overwritten by the subclass.
Internally, the \codeword{Component} type contains virtual functions which throw an error when called, ensuring they cannot be called accidentally and cause unwanted results.
To start with, none of the classes will support any advanced features like simplification or calculus, but will support a simple, numeric evaluation function so we can check that the whole system is working correctly.
\begin{lstlisting}[language=C++]
virtual Scalar eval() const;
virtual std::string type() const;
virtual std::string name() const;
virtual std::string str(ui64 indent) const;
virtual std::string repr(ui64 indent,
ui64 typeWidth,
ui64 valWidth) const;
\end{lstlisting}
\subsubsection{Evaluation}
The \codeword{eval} function returns a scalar value, which is the result of evaluating the object. Note that all of these functions are \codeword{const}, and hence cannot change the data contained within the node. The evaluation function for each type will be covered later.
\subsection{Type Detection}
The \codeword{type} function returns the type of the object (i.e. \codeword{"NUMBER"}, \codeword{"VARIABLE"}, \codeword{"FUNCTION"}, etc.). This is used in the cases where more information needs to be known about a specific object, as the program cannot apply a general algorithm. It is also closely linked to the helper functions, and is used for debugging purposes.
\subsection{Naming}
Objects like functions and variables have names associated with them, and hence a procedure to extract the name of a given object is required. For objects which do not have a name associated with them, calling this function will raise an error, limiting unwanted behaviour.
\subsection{Helpers}
The \codeword{str} and \codeword{repr} functions are purely to debug the algorithms and ensure that everything is working correctly. They both return string representations of the object, but in slightly different formats, with different information.
\subsection{The Number Type}
The \codeword{Number} class is incredibly simple, and stores a single \codeword{Scalar} object (note that Scalar is typedef'ed, so it can represent any compile-time data type). The \codeword{eval} function simply returns the value stored in the class.
\begin{lstlisting}[language=C++]
class Number : public Component {
public:
/* [ code omitted ] */
Scalar eval() const override { return m_value; }
/* [ code omitted ] */
private:
Scalar m_value = 0;
};
\end{lstlisting}
\subsection{Variables}
Symbols such as $x$ and $y$ will be represented by the \codeword{Variable} class, which implements a few basic functions.
Firstly, the evaluation method of the class will always raise error, as, in this implementation, a variable must be replaced with a number in order to evaluate it. To support this replacement, the class supports a \codeword{Substitute} method, which uses a mapping of strings to objects, where the string is the variable name and the object is any \Symbo{} data type. Any references to the variables provided in the map will be replaced with their corresponding objects.
The variable class also implements a method to access the symbol's name, which is used in table lookups and when deciding how to differentiate or integrate the symbol.
\begin{lstlisting}[language=C++]
class Variable : public Component {
public:
/* [ code omitted ] */
std::string name() const override { return m_name; }
/* [ code omitted ] */
private:
std::string m_name = "NONAME";
};
\end{lstlisting}
\subsection{Functions}
The \codeword{Function} class is responsible for handing arithmetic operations such as addition and subtraction and single-argument functions like $\sin(x)$ and $\cos(y)$. The class can also be used to handle multi-argument functions like $\text{max}(x, y, z)$ if required.
\subsubsection{The Functor}
In order to maintain runtime-compatible evaluation and to reduce the complexity of the program, the functor member of the function objects will be an instance of the \CC standard library \codeword{function} type, which provides a simple way to wrap a lambda function.
As the function definition must be known at compile-time, the functor will take a list of \codeword{Scalar} objects and will return another \codeword{Scalar} object, which is the result of the calculation. This allows for arbitrarily many arguments to be passed to a function at runtime.
\subsubsection{Functor Arguments}
The \codeword{Function} class also needs to know what arguments it is operating on, and these must be set at runtime. For this reason, each instance of the class will store a list of \codeword{Component} pointers (remember, these could be numbers, variables or functions) -- one for each argument to the function.
One downside to this approach is that the number of arguments cannot be determined from the functor argument itself, so another member variable is required to store this information, with a function to access it.
\subsubsection{Function Evaluation}
In order to evaluate the result of the function, the arguments, which are still \codeword{Component} pointers, must first be evaluated and stored in a list. That list can then be fed directly into the functor object and the result can be returned.
\vspace{1cm}
\begin{lstlisting}[language=C++]
class Function : public Component {
public:
/* [ code omitted ] */
Scalar eval() const override {
std::vector<Scalar> operands;
for (const auto &val : m_values)
operands.push_back(val->eval());
return m_functor(operands);
}
uint64_t numOperands() const { return m_numOperands; }
std::string name() const override { return m_name; }
/* [ code omitted ] */
private:
std::string m_name = "NULLOP";
std::function<Scalar(const std::vector<Scalar> &)> m_functor;
uint64_t m_numOperands = 0;
std::vector<std::shared_ptr<Component>> m_values = {};
};
\end{lstlisting}
\subsection{The Tree Type}
The \codeword{Tree} class is mainly intended to store a single function object which can then be evaluated. It also provides a few helper functions to format equations, though these are purely visual and have no impact on the program itself.
In the code listing, you may notice that the \codeword{m_tree} member is a list of pointers; this is done for future-proofing to allow variable values to be stored on a per-equation basis, for example. However, this currently serves no purpose and can easily be changed to store a single object.
\vspace{0.25cm}
\begin{lstlisting}[language=C++]
class Tree : public Component {
public:
/* [ code omitted ] */
Scalar eval() const override { return m_tree[0]->eval(); }
/* [ code omitted ] */
private:
std::vector<std::shared_ptr<Component>> m_tree;
};
\end{lstlisting}
\pagebreak
\section{Processing the Equation Input}
Before it is possible to generate the tree from the input equation (in this case, a \codeword{std::string}), the text must first be processed into a form that can be understood more easily by the computer.
\subsection{Tokenizing}
The first step in transforming the user's input into something the computer can process is called ``tokenizing''\footnote{Tokenizing and lexing are technically the same process, however, in \Symbo{} the process is split into two parts, and ``tokenizing'' fits the first part more closely than ``lexing''.}. It is the process of splitting the input string into a list of ``tokens'', which, in this case, are individual characters \footnote{``Character'' in reference to ASCII characters -- i.e., not limited to letters of the alphabet.} the program is allowed to use.
The process is very simple, and involves iterating over each character in the input text, checking whether it's a valid character to be in an equation (e.g. emojis are invalid) and, if it is, storing it in a list. This process also removes spaces from the input, as they are not important to the equation itself.
Another data type is required to store the result of this process, which contains information about the type of the value stored, along with the actual character.
\begin{lstlisting}[language=C++, caption={The token object definition}]
struct Token {
uint64_t type;
char val;
};
\end{lstlisting}
\subsubsection{Example} \label{tokenexample}
For example, the input $12 + 3$ will produce the list of tokens shown below. Note, however, that \codeword{TYPE_DIGIT}, \codeword{TYPE_ADD} and \codeword{TYPE_OPERATION} are all numeric values.
\begin{lstlisting}[language=C++]
// ===== [ 0 ] =====
{
uint64_t type = TYPE_DIGIT;
char val = '1';
}
// ===== [ 1 ] =====
{
uint64_t type = TYPE_DIGIT;
char val = '2';
}
// ===== [ 2 ] =====
{
uint64_t type = TYPE_ADD | TYPE_OPERATION;
char val = '+';
}
// ===== [ 3 ] =====
{
uint64_t type = TYPE_DIGIT;
char val = '3';
}
\end{lstlisting}
\subsection{Lexing}
The individual characters returned from the tokenizing step are entirely useless unless we can make sense of what they mean in a larger context. This is the task of the ``lexer'', which takes the characters identified in the previous step and produces a list of objects which the computer can easily make sense of.
The lexer algorithm itself is quite simple, and joins together strings of digits to form numbers, strings of characters to form text and identifies arithmetic operations and brackets.
Another data type is also required for the output of the lexing process. It is almost exactly the same as the token object, except that it stores a string instead of a single character.
Taking the example given in \ref{tokenexample}, the algorithm will output the following.
\begin{lstlisting}[language=C++]
// ===== [ 0 ] =====
{
uint64_t type = TYPE_NUMBER | TYPE_VARIABLE;
std::string val = "12";
}
// ===== [ 1 ] =====
{
uint64_t type = TYPE_ADD | TYPE_OPERATION;
std::string val = "+";
}
// ===== [ 2 ] =====
{
uint64_t type = TYPE_NUMBER | TYPE_VARIABLE;
std::string val = "3";
}
\end{lstlisting}
\subsection{Processing the Lexed Results}
This section of the program isn't necessarily required, but it will serve a very important purpose later down the line, as well as allowing for a few more mathematical inputs.
When writing an equation by hand, it is not uncommon to write something like $5(3x-2)$ or $2\sin(x)$. In these cases, we understand that the term preceded by a coefficient should be multiplied by that coefficient, however the computer will not currently understand this.
To simplify things, a \codeword{Lexed{TYPE_MUL, "*"}} can be inserted between the coefficient and the rest of the term, leaving it completely unchanged from a mathematical point of view, but making it much easier for the later stages to use.
We also need to check for equations like $x(5+y)$, however this is more complicated as it is not always possible to check for text followed by an open bracket. To show why, take the expression $\sin(x)$ -- using the naive method from above will lead to $\text{sin} \cdot x$, which is obviously not a valid equation.
To mitigate this issue, \Symbo{} stores a list of all defined functions, and checks whether the value preceding the bracket is registered as one. If no corresponding function is found, it can be assumed that the value is referring to a variable; the value of which will be specified when the equation is evaluated. In this case, a multiplication can safely be inserted between the coefficient and the rest of the term.
If the value is identified as a registered function, further modification is required.
\subsection{Conversion to Postfix Notation}
Currently, the equations are in ``infix'' form, which refers to how we normally write equations, where the operator sits between the two operands.
One downside of representing equations with infix notation is that the operator precedence plays a large role in the final result. While it is possible to process equations in this form directly, converting them to ``postfix'' notation dramatically simplifies the tree generation algorithm.
\subsubsection{Postfix Notation}
Postfix notation (sometimes called ``Reverse Polish Notation'') places the operator after the operands, and relies on a stack-based approach to calculate the final result. Postfix notation also removes the requirement for brackets, as the order of precedence is implied by the order of the operations in the equation.
Below are three different examples of postfix notation:
\begin{equation}
\begin{split}
\text{Infix} \ & \ \ \ \ \text{Postfix} \\
5+10&=5 \ 10 \ + \\
\left( 1+2 \right) \cdot 3&=1 \ 2 \ + \ 3 \ \cdot \\
2 + 3 \cdot 4 ^ x&=2 \ 3 \ 4 \ x \ \textit{pow} \ \cdot \ +
\end{split}
\end{equation}
To evaluate the result, start on the first number and push it to the stack -- repeat this until an operator is found. The operator acts on $n$ values, so pop off the top $n$ items from the stack, apply the operation to those $n$ values and push the result back onto the stack. Repeat this until the stack is empty.
\subsubsection{Infix to Postfix}
To convert from infix to postfix notation, we can use Dijkstra's ``Shunting Yard Algorithm''\cite{shuntingyard}, which acts as follows:
\begin{algorithm}
\caption{Dijkstra's Shunting Yard Algorithm}\label{shuntingalgorithm}
\begin{algorithmic}[1]
\Function{to\_postfix}{$\textit{input}$}\Comment{Convert from infix to postfix}
\State $\textit{postfix} \gets \{\}$
\State $\textit{stack} \gets \{\}$
\For{$\textit{lex} \ \textbf{in} \ \textit{input}$}
\If{$\textit{lex} \rightarrow \textit{type} \ \textbf{is} \ \textit{variable}$} \Comment{Number or string}
\State $\textit{postfix} \ \textbf{push back} \ \textit{lex}$
\ElsIf{$\textit{lex} \rightarrow \textit{type} \ \textbf{is} \ \textit{(operator, function)}$}
\While{$\textit{stack} \ \textbf{not empty} \ \textbf{and} \ \textit{stack} \rightarrow \textit{back} \rightarrow \textit{precedence} \ \ge \ \textit{lex} \rightarrow \textit{precedence}$}
\State $\textit{postfix} \ \textbf{push back} \ \textit{stack} \rightarrow \textit{back}$
\State $\textbf{pop} \ \textit{stack}$
\EndWhile
\State $\textit{stack} \ \textbf{push back} \ \textit{lex}$
\ElsIf{$\textit{lex} \rightarrow \textit{type} \ \textbf{is} \ \textit{leftparen}$}
\State $\textit{stack} \ \textbf{push back} \ \textit{lex}$ \Comment{Store the bracket for later}
\ElsIf{$\textit{lex} \rightarrow \textit{type} \ \textbf{is} \ \textit{rightparen}$}
\While{$\textit{stack} \rightarrow \textit{back} \ \textbf{is not} \ \textit{leftparen}$}
\State $\textit{postfix} \ \textbf{push back} \ \textit{stack} \rightarrow \textit{back}$
\EndWhile
\State $\textbf{pop} \ \textit{stack}$ \Comment{Remove the left bracket}
\EndIf
\EndFor\
\While{$\textit{stack} \ \textbf{not empty}$} \Comment{Pop remaining operators}
\State $\textit{postfix} \ \textbf{push back} \ \textit{stack} \rightarrow \textit{back}$
\State $\textbf{pop} \ \textit{stack}$
\EndWhile
\EndFunction
\end{algorithmic}
\end{algorithm}
\pagebreak
\subsection{Parsing}
The final stage is to parse the resulting objects. This process involves identifying whether the object is a number, a variable or a function, and mapping it to the correct type for the system to understand it.
\subsubsection{Numbers}
If the lexed type is a number, \Symbo{} simply outputs a \linebreak \codeword{std::make_shared<Number>(lex.val)}.
\subsubsection{Strings}
A string could represent either a variable or a function. \Symbo{} operates on the premise that functions must be declared before the equation is constructed, but variables can be specified afterwards, meaning the program must check if the string exists as a function and otherwise assumes it is a variable.
\begin{lstlisting}[language=C++, caption={Convert from text to either a function or variable}]
auto func = findFunction(lex.val);
if (func != functions.end()) {
res.emplace_back(*func);
} else {
res.emplace_back(std::make_shared<Variable>(lex.val));
}
\end{lstlisting}
\subsubsection{Operators}
Arithmetic operators are also functions, so a simple lookup is all that is needed. To reduce bugs, the code still checks whether the functions were successfully found, as it's possible the functions haven't been registered at this point in the program. \Symbo{} will throw an error if this is the case.
\begin{lstlisting}[language=C++, caption={Identify arithmetic operators and convert them to function objects}]
auto func = findFunction("_"); // Default to a nullary function
if (lex.type & TYPE_ADD) func = findFunction("ADD");
if (lex.type & TYPE_SUB) func = findFunction("SUB");
if (lex.type & TYPE_MUL) func = findFunction("MUL");
if (lex.type & TYPE_DIV) func = findFunction("DIV");
if (lex.type & TYPE_CARET) func = findFunction("POW");
LR_ASSERT(func != functions.end(), "Operator not found");
res.emplace_back(*func);
\end{lstlisting}
\pagebreak
\section{Equation Tree Generation}
With the equations in postfix notation, it would be quite trivial to evaluate a numeric result with no further manipulation, however, to work with variables, implement calculus and more advanced manipulations, it will be necessary to transform the equation into a tree-like structure.
To do this, we can evaluate the postfix equation and apply the typing system implemented earlier to store the final equation, instead of evaluating it numerically.
\begin{algorithm}
\caption{Postfix to Tree}\label{treegenalg}
\begin{algorithmic}[1]
\Function{gen\_tree}{$\textit{input}$}\Comment{Generate a tree from postfix notation}
\State $\textit{tree} \gets \textit{empty tree}$
\State $\textit{stack} \gets \textit{empty stack}$
\For{$\textit{lex} \ \textbf{in} \ \textit{input}$}
\If{$\textit{lex} \rightarrow \textit{type} \ \textbf{is} \ \textit{(number, variable)}$}
\State $\textit{stack} \ \textbf{push back} \ \textit{lex}$
\ElsIf{$\textit{lex} \rightarrow \textit{type} \ \textbf{is} \ \textit{function}$}
\State $\textit{args} \gets \textit{\{\}}$
\For{$i \ \textbf{in} \ 0..(\textit{lex} \rightarrow \textit{numOperands})$}
\State $\textit{args} \ \textbf{push back} \ \textit{stack} \rightarrow \textit{back}$
\State $\textbf{pop} \ \textit{stack}$
\EndFor
\State $\textit{node} \gets \textbf{copy} \ \textit{lex}$ \Comment{lex is a function}
\For{$arg \ \textbf{in} \ \textbf{reverse} \ \textit{args}$}
\State $\textit{node} \ \textbf{add value} \ \textit{arg}$
\EndFor
\State $\textit{stack} \ \textbf{push back} \ \textit{node}$ \Comment{Push node back onto stack}
\EndIf
\EndFor
\State $\textit{tree} \ \textbf{set node} \ \textit{stack} \rightarrow \textit{back}$
\EndFunction
\end{algorithmic}
\end{algorithm}
\pagebreak
As an example, we can test the algorithm on the trees from \ref{treeexample}. Each of the three examples below shows the input to the program, the generated tree and the numeric result after calling \codeword{eval} on the main tree object.
\subsection{Example 1}
\begin{lstlisting}[language=C++]
Input: "5 + 8"
[ TREE ]
[ FUNCTION ] [ ADD ]
[ NUMBER ] [ 5.0000000000 ]
[ NUMBER ] [ 8.0000000000 ]
Result: 13
\end{lstlisting}
\subsection{Example 2}
\begin{lstlisting}[language=C++]
Input: "2 + 3 * 4"
[ TREE ]
[ FUNCTION ] [ ADD ]
[ NUMBER ] [ 2.0000000000 ]
[ FUNCTION ] [ MUL ]
[ NUMBER ] [ 3.0000000000 ]
[ NUMBER ] [ 4.0000000000 ]
Result: 14
\end{lstlisting}